Errors
What an EPD error response looks like, what each code means, and how to react.
What this is and who needs it
When something goes wrong, EPD returns a JSON body in a consistent shape with a code your code can switch on. Reading this page once will save you a lot of guessing later.
This page covers server-side REST errors: what your backend gets back from api.epd.com. The EPD Elements browser SDK raises its own client-side errors (card_invalid, card_declined, …) in JavaScript before any REST call is made; those are documented in the Elements guide’s Errors section.
Error response shape
{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "One or more fields are invalid.",
"request_id": "req_a1b2c3d4e5f67890abcdef0123456789",
"param": "amount",
"field_errors": [
{ "field": "amount", "code": "invalid_value", "message": "amount must be a positive integer" },
{ "field": "currency", "code": "required_field", "message": "currency is required" }
]
}
}
| Field | Description |
|---|---|
type | Coarse category (see the table below). |
code | Specific machine-readable code. Stable across versions; switch on this in code. |
message | Human-readable explanation. May change between versions; do not parse it. |
request_id | Trace id. Include in any support ticket; it lets us find your call instantly. |
param | The single field most responsible for the error (when applicable). |
field_errors | Per-field breakdown for validation errors. Surface these directly in your UI when possible. |
Error types
type | Status | What it means |
|---|---|---|
invalid_request_error | 400 | Malformed request, missing field, invalid value, business rule broken. |
authentication_error | 401 | Missing, malformed, expired, or revoked key. |
authorization_error | 403 | Key valid but not allowed to perform this action. |
idempotency_error | 409 / 422 | Idempotency key reused incorrectly or in flight. |
rate_limit_error | 429 | Too many requests for the current window. |
processing_error | 4xx / 5xx | Gateway or internal processing failure. (A card decline is not this: it returns a failed order with a failure_code, see Decline reason codes.) |
webhook_error | varies | Webhook delivery, signature, or endpoint configuration error. |
Codes you will hit most often
Validation
validation_error: top-level catch-all; checkfield_errors[]for details.required_field: a field was omitted.invalid_value: a field had the wrong type, range, or shape.invalid_format: string did not match the required format (e.g. email).contains_html: a free-text field contained HTML and was rejected for safety.invalid_requestwithparam: card_token: thecard_tokenfrom EPD Elements is invalid, expired (they live 15 minutes), or already used. A token is single-use: capture a fresh card and attach it again; never resend a spent token. The same code andparamare returned when the captured card cannot be read or verified.invalid_requestwithparam: billing_details: the processor requires a billing address before it will vault the card. Resend Add Payment Method withbilling_details(or, on Inbound Card Capture,billing_details.address).
Resource state
resource_not_found: the id does not exist (or you do not have access to it).resource_already_exists: unique constraint clash.resource_in_use: cannot delete because something still references it.email_already_exists,phone_already_exists,sku_already_exists: narrow variants ofresource_already_exists.invalid_state_transition: e.g. canceling an already-canceled subscription.customer_has_active_subscriptions,product_in_active_plans,subscription_not_modifiable,already_canceled.
Authentication & authorization
missing_api_key,invalid_api_key,invalid_api_key_format,expired_api_key,revoked_api_key.insufficient_permissions: restricted key lacks the right scope.ip_not_allowed: request came from an IP off the allowlist.environment_mismatch: sandbox-only data with a live key, or vice versa.invalid_api_version,api_version_sunset: bad or retiredepd-versionheader.
Idempotency
missing_idempotency_key: endpoint required one and you sent none.invalid_idempotency_key: empty, too long, or wrong characters.request_in_progress: same key still in flight; retry after a short delay.idempotency_key_conflict: same key, different payload. Use a fresh key.
Rate limiting
rate_limit_exceeded: per-category limit hit.global_rate_limit_exceeded: global per-merchant limit hit.
Processing & payments
A card decline is not returned as a top-level error code. The charge endpoint returns a failed order carrying a normalized failure_code (processor_declined, insufficient_funds, expired_card, and so on): see Decline reason codes below. The codes here are genuine request/processing errors:
gateway_error: temporary downstream failure; safe to retry with the same idempotency key.gateway_delete_failed: could not remove a card from the gateway vault.replacement_required: the customer’s card needs to be re-collected.internal_error: unexpected EPD failure. Safe to retry.service_unavailable: EPD or a dependency is temporarily down.not_implemented: endpoint exists but is not yet enabled (e.g. subscription pause).merchant_not_configured: your account is missing required setup; contact support.customer_not_configured: the customer has no saved payment method to charge. Returned (withparam: customer_id) when you create an order or subscription for a customer that has no card on file. Add one first: see Card Vaulting, EPD Elements, or Inbound Card Capture.
Decline reason codes
When a charge is declined, it does not come back as a REST error. The order (or
transaction) is returned with status: "failed" and a normalized failure_code, and the
POST /v1/orders response is still 201 Created. Branch on failure_code, not on a
top-level error code; the accompanying message is a safe human-readable default. (A
declined charge is distinct from a validation or vault failure, which does use the error
envelope above.) The reasons group by how you should respond:
- Soft (a later retry may succeed):
insufficient_funds,do_not_honor,card_limit_exceeded,processor_declined,issuer_unavailable,unknown - Card data (re-collect the card):
expired_card,incorrect_cvv,invalid_account - Account status (the customer must contact their bank):
blocked_card,closed_card,contact_bank,transaction_not_allowed - Fraud / security (do not retry):
lost_stolen_card,fraud_suspected - Configuration / system (not retryable; fix the config or payload):
duplicate_transaction,invalid_merchant_configuration,validation_error(on a decline this means the processor rejected a submitted field, distinct from the top-level request-validationvalidation_errorabove)
Webhooks
webhook_delivery_failed,webhook_endpoint_invalid,webhook_signature_failed.
How to react
These mean your request was wrong. Surface the message (or the field-level field_errors) to the user; do not blindly retry.
Honor the Retry-After header. Add jitter. Avoid thundering-herd retries from many workers at once.
EPD treats these as transient. Use exponential backoff (e.g. 1s, 2s, 4s, 8s, capped). Reusing the same X-EPD-Idempotency-Key makes the retry safe.
Always log request_id. With it, EPD support can find your exact call in seconds; without it, debugging is much slower.