GoliathDevelopersGet an API key
Guides / Errors, retries & idempotency

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, honoring Retry-After when 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 an idempotencyKey.
  • A keyed 409 idempotency_conflict is 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-execution 429 retries still apply.
  • 402 subscription_inactive is 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.