Documentation

Philosophy

The principles the API is designed around: repeats are harmless, access is narrow, and rate limits are a budget.

The API assumes that your code will be interrupted. Connections drop mid-write, requests arrive together, tokens leak, and backlogs get processed in bursts. Three principles follow, and they hold across every endpoint.

Repeats are normal, so make them harmless

A common failure is not an error but an unknown outcome: a request that timed out after the server had already committed it.

There is no general Idempotency-Key header, so repeating an arbitrary write is not safe by default. Treat a write whose response you never saw as having an unknown outcome, not a failed one. Re-read the resource and decide before sending it again.

Where a repeat is likely, the endpoint handles it:

  • Reward redemption takes an idempotency_key. If you omit one, a key is derived from the reward, the recipient and the scope, so a retry with the same three is recognized as the same redemption.
  • Check-in derives a key from the session and the wallet, so a second attempt is refused with a 409 rather than recorded twice.
  • Webhook deliveries retry, so the same event can arrive twice. Every delivery carries X-Webhook-Delivery to deduplicate on.

A derived key's notion of "the same" may be narrower than yours. If your rule is one reward per visit rather than one per person, the default suppresses the second visit. Supply a key that carries whatever makes the visit unique.

A 429 is not a failed write. The request was refused before it ran, so retrying after Retry-After is safe and needs no reconciliation.

Assume every request may happen twice, and make the second one a no-op rather than a duplicate.

Ask for the least you need

Two independent checks decide every request: what the application was allowed to ask for (its scopes) and what the user may actually do (their role). Both must permit an action, and neither substitutes for the other. A token holding every scope grants nothing on a resource its user has no role in.

You may be granted fewer scopes than you requested. What a user can consent to is bounded by what they can do, so an over-broad request is narrowed rather than refused, and the consent records the narrowed set. Read scope from the token response, or the granted scopes on /users/me, instead of assuming you got what you asked for.

Ask narrowly:

  • Consent is a conversion step. Every scope on the consent screen is a reason for a user to hesitate, especially one they cannot connect to your product.
  • Some scopes are marked sensitive and are shown with an extra warning. Requesting one you rarely use costs you on every consent.
  • Narrow scopes separate narrow jobs. Enrolling somebody, cancelling, renewing and changing tier are four separate scopes over one resource, so an integration that only signs people up can hold membership:join and nothing that ends a membership.

Scopes are fixed at registration, not per request. Register what the integration needs, including features you have not built yet, but not ones you are unlikely to build.

Prefer the narrower token. An organization-scoped token acts for one organization, which keeps a bug contained to the organization it was working on.

Treat rate limits as a budget

Everything needed to stay inside the rate limits is given to you up front, so plan to spend the allowance rather than catch the error.

Every response carries the rate-limit headers, not just a 429. A client can know where it stands before it is throttled.

  • Pace against RateLimit-Remaining, and slow down as it falls. A steady rate finishes sooner than sending until refused and then backing off.
  • Spend the expensive allowance deliberately. The expensive bucket is a named list of operations that do real work, and it allows roughly one every three seconds. If your design puts one at a door or a checkout, that limit is a design input.
  • Bulk endpoints are billed per item. Registering a hundred cards in one call spends a hundred.

The buckets are keyed on user and application together. Your traffic never competes with another integration the same user has authorized, and a runaway loop of yours exhausts only your own allowance.

The values are in Errors and Rate Limits.

Next