Errors and Recovery
Every Agentic Payments error is an RFC 7807 Problem Details document whose type is a stable machine-readable code. The code, not the HTTP status, is what your integration should branch on.
Error Response Format
{
"type": "VALIDATION_ERROR",
"title": "Unrecognized key: \"recurring\"",
"status": 400,
"errors": {
"recurring": ["Unrecognized key: \"recurring\""]
}
}
| Attribute | Type | Description |
|---|---|---|
type | string | Stable error code. Branch on this |
title | string | Human-readable summary. For a validation failure, the first message |
status | integer | HTTP status code |
detail | string | Present where remediation guidance exists, mainly on provider failures |
errors | object | Present on validation failures. Maps each offending field path to its messages, reporting every failing field rather than only the first |
Every response carries a BT-TRACE-ID header. Include it when contacting support. Provider failures may also carry a debug.provider_correlation value for the same purpose. Internal diagnostics, provider response bodies, and credential values never appear in a response body.
These codes apply to every endpoint and are not listed again below. The framing codes are raised before the request body is validated, so no errors map accompanies them and nothing was read:
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | The BT-API-KEY header is missing or invalid |
FORBIDDEN | 403 | The key is valid but lacks the permission the endpoint requires |
NOT_FOUND | 404 | No route matches the method and path. A resource that does not exist returns its own *_NOT_FOUND code instead |
INVALID_BODY | 400 | The body is not parseable JSON, or is empty on a request that requires one |
BODY_TOO_LARGE | 413 | The body exceeds the maximum accepted size |
UNSUPPORTED_MEDIA_TYPE | 415 | Send the request as application/json |
Classify Before You Retry
Four classes of failure, and the class determines what is safe to do next.
| Class | Typical HTTP | Credential mint dispatched? | Allowance | Safe next action |
|---|---|---|---|---|
| Invalid request | 400 or 404 | No | Unchanged | Fix the request. If its body changes after an idempotency claim was made, use a new key |
| Conclusive failure | Usually 422; listed pre-dispatch failures may be 500 or 502 | Either no, or the provider explicitly rejected the mint | A mint's reserved amount is cleared, because no credential was issued | Address the cause. A credential retry is a new operation: use a new key or omit it |
| Operation in progress | 409 …_IN_PROGRESS | The current call did not dispatch; the operation already running may have | A mint may be reserved | Repeat the exact operation after a delay. Keep the same idempotency key if one was supplied |
| Unknown outcome | 409 CREATE_OUTCOME_UNKNOWN or 409 CREDENTIAL_OUTCOME_UNKNOWN | Possibly | A credential amount is consumed as spent | Treat this operation as terminal. Follow the resource-specific recovery below |
Invalid Requests
No credential-provider call was made, so allowance capacity is unchanged. If correcting the request changes its body, use a new idempotency key; a claimed key cannot be reused with a different body.
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | Body, query, rail and provider pairing, credential format, or verification action failed validation. Read errors |
INVALID_TOKEN | 400 | source.token_id is not a readable token in this tenant, or the token's type is not card |
UNSUPPORTED_PROVIDER | 400 | The source has no supported provider for the requested rail (for a card, its network decides), or the rail is not configured for this deployment |
RAIL_NOT_FOUND | 400 | That rail and provider pair is not present on the resource |
RAIL_NOT_ACTIVE | 400 | The allowance rail is not active. Complete verification first |
NO_ACTIVE_RAILS | 400 | The payment method has no enabled rail. Retry a failed payment method rail before creating an allowance |
PAYMENT_METHOD_DELETED | 400 | The payment method was deleted. Create a new one |
ALLOWANCE_CANCELLED | 400 | The allowance was cancelled. Create a new one |
ALLOWANCE_EXPIRED | 400 | The allowance expiration has passed. Expiry is terminal, so create a new allowance |
ALLOWANCE_AMOUNT_EXCEEDED | 400 | The requested mint exceeds amount_available |
VERIFICATION_STATE_INVALID | 400 | The verification action is out of order. Restart from start |
VERIFICATION_NOT_REQUIRED | 400 | That rail has no verification ceremony, so there is nothing to verify. spt allowance rails are active from creation |
INVALID_OTP | 400 | The code is invalid or expired. Retry the step; the provider enforces the attempt limit |
PAYMENT_METHOD_NOT_FOUND | 404 | No such payment method in this tenant |
AGENT_NOT_FOUND | 404 | The supplied agent_id is not an agent in this tenant |
ALLOWANCE_NOT_FOUND | 404 | No such allowance in this tenant |
PAYMENT_CREDENTIAL_NOT_FOUND | 404 | No such credential under that allowance |
Provider Rejections and Other Conclusive Failures
These errors are conclusive. Some are provider rejections; others prove that the credential request never left Basis Theory. For a credential mint, either no provider request was dispatched or the provider explicitly reported that it issued nothing, so the amount reserved for that mint is cleared and allowance capacity is unchanged. Other operations may persist an error rail that you can retry in place.
Clearing a reservation is not a release capability. It only applies while a mint is in flight and only when Basis Theory can prove no credential was issued. Once a credential mints, its amount is spent permanently: there is no void, release, or refund-to-allowance operation, and the amount is consumed even if the credential is never presented.
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
CARD_REJECTED | 422 | The network refused the card during registration | Use a different card. The same card will be refused again |
PROVIDER_ENROLLMENT_FAILED | 422 | Rail provisioning failed at the provider | Retry the payment method rail |
RAIL_ENROLLMENT_INVALID | 422 | The provider no longer recognizes the primitive this rail was provisioned with | Retry the rail. If it fails again, register the source as a new payment method |
RAIL_ALREADY_ENROLLED | 409 | The provider reports the source as already enrolled | Use the existing payment method for this source, or retry the rail |
PROVIDER_ALLOWANCE_FAILED | 422 | Allowance setup failed at the provider | Retry the allowance rail |
PROVIDER_ALLOWANCE_UPDATE_FAILED | 422 | The provider rejected the allowance update. The local allowance is unchanged | Correct the values and retry |
PROVIDER_CREDENTIALS_FAILED | 422 | The provider refused to issue the credential, so the reserved amount was cleared and capacity is unchanged | Replaying the same key returns this failure. Use a new key for a deliberate new mint attempt |
PROVIDER_VERIFICATION_FAILED | 422 | The provider rejected or expired the verification session, or rejected the submitted assurance data, such as a passkey assertion | Restart verification from start |
VERIFICATION_NOT_SUPPORTED | 422 | The source cannot complete the ceremony, such as a Mastercard without managed authentication or a Visa card offering no supported code method | Fall back to a rail with no verification ceremony, such as spt |
PROVIDER_NOT_CONFIGURED | 422 | Tenant onboarding for that provider is incomplete | Contact us. No retry will succeed |
MPP_KEY_UNAVAILABLE | 502 | The merchant's MPP Card encryption key could not be fetched from its jwksUri: a connection failure, timeout, non-2xx response, oversized body, or unparseable JSON. The credential provider was not called, so the reserved amount was cleared and capacity is unchanged. A jwksUri that breaks one of the safety constraints is a 400 VALIDATION_ERROR instead | Verify the challenge or key service, then make a deliberate new attempt with a new key |
UNSUPPORTED_RAIL | 500 | Internal: a rail was selected that cannot perform this operation | Contact us |
INTERNAL_ERROR | 500 | An unexpected server-side failure | Repeat the exact request once, preserving its idempotency key when present; then contact us with the BT-TRACE-ID |
PROVIDER_ALLOWANCE_CANCEL_FAILED | 500 | A provider-side allowance cancellation failed or could not be confirmed (e.g. at vic). The allowance remains active locally | Repeat the same DELETE; if it fails again, contact us with the BT-TRACE-ID |
Rail Error Codes
rails[].error.code on a payment method or allowance is read from this same catalog. CARD_REJECTED, PROVIDER_ENROLLMENT_FAILED, PROVIDER_ALLOWANCE_FAILED, and UNSUPPORTED_PROVIDER are the common ones.
When a provider reports a failure without a code specific enough to classify, the rail records the generic fallback for that operation: PROVIDER_ENROLLMENT_FAILED for payment method provisioning and PROVIDER_ALLOWANCE_FAILED for allowance setup. A rail retry that fails the same way returns it as a 422. Treat either as "this rail failed at its provider for an unclassified reason," read the rail's full history at the resource's /errors endpoint, and retry the rail.
Operations in Progress
Operations that mutate provider state on the same resource are mutually exclusive, so verification, an allowance update, a cancellation, and a credential reservation cannot overlap. A 409 ending in _IN_PROGRESS names the operation holding the resource. All of them mean the same thing: wait briefly and repeat the request.
| Code | HTTP | Operation holding the resource |
|---|---|---|
IDEMPOTENCY_IN_PROGRESS | 409 | Another request with the same idempotency key |
ALLOWANCE_VERIFICATION_IN_PROGRESS | 409 | Provider verification |
ALLOWANCE_UPDATE_IN_PROGRESS | 409 | A provider mandate update |
ALLOWANCE_DELETE_IN_PROGRESS | 409 | Provider cancellation |
ALLOWANCE_CREDENTIAL_IN_PROGRESS | 409 | A credential mint that has already reserved budget |
PAYMENT_METHOD_DELETE_IN_PROGRESS | 409 | A payment method deletion cascade |
PAYMENT_METHOD_OPERATION_IN_PROGRESS | 409 | Another child operation on the payment method |
IDEMPOTENCY_CONFLICT | 409 | Not an in-progress condition: the key was reused with a different body |
CREDENTIAL_PAYLOAD_UNAVAILABLE | 409 | Not an in-progress condition: the credential was already returned and cannot be replayed |
RAIL_ALREADY_ENROLLED | 409 | Not an in-progress condition: the provider reports this source as already enrolled. Repeating unchanged will not help |
PAYMENT_METHOD_DELETE_INCOMPLETE | 409 | Not an in-progress condition: a previous delete stopped during its cascade. Retry DELETE on that payment method; other operations do not revive it |
Only one credential mint can reserve a given allowance at a time. A second operation with a different key or no key returns ALLOWANCE_CREDENTIAL_IN_PROGRESS; a replay of the running mint's supplied key returns IDEMPOTENCY_IN_PROGRESS. Reserving an amount, clearing it, and committing it to spend are atomic conditional writes, so neither path can overspend the mandate.
The Outcome Is Unknown
Basis Theory cannot reliably query any provider to recover a bearer credential or prove that one was not issued, so the API makes a conservative terminal decision.
| Code | What may have happened | Durable local result | What to do |
|---|---|---|---|
CREATE_OUTCOME_UNKNOWN | A payment method or allowance may have committed even though its response was lost | The supplied idempotency key is terminal and cannot create again | For an allowance, list by payment_method_id. For a payment method, filter by consumer_id when you supplied one; otherwise paginate and match the returned consumer and source. If no matching resource exists and you intentionally try again, use a new key |
CREDENTIAL_OUTCOME_UNKNOWN | The provider may have issued a spendable credential, but Basis Theory cannot return its bearer value | The amount is moved to amount_spent; no credential record is created; a supplied key remains terminal | Do not retry this operation. Continue with the allowance's remaining capacity, or create a new allowance if necessary |
CREDENTIAL_OUTCOME_UNKNOWN, another mint can consume more allowance capacity; it cannot recover the missing credential. After CREATE_OUTCOME_UNKNOWN, list first so you do not create an accidental duplicate.If a process disappears while a mint is in progress, the allowance can temporarily show the amount in amount_reserved. Once its five-minute operation lease is stale, the next mint, allowance update, cancellation, or verification attempt converts that exact reservation to amount_spent before proceeding. It is never cleared, because the stored state cannot prove that the provider issued nothing.
No reconciliation endpoint exists, and support cannot recover the credential value. A conclusive provider rejection is different: it returns 422, clears the reserved amount, and leaves capacity unchanged.
Idempotency
BT-IDEMPOTENCY-KEY is optional on payment method creates, allowance creates, and credential mints. Supplying it opts into durable retry handling. Unlike the platform-wide default, Agentic retains failed results and does not delete keys after 24 hours. Records are terminal; keys do not expire or become reusable. Without a key, every request is a new operation and an identical retry can create or mint again.
| Situation | Payment method or allowance create | Credential mint |
|---|---|---|
| No key | Every request is a new create | Every request is a new mint and can spend again |
| Same key and body after success | Returns the original resource | 409 CREDENTIAL_PAYLOAD_UNAVAILABLE; no provider call and no second spend |
| Same key with a different body | 409 IDEMPOTENCY_CONFLICT | 409 IDEMPOTENCY_CONFLICT |
| Same key while running | 409 IDEMPOTENCY_IN_PROGRESS; retry the same request later | 409 IDEMPOTENCY_IN_PROGRESS; retry the same request later |
| Conclusive failure | Replays the same stable code and status | Replays the same stable code and status; use a new key only for a deliberate new attempt |
| Outcome cannot be determined | 409 CREATE_OUTCOME_UNKNOWN on every replay | 409 CREDENTIAL_OUTCOME_UNKNOWN on every replay; capacity is spent |
A successful credential replay cannot return the value because spendable credential payloads are never persisted. Calling the provider again would mint a second credential rather than replay the first.
Provider Error History
Both resources keep a paginated history of provider failures, retained for 90 days:
GET /agentic/payment-methods/{payment_method_id}/errorsGET /agentic/allowances/{allowance_id}/errors
The allowance history includes failed verification attempts and provider-reported credential failures. Both are safe to surface in your own customer-facing UI, because provider response bodies, provider messages, source metadata, and credential values are never included. An unknown mint outcome is reflected in the allowance counters and the mint response instead; it is not a provider-reported failure.
Provider Error Object
| Attribute | Type | Description |
|---|---|---|
id | string | Prefixed with prv_ |
code | string | Stable Agentic Payments error code |
title | string | Basis Theory-controlled summary, suitable for display |
detail | string | Basis Theory-controlled remediation guidance, suitable for display |
provider | string | vic, agentpay, or stripe |
operation | string | The operation that failed, such as credentials or verification |
rail | string | The rail the operation was performed on |
provider_code | string | Provider-native machine-readable code, when available. Nullable |
provider_correlation_id | string | Safe provider correlation identifier for support. Nullable |
payment_method_id | string | Nullable |
allowance_id | string | Nullable |
payment_credential_id | string | Nullable. The generated credential attempt ID. Present even when minting failed and no credential resource exists |
occurred_at | string | ISO 8601 timestamp |
A failed mint's payment_credential_id identifies the attempt in Basis Theory's logs; include it when contacting support.
Escalating to Basis Theory
For a declined checkout, start with the merchant, its acquirer, or your payment processor. They hold the authorization response and Basis Theory does not: minting the credential is the last event it observes. See transaction outcomes for why a credential's status never changes.
When you do escalate, send identifiers:
- The payment method ID, allowance ID, and credential ID
- The mint or attempt timestamp
- The amount and currency
- The merchant name and URL
- The rail and provider
- The
BT-TRACE-IDfrom the response - Any
provider_correlation_idandprovider_codefrom the error history - For a decline, the merchant or processor decline code and reference
credential.value to support, through any channel. These are bearer credentials. Basis Theory does not need them to diagnose anything, and the identifiers above are sufficient.