Skip to main content
reference 4 min read

Errors

HTTP status reference, OAuth and bearer-token error codes, rate-limit and theft-detection behaviour.

Every error response has the shape:

{
  "error": "code",
  "error_description": "human-readable explanation"
}

The error value is a stable string — your code can match against it without parsing English prose. Descriptions are best-effort and may evolve.

HTTP status reference

StatusMeaningTypical error
400 Bad RequestYour request was malformed, or the IGC file could not be parsedinvalid_request
401 UnauthorizedMissing or invalid credentialsinvalid_token / invalid_client
403 ForbiddenAuthenticated, but not allowedinsufficient_scope, unauthorized_client
404 Not FoundResource doesn't exist (or doesn't belong to you)not_found
410 GoneA delta cursor is older than the change-retention windowcursor_expired
413 Payload Too LargeIGC body over 3 MBpayload_too_large
429 Too Many RequestsRate limit exceeded(no error code — see Retry-After)
500 Internal Server ErrorBug on our sideserver_error

Delta-read errors (?since)

Two statuses, deliberately distinct, because the recovery actions are opposite and a client that confuses them either loops or loses data. Both bodies carry "recovery": "seed" so you can branch on one field.

StatusCodeMeaningWhat to do
400invalid_cursorMalformed, damaged, from another stream or account, or ahead of the streamA bug on one side or the other. Log it, then reseed
410cursor_expiredOlder than the 90-day retention, or the stream's history was resetReseed and reconcile
{
  "error": "cursor_expired",
  "error_description": "This cursor is older than the change-retention window, so a deletion may have aged out. Reseed with since=0 and delete every local row the seed does not return.",
  "recovery": "seed"
}

Neither is ever answered with a full collection and a 200. Silently succeeding is the one failure mode a client cannot detect. Reseeding means paging ?since=0 to completion and deleting local rows the seed did not return — see Incremental sync.

OAuth-specific errors

These follow RFC 6749 §5.2. They appear in 4xx responses from /api/oauth/token, /api/oauth/revoke, and /api/oauth/introspect.

CodeMeaning
invalid_requestRequired parameter missing or malformed
invalid_clientClient authentication failed (bad client_id/secret, or wrong auth method)
invalid_grantThe code/refresh_token is expired, already used, revoked, or doesn't match redirect_uri/PKCE
unauthorized_clientYour client isn't allowed to use this grant type, or your app is suspended
unsupported_grant_typeWe don't support that grant_type (we accept client_credentials, authorization_code, refresh_token)
invalid_scopeA requested scope is unknown or not allowed for your app
access_deniedThe user clicked "Cancel" on the consent screen

Device-code grant (RFC 8628 §3.5)

Returned from POST /api/oauth/token with grant_type=urn:ietf:params:oauth:grant-type:device_code:

CodeMeaningWhat to do
authorization_pendingThe user has not yet entered the user_codeKeep polling at the advertised interval
slow_downYou're polling faster than the advertised intervalAdd 5 seconds to your interval, keep polling
expired_tokenThe 10-minute code lifetime has elapsedRestart the flow at /api/oauth/device_authorization
access_deniedThe user clicked "Cancel"Surface to the user; don't restart automatically

Bearer-token errors (API v1)

These appear with a WWW-Authenticate header per RFC 6750 §3.

WWW-Authenticate: Bearer error="insufficient_scope", error_description="…", scope="flights:write"
CodeMeaningWhat to do
invalid_requestMissing or malformed Authorization headerFix the request
invalid_tokenToken unknown, expired, or revokedRefresh (OAuth) or re-issue (API key)
insufficient_scopeToken doesn't carry the required scopeRe-authorise with the right scope

Rate-limit errors

429 Too Many Requests is returned with:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1748426400

{
  "success": false,
  "error": "Rate limit exceeded. Please try again later.",
  "retryAfter": 12
}

Sleep Retry-After seconds before retrying. Permanent loops will get your app suspended.

Theft-detection cascade

If you present a refresh token that has already been rotated, all of your tokens for that user are revoked — RFC 6819 §5.2.2.3. You will receive invalid_grant immediately, and subsequent API calls with previously-issued access tokens will return invalid_token. The user must re-authorise from scratch.

This is non-overridable. Treat refresh tokens as single-use and atomically replace your stored copy with the new one before any retries.