TidePay

Consumption limits

Per-endpoint request limits for the Tidepay API, in sandbox and in production.

Every endpoint of the Tidepay API has a consumption limit. Limits apply per merchant, per endpoint, per environment — your test key and your live key never draw from the same allowance, so a runaway integration test can't consume the quota your production traffic depends on.

Two windows are enforced on every request:

  • Per day — the consumption quota you plan against. The window is a fixed UTC day: it resets at 00:00 UTC, not 24 hours after your first call.
  • Per minute — a burst guard. It stops a runaway loop from saturating the API before your daily quota would ever notice.

Each (endpoint, method) pair is its own bucket. Exhausting POST /v1/offers does not affect GET /v1/offers, and fetching one customer by id draws from the same bucket as fetching any other.

Sandbox vs. production

Sandbox limits are deliberately lower than production. Test traffic is integration work — bursty, but low in total volume — while live traffic carries real business. A sandbox key hitting its ceiling is usually a signal that a test loop is misbehaving.

Since both environments share one base URL, the environment is decided by which key you send (see Authentication). A tp_test_... key draws from the sandbox column below; a tp_live_... key draws from the production column.

Exceeding a limit

When either window is exhausted the API responds 429 Too Many Requests:

{ "error": "Rate limit exceeded" }

with these headers:

HeaderMeaning
Retry-AfterSeconds until the exhausted window rolls over.
X-RateLimit-LimitThe limit that was hit.
X-RateLimit-RemainingAlways 0 on a 429.
X-RateLimit-ResetUnix timestamp (seconds) at which that window resets.
X-RateLimit-ScopePresent and set to day when it was the daily quota that was exhausted. Absent when it was the per-minute burst guard.

Check X-RateLimit-Scope before retrying. A missing scope means you hit the burst guard and Retry-After will be under a minute — a short backoff is enough. A day scope means your quota is spent until 00:00 UTC, and retrying in a loop will not recover it.

Blocked requests still count

A request rejected with 429 still increments the counter. Retrying immediately does not restore your allowance — honor Retry-After.

Nothing is unblocked manually: both windows clear on their own schedule, so a blocked endpoint recovers without any action on your side.

Limits per endpoint

Currencies

EndpointProduction / dayProduction / minSandbox / daySandbox / min
GET /v1/currencies60,00024020,000120
POST /v1/offers15,000605,00030
GET /v1/offers30,00012010,00060
GET /v1/offers/{offerId}60,00024020,000120
PATCH /v1/offers/{offerId}15,000605,00030
DELETE /v1/offers/{offerId}15,000605,00030
POST /v1/wallets15,000605,00030
GET /v1/wallets30,00012010,00060
DELETE /v1/wallets/{id}15,000605,00030
POST /v1/customers15,000605,00030
GET /v1/customers30,00012010,00060
GET /v1/customers/{customerId}60,00024020,000120
PATCH /v1/customers/{customerId}15,000605,00030
DELETE /v1/customers/{customerId}15,000605,00030
POST /v1/subscriptions15,000605,00030
GET /v1/subscriptions30,00012010,00060
GET /v1/subscriptions/{subscriptionId}60,00024020,000120
PATCH /v1/subscriptions/{subscriptionId}15,000605,00030
DELETE /v1/subscriptions/{subscriptionId}15,000605,00030
POST /v1/subscriptions/{subscriptionId}/retry15,000605,00030
POST /v1/payments15,000605,00030
GET /v1/payments30,00012010,00060
GET /v1/payments/{paymentId}60,00024020,000120
POST /v1/payments/{paymentId}/cancel15,000605,00030
POST /v1/subscriptions/{subscriptionId}/migrate15,000605,00030
POST /v1/subscriptions/{subscriptionId}/resume15,000605,00030
GET /v1/webhooks/secret5,000602,00030
POST /v1/webhooks/secret/rotate2001020010
POST /v1/sandbox/charge002,00030
POST /v1/sandbox/advance002,00030

All endpoints

EndpointProduction / dayProduction / minSandbox / daySandbox / min
GET /v1/currencies60,00024020,000120
POST /v1/offers15,000605,00030
GET /v1/offers30,00012010,00060
GET /v1/offers/{offerId}60,00024020,000120
PATCH /v1/offers/{offerId}15,000605,00030
DELETE /v1/offers/{offerId}15,000605,00030
PATCH /v1/offers/batch5,000202,00010
DELETE /v1/offers/batch5,000202,00010
POST /v1/wallets15,000605,00030
GET /v1/wallets30,00012010,00060
DELETE /v1/wallets/{id}15,000605,00030
POST /v1/customers15,000605,00030
GET /v1/customers30,00012010,00060
GET /v1/customers/{customerId}60,00024020,000120
PATCH /v1/customers/{customerId}15,000605,00030
DELETE /v1/customers/{customerId}15,000605,00030
POST /v1/subscriptions15,000605,00030
GET /v1/subscriptions30,00012010,00060
GET /v1/subscriptions/{subscriptionId}60,00024020,000120
PATCH /v1/subscriptions/{subscriptionId}15,000605,00030
DELETE /v1/subscriptions/{subscriptionId}15,000605,00030
POST /v1/subscriptions/{subscriptionId}/retry15,000605,00030
POST /v1/payments15,000605,00030
GET /v1/payments30,00012010,00060
GET /v1/payments/{paymentId}60,00024020,000120
POST /v1/payments/{paymentId}/cancel15,000605,00030
POST /v1/subscriptions/{subscriptionId}/migrate15,000605,00030
POST /v1/subscriptions/{subscriptionId}/resume15,000605,00030
GET /v1/webhooks/secret5,000602,00030
POST /v1/webhooks/secret/rotate2001020010
POST /v1/sandbox/charge002,00030
POST /v1/sandbox/advance002,00030

POST /v1/sandbox/charge and POST /v1/sandbox/advance only accept test keys — a live key is rejected with 403 before any limit is consulted. See Test mode.

Endpoints without limits

These are outside the metered /v1 surface and are not counted against any quota:

  • GET /api/openapi.json — the public OpenAPI document.
  • GET /api/health — the health probe.
  • The subscriber-facing checkout endpoints used by the hosted checkout page and the embed widget. Those carry no API key and are rate limited per IP address instead, since they're called by your payers' browsers rather than by your server.

Pagination

Every list endpoint (GET /v1/offers, /customers, /payments, /subscriptions) is offset-paginated: limit (max 100, default 20) and start (default 0). See Pagination for the full response shape, filters per endpoint, and how to detect the last page.

Idempotent creates

POST /v1/offers, /customers, and /subscriptions all accept an optional idempotencyKey in the request body (POST /v1/payments already requires one). Include a key you generate per logical operation — a UUID, or your own internal order id — and a retried request with the same key returns the original resource with 200 instead of creating a duplicate. This is what makes it safe to retry a POST after a timeout without knowing whether the first attempt actually landed.

The key is scoped to your merchant and to live/test mode, so a sandbox integration test can never collide with a live key. Omit idempotencyKey entirely and every call creates a new resource, exactly as before this field existed.

Need a higher limit?

The numbers above are defaults sized for typical integrations. If a legitimate workload needs more, get in touch before building around the limit — batching, caching offer and customer reads, or reacting to webhooks instead of polling usually removes the pressure entirely.

Prefer webhooks over polling

Polling GET /v1/subscriptions or GET /v1/payments on a timer is the most common way to burn a daily quota. Tidepay pushes subscription.*, payment.*, and wallet.* events to your endpoint as they happen — see Webhooks.

On this page