Skip to content
Developers
OpenAPI

Errors

API v1 Last updated 2026-09-30 View as Markdown

#Errors

Every response on /api/v1 other than the token endpoint is application/problem+json on failure:

{
  "type": "https://sailracing.club/docs/errors#API_TOKEN_INVALID",
  "status": 401,
  "code": "API_TOKEN_INVALID",
  "params": {},
  "traceId": "00-4bf9…-01"
}

type is this page, anchored at the code (the anchors below match exactly). params is only present when the code carries one (e.g. API_SCOPE_MISSING's scope). There is no title/detail sentence to parse — build your own copy from the code, using the table below.

The token endpoint follows RFC 6749 instead (an error field, not type/status) — see Get access token for its error shape specifically.

#Quick table

HTTP Code Where
400 API_TOKEN_REQUEST_INVALID token endpoint
400 API_GRANT_TYPE_UNSUPPORTED token endpoint
401 API_CLIENT_CREDENTIALS_INVALID token endpoint
400 API_SCOPE_INVALID token endpoint
401 API_TOKEN_MISSING data calls
401 API_TOKEN_INVALID data calls
401 API_CLIENT_DISABLED token endpoint + data calls
401 API_CLIENT_EXPIRED token endpoint + data calls
403 API_IP_NOT_ALLOWED token endpoint + data calls
403 API_SCOPE_MISSING data calls
400 API_PARAM_INVALID list paging
404 EVENT_NOT_FOUND event/race lookups
404 RACE_NOT_FOUND race lookups
429 RATE_LIMIT_EXCEEDED any call
503 API_UNAVAILABLE data calls
500 INTERNAL_ERROR any call

#Token endpoint errors

#API_TOKEN_REQUEST_INVALID

400. The request wasn't a valid client_credentials form post — e.g. credentials sent in both the Authorization header and the form body, or no Content-Type: application/x-www-form-urlencoded. Fix: send exactly one of Basic auth or form-body credentials, as application/x-www-form-urlencoded.

#API_GRANT_TYPE_UNSUPPORTED

400. grant_type was missing or wasn't client_credentials (this API doesn't support any other grant). Fix: always send grant_type=client_credentials.

#API_CLIENT_CREDENTIALS_INVALID

401. The client_id is unknown, or the client_secret is wrong — the same answer either way, so a brute-force attempt can't tell which half failed. Fix: double-check both values with the club admin; if correct, the secret may have been rotated — ask for a fresh one.

#API_SCOPE_INVALID

400. The scope= you asked for at the token endpoint includes something the client doesn't hold (or doesn't exist). params.scope names the offending value. Fix: request a subset of the client's actual scopes, or omit scope to get all of them. See Scopes.

#Shared (token endpoint + data calls)

#API_CLIENT_DISABLED

401 on data calls, 400 (unauthorized_client) on the token endpoint. The club admin disabled this client. Fix: ask the club admin to re-enable it (Settings → API access) — nothing you can do from the integration side.

#API_CLIENT_EXPIRED

401 on data calls, 400 (unauthorized_client) on the token endpoint. The client passed its expiresAt date. Fix: ask the club admin to extend or remove the expiry, or issue a new client.

#API_IP_NOT_ALLOWED

403 on data calls, 400 (unauthorized_client) on the token endpoint. The caller's IP isn't on the client's IP allowlist. Fix: ask the club admin to add your server's outbound IP, or remove the restriction if it isn't needed.

#Data call errors

#API_TOKEN_MISSING

401. No Authorization header was sent. Fix: send Authorization: Bearer <access_token> on every data call — see Authentication.

#API_TOKEN_INVALID

401. The bearer token is unknown, expired, was killed by a secret rotation, or isn't one of ours at all (e.g. you sent a Firebase/panel-user token by mistake — those are rejected here). Fix: fetch a fresh token from POST /api/v1/oauth/token and retry once; if it still fails, the credential itself needs attention.

#API_SCOPE_MISSING

403. The endpoint needs a scope your token doesn't have; params.scope names it. Fix: ask the club admin to add the scope to the client (Settings → API access → the client → Edit), then fetch a new token. See Scopes.

#API_PARAM_INVALID

400. A paging parameter on GET /api/v1/events was out of range; params.name is "page" or "pageSize". Fix: page ≥ 1, pageSize 1–100.

#EVENT_NOT_FOUND

404. The event id doesn't exist, isn't this club's (main or co-host), isn't published, or is a legacy id — all answered identically as "not found" rather than leaking which case it was. Fix: re-check the id came from GET /api/v1/events for this same credential; don't retry with the same id.

#RACE_NOT_FOUND

404. Same rule as EVENT_NOT_FOUND, for a race id. A malformed (non-UUID) id is also a 404, not a 400.

#Cross-cutting

#RATE_LIMIT_EXCEEDED

429. Too many requests. Data calls: 60/min per client (default, may be raised per club) — never per IP. The token endpoint: 20/min per client_id and 20/min per caller IP, whichever is hit first. Retry-After: 60 on both — always an integer number of seconds, never an HTTP date. Fix: back off for exactly Retry-After, with jitter if you're calling on behalf of many clients/events from one process; see Rate limits.

#API_UNAVAILABLE

503. The read model behind /api/v1 didn't answer in time — nothing is cached for this response. Retry-After: 30 (same integer-seconds format as 429, a shorter number because this is a different, usually brief problem). Fix: treat exactly like RATE_LIMIT_EXCEEDED — wait and retry; this is not your integration's fault.

#INTERNAL_ERROR

500. An unexpected server error; traceId is included. Fix: retry up to 3 attempts total, with exponential backoff (1s, then 2s, then 4s); if it still fails, contact support with the traceId.