Skip to content
Documentation menu

Errors

Vitix Pay uses conventional HTTP status codes and returns one error object per failed request. Branch on the stable code, show the human-readable message, and log the request id.

The error object

400 Bad Request
{  "error": {    "type": "invalid_request_error",    "code": "amount_too_small",    "message": "Amount is below the minimum of $10.00 (≈ 260,000 VND).",    "param": "amount",    "request_id": "req_dCjruuTgsXzq3EwlIAWS"  }}
FieldDescription
typeThe category: authentication_error, invalid_request_error, idempotency_error, rate_limit_error or api_error.
codeA stable machine-readable reason. New codes may be added; treat unknown codes as generic failures of their type.
messageA sentence for developers. Safe to log; not meant to be shown to end users verbatim.
paramThe offending body or query parameter, when there is one (e.g. amount, success_url, id).
request_idUnique id of this request (req_…), also sent as the request-id response header. Quote it when contacting support.

HTTP status codes

StatusMeaning
200 / 201Request succeeded (201 for newly created checkouts, refunds, endpoints and deliveries).
400The request was malformed or violated a rule: missing parameter, wrong type, amount out of range, idempotency conflict.
401No API key, a malformed key, or a key that was revoked.
403Valid key, but the account is suspended.
404The id does not exist in this account and mode. Live keys cannot see test objects.
429Rate limit exceeded: more than 100 requests in 10 seconds on one key.
5xxSomething went wrong on Vitix's side. Safe to retry with the same Idempotency-Key.

Error codes

typecodeHTTPMeaning
authentication_errorapi_key_missing401No Authorization header.
authentication_errorapi_key_invalid401Malformed, unknown or revoked key.
authentication_erroraccount_suspended403The partner account is suspended; contact support.
invalid_request_errorparameter_invalid400A body or query parameter failed validation. param names it.
invalid_request_errorinvalid_json400The body is not valid JSON.
invalid_request_errorinvalid_content_type400POST bodies must be sent with Content-Type: application/json.
invalid_request_erroramount_too_small400Below the minimum ($10.00 live, or your account minimum).
invalid_request_erroramount_too_large400Above your account's per-checkout maximum.
invalid_request_errorurl_not_allowed400success_url / cancel_url is not on an allowed domain, or is not https.
invalid_request_errorcheckout_not_open400Expire called on a checkout that is already complete or expired.
invalid_request_erroramount_invalid400Refund amount exceeds the unrefunded balance, or the payment is fully refunded.
invalid_request_errorrefund_pending400Another refund of the same payment is still processing.
invalid_request_errorrefund_unavailable400The payment cannot be refunded through the API.
invalid_request_errorinvoice_unavailable400Test payment, or the PDF is still being generated (retry shortly).
invalid_request_errorlimit_reached400More than 10 webhook endpoints in one mode.
invalid_request_erroridempotency_key_invalid400Idempotency-Key longer than 255 characters.
invalid_request_errorresource_missing404No object with that id in this account and mode.
idempotency_erroridempotency_key_reused400The key was already used with a different path or payload.
rate_limit_errorrate_limit_exceeded429Slow down and retry with backoff.
api_errorapi_error500Unexpected failure on Vitix's side. Retry; include request_id if you contact support.

Handling errors

With the SDK, errors are thrown as typed classes. With raw HTTP, read res.ok and the error object:

ts
const res = await fetch("https://api.vitix.net/v1/checkouts", { method: "POST", headers, body });const json = await res.json();if (!res.ok) {  const { type, code, message, param, request_id } = json.error;  if (code === "amount_too_small" || code === "amount_too_large") return showFieldError("amount", message);  if (type === "rate_limit_error") return retryLater();  if (res.status >= 500) return retryWithSameIdempotencyKey();  throw new Error(`Vitix Pay ${code} (${request_id}): ${message}`);}

Idempotency conflicts

A replayed request (same key, same path, same body) returns the original response with idempotent-replayed: true and is never an error, even if the original was a 400. Reusing a key for a different request is rejected with idempotency_key_reused so a bug in your key generation cannot silently return the wrong checkout. Keys live for 24 hours.

Rate limits

Each key may make 100 requests per 10 seconds. A 429 response carries the standard error body; wait at least a second, add jitter, and retry. If you need sustained higher throughput, batch reconciliation reads with limit=100 and avoid polling: webhooks push every change to you.

Questions or a missing feature? Email [email protected]