Skip to main content

Errors Are Return Values

Unlike SDKs that throw on API errors, every method in @omophub/omophub-node returns a discriminated { data, error, meta, headers } union. Network failures, 4xx, and 5xx all surface as { data: null, error, headers }. TypeScript narrows data to the success type in the else branch:
The only thing the SDK throws is OMOPHubError on constructor misuse (missing API key) and OMOPHubIteratorError from async iterators (see below).

Error Shape

Error Codes

CodeCause
missing_api_key, invalid_api_key, restricted_api_key401 / 403
not_found404
validation_error, missing_required_field, invalid_argument400
method_not_allowed, conflict405 / 409
rate_limit_exceeded, tier_limit_exceeded429
service_unavailable, internal_server_error5xx
connection_error, timeout_errorTransport
application_errorCatch-all
Body-supplied error codes (from error.code or error.name in the response body) win over the generic status mapping - so a 400 carrying { error: { code: 'missing_required_field' } } surfaces as missing_required_field, not the generic validation_error.

Narrowing by Code

Synthetic Validation Errors

The SDK validates a few invariants client-side before issuing the request - concepts.batch (1–100 ids), mappings.map (exactly one of sourceConcepts/sourceCodes, non-empty), search.similar (exactly one of conceptId/conceptName/query), fhir.resolveBatch (1–100 codings), fhir.resolveCodeableConcept (1–20 codings). These return a synthetic error with statusCode: null and never hit the network:

Retries

The client automatically retries on:
  • HTTP 429, 502, 503, 504
  • Transient network errors (DNS failures, connection drops)
Backoff is full-jitter exponential: min(500 * 2^attempt, 8000) * (1 - 0.25 * random()). The Retry-After header is honoured up to a 60 s cap and floored at 100 ms (so Retry-After: 0 doesn’t become a spam-retry).

POST/PATCH Idempotency

To prevent accidental duplicate writes on retry, POST and PATCH only retry when an Idempotency-Key is set. Idempotent verbs (GET/HEAD/OPTIONS/PUT/DELETE) always retry.

Timeout

The per-request timeout defaults to 30 s. On expiry the SDK returns a timeout_error (it does not retry timeouts):

AbortSignal

Pass an AbortSignal via the per-call options to cancel a request mid-flight. Caller-initiated aborts re-throw as AbortError - they’re a deliberate cancellation, not an API error:

Async Iterators

*Iter variants (search.basicIter, search.semanticIter) are async generators. Generators can’t gracefully yield discriminated errors, so they throw OMOPHubIteratorError on page failure:
Prefer the eager *All variant if you want errors as values:

Headers and Request IDs

Every response - success or error - includes the wire headers as a plain Record<string, string>:
error.requestId is also populated automatically for support ticket triage.

Custom Fetch (Proxy / Instrumentation)

Pass your own fetch implementation for proxying, logging, or test injection:

Best Practices

Switch on error.name, Not Strings

Log error.requestId for Support

Set Idempotency Keys for Write-Like POSTs