Error Handling
Error response format
Section titled “Error response format”All GoTab API errors return a JSON body. The shape is consistent across REST endpoints:
{ "error": "invalid_token", "message": "The access token has expired.", "statusCode": 401}Some validation errors (422) include field-level detail:
{ "error": "validation_failed", "message": "Request validation failed.", "statusCode": 422, "details": [ { "field": "api_access_id", "message": "Required" }, { "field": "grant_type", "message": "Must be one of: authorization_code, refresh_token" } ]}GraphQL errors follow the standard GraphQL error envelope:
{ "data": null, "errors": [ { "message": "Unauthorized", "extensions": { "code": "UNAUTHENTICATED" } } ]}HTTP status codes
Section titled “HTTP status codes”| Status | Meaning in GoTab context |
|---|---|
400 Bad Request | Malformed request — missing required fields or invalid JSON |
401 Unauthorized | Missing, expired, or revoked Bearer token |
403 Forbidden | Valid token but insufficient permissions for this resource |
404 Not Found | Resource doesn’t exist, or the endpoint path is wrong |
409 Conflict | Request conflicts with existing state (e.g. duplicate creation) |
422 Unprocessable Entity | Request is well-formed but fails validation — check details |
429 Too Many Requests | Rate limit exceeded — see Rate Limits |
500 Internal Server Error | GoTab-side error — safe to retry with backoff |
Authentication errors
Section titled “Authentication errors”401 — Token expired or revoked
{ "error": "invalid_token", "message": "The access token has expired.", "statusCode": 401 }Action: refresh the token using your refresh_token and retry the request. See OAuth Flows for the refresh call.
401 — Missing Authorization header
{ "error": "unauthorized", "message": "No authorization token provided.", "statusCode": 401 }Action: ensure every request includes Authorization: Bearer YOUR_TOKEN. See Authentication.
403 — Insufficient permissions
{ "error": "forbidden", "message": "Your credentials do not have access to this resource.", "statusCode": 403 }Action: verify the location has authorized your integration. Do not retry without re-authorizing.
Rate limit errors (429)
Section titled “Rate limit errors (429)”When you exceed the rate limit, the API returns:
{ "error": "rate_limit_exceeded", "message": "Too many requests.", "statusCode": 429 }The response includes a Retry-After header indicating how many seconds to wait:
HTTP/1.1 429 Too Many RequestsRetry-After: 15Wait the indicated time before retrying. For proactive management, see Rate Limits.
Validation errors (422)
Section titled “Validation errors (422)”Validation errors include a details array that identifies which fields failed and why:
{ "error": "validation_failed", "statusCode": 422, "details": [ { "field": "locationUuid", "message": "Invalid UUID format" }, { "field": "total", "message": "Must be a positive number" } ]}These are not retryable — fix the request data before retrying.
Retry strategies
Section titled “Retry strategies”Retryable errors: 429, 500, 502, 503, 504, and network timeouts.
Non-retryable errors: 400, 401 (until token is refreshed), 403, 404, 409, 422.
Exponential backoff
Section titled “Exponential backoff”For retryable errors, wait progressively longer between attempts:
async function fetchWithRetry(url, options, maxRetries = 4) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await fetch(url, options);
if (res.ok) return res.json();
// Non-retryable if ([400, 403, 404, 409, 422].includes(res.status)) { throw new Error(`Non-retryable error: ${res.status}`); }
// Respect Retry-After on 429 if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') ?? '15', 10); await sleep(retryAfter * 1000); continue; }
// Exponential backoff for 5xx and last attempt if (attempt === maxRetries) throw new Error(`Failed after ${maxRetries} retries`); await sleep(Math.min(1000 * 2 ** attempt, 30_000)); }}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));See also
Section titled “See also”- Rate Limits — Limits by API type and how to stay under them
- Authentication — Getting and refreshing Bearer tokens
- OAuth Flows — Token lifecycle, refresh, and revocation