# Error Handling
URL: https://backworkhealth.com/docs/errors

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 [#error-response-format]

All error responses follow this structure:

```json
{
  "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"
  }
}
```

* `code` is stable; branch on it, not on `message`.
* `hint` names an action that works in production.
* `docUrl` links to the entry for the code on this page.
* `retryable` is `true` only when repeating the identical request later can succeed.

## HTTP Status Codes [#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 [#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`](#billing-credits-exhausted) and does not run the call:

```json
{
  "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](https://backworkhealth.com/api-usage); once the balance covers the call, send the request again. The refused call spends nothing. [Pricing and credits](/docs/pricing#when-credits-run-out) explains the packs, overage, and what each endpoint costs.

## Keyless Endpoints [#keyless-endpoints]

The keyless lookup endpoints under `/api/public/v1` are disabled in production and return `404` with [`COMMERCIAL_SURFACE_UNAVAILABLE`](#commercial-surface-unavailable). Use the authenticated `/api/v1` endpoints with a live key.

## Error Codes [#error-codes]

Every code the API returns, with its HTTP status, whether a retry can help, and what to do next.

### `AUTH_MISSING` [#auth-missing]

* **Message:** Missing Authorization header
* **HTTP status:** 401
* **Retryable:** No
* **Hint:** Include "Authorization: Bearer bwk\_xxx" header

### `AUTH_INVALID_FORMAT` [#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` [#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` [#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` [#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` [#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](https://backworkhealth.com/api-usage)

### `AUTH_KEY_SUSPENDED` [#auth-key-suspended]

* **Message:** API key has been suspended
* **HTTP status:** 401
* **Retryable:** No
* **Hint:** Contact [support@backworkhealth.com](mailto:support@backworkhealth.com) with the request\_id from this response

### `AUTH_KEY_EXPIRED` [#auth-key-expired]

* **Message:** API key has expired
* **HTTP status:** 401
* **Retryable:** No
* **Hint:** Create a new key at [https://backworkhealth.com/api-usage](https://backworkhealth.com/api-usage)

### `AUTH_SANDBOX_TEST_KEY_REQUIRED` [#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](https://backworkhealth.com/api/sandbox/v1/keys). Do not send a live key to the sandbox

### `AUTH_TEST_KEY_SANDBOX_ONLY` [#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](https://backworkhealth.com/api/sandbox/v1), or use a live key here

### `AUTHZ_SCOPE_REQUIRED` [#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` [#authz-ip-not-allowed]

* **Message:** Request from unauthorized IP address
* **HTTP status:** 403
* **Retryable:** No

### `AUTHZ_ORIGIN_NOT_ALLOWED` [#authz-origin-not-allowed]

* **Message:** Request from unauthorized origin
* **HTTP status:** 403
* **Retryable:** No

### `BILLING_FEATURE_REQUIRED` [#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](https://backworkhealth.com/api-usage)

### `BILLING_CREDITS_EXHAUSTED` [#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](https://backworkhealth.com/api-usage)

### `RATE_LIMIT_EXCEEDED` [#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` [#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` [#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` [#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` [#validation-invalid-json]

* **Message:** Invalid JSON body
* **HTTP status:** 400
* **Retryable:** No

### `VALIDATION_REQUIRED_FIELD` [#validation-required-field]

* **Message:** Required field is missing
* **HTTP status:** 400
* **Retryable:** No

### `VALIDATION_OUT_OF_RANGE` [#validation-out-of-range]

* **Message:** Value is out of allowed range
* **HTTP status:** 400
* **Retryable:** No

### `VALIDATION_INVALID_FORMAT` [#validation-invalid-format]

* **Message:** Invalid field format
* **HTTP status:** 400
* **Retryable:** No

### `PHI_NOT_ALLOWED` [#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` [#resource-not-found]

* **Message:** Resource not found
* **HTTP status:** 404
* **Retryable:** No

### `RESOURCE_POLICY_NOT_FOUND` [#resource-policy-not-found]

* **Message:** Policy not found
* **HTTP status:** 404
* **Retryable:** No

### `RESOURCE_CODE_NOT_FOUND` [#resource-code-not-found]

* **Message:** Medical code not found
* **HTTP status:** 404
* **Retryable:** No

### `RESOURCE_JURISDICTION_NOT_FOUND` [#resource-jurisdiction-not-found]

* **Message:** Jurisdiction not found
* **HTTP status:** 404
* **Retryable:** No

### `RESOURCE_PAYER_NOT_FOUND` [#resource-payer-not-found]

* **Message:** Payer not found
* **HTTP status:** 404
* **Retryable:** No

### `RESOURCE_RELEASE_NOT_AVAILABLE` [#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` [#request-invalid-cursor]

* **Message:** Invalid pagination cursor
* **HTTP status:** 400
* **Retryable:** No
* **Hint:** Use the cursor value from the previous response

### `REQUEST_IDEMPOTENCY_CONFLICT` [#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` [#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` [#request-unsupported-content-type]

* **Message:** Unsupported content type
* **HTTP status:** 400
* **Retryable:** No

### `REQUEST_FORMAT_NOT_ACCEPTABLE` [#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` [#server-internal-error]

* **Message:** Internal server error
* **HTTP status:** 500
* **Retryable:** No
* **Hint:** Contact [support@backworkhealth.com](mailto:support@backworkhealth.com) with the request\_id from this response

### `SERVER_DATABASE_ERROR` [#server-database-error]

* **Message:** Database error
* **HTTP status:** 500
* **Retryable:** No
* **Hint:** Contact [support@backworkhealth.com](mailto:support@backworkhealth.com) with the request\_id from this response

### `SERVER_SEARCH_UNAVAILABLE` [#server-search-unavailable]

* **Message:** Search service unavailable
* **HTTP status:** 500
* **Retryable:** Yes
* **Hint:** Retry with exponential backoff

### `SERVER_TIMEOUT` [#server-timeout]

* **Message:** Request timed out
* **HTTP status:** 500
* **Retryable:** Yes
* **Hint:** Retry with exponential backoff

### `SERVER_UNAVAILABLE` [#server-unavailable]

* **Message:** Service temporarily unavailable
* **HTTP status:** 503
* **Retryable:** Yes
* **Hint:** Retry with exponential backoff

### `SERVER_IDEMPOTENCY_UNAVAILABLE` [#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` [#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` [#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 [#handling-errors]

### JavaScript Example [#javascript-example]

```javascript
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 [#python-example]

```python
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 [#request-id]

Every response includes a `request_id` in the meta object. Include this when contacting support to help us debug issues:

```json
{
  "meta": {
    "request_id": "req_abc123def456"
  }
}
```

## Retry Strategy [#retry-strategy]

Retry only when `retryable` is `true`:

1. For `429` responses, wait the number of seconds in the `Retry-After` header.
2. Otherwise, wait 1 second, then 2, then 4.
3. Give up after 3 retries.

For `REQUEST_IDEMPOTENCY_IN_PROGRESS` and `SERVER_IDEMPOTENCY_UNAVAILABLE`, retry with the same `X-Idempotency-Key`.
