Error Handling

Error Handling

BillerAPI uses conventional HTTP status codes and one structured error envelope on every non-2xx response, so you can branch on a stable code and show an actionable message.

The error envelope

Every error response returns this JSON body. It is the exact shape on the wire — no other error dialects.

Error response
JSON
{
  "error_code": "BILL_NOT_FOUND",
  "error_type": "invalid_request",
  "error_message": "No bill exists with id bill_abc123.",
  "hint": "Verify the bill_id from a recent list call.",
  "docs_url": "https://docs.billerapi.com/errors/BILL_NOT_FOUND",
  "request_id": "d94f5e2a-8c3b-4f1e-9a7d-6b2c1e0f8a34",
  "retryable": false
}
error_codeA stable, machine-readable code in UPPER_SNAKE_CASE (see the reference below). Branch on this — it is an additive-only contract.
error_typeCoarse category for retry/backoff decisions: invalid_request, rate_limit, auth, upstream, or api_error.
error_messageA human-readable description of the problem.
hintAn actionable next step for resolving the error.
docs_urlA link to this exact error code’s docs page (https://docs.billerapi.com/errors/<code>).
request_idCorrelation id for the failing request — also returned as the X-Request-Id response header on every response. Quote it in support tickets.
retryableWhether retrying or resuming the operation is supported. Older pass-through errors may omit this field during migration.
retry_afterOptional. On 429, the number of seconds to wait before retrying (also on the Retry-After header).
errorsOptional. On 400 validation failures, an array of per-field problems (see below).
Deprecated alias. During the current migration window some responses also include a documentation_url field. It duplicates docs_url and will be removed — read docs_url.

Validation errors

A 400 VALIDATION_ERROR carries an errors array — one entry per offending field, so you can map problems straight onto a form. Each item names the field in the same snake_case you sent it.

400 VALIDATION_ERROR
JSON
{
  "error_code": "VALIDATION_ERROR",
  "error_type": "invalid_request",
  "error_message": "One or more request fields are invalid. See errors[] for details.",
  "hint": "Inspect errors[] for the offending fields and retry with a corrected request.",
  "docs_url": "https://docs.billerapi.com/errors/VALIDATION_ERROR",
  "request_id": "d94f5e2a-8c3b-4f1e-9a7d-6b2c1e0f8a34",
  "errors": [
    { "param": "given_name", "code": "required", "message": "given_name should not be empty" },
    { "param": "email", "code": "invalid_format", "message": "email must be an email" }
  ]
}
paramThe offending field, in the snake_case the API accepts. A stray camelCase field is reported with code unknown_parameter and a hint about the expected name.
codeA stable reason: required, invalid_type, invalid_format, too_short, too_long, unknown_parameter.
messageA human-readable, single-field explanation.

HTTP Status Codes

CodeMeaningDescription
200OKRequest succeeded.
201CreatedResource was created successfully.
202AcceptedRequest accepted for async processing (e.g., bill sync).
400Bad RequestInvalid request body or parameters (see errors[]).
401UnauthorizedMissing or invalid authentication credentials.
403ForbiddenValid credentials but insufficient permissions.
404Not FoundThe requested resource does not exist.
429Too Many RequestsRate limit exceeded. See retry_after / Retry-After.
500Internal Server ErrorSomething went wrong on our end. Contact support if persistent.

Error code reference

The error_code field is one of these stable codes. Each links to a dedicated page at https://docs.billerapi.com/errors/<code> with cause, fix, and an example. The list is additive-only.

General

Error CodeHTTPTypeSummary
INTERNAL_ERROR500api_errorAn unexpected error occurred on our end.
VALIDATION_ERROR400invalid_requestOne or more request fields are invalid.
NOT_FOUND404invalid_requestThe requested resource does not exist.
CONFLICT409invalid_requestThe request conflicts with the current resource state.
INVALID_CURSOR400invalid_requestThe pagination cursor is malformed or stale.
IDEMPOTENCY_KEY_MISMATCH409invalid_requestThe Idempotency-Key was reused with a different body.
IDEMPOTENCY_KEY_NOT_REPLAYABLE409invalid_requestThe one-time response for this Idempotency-Key cannot be replayed.
RATE_LIMITED429rate_limitYou have exceeded the allowed request rate.
SERVICE_UNAVAILABLE503upstreamAn upstream dependency is temporarily unavailable.
WEBHOOK_NOT_FOUND404invalid_requestNo webhook with that id.
WEBHOOK_ENDPOINT_NOT_FOUND404invalid_requestNo webhook endpoint with that id.
INVALID_WEBHOOK_UPDATE400invalid_requestThe webhook patch failed validation.
WEBHOOK_REGISTRATION_FAILED500api_errorThe webhook could not be persisted.
WEBHOOK_CONFIGURATION_NOT_FOUND404invalid_requestThis client has no webhook configuration for that environment.
INVALID_IDEMPOTENCY_KEY400invalid_requestThe Idempotency-Key header is missing or malformed.
IDEMPOTENCY_KEY_REUSED409invalid_requestThe Idempotency-Key was reused with different parameters.
WEBHOOK_LOOKUP_FAILED500api_errorThe webhook ownership row could not be read.
WEBHOOK_UPDATE_FAILED500api_errorThe endpoint update could not be persisted.
WEBHOOK_UPDATE_OUTCOME_UNKNOWN500api_errorThe update committed but its stored state could not be read back.
WEBHOOK_ROTATION_FAILED500api_errorThe signing-secret rotation could not be persisted.
INVALID_WEBHOOK_OUTCOME_ID400invalid_requestThe delivery-outcome identity is missing or malformed.
WEBHOOK_OUTCOME_UNKNOWN500api_errorA prior delivery outcome could not be resolved.
WEBHOOK_OUTCOME_ID_REUSED409invalid_requestThe delivery-outcome identity was reused with different parameters.
WEBHOOK_DISABLE_NOTIFICATION_PENDING500api_errorThe endpoint was auto-disabled but the owner was not notified.
WEBHOOK_UPDATE_CONFLICT409invalid_requestA concurrent writer updated this endpoint first.
WEBHOOK_SAVE_OUTCOME_UNKNOWN500api_errorThe write may or may not have committed.
WEBHOOK_REGISTRATION_CONFLICT409invalid_requestA concurrent registration for this environment won the race.
WEBHOOK_REGISTRATION_OUTCOME_UNKNOWN500api_errorThe registration may have committed without a confirmed result.
WEBHOOK_ROTATION_CONFLICT409invalid_requestA concurrent rotation replaced the signing secret first.
WEBHOOK_ROTATION_OUTCOME_UNKNOWN500api_errorThe rotation may have committed without a confirmed result.
NOT_IMPLEMENTED500api_errorThe operation is declared but not implemented on this service.
UNKNOWN500api_errorThe identity provider refused the operation for an unclassified reason.
BAD_REQUEST400invalid_requestA caller-side precondition failed.

Authentication

Error CodeHTTPTypeSummary
UNAUTHORIZED401authAuthentication failed or is missing.
FORBIDDEN403authAuthenticated, but not authorized for this resource.
CLIENT_ID_MISMATCH403authThe supplied client_id does not match your session.
NEEDS_RESIGNIN401authNo authenticated client context was resolved.
SIGNIN_CREDENTIALS_REJECTED401authSign-in was refused.
AUTHENTICATION_INCOMPLETE401authSign-in stopped short of issuing tokens.
USER_NOT_CONFIRMED401authThe account was never confirmed after signup.
INVALID_REFRESH_TOKEN401authThe refresh token was rejected.
INVALID_CONFIRMATION_CODE400invalid_requestThe signup confirmation code did not match.
PASSWORD_RESET_FAILED400invalid_requestThe password reset could not be applied.
FORGOT_PASSWORD_FAILED500api_errorThe forgot-password flow could not be started.
SIGNUP_FAILED400invalid_requestThe account could not be created.
WRONG_PASSWORD401authThe current password on a change-password call is wrong.
WEAK_PASSWORD400invalid_requestThe proposed password fails the password policy.
USER_NOT_FOUND404invalid_requestNo user with that id under this client.
CLIENT_NOT_FOUND404invalid_requestNo client account with that client_id.
EMAIL_ALREADY_VERIFIED409invalid_requestThe address is already verified.
VERIFICATION_COOLDOWN429rate_limitA verification code was requested too soon after the last one.
VERIFICATION_CODE_INVALID400invalid_requestThe email verification code did not match.
VERIFICATION_CODE_EXPIRED400invalid_requestThe email verification code has expired.
VERIFICATION_SEND_FAILED500api_errorThe verification email could not be sent.
EMAIL_NOT_VERIFIED400invalid_requestMinting a secret requires a verified email.
OPERATOR_VERIFICATION_REQUIRED400invalid_requestProduction secrets require completed business verification.
SANDBOX_FIXED_SECRET400invalid_requestThe sandbox secret cannot be minted, rotated, or revoked.
SECRET_ALREADY_EXISTS409invalid_requestAn active secret already exists for this environment.
INVALID_ENVIRONMENT400invalid_requestThe environment value is not recognized.
SECRET_NOT_FOUND404invalid_requestNo secret exists for this client and environment.
SECRET_EXPIRED401authThe client secret is past its expiry.
SECRET_ROTATION_FAILED500api_errorThe secret rotation could not be completed.
SECRET_REVOCATION_FAILED500api_errorThe secret revocation could not be completed.
SECRET_GENERATION_FAILED500api_errorA new secret value could not be generated or stored.
CLIENT_SECRET_ROTATION_CONFLICT409invalid_requestA concurrent rotate or revoke won the race.
CLIENT_SECRET_REVOKE_CONFLICT409invalid_requestThe secret you asked to revoke was already replaced.
CLIENT_SECRET_CLEANUP_EXHAUSTED500api_errorToo many stranded credentials to retire automatically.
WEBAUTHN_CHALLENGE_FAILED500api_errorThe passkey challenge could not be issued.
WEBAUTHN_VERIFICATION_FAILED400invalid_requestThe passkey ceremony response did not verify.
WEBAUTHN_CREDENTIAL_NOT_FOUND404invalid_requestNo registered passkey matches that credential id.
VERIFICATION_REQUEST_FAILED500api_errorThe business-verification request could not be recorded.
LIVE_ACCESS_REQUEST_FAILED500api_errorThe live-access request could not be recorded.
SENDER_NOT_VERIFIED500api_errorThe platform sender domain is not verified with the mail provider.
CONFIG_MISSING500api_errorEmail delivery is not configured in this environment.
CODE_MISMATCH400invalid_requestThe emailed code did not match.
CODE_EXPIRED400invalid_requestThe emailed code is past its expiry window.
NO_ACTIVE_CODE400invalid_requestNo code has been requested for this user.
LOCKED429rate_limitVerification is locked after too many wrong attempts.
COOLDOWN429rate_limitA code was sent recently — resend is on cooldown.
CODE_ROTATED409invalid_requestA newer code was issued while this attempt was in flight.
INVALID_CODE_FORMAT400invalid_requestThe verification code must be exactly 6 digits.
LOOKUP_FAILED500api_errorThe stored verification code could not be read.
INVALID_PASSWORD400invalid_requestThe new password was rejected by the password policy.
TERMS_ACCEPTANCE_REQUIRED400invalid_requestSignup requires explicit acceptance of the current legal documents.
LEGAL_VERSION_OUTDATED400invalid_requestThe accepted legal document versions are no longer current.
LEGAL_ACCEPTANCE_INVALID400invalid_requestThe legal acceptance evidence is malformed.

Bills & statements

Error CodeHTTPTypeSummary
BILL_NOT_FOUND404invalid_requestNo bill exists with that id.
ACCOUNT_LINK_NOT_FOUND404invalid_requestThe account link does not exist.
STATEMENT_NOT_EXTRACTED425invalid_requestThe statement has not been extracted yet.
EXTRACTION_FAILED503upstreamStatement extraction failed upstream.
EXTRACTION_UNSUPPORTED_FOR_BILLER422invalid_requestThis biller does not support statement extraction.
STATEMENT_TOO_LARGE413upstreamThe statement exceeds the size limit.

Links & Connect

Error CodeHTTPTypeSummary
LINK_NOT_FOUND404invalid_requestNo link exists with that id.
LINK_TOKEN_NOT_FOUND404invalid_requestThe link token is unknown or expired.
BACKGROUND_NOT_ELIGIBLE409invalid_requestThe link token cannot be backgrounded now.
UPDATE_BILLER_MISMATCH409invalid_requestThe update token targets a different biller.
INVALID_UPDATE_REASON400invalid_requestThe update reason is not a recognized value.
LINK_UPDATE_FORBIDDEN403authThe update token belongs to a different client.
REDIRECT_URI_NOT_REGISTERED400invalid_requestThe redirect_uri is not in your registered allowlist.
LINK_TOKEN_LOOKUP_FAILED503upstreamThe link token could not be read.
LINK_TOKEN_EXPIRED410invalid_requestThe link token expired before the flow completed.
LINK_TOKEN_NOT_USABLE409invalid_requestThe link token cannot accept this step.
LINK_ALREADY_EXISTS409invalid_requestAn active link already exists for this account.
REQUEST_TO_LINK_NOT_FOUND404invalid_requestNo request-to-link exists with that id.
CLIENT_ID_REQUIRED400invalid_requestThis read refuses to run unscoped.
INVALID_PUBLIC_TOKEN400invalid_requestThe public token is invalid or already exchanged.
INVALID_CREDENTIALS400invalid_requestThe biller rejected the credentials.
BILLER_ACCOUNT_LOCKED409invalid_requestThe biller locked the account.
MFA_NO_CHALLENGE409invalid_requestNo MFA challenge is outstanding.
MFA_ATTEMPTS_EXHAUSTED409invalid_requestEvery MFA attempt was used.
MFA_RESEND_LIMIT_REACHED429rate_limitThe MFA resend limit was reached.
EMPTY_ACCOUNT_SELECTION400invalid_requestNo accounts were selected.
NO_DISCOVERED_ACCOUNTS409invalid_requestNothing was discovered at the biller.
CREDENTIAL_NOT_FOUND404invalid_requestNo vaulted credential with that id.
CREDENTIAL_INVALID400invalid_requestThe credential failed validation.
CREDENTIAL_OWNER_MISMATCH403authThe credential belongs to another owner.
CREDENTIAL_REVOKED409invalid_requestThe credential is revoked.
CREDENTIAL_TYPE_REQUIRED400invalid_requestcredential_type is missing.
CREDENTIAL_NOT_ROTATABLE409invalid_requestThe credential cannot be rotated now.
CREDENTIAL_ENCRYPTION_FAILED500api_errorThe credential could not be encrypted or decrypted.
CREDENTIAL_PERSIST_FAILED500api_errorThe credential could not be stored.
CREDENTIAL_LOOKUP_FAILED500api_errorThe credential store could not be read.
SCRAPING_SESSION_KEY_REQUIRED400invalid_requestThe session cache key is incomplete.
SCRAPING_SESSION_LOOKUP_FAILED500api_errorThe session cache could not be read.
SCRAPING_SESSION_SAVE_FAILED500api_errorThe session cache could not be written.
SCRAPING_SESSION_INVALIDATE_FAILED500api_errorThe cached session could not be invalidated.
SCRAPING_SESSION_INVALID400invalid_requestThe session snapshot failed validation.
MFA_SUBMISSION_INVALID400invalid_requestThe MFA submission failed validation.
MFA_SUBMISSION_LOOKUP_FAILED500api_errorThe MFA submission store could not be read.
MFA_SUBMISSION_PERSIST_FAILED500api_errorThe MFA submission could not be recorded.
MFA_CONTINUATION_UNAVAILABLE409invalid_requestNo live MFA continuation is awaiting this code.
MFA_CONTINUATION_STALE409invalid_requestThe continuation_id is out of date.
PENDING_MFA_INVALID400invalid_requestThe pending-MFA snapshot failed validation.
PENDING_MFA_LOOKUP_FAILED500api_errorThe pending-MFA store could not be read.
PENDING_MFA_PERSIST_FAILED500api_errorThe pending-MFA snapshot could not be written.
PENDING_MFA_RESEND_FAILED500api_errorThe OTP resend could not be recorded.

Billers

Error CodeHTTPTypeSummary
BILLER_NOT_CONNECT_READY409invalid_requestThe biller is not ready to connect.
BILLER_NOT_FOUND404invalid_requestNo biller exists with that id.
BILLER_UNSUPPORTED422invalid_requestThis biller cannot be automated.

Payments

Error CodeHTTPTypeSummary
PAYMENT_EXECUTION_NOT_AVAILABLE501upstreamPayment execution is not yet available.

Feedback

Error CodeHTTPTypeSummary
FEEDBACK_RUN_NOT_FOUND404invalid_requestThe referenced resource does not exist.
FEEDBACK_RUN_NOT_OWNED403authThe referenced resource belongs to another client.
FEEDBACK_RUN_EXPIRED410invalid_requestThe feedback window has closed.
FEEDBACK_INVALID_CATEGORY422invalid_requestThe category is not valid for this resource type.
FEEDBACK_INVALID_SIGNAL400invalid_requestThe signal is not valid for the resource_type.
FEEDBACK_ALREADY_SUBMITTED409invalid_requestFeedback was already submitted for this resource.
FEEDBACK_RATE_LIMITED429rate_limitToo many feedback submissions.

Messaging

Error CodeHTTPTypeSummary
MESSAGING_CONSENT_NOT_GRANTED403invalid_requestThe customer has not granted messaging consent.
MESSAGING_LIVE_ACCESS_REQUIRED403authMessaging requires production (live) access.
MESSAGING_SENDER_NOT_AUTHORIZED403authThe sender is not authorized for this account link.
MESSAGING_CONTENT_FLAGGED422invalid_requestThe message content was flagged.
MESSAGING_RATE_LIMITED429rate_limitMessaging rate limit exceeded.
MESSAGING_SUPPRESSED200invalid_requestThe recipient is suppressed; message not sent.
MESSAGING_AUP_NOT_ACCEPTED428invalid_requestThe messaging Acceptable Use Policy is not accepted.
MESSAGING_AUP_REACCEPT_REQUIRED412invalid_requestThe messaging AUP must be re-accepted.
MESSAGING_INVALID_PAYLOAD400invalid_requestThe message payload is malformed.
MESSAGING_INVALID_CATEGORY400invalid_requestThe message category is not recognized.
MESSAGING_PERSIST_FAILED503upstreamA transient error prevented recording the message.
MESSAGING_INTERNAL_ERROR500upstreamAn unexpected messaging error occurred.
MESSAGING_NOT_FOUND404invalid_requestThe referenced message or thread does not exist.

Email & discovery

Error CodeHTTPTypeSummary
EMAIL_MESSAGE_NOT_FOUND404invalid_requestNo stored email message with that id.
EMAIL_MESSAGE_LOOKUP_FAILED503upstreamThe message store could not be read.
EMAIL_MESSAGE_PERSIST_FAILED500api_errorThe message could not be stored.
EMAIL_CLIENT_ID_REQUIRED400invalid_requestclient_id is required to read a message.
EMAIL_CLASSIFICATION_NOT_FOUND404invalid_requestNo classification for that id or message.
EMAIL_CLASSIFICATION_LOOKUP_FAILED503upstreamThe classification store could not be read.
EMAIL_CLASSIFICATION_PERSIST_FAILED500api_errorThe classification could not be stored.
EMAIL_CLASSIFICATION_INVALID400invalid_requestThe classification aggregate refused the transition.
EMAIL_CLASSIFICATION_FORBIDDEN403authThe message behind this classification belongs to another client.
HUMAN_REVIEW_NOT_FOUND404invalid_requestNo human-review task with that id.
HUMAN_REVIEW_LOOKUP_FAILED503upstreamThe review store could not be read.
HUMAN_REVIEW_PERSIST_FAILED500api_errorThe review could not be stored.
HUMAN_REVIEW_CLAIM_CONFLICT409invalid_requestAnother worker already holds this review.
HUMAN_REVIEW_INVALID_STATE409invalid_requestThe review is not in a state this operation accepts.
HUMAN_REVIEW_COMPLETION_REJECTED409invalid_requestThe review completion was refused.
GMAIL_OAUTH_STATE_INVALID400authThe Gmail OAuth state did not verify.
GMAIL_TOKEN_EXCHANGE_FAILED503upstreamGoogle refused the authorization-code exchange.
GMAIL_ACCESS_TOKEN_UNAVAILABLE503upstreamNo usable Gmail access token for this connection.
GMAIL_WATCH_NOT_FOUND404invalid_requestNo Gmail watch registration for that mailbox.
GMAIL_IMPORT_FAILED503upstreamMail could not be imported from Gmail.
GMAIL_DISCONNECT_FAILED500api_errorThe Gmail disconnect did not complete.
GMAIL_MAILBOX_ALREADY_CONNECTED409invalid_requestThis Gmail mailbox is already connected to another account.

Agent traces & debug

Error CodeHTTPTypeSummary
TRACE_NOT_FOUND404invalid_requestNo agent-trace run with that run_id.
TRACE_LOOKUP_FAILED500api_errorThe trace metadata store could not be read.
TRACE_LIST_FAILED500api_errorThe trace list or recent-feed query was rejected.
TRACE_PERSIST_FAILED500api_errorThe trace row could not be written.
TRACE_INVALID_STATE500api_errorThe stored trace row cannot be acted on.
TRACE_ASSET_FORBIDDEN403invalid_requestThe asset key is malformed or outside the run prefix.
TRACE_ASSET_READ_FAILED500api_errorThe artifact object could not be read.
TRACE_ARTIFACT_REJECTED400invalid_requestThe artifact is refused by credential containment.
TRACE_ARTIFACT_UPLOAD_FAILED500api_errorThe artifact could not be written to storage.
TRACE_FIXTURE_EXPORT_FAILED500api_errorThe fixture archive could not be built.
EVENT_ARCHIVE_QUERY_FAILED500api_errorThe archived-event store could not be queried.
EVENT_ARCHIVE_EVENT_NOT_FOUND404invalid_requestNo archived event with that id.
DEBUG_PARAMETER_NOT_FOUND404invalid_requestNo configuration parameter at that path.
DEBUG_PARAMETER_NAME_INVALID400invalid_requestThe parameter name is outside the writable namespace.
DEBUG_PARAMETER_LOOKUP_FAILED500upstreamParameter Store could not be read.
DEBUG_PARAMETER_UPDATE_FAILED500upstreamThe parameter write was rejected.
DEBUG_SERVICE_LIST_FAILED500upstreamThe per-service parameter rollup could not be built.
DEBUG_ALARM_QUERY_FAILED500upstreamLive alarm state could not be read.

Agent improvement

Error CodeHTTPTypeSummary
AGENT_TRACE_NOT_FOUND404invalid_requestNo agent trace for that run.
AGENT_TRACE_INVALID400invalid_requestThe agent trace payload is invalid.
AGENT_TRACE_LOOKUP_FAILED503upstreamThe agent trace store could not be read.
AGENT_TRACE_PERSIST_FAILED500api_errorThe agent trace could not be written.
AGENT_TRACE_OUTCOME_EMIT_FAILED500api_errorThe trace outcome event could not be published.
AGENT_TRACE_EVENT_BUS_UNAVAILABLE503upstreamNo event bus is wired for trace outcomes.
AGENT_AUDIT_LOG_INVALID400invalid_requestThe audit log entry is invalid.
AGENT_AUDIT_LOG_PERSIST_FAILED500api_errorThe audit log entry could not be appended.
AGENT_AUDIT_LOG_LOOKUP_FAILED503upstreamThe audit log could not be read.
PROMPT_VERSION_NOT_FOUND404invalid_requestNo prompt version with that hash.
PROMPT_VERSION_INVALID400invalid_requestThe prompt version payload is invalid.
PROMPT_VERSION_LOOKUP_FAILED503upstreamThe prompt version store could not be read.
PROMPT_VERSION_PERSIST_FAILED500api_errorThe prompt version could not be written.
PROMPT_POINTER_NOT_FOUND404invalid_requestNo production prompt pointer for that agent.
PROMPT_POINTER_CAS_MISMATCH409invalid_requestThe production pointer moved under you.
PROMPT_POINTER_INVALID400invalid_requestThe production pointer payload is invalid.
PROMPT_POINTER_FLIP_FAILED500api_errorThe production pointer could not be written.
PROMPT_POINTER_LOOKUP_FAILED503upstreamThe production pointer could not be read.
PROMPT_POINTER_ROLLBACK_NO_PRIOR409invalid_requestNo prior prompt version to roll back to.
PROMPT_POINTER_ROLLBACK_HASH_MISMATCH409invalid_requestThe rollback guard hash does not match.
PROMPT_EXPERIMENT_NOT_FOUND404invalid_requestNo prompt experiment with that id.
PROMPT_EXPERIMENT_ALREADY_ACTIVE409invalid_requestAn experiment is already running for that agent.
PROMPT_EXPERIMENT_VARIANT_NOT_FOUND400invalid_requestThe experiment arm is not a registered prompt version.
PROMPT_EXPERIMENT_NO_CONTROL400invalid_requestThe experiment has no control arm.
PROMPT_EXPERIMENT_INVALID400invalid_requestThe prompt experiment payload is invalid.
PROMPT_EXPERIMENT_LOOKUP_FAILED503upstreamThe prompt experiment store could not be read.
PROMPT_EXPERIMENT_PERSIST_FAILED500api_errorThe prompt experiment could not be written.
PROMPT_EVAL_SCORE_INVALID400invalid_requestThe prompt eval score is invalid.
PROMPT_EVAL_SCORE_PERSIST_FAILED500api_errorThe prompt eval score could not be written.
BRAIN_ENTRY_NOT_FOUND404invalid_requestNo brain entry with that id.
BRAIN_ENTRY_INVALID400invalid_requestThe brain entry payload is invalid.
BRAIN_ENTRY_INVALID_STATE409invalid_requestThe brain entry is in the wrong state for that decision.
BRAIN_ENTRY_LOOKUP_FAILED503upstreamThe brain store could not be read.
BRAIN_ENTRY_PERSIST_FAILED500api_errorThe brain entry could not be written.
BRAIN_EFFECT_LOOKUP_FAILED503upstreamThe brain effect snapshots could not be read.
BRAIN_EFFECT_COMPUTE_FAILED500api_errorThe brain entry effect could not be computed.
BRAIN_SEARCH_FAILED503upstreamThe brain search could not be executed.
RECORDING_VERSION_NOT_FOUND404invalid_requestNo recording version with that hash.
RECORDING_VERSION_INVALID400invalid_requestThe recording version payload is invalid.
RECORDING_VERSION_LOOKUP_FAILED503upstreamThe recording version store could not be read.
RECORDING_VERSION_PERSIST_FAILED500api_errorThe recording version could not be written.
RECORDING_POINTER_NOT_FOUND404invalid_requestNo production recording pointer for that biller.
RECORDING_POINTER_LOOKUP_FAILED503upstreamThe production recording pointer could not be read.
CANARY_ALREADY_RUNNING409invalid_requestA canary is already running.
CANARY_NOT_RUNNING409invalid_requestNo canary is running.
CANARY_TERMINAL409invalid_requestThe canary already reached a terminal state.
CANARY_CANDIDATE_NOT_FOUND404invalid_requestThe canary candidate recording does not exist.
CANARY_NO_BASELINE409invalid_requestThe canary has no baseline to compare against.
CANARY_INVALID400invalid_requestThe canary payload is invalid.
CANARY_CAS_MISMATCH409invalid_requestThe recording pointer moved during the canary decision.
CANARY_LOOKUP_FAILED503upstreamThe canary state could not be read.
CANARY_PERSIST_FAILED500api_errorThe canary state could not be written.
CANARY_METRICS_UNAVAILABLE503upstreamThe canary metrics could not be computed.
AGENT_FREEZE_INVALID400invalid_requestThe agent freeze request is invalid.
AGENT_FREEZE_PERSIST_FAILED500api_errorThe agent freeze state could not be written.
AGENT_FREEZE_LOOKUP_FAILED503upstreamThe agent freeze state could not be read.
CLIENT_FEEDBACK_NOT_FOUND404invalid_requestNo client feedback for that run.
CLIENT_FEEDBACK_INVALID400invalid_requestThe client feedback payload is invalid.
CLIENT_FEEDBACK_ALREADY_SUBMITTED409invalid_requestFeedback was already submitted for that run.
CLIENT_FEEDBACK_IDEMPOTENCY_CONFLICT409invalid_requestThe idempotency key was reused with different content.
CLIENT_FEEDBACK_LOOKUP_FAILED503upstreamThe client feedback store could not be read.
CLIENT_FEEDBACK_PERSIST_FAILED500api_errorThe client feedback could not be written.
FEEDBACK_INVITED_MARKER_FAILED500api_errorThe feedback-invited marker could not be written.
EXECUTION_RUN_NOT_FOUND404invalid_requestNo execution run with that id.
EXECUTION_RUN_INVALID400invalid_requestThe execution run payload is invalid.
EXECUTION_RUN_INVALID_STATE409invalid_requestThe execution run is in the wrong state for that transition.
EXECUTION_RUN_LOOKUP_FAILED503upstreamThe execution run store could not be read.
EXECUTION_RUN_PERSIST_FAILED500api_errorThe execution run could not be written.
AGENT_REPORT_WINDOW_INVALID400invalid_requestThe report window is invalid.
AGENT_REPORT_DATA_UNAVAILABLE503upstreamThe report data could not be read.

Handling Errors

Always check the HTTP status code and parse the error envelope for details.

Error handling
try {
  const response = await fetch('https://sandbox.api.billerapi.com/v1/billers', {
    headers: {
      Authorization: `Bearer ${process.env.BILLERAPI_SECRET_KEY}`,
    },
  });

  if (!response.ok) {
    const error = await response.json();
    console.error(`[${response.status}] ${error.error_code}: ${error.error_message}`);
    console.error('Fix:', error.hint, '·', error.docs_url);

    if (error.error_code === 'VALIDATION_ERROR') {
      for (const field of error.errors ?? []) {
        console.error(`  ${field.param} (${field.code}): ${field.message}`);
      }
    }

    if (error.error_type === 'rate_limit') {
      // Prefer the body field; fall back to the Retry-After header.
      const retryAfter = error.retry_after ?? response.headers.get('Retry-After');
      console.log(`Retry after ${retryAfter} seconds`);
    }
    return;
  }

  const data = await response.json();
  console.log(data);
} catch (err) {
  console.error('Network error:', err.message);
}

404 after a webhook is normal

BillerAPI’s webhook bodies are intentionally minimal — they carry the envelope plus a small data.object with the resource ID and a few routing keys. To get the full resource you call GET /v1/<resource>/<id> after handling the webhook.

That follow-up GET can return 404 Not Found even though you just received an event for the resource. This is a normal consequence of two facts:

  • Webhook delivery is asynchronous; minutes can pass between the event being emitted and your handler running.
  • Some resources are short-lived (a link can be disconnected, a request-to-link can be cancelled, a bill can be re-extracted with a new id).

Treat 404 as “gone is gone”: log the event id and return 200 OK from your webhook endpoint. Do not return non-2xx — that triggers BillerAPI to retry the same webhook, which will hit the same 404 on the next attempt and burn your retry budget. The state the event reflected (e.g. link.disconnected) is still actionable from the envelope alone.

Tolerating 404 on the follow-up GET
async function handleBillCreated(envelope) {
  const billId = envelope.data.object.id;
  const res = await fetch(`https://api.billerapi.com/v1/bills/${billId}`, {
    headers: { Authorization: `Bearer ${linkScopedToken}` },
  });

  if (res.status === 404) {
    // Resource gone between webhook fan-out and our follow-up GET.
    // Acknowledge so we are not retried; we already know the bill id.
    logger.info('bill_gone_at_consume_time', { event_id: envelope.id, bill_id: billId });
    return; // caller returns 200 OK
  }

  if (!res.ok) throw new Error(`unexpected ${res.status}`);
  await persistBill(await res.json());
}

Related

Was this page helpful?