Skip to main content

Rate limits & errors

Every error response, on every route, uses one JSON envelope. Rate limits are per surface: public link resolution, /match, and the key-authenticated routes each carry their own budget.

The error envelope​

{
"statusCode": 404,
"error": "Not Found",
"message": "Link not found",
"timestamp": "2026-10-06T12:00:00.000Z",
"path": "/demo1/welcome1"
}
FieldContents
statusCodeHTTP status, repeated in the body
errorStatus name, e.g. Payment Required
messageHuman-readable cause
detailsPresent only on validation failures: one entry per bad field (below)
timestampISO 8601 UTC time of the response
pathRequest path that failed
requestIdYour X-Request-Id request header, echoed when you send one

Every response also carries an X-Request-Id header: your value if you sent one, otherwise a server-generated UUID. Send your own header to correlate a request across your logs and ours, and quote the value when contacting support about a failed call.

Validation errors​

Requests that fail field validation return 400 with message: "Validation failed" and one entry per bad field:

{
"statusCode": 400,
"error": "Bad Request",
"message": "Validation failed",
"details": [
{ "message": "clipboardToken must be shorter than or equal to 100 characters" }
],
"timestamp": "2026-10-06T12:00:00.000Z",
"path": "/match",
"requestId": "0d9a6f1e-2b3c-4d5e-8f90-1a2b3c4d5e6f"
}

Structured error codes​

Plan and workflow errors carry a machine-readable code next to the standard fields, so your code can branch without parsing messages:

StatuscodeExtra fieldsMeaning
402QUOTA_EXCEEDEDquotaA plan quota is exhausted; quota names it (e.g. "links")
402PLAN_NOT_SELECTED—The organization has no plan selected yet
403FEATURE_NOT_AVAILABLEfeatureThe plan doesn't include the feature (e.g. custom_domains)
422USE_BILLING_FLOWbillingEndpointA paid plan must be selected through checkout, not written directly
428APP_CONFIG_REQUIRED—Register your app before creating the first link

A quota error looks like this:

{
"statusCode": 402,
"error": "Payment Required",
"message": "You've reached your links limit.",
"code": "QUOTA_EXCEEDED",
"quota": "links",
"timestamp": "2026-10-06T12:00:00.000Z",
"path": "/links"
}

How to tell quota from auth problems:

  • 401 — the credential is missing, invalid, or the organization is suspended.
  • 403 — the credential is valid but not permitted: the wrong key tier on the route, or a role below the route's minimum. FEATURE_NOT_AVAILABLE (with a code) is a plan limit, not a permission problem.
  • 402 with a code — the account is fine; the plan is the limit. Limits per plan: Plans, quotas & entitlements.

Status codes​

StatusWhen you'll see it
400Validation failed, or a malformed path parameter (e.g. an orgKey that isn't 4 alphanumeric characters)
401Missing or invalid credential, or suspended organization
402Plan limit reached (QUOTA_EXCEEDED, PLAN_NOT_SELECTED)
403Wrong key tier, insufficient role, or feature not on the plan
404Unknown resource: link, link ID, or domain
409Conflict, e.g. a short code already in use in your organization
410Link no longer resolves: deactivated, expired, or organization suspended (resolution routes)
422Semantically invalid request (USE_BILLING_FLOW)
428Predecessor step missing (APP_CONFIG_REQUIRED)
429Rate limit exceeded (below)
500Server-side failure; retry with backoff and include the requestId

Rate limits​

Limits are fixed and counted in sliding windows. A request counts against your API key on key-authenticated routes and against the request IP everywhere else.

SurfaceLimitCounted per
Link resolution: redirect page and /data500 requests/secondIP
POST /match30 requests/minuteIP
GET /links, GET /links/:id, QR endpoints1,000 requests/secondAPI key
POST / PATCH / DELETE /links100 requests/secondAPI key
/sdk/session, /sdk/identity, /sdk/identity/clear60 requests/minuteAPI key
/sdk/events600 requests/minuteAPI key
Portal routes (/portal/*)30 requests/secondIP

The 429 response​

{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Retry after 42 second(s).",
"timestamp": "2026-10-06T12:00:00.000Z",
"path": "/match"
}

There are no rate-limit response headers: no Retry-After, no remaining-count headers. The retry delay is only in the message string, so clients that back off automatically should parse the seconds out of Retry after N second(s).

The /match limit is deliberately the tightest: 30 attempts per minute per IP blunts brute-force enumeration of one-time match tokens.