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.

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" }
    ]
  }
}
FieldDescription
typeCoarse category — see the table below.
codeSpecific machine-readable code. Stable across versions; switch on this in code.
messageHuman-readable explanation. May change between versions; do not parse it.
request_idTrace id. Include in any support ticket — it lets us find your call instantly.
paramThe single field most responsible for the error (when applicable).
field_errorsPer-field breakdown for validation errors. Surface these directly in your UI when possible.

Error types

typeStatusWhat it means
invalid_request_error400Malformed request, missing field, invalid value, business rule broken.
authentication_error401Missing, malformed, expired, or revoked key.
authorization_error403Key valid but not allowed to perform this action.
idempotency_error409 / 422Idempotency key reused incorrectly or in flight.
rate_limit_error429Too many requests for the current window.
processing_error4xx / 5xxCard declined, gateway error, internal failure.
webhook_errorvariesWebhook delivery, signature, or endpoint configuration error.

Codes you will hit most often

Validation

  • validation_error — top-level catch-all; check field_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.

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 of resource_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 retired epd-version header.

Idempotency

  • missing_idempotency_key — endpoint required one and you sent none.
  • invalid_idempotency_key — empty, too long, or wrong characters.
  • idempotency_key_in_use — 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

  • payment_failed — generic payment failure; check the underlying message.
  • card_declined — issuer declined.
  • insufficient_funds — card had no funds.
  • expired_card — card past its expiry.
  • invalid_vault_idbilling_id does not exist in EPD Gateway, or wrong environment.
  • 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.

Webhooks

  • webhook_delivery_failed, webhook_endpoint_invalid, webhook_signature_failed.

How to react

On 4xx — fix and re-send

These mean your request was wrong. Surface the message (or the field-level field_errors) to the user; do not blindly retry.

On 429 — back off

Honor the Retry-After header. Add jitter. Avoid thundering-herd retries from many workers at once.

On 5xx — retry with the same idempotency key

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.