Error Handling
Understanding API errors and how to handle them
The Backwork API uses conventional HTTP response codes and returns consistent error responses to help you handle errors gracefully.
Error Response Format
All error responses follow this structure:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable error message",
"hint": "Suggestion for fixing the error",
"docUrl": "https://backworkhealth.com/docs/errors#validation-error",
"retryable": false,
"details": { ... }
},
"meta": {
"request_id": "req_abc123"
}
}codeis stable; branch on it, not onmessage.hintnames an action that works in production.docUrllinks to the entry for the code on this page.retryableistrueonly when repeating the identical request later can succeed.
HTTP Status Codes
| Code | Description |
|---|---|
200 | Success |
400 | Bad Request - Invalid parameters |
401 | Unauthorized - Invalid or missing API key |
402 | Payment Required - The organization's request credits cannot cover the call, or its pack does not include the feature |
403 | Forbidden - Valid key but insufficient permissions |
404 | Not Found - Resource doesn't exist, or the endpoint is not available in production |
406 | Not Acceptable - Requested response format is not available |
409 | Conflict - Idempotency key conflict or request still in progress |
429 | Too Many Requests - Rate limit exceeded |
500 | Internal Server Error - Something went wrong on our end |
503 | Service Unavailable - A dependency is temporarily unavailable |
Request Credits
Every /api/v1 call spends request credits. When the organization's credits cannot cover a call and it has no active monthly pack billing overage, the API answers 402 with BILLING_CREDITS_EXHAUSTED and does not run the call:
{
"success": false,
"error": {
"code": "BILLING_CREDITS_EXHAUSTED",
"message": "Your organization has no request credits left for this call",
"hint": "details.requests_required is this call's cost and details.requests_available is the balance. Retry once the balance covers the call. An organization admin can manage the plan and usage in the Backwork dashboard at https://backworkhealth.com/api-usage",
"docUrl": "https://backworkhealth.com/docs/errors#billing-credits-exhausted",
"retryable": false,
"details": { "requests_required": 6, "requests_available": 4 }
},
"meta": { "request_id": "req_...", "timestamp": "..." }
}details.requests_required is the call's cost and details.requests_available is the balance. The error is not retryable: the same request fails until the balance changes. An organization admin can manage the plan and usage in API usage and keys; once the balance covers the call, send the request again. The refused call spends nothing. Pricing and credits explains the packs, overage, and what each endpoint costs.
Keyless Endpoints
The keyless lookup endpoints under /api/public/v1 are disabled in production and return 404 with COMMERCIAL_SURFACE_UNAVAILABLE. Use the authenticated /api/v1 endpoints with a live key.
Error Codes
Every code the API returns, with its HTTP status, whether a retry can help, and what to do next.
AUTH_MISSING
- Message: Missing Authorization header
- HTTP status: 401
- Retryable: No
- Hint: Include "Authorization: Bearer bwk_xxx" header
AUTH_INVALID_FORMAT
- Message: Invalid Authorization header format
- HTTP status: 401
- Retryable: No
- Hint: Use format: Authorization: Bearer bwk_xxx or Authorization: Bearer <OAuth access token>
AUTH_INVALID_KEY_FORMAT
- Message: Invalid API key format
- HTTP status: 401
- Retryable: No
- Hint: API keys start with bwk_live_ or bwk_test_ (legacy vrt_ keys still work)
AUTH_INVALID_TOKEN
- Message: Invalid or expired bearer token
- HTTP status: 401
- Retryable: No
- Hint: Use a valid Backwork API key or OAuth access token
AUTH_KEY_NOT_FOUND
- Message: API key not found
- HTTP status: 401
- Retryable: No
- Hint: Check that your API key is correct
AUTH_KEY_REVOKED
- Message: API key has been revoked
- HTTP status: 401
- Retryable: No
- Hint: A revoked key cannot be restored. Create a new key at https://backworkhealth.com/api-usage
AUTH_KEY_SUSPENDED
- Message: API key has been suspended
- HTTP status: 401
- Retryable: No
- Hint: Contact support@backworkhealth.com with the request_id from this response
AUTH_KEY_EXPIRED
- Message: API key has expired
- HTTP status: 401
- Retryable: No
- Hint: Create a new key at https://backworkhealth.com/api-usage
AUTH_SANDBOX_TEST_KEY_REQUIRED
- Message: The sandbox accepts only bwk_test_ keys
- HTTP status: 401
- Retryable: No
- Hint: Get a test key with POST https://backworkhealth.com/api/sandbox/v1/keys. Do not send a live key to the sandbox
AUTH_TEST_KEY_SANDBOX_ONLY
- Message: Test keys work only on the sandbox
- HTTP status: 401
- Retryable: No
- Hint: Send this key to https://backworkhealth.com/api/sandbox/v1, or use a live key here
AUTHZ_SCOPE_REQUIRED
- Message: Insufficient permissions for this operation
- HTTP status: 403
- Retryable: No
- Hint: details.required_scopes lists the scopes this operation accepts
AUTHZ_IP_NOT_ALLOWED
- Message: Request from unauthorized IP address
- HTTP status: 403
- Retryable: No
AUTHZ_ORIGIN_NOT_ALLOWED
- Message: Request from unauthorized origin
- HTTP status: 403
- Retryable: No
BILLING_FEATURE_REQUIRED
- Message: This organization's API access does not include this feature
- HTTP status: 402
- Retryable: No
- Hint: An organization admin can manage the plan and usage in the Backwork dashboard at https://backworkhealth.com/api-usage
BILLING_CREDITS_EXHAUSTED
- Message: Your organization has no request credits left for this call
- HTTP status: 402
- Retryable: No
- Hint: details.requests_required is this call's cost and details.requests_available is the balance. Retry once the balance covers the call. An organization admin can manage the plan and usage in the Backwork dashboard at https://backworkhealth.com/api-usage
RATE_LIMIT_EXCEEDED
- Message: Rate limit exceeded
- HTTP status: 429
- Retryable: Yes
- Hint: Wait the number of seconds in the Retry-After header, then retry
RATE_QUOTA_EXCEEDED
- Message: Daily quota exceeded
- HTTP status: 429
- Retryable: Yes
- Hint: Retry after the time in the X-RateLimit-Reset header
RATE_CONCURRENT_EXCEEDED
- Message: Too many concurrent requests
- HTTP status: 429
- Retryable: Yes
- Hint: Wait for in-flight requests to finish, then retry
VALIDATION_ERROR
- Message: Request validation failed
- HTTP status: 400
- Retryable: No
- Hint: Fix the fields listed in details, then send the corrected request
VALIDATION_INVALID_JSON
- Message: Invalid JSON body
- HTTP status: 400
- Retryable: No
VALIDATION_REQUIRED_FIELD
- Message: Required field is missing
- HTTP status: 400
- Retryable: No
VALIDATION_OUT_OF_RANGE
- Message: Value is out of allowed range
- HTTP status: 400
- Retryable: No
VALIDATION_INVALID_FORMAT
- Message: Invalid field format
- HTTP status: 400
- Retryable: No
PHI_NOT_ALLOWED
- Message: PHI/ePHI is not allowed for this endpoint
- HTTP status: 400
- Retryable: No
- Hint: Remove patient identifiers and submit only rule-level coverage inputs
RESOURCE_NOT_FOUND
- Message: Resource not found
- HTTP status: 404
- Retryable: No
RESOURCE_POLICY_NOT_FOUND
- Message: Policy not found
- HTTP status: 404
- Retryable: No
RESOURCE_CODE_NOT_FOUND
- Message: Medical code not found
- HTTP status: 404
- Retryable: No
RESOURCE_JURISDICTION_NOT_FOUND
- Message: Jurisdiction not found
- HTTP status: 404
- Retryable: No
RESOURCE_PAYER_NOT_FOUND
- Message: Payer not found
- HTTP status: 404
- Retryable: No
RESOURCE_RELEASE_NOT_AVAILABLE
- Message: Certified release is not available
- HTTP status: 404
- Retryable: No
- Hint: Use the release ID assigned to this dedicated certified-data key
REQUEST_INVALID_CURSOR
- Message: Invalid pagination cursor
- HTTP status: 400
- Retryable: No
- Hint: Use the cursor value from the previous response
REQUEST_IDEMPOTENCY_CONFLICT
- Message: Idempotency key conflict
- HTTP status: 409
- Retryable: No
- Hint: Use a new idempotency key for a request with a different body
REQUEST_IDEMPOTENCY_IN_PROGRESS
- Message: A request with this idempotency key is still in progress
- HTTP status: 409
- Retryable: Yes
- Hint: Retry the same request after a short delay to receive its result
REQUEST_UNSUPPORTED_CONTENT_TYPE
- Message: Unsupported content type
- HTTP status: 400
- Retryable: No
REQUEST_FORMAT_NOT_ACCEPTABLE
- Message: Requested response format is not available
- HTTP status: 406
- Retryable: No
- Hint: Certified release endpoints support JSON only
SERVER_INTERNAL_ERROR
- Message: Internal server error
- HTTP status: 500
- Retryable: No
- Hint: Contact support@backworkhealth.com with the request_id from this response
SERVER_DATABASE_ERROR
- Message: Database error
- HTTP status: 500
- Retryable: No
- Hint: Contact support@backworkhealth.com with the request_id from this response
SERVER_SEARCH_UNAVAILABLE
- Message: Search service unavailable
- HTTP status: 500
- Retryable: Yes
- Hint: Retry with exponential backoff
SERVER_TIMEOUT
- Message: Request timed out
- HTTP status: 500
- Retryable: Yes
- Hint: Retry with exponential backoff
SERVER_UNAVAILABLE
- Message: Service temporarily unavailable
- HTTP status: 503
- Retryable: Yes
- Hint: Retry with exponential backoff
SERVER_IDEMPOTENCY_UNAVAILABLE
- Message: Idempotency service temporarily unavailable; the request was not executed
- HTTP status: 503
- Retryable: Yes
- Hint: Retry the same request with the same idempotency key
SERVER_UPSTREAM_UNAVAILABLE
- Message: An upstream provider this endpoint depends on is temporarily unavailable
- HTTP status: 503
- Retryable: Yes
- Hint: No request credits were charged. Retry with exponential backoff
COMMERCIAL_SURFACE_UNAVAILABLE
- Message: This keyless endpoint is not available in production.
- HTTP status: 404
- Retryable: No
- Hint: Call the authenticated API with a live key instead, for example GET /api/v1/codes/lookup or GET /api/v1/policies/{id}
Handling Errors
JavaScript Example
async function makeApiRequest(url, attempt = 0) {
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
const data = await response.json();
if (!data.success) {
const { code, message, hint, retryable } = data.error;
if (retryable && attempt < 3) {
const retryAfter = Number(response.headers.get('Retry-After'));
const delaySeconds = retryAfter > 0 ? retryAfter : 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
return makeApiRequest(url, attempt + 1);
}
throw new Error(`${code}: ${message}${hint ? ` (${hint})` : ''}`);
}
return data.data;
}Python Example
import requests
class BackworkAPIError(Exception):
def __init__(self, code, message, hint=None, retryable=False):
self.code = code
self.message = message
self.hint = hint
self.retryable = retryable
super().__init__(message)
def make_api_request(url, api_key):
response = requests.get(url, headers={'Authorization': f'Bearer {api_key}'})
data = response.json()
if not data.get('success'):
error = data.get('error', {})
raise BackworkAPIError(
error.get('code'),
error.get('message'),
error.get('hint'),
error.get('retryable', False),
)
return data['data']Request ID
Every response includes a request_id in the meta object. Include this when contacting support to help us debug issues:
{
"meta": {
"request_id": "req_abc123def456"
}
}Retry Strategy
Retry only when retryable is true:
- For
429responses, wait the number of seconds in theRetry-Afterheader. - Otherwise, wait 1 second, then 2, then 4.
- Give up after 3 retries.
For REQUEST_IDEMPOTENCY_IN_PROGRESS and SERVER_IDEMPOTENCY_UNAVAILABLE, retry with the same X-Idempotency-Key.
Last updated on