Core concepts

Rate limits

What the ceilings are, which headers describe them, and what to do at 429.

Limits exist so one partner's retry storm cannot slow another's checkout. At a restaurant's real order volume you will not meet them; a backfill script will.

ScopeLimit
Per secret key600 requests/minute
Per IP, before a key is checked1,200 requests/minute
POST /v1/customers/lookup120 requests/minute

Every limited response carries the headers describing the tightest limit you are against:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1757721600
Retry-After: 12

At 429

Wait Retry-After seconds, then retry with the same idempotency key. The request was refused before it reached the ledger, so nothing was applied — but using the same key costs nothing and keeps the habit right.

Do not retry immediately, and do not retry in a tight loop. A checkout request should not stall for thirty seconds either: put the work in your own queue and answer the customer.

Our SDK deliberately does not sleep through a 429 for you. It surfaces it as a TrovyError with retryAfterSeconds, because only you know whether this request has a customer waiting on it.

if (isTrovyError(err) && err.status === 429) {
  await queue.retryAfter(err.retryAfterSeconds ?? 30, job);
}

Counting, honestly

These limits are counted in memory, by each API instance, so treat them as approximate ceilings: a fixed window can allow up to twice the limit across a window boundary.

Anything where money or a text message is at stake is counted in the database instead, where it is exact — the widget's send caps. Those are not approximations.

Sign-up texts

The sign-up form sends one text per code, so it is capped where the texts are counted:

ScopeLimit
One phone number, at your business3 codes in 15 minutes, 6 in a day
One phone number, across every business10 codes in a day
One publishable key200 codes an hour
Your business500 codes a day

A customer who reaches a limit is told how long to wait (TOO_MANY_REQUESTS, with Retry-After). Past those, Trovy has a daily ceiling across every business; when it is reached, sign-up codes stop until the day moves on (SMS_UNAVAILABLE). Planning a sign-up campaign? Email developers@trovy.ca first.

Staying under them

  • Do not poll. Almost nothing changes without you causing it; the exceptions (time, and purchases at the till) are listed under Webhooks. Read a customer's rewards when you are about to show or spend them.
  • Cache the business. GET /v1/business changes when you change it. Read it at start-up, not per order.
  • Batch a backfill politely. If you are importing history, a few requests per second finishes overnight and upsets nobody.