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:
OMOPHubError on constructor misuse (missing API key) and OMOPHubIteratorError from async iterators (see below).
Error Shape
Error Codes
| Code | Cause |
|---|---|
missing_api_key, invalid_api_key, restricted_api_key | 401 / 403 |
not_found | 404 |
validation_error, missing_required_field, invalid_argument | 400 |
method_not_allowed, conflict | 405 / 409 |
rate_limit_exceeded, tier_limit_exceeded | 429 |
service_unavailable, internal_server_error | 5xx |
connection_error, timeout_error | Transport |
application_error | Catch-all |
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)
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 anIdempotency-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 atimeout_error (it does not retry timeouts):
AbortSignal
Pass anAbortSignal 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:
*All variant if you want errors as values:
Headers and Request IDs
Every response - success or error - includes the wire headers as a plainRecord<string, string>:
error.requestId is also populated automatically for support ticket triage.
Custom Fetch (Proxy / Instrumentation)
Pass your ownfetch implementation for proxying, logging, or test injection: