Skip to content

Rate limits and pagination

How much you can ask for in a window, and how to walk a list longer than one page.

How much you can ask for

Limits are counted per key, over a window of 10 minutes, and reads and writes are counted separately. A read is any GET. A write is any POST. How many keys you can have, and how much each one can do, come with your plan.

  • Foundation: 2 keys, and per key 600 reads and 60 writes every 10 minutes.
  • Portfolio: 5 keys, and per key 1200 reads and 120 writes every 10 minutes.
  • Holdings: 15 keys, and per key 3000 reads and 300 writes every 10 minutes.

The headers to watch

  • ratelimit-limit: how many requests this key may make in the current window.
  • ratelimit-remaining: how many of those are left.
  • ratelimit-reset: when the window resets, as a Unix time in seconds.
  • retry-after: on a rate_limited response only, how many seconds to wait.

The three ratelimit- headers are on every response once the request has been counted against the key's budget, whether it then succeeded or failed. Answers that come before that point carry none of them: invalid_key, insufficient_scope, the rate_limited response itself (which carries retry-after instead), and a not_found for a path or method the API does not have.

What to do when you are rate limited

Wait. retry-after says how many seconds. Sleep that long and send the request again. Do not retry in a tight loop: the window does not move because you knocked, and a loop is how a job that was late becomes a job that is stuck.

If you are regularly hitting the limit, read less rather than faster. Filter a list with dateFrom and dateTo instead of paging the whole thing, ask for a larger limit per page, and cache what does not change often, such as the chart of accounts.

Failed authentication is limited separately

A request whose key does not work is counted against the address it came from, not against any key. After 300 failures from one address in 10 minutes, the next one answers rate_limited instead of invalid_key. It exists to bound a flood of bad keys. A key that works is never counted against it.

Pagination

These lists are paged with a cursor: GET /customers, GET /vendors, GET /transactions, GET /invoices. Send limit (1 to 100, default 50) and read nextCursor from the response. Pass that value back as cursor for the next page. A nextCursor of null means you already have everything.

Walking a list
curl "https://www.numm.io/api/v1/customers?limit=100" \
  -H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY"

# Take nextCursor from that response and pass it back as cursor.
# Keep going until nextCursor comes back null.
curl "https://www.numm.io/api/v1/customers?limit=100&cursor=NEXT_CURSOR_FROM_THE_LAST_PAGE" \
  -H "Authorization: Bearer nmo_live_REPLACE_WITH_YOUR_KEY"

Do not build a cursor yourself, and do not keep one for later. It is an opaque value from a response, meant to be handed straight back.

Two lists work differently. GET /accounts is not paged at all: a chart of accounts is a bounded tree, and you need the whole tree to resolve a path. GET /review-queue is limited rather than paged: send limit and read hasMore, which says whether more is waiting behind it. There is no cursor on either.

One thing to watch on GET /invoices: overdueOnly is applied to the page you asked for, not to the whole list. So a page can come back with fewer rows than limit, or with none at all, while nextCursor is still set. Keep going until nextCursor is null rather than stopping on an empty page.

Rate limits and pagination — Nummio API