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.
| Scope | Limit |
|---|---|
| Per secret key | 600 requests/minute |
| Per IP, before a key is checked | 1,200 requests/minute |
POST /v1/customers/lookup | 120 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: 12At 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:
| Scope | Limit |
|---|---|
| One phone number, at your business | 3 codes in 15 minutes, 6 in a day |
| One phone number, across every business | 10 codes in a day |
| One publishable key | 200 codes an hour |
| Your business | 500 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/businesschanges 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.