API documentation
Errors & idempotency
Every error response uses the same envelope shape. POST endpoints accept an Idempotency-Key so safe retries return the original response.
Error envelope
Errors always come back in a stable shape so your code can branch on error.type and error.code without string-matching the message.
generic error
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "Missing required parameter: amount_cents",
"param": "amount_cents"
}
}card decline
{
"error": {
"type": "card_error",
"code": "card_declined",
"message": "Your bank declined this charge due to insufficient funds.",
"decline_code": "insufficient_funds"
}
}Top-level categories
The error.type field tells you whether to retry, surface to the customer, or alert your ops team. The exhaustive code enum lives in the gated reference inside the merchant dashboard.
| Type | HTTP | When |
|---|---|---|
| authentication_error | 401 | The Authorization header is missing, malformed, or the secret key has been revoked. |
| invalid_request_error | 400 | The request body is missing a required field, has an invalid value, or violates an API contract. |
| card_error | 402 | The card was declined by the issuing bank or failed a Veyra-side check. The response includes a decline_code with retry guidance. |
| rate_limit_error | 429 | You sent too many requests in a short window. Back off and retry with exponential delay. |
| idempotency_error | 422 | An Idempotency-Key was reused with a different request body. Pick a new key or send the same body. |
| api_error | 500-503 | Something went wrong on Veyra's side. Safe to retry with exponential backoff and the same Idempotency-Key. |
Idempotency-Key
Pass Idempotency-Key on any POST. A second request with the same key and body returns the original response unchanged. Different body, same key returns 422 idempotency_key_in_use. Keys are scoped to your account and live for 24 hours.
retry pattern
# First call - assigns the idempotency key.
curl https://veyragate.com/api/v1/charges \
-H "Authorization: Bearer vg_sk_test_..." \
-H "Idempotency-Key: 9c8f3-order-1001" \
-H "Content-Type: application/json" \
-d '{"amount": 4299, "currency": "usd", "payment_method_id": "pm_...", "decision_id": "..."}'
# Retry with the exact same body - returns the original response, does NOT re-charge.
curl https://veyragate.com/api/v1/charges \
-H "Authorization: Bearer vg_sk_test_..." \
-H "Idempotency-Key: 9c8f3-order-1001" \
-H "Content-Type: application/json" \
-d '{"amount": 4299, "currency": "usd", "payment_method_id": "pm_...", "decision_id": "..."}'
# Same key, different body -> 422 idempotency_key_in_use.
curl https://veyragate.com/api/v1/charges \
-H "Authorization: Bearer vg_sk_test_..." \
-H "Idempotency-Key: 9c8f3-order-1001" \
-H "Content-Type: application/json" \
-d '{"amount": 5000, "currency": "usd", "payment_method_id": "pm_...", "decision_id": "..."}'Where to go next
- See the test cards page for amount triggers that simulate every decline branch.
- Read Verifying webhook signatures for the asynchronous notification that lands after each charge.