Backwork

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"
  }
}
  • 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

CodeDescription
200Success
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing API key
402Payment Required - The organization's request credits cannot cover the call, or its pack does not include the feature
403Forbidden - Valid key but insufficient permissions
404Not Found - Resource doesn't exist, or the endpoint is not available in production
406Not Acceptable - Requested response format is not available
409Conflict - Idempotency key conflict or request still in progress
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Something went wrong on our end
503Service 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

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

AUTH_SANDBOX_TEST_KEY_REQUIRED

AUTH_TEST_KEY_SANDBOX_ONLY

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:

  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.

Last updated on

On this page