Errors, retries & idempotency
Every @goliath-data/sdk error class, the built-in retry policy, and how idempotent writes work
Errors
Gateway rejections and GraphQL errors throw typed exceptions:
| Class | When |
|---|---|
GoliathAuthenticationError (401) |
Missing/invalid/revoked key |
GoliathApiError (402) |
subscription_inactive: the organization has no usable plan. The SDK uses its base HTTP error class, with status and code; there is no dedicated subscription-error subclass |
GoliathPermissionError (403) |
Missing scope, or a guarded resource that is missing, deleted, or outside your organization’s access |
GoliathBadRequestError (400) |
Bad body, unknown operation, raw GraphQL, misplaced Idempotency-Key |
GoliathIdempotencyConflictError (409) |
Same Idempotency-Key still in flight — retry with the same key |
GoliathRateLimitError (429) |
Analytics rate limit, repeated subscription refusals, or pre-authentication IP throttle reached; retryAfterSeconds is null when no header was sent |
GoliathConcurrencyLimitError (429) |
Too many in-flight requests on this key |
GoliathServerError (5xx) |
Gateway-side failure |
GoliathOperationError |
The operation executed and returned errors (HTTP 200); carries every shaped error plus any partial data |
GoliathNetworkError / GoliathTimeoutError |
Transport failure or per-attempt timeout; a non-JSON success response also produces GoliathNetworkError |
To branch on a code without string literals, the package exports
GATEWAY_ERROR_CODES (the gateway’s own codes: unauthorized,
raw_query_forbidden, rate_limited, …) and OPERATION_ERROR_CODES (the
codes an operation’s errors carry: UNAUTHENTICATED, BAD_USER_INPUT, …).
onMeta receives a ResponseMeta: { status, replayed, retries }.
GoliathOperationError is the one to handle in normal application flow — it
means the gateway accepted and ran your call, but the operation itself
reported errors.
An operation can also report an outcome in its successful payload.
For example, applyTaskSet.failedStep signals partial completion without a
GraphQL error, so it does not throw GoliathOperationError. Check the
operation’s documented result fields too.
Retries (built in)
- Both
429s are pre-execution rejections — retried for any operation, honoringRetry-Afterwhen provided; otherwise they use backoff. - Network errors, timeouts, and
5xxs are retried only when a re-send cannot double-fire a side effect: queries, or mutations sent with anidempotencyKey. - A keyed
409 idempotency_conflictis retried with the same key, within the retry budget, to retrieve the original request’s result. - A mutation without an idempotency key is not retried after a network
failure, timeout, or
5xx; the pre-execution429retries still apply. 402 subscription_inactiveis not retried. Restore the subscription in the app; billing and profile recovery reads remain available with normal scopes.- Backoff is exponential with full jitter: 500ms base, 8s cap.
Idempotency
Every mutation accepts options.idempotencyKey:
await goliath.contacts.createContact(
{ input: { sellerFirstName: 'Jane', sellerLastName: 'Doe' } },
{ idempotencyKey: crypto.randomUUID() }
)
Always pass one on writes. Without it, a timed-out request may already have
changed data, and retrying it by hand can double-fire. Passing idempotencyKey
to a read is a compile error, matching the 400 the gateway returns at
runtime.
The key is any string of 1-255 characters (a UUID is the obvious choice). The gateway reserves it before executing and stores the response when the request completes; keys expire after 24 hours.