How errors work
When a request fails, Tabby responds with a4xx/5xx HTTP status code and a JSON error body:
errors array instead of a single error string:
- Branch your handling on the HTTP status code and
errorType. PossibleerrorTypevalues:bad_data,not_authorized,no_permission,not_found,conflict; a500carriesinternal. erroris a human-readable English message to aid debugging — log it, but don’t parse it and never show it to customers.- On
500or a network failure, retry per your retry policy; for captures and refunds always retry with the same idempotency key — your own uniquereference_idin the request body, see Idempotent requests — so retries can’t duplicate money movement. - Use the base URL that matches the merchant’s region:
api.tabby.ai(UAE) orapi.tabby.sa(KSA) — see Base URLs. Calling the wrong region returns403(see below). - A pre-scoring rejection is not an error:
POST /api/v2/checkoutreturns200 OKwithstatus: "rejected". For handling and the customer-facing rejection messages, see Show rejection.
Quick reference
POST /api/v2/checkout
Creates a checkout session. Authenticate with the Secret Key: Authorization: Bearer {secret_key}.
400 Bad Request — could not decode request
400 Bad Request — could not decode request
400 Bad Request — currency / merchant_code mismatch
400 Bad Request — currency / merchant_code mismatch
currency in the payload does not match the country assigned to your merchant_code (e.g. a UAE merchant_code used with SAR). Use the correct currency for the country.400 Bad Request — field validation failed
400 Bad Request — field validation failed
field points at the offending path. The same envelope covers unsupported currency, wrong formats, and similar per-field issues.400 Bad Request — payment validation failed
400 Bad Request — payment validation failed
error. Read the error field for the specific cause.403 Forbidden — no permission
403 Forbidden — no permission
- You are passing the
tokenparameter, but it is not enabled for your integration. Removetokenfrom the payload. - The request hit the wrong regional base URL — the body then also carries
"error": "merchant region does not match cluster region". Use the base URL that matches the merchant’s region:api.tabby.ai(UAE) orapi.tabby.sa(KSA).
404 Not Found
404 Not Found
404.500 Internal Server Error
500 Internal Server Error
500, contact the Tabby Integrations team and consider hiding Tabby temporarily. Follow tabby-status.com for maintenance and incident updates.GET /api/v2/payments/{id}
Retrieves the current state of a payment. Successful responses and payment statuses are covered in Payment statuses.
400 Bad Request — invalid payment id
400 Bad Request — invalid payment id
payment_id is not a valid UUID. Fix the format.404 Not Found — no such payment
404 Not Found — no such payment
payment_id is visible to your API key — either the id is wrong, or the payment belongs to a different store / API key. Check the payment_id and that you query with the same key that created the session.500 Internal Server Error
500 Internal Server Error
500 persists, contact the Tabby Integrations team and check tabby-status.com.GET /api/v2/payments
Retrieves a list of payments filtered by query parameters. Errors use the same envelopes as GET /api/v2/payments/{id}:
400 Bad Request(bad_data) — an invalid query-parameter value:"error": "failed to decode request".401 Unauthorized(not_authorized) — missing/wrongAuthorizationheader.500 Internal Server Error(internal) — retry per your retry policy.
PUT /api/v2/payments/{id}
Updates a payment (e.g. reference_id). Errors use the same envelopes as the other Payments endpoints:
400 Bad Request(bad_data) — malformed body:"error": "failed to decode request".401 Unauthorized(not_authorized) — missing/wrongAuthorizationheader.404 Not Found(not_found) —"error": "no such payment": wrongpayment_id, or a payment of a different store / API key.500 Internal Server Error(internal) — retry per your retry policy.
POST /api/v2/payments/{id}/captures
Captures funds from an AUTHORIZED payment.
400 Bad Request — invalid amount or body
400 Bad Request — invalid amount or body
amount has a wrong value — negative, too many decimal places, etc. An empty or truncated request body returns the same envelope with "error": "EOF".400 Bad Request — amount exceeds remaining
400 Bad Request — amount exceeds remaining
- Total 100, capture request 150.
- Total 100, one successful capture of 50, another capture for 100.
- Total 100, one successful capture of 100, another capture for 100.
400 Bad Request — already closed
400 Bad Request — already closed
CLOSED state — usually a duplicate capture of an already fully-captured payment, or a capture after close. Check the payment status; if your first capture actually succeeded, no action is needed.400 Bad Request — invalid payment id
400 Bad Request — invalid payment id
payment_id is not a valid UUID. Fix the format.404 Not Found — no such payment
404 Not Found — no such payment
payment_id is visible to your API key — wrong id, or a payment of a different store / API key.409 Conflict — concurrent request
409 Conflict — concurrent request
500 Internal Server Error
500 Internal Server Error
GET /api/v2/payments/{id} first and check already-applied captures before retrying. If 500 persists, contact the Tabby Integrations team and check tabby-status.com.POST /api/v2/payments/{id}/refunds
Refunds a captured (CLOSED) payment.
400 Bad Request — invalid amount format
400 Bad Request — invalid amount format
amount has a wrong value — too many decimal places for the currency, zero or negative ("error": "refund amount is less then or equals zero"), or not a number at all ("error": "error decoding string 'NaN': can't convert NaN to decimal").400 Bad Request — amount exceeds refundable
400 Bad Request — amount exceeds refundable
- Captured 100, refund request 150.
- Captured 100, one successful refund of 50, another refund for 100.
- Captured 100, one successful refund of 100, another refund for 100.
400 Bad Request — cannot refund payment
400 Bad Request — cannot refund payment
CLOSED or has no captures. Check the payment status and act accordingly.400 Bad Request — already closed
400 Bad Request — already closed
GET /api/v2/payments/{id}.404 Not Found — no such payment
404 Not Found — no such payment
payment_id is visible to your API key — wrong id, or a payment of a different store / API key.409 Conflict
409 Conflict
- Active dispute on the payment — the body carries
"error": "payment is disputed". The refund is blocked until the dispute is resolved; see Disputes. - Concurrent refunds on the same payment — the body carries
"error": "could not acquire lock: payment is locked". Another request is being processed; retry shortly with the same idempotency key.
500 Internal Server Error
500 Internal Server Error
GET /api/v2/payments/{id} first and check already-applied refunds before retrying. If 500 persists, contact the Tabby Integrations team and check tabby-status.com.POST /api/v2/payments/{id}/close
Closes an AUTHORIZED payment (e.g. when the order is cancelled before capture).
400 Bad Request — payment already expired
400 Bad Request — payment already expired
AUTHORIZED earlier, but the authorization expired before close was called. No action is needed — the customer is not charged; treat the payment as expired.400 Bad Request — already closed
400 Bad Request — already closed
CLOSED — usually a duplicate close call. No action is needed.400 Bad Request — invalid payment id
400 Bad Request — invalid payment id
payment_id is not a valid UUID. Fix the format.404 Not Found — no such payment
404 Not Found — no such payment
payment_id is visible to your API key — wrong id, or a payment of a different store / API key.500 Internal Server Error
500 Internal Server Error
GET /api/v2/payments/{id} to check the current status before retrying. If 500 persists, contact the Tabby Integrations team and check tabby-status.com.Webhooks — /api/v1/webhooks
Applies to POST /api/v1/webhooks, GET /api/v1/webhooks, GET /api/v1/webhooks/{id}, PUT /api/v1/webhooks/{id}, DELETE /api/v1/webhooks/{id}. Authenticate with the Secret Key.
api.tabby.ai a missing or malformed Authorization header and an unknown secret key are rejected at the edge with an empty body (400 for a missing header or a non-Bearer scheme, 401 for an unknown key). The JSON bodies below are returned once the request reaches the service (today every such request on api.tabby.sa reaches the service). Do not rely on a response body for 400/401 authentication errors; branch on the status code.400 Bad Request — invalid webhook url
400 Bad Request — invalid webhook url
url is not a valid, publicly reachable HTTPS URL. Fix the URL.400 Bad Request — webhook already exists
400 Bad Request — webhook already exists
GET /api/v1/webhooks instead of re-creating.400 Bad Request — max number of webhooks reached
400 Bad Request — max number of webhooks reached
DELETE /api/v1/webhooks/{id} before adding new.400 Bad Request — malformed request
400 Bad Request — malformed request
id in the path is not a valid UUID.404 Not Found — no such webhook
404 Not Found — no such webhook
id exists for your merchant. List the registered webhooks with GET /api/v1/webhooks.500 Internal Server Error
500 Internal Server Error
500 persists, contact the Tabby Integrations team and check tabby-status.com.Dispute webhooks — /api/v1/dispute-webhooks
Applies to POST /api/v1/dispute-webhooks, GET /api/v1/dispute-webhooks, GET /api/v1/dispute-webhooks/{id}, PUT /api/v1/dispute-webhooks/{id}, DELETE /api/v1/dispute-webhooks/{id}. Authenticate with the live Secret Key; the X-Merchant-Code header is required on every request. See Dispute webhooks for how they work.
api.tabby.ai a missing or malformed Authorization header and an unknown secret key are rejected at the edge with an empty body (400 for a missing header or a non-Bearer scheme, 401 for an unknown key); a merchant code that does not belong to the key may also be answered at the edge. The JSON bodies below are returned once the request reaches the service (today every such request on api.tabby.sa reaches the service). Do not rely on a response body for 400/401 authentication errors; branch on the status code. Always call the base URL of your merchant’s region.400 Bad Request — code is required
400 Bad Request — code is required
X-Merchant-Code header is missing. It is required on every dispute-webhook request, even when your secret key maps to a single merchant.400 Bad Request — invalid webhook url
400 Bad Request — invalid webhook url
url points to localhost, to a raw IP address, or to a host name that does not resolve in DNS. Use a publicly reachable host name (HTTPS recommended).400 Bad Request — webhook already exists
400 Bad Request — webhook already exists
PUT: the new URL belongs to another of your dispute webhooks). URLs are compared after normalisation (lower-case scheme and host, default port and trailing slash removed). List the registered webhooks with GET /api/v1/dispute-webhooks instead of re-creating.400 Bad Request — max number of webhooks reached
400 Bad Request — max number of webhooks reached
DELETE /api/v1/dispute-webhooks/{id} before adding new.400 Bad Request — malformed request
400 Bad Request — malformed request
url is missing, empty or not an absolute URL. Send a JSON object with a url, as in Register a dispute webhook.400 Bad Request — invalid header
400 Bad Request — invalid header
header is present but title or value is missing, empty or invalid: title must be a valid HTTP header name (no spaces, up to 128 characters), value up to 1024 characters with no control characters. The errors array points at the offending field. Omit header entirely if you do not want one.403 Forbidden — disputes have no test mode
403 Forbidden — disputes have no test mode
sk_test_...). Dispute webhooks exist for live payments only — use the live secret key (sk_...).404 Not Found — webhook not found
404 Not Found — webhook not found
id exists for your merchant (including one that was already removed or belongs to another merchant), or the id is not a valid UUID. List the registered webhooks with GET /api/v1/dispute-webhooks.500 Internal Server Error
500 Internal Server Error
500 persists, contact the Tabby Integrations team and check tabby-status.com.Disputes — /api/v1/disputes
Applies to GET /api/v1/disputes, GET /api/v1/disputes/{disputeId}, POST /api/v1/disputes/{disputeId}/provide-evidence, POST /api/v1/disputes/approve, POST /api/v1/disputes/challenge, POST /api/v1/disputes/attachments/upload. Authenticate with the Secret Key. See Disputes for the flow itself.
400 Bad Request
400 Bad Request
dispute_id format, missing required fields). Read the error field for the specific cause.404 Not Found — dispute not found
404 Not Found — dispute not found
disputeId is visible to your API key. Check the id against GET /api/v1/disputes.409 Conflict (provide-evidence)
409 Conflict (provide-evidence)
GET /api/v1/disputes/{disputeId} and check its status.500 Internal Server Error
500 Internal Server Error
500 persists, contact the Tabby Integrations team and check tabby-status.com.