Skip to content

Error Handling & Retries

The gateway is the single place that decides what to do when one of our resources fails. Consumers see one error contract; our resources can fail however they want.

Error Categories

Every error falls into one of four buckets. The retry policy is determined by the bucket, not by the specific error code.

CategoryExamplesRetry?
TransientNetwork timeout, 5xx, 429Yes, with backoff
Permanent400, 401, 403, 404, schema mismatchNo — surface to consumer
QuotaSF governor limit hitYes, after backoff window
UnknownAnything not classifiedYes, once, with backoff

Retry Strategy

Bounded

  • Max attempts: 3 (5 for idempotent writes with a key)
  • Max total time: 30s
  • Backoff: exponential with jitter, starting at 500ms

Idempotent

Only idempotent requests are retried. Non-idempotent requests fail fast on the first error. This is why the Idempotency-Key header is required on writes.

Circuit Breaker

If a resource returns errors above a threshold (e.g., >50% errors over 1 minute), the circuit opens. Subsequent calls fail fast with a circuit_open error for a cool-down period, then half-open probes.

Error Response Shape

Every error from the gateway uses this shape, regardless of which resource failed:

json
{
  "error": {
    "code": "sf_quota_exceeded",
    "message": "Salesforce API limit reached for this user",
    "retryable": true,
    "vendor_code": "REQUEST_LIMIT_EXCEEDED",
    "request_id": "uuid"
  }
}

Consumers branch on retryable and code. Resource-specific details are in vendor_code for debugging.

Mapping Resource Errors to Categories

Each connector has an explicit mapping table. For Salesforce:

SF ErrorCategory
REQUEST_LIMIT_EXCEEDEDQuota
INVALID_SESSION_IDTransient (refresh token, retry once)
MALFORMED_QUERYPermanent
STORAGE_LIMIT_EXCEEDEDPermanent
Network timeoutTransient
5xxTransient

The mapping lives in code, not in prompt logic. It is unit-tested.

Logging

Every error logs:

  • request_id
  • resource
  • endpoint
  • error_code
  • vendor_code
  • latency_ms
  • attempt (1, 2, 3)
  • trace_id
  • consumer_id

See Observability.

Marchay Platform Documentation