Skip to main content
Check the usual errors the Tabby API returns and what to do about each.

How errors work

When a request fails, Tabby responds with a 4xx/5xx HTTP status code and a JSON error body:
For field-level validation failures the body carries an errors array instead of a single error string:
  • Branch your handling on the HTTP status code and errorType. Possible errorType values: bad_data, not_authorized, no_permission, not_found, conflict; a 500 carries internal.
  • error is a human-readable English message to aid debugging — log it, but don’t parse it and never show it to customers.
  • On 500 or a network failure, retry per your retry policy; for captures and refunds always retry with the same idempotency key — your own unique reference_id in 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, Kuwait) or api.tabby.sa (KSA) — see Base URLs. Calling the wrong region returns 403 (see below).
  • A pre-scoring rejection is not an error: POST /api/v2/checkout returns 200 OK with status: "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}.
The JSON is malformed or a parameter has the wrong type. Validate the payload against the Session payload model.
The 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.
A field value is invalid — field points at the offending path. The same envelope covers unsupported currency, wrong formats, and similar per-field issues.
Other validation failures on the payment object (amounts and quantities must be positive, etc.) use the same envelope with a human-readable error. Read the error field for the specific cause.
Two possible causes:
  1. The Authorization header is missing or wrong — it must be sent as Authorization: Bearer {secret_key}.
  2. The merchant_code in the payload doesn’t match what the API key is bound to. Check the merchant_code you’re passing and use the correct one for the environment (live vs sandbox).
Two possible causes:
  1. You are passing the token parameter, but it is not enabled for your integration. Remove token from the payload.
  2. 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, Kuwait) or api.tabby.sa (KSA).
The URL path is incorrect. Verify it against the API reference. Unsupported HTTP methods also return 404.
Retry the request per your retry policy. If all requests keep returning 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.
The payment_id is not a valid UUID. Fix the format.
The Authorization header is missing or wrong. It must be sent as Authorization: Bearer {secret_key}.
No payment with this 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.
Retry per your retry policy. If 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/wrong Authorization header.
  • 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/wrong Authorization header.
  • 404 Not Found (not_found) — "error": "no such payment": wrong payment_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.
Always send an idempotency key — your own unique reference_id in the request body (see Idempotent requests) — so that retries after a 500 or 409 do not produce duplicate captures.
The amount has a wrong value — negative, too many decimal places, etc. An empty or truncated request body returns the same envelope with "error": "EOF".
The amount is higher than the remaining uncaptured amount. Examples:
  1. Total 100, capture request 150.
  2. Total 100, one successful capture of 50, another capture for 100.
  3. Total 100, one successful capture of 100, another capture for 100.
Case 3 is usually an unsafe retry — use an idempotency key instead of re-sending the capture.
The payment status is not AUTHORIZED, so captures cannot be made. Check the payment status via GET /api/v2/payments/{id} and act accordingly.
The payment is already in a terminal 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.
The payment_id is not a valid UUID. Fix the format.
The Authorization header is missing or wrong. It must be sent as Authorization: Bearer {secret_key}.
No payment with this payment_id is visible to your API key — wrong id, or a payment of a different store / API key.
Another concurrent request for this payment is being processed. Retry shortly with the same idempotency key.
If you send an idempotency key, retry safely per your retry policy. Without one, call 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.
Always send an idempotency key — your own unique reference_id in the request body (see Idempotent requests) — to make refund retries safe.
The 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").
The refund amount exceeds the still-refundable amount. Examples:
  1. Captured 100, refund request 150.
  2. Captured 100, one successful refund of 50, another refund for 100.
  3. Captured 100, one successful refund of 100, another refund for 100.
Case 3 is usually an unsafe retry — use an idempotency key instead of re-sending the refund.
The payment is not CLOSED or has no captures. Check the payment status and act accordingly.
The payment is in a state that no longer accepts this refund (e.g. it was closed without captures). Check the payment status via GET /api/v2/payments/{id}.
The Authorization header is missing or wrong. It must be sent as Authorization: Bearer {secret_key}.
No payment with this payment_id is visible to your API key — wrong id, or a payment of a different store / API key.
Two possible causes:
  1. Active dispute on the payment — the body carries "error": "payment is disputed". The refund is blocked until the dispute is resolved; see Disputes.
  2. 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.
If you send an idempotency key, retry safely per your retry policy. Without one, call 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).
Only AUTHORIZED payments can be closed. Check the payment status and act accordingly.
The payment was AUTHORIZED earlier, but the authorization expired before close was called. No action is needed — the customer is not charged; treat the payment as expired.
The payment is already CLOSED — usually a duplicate close call. No action is needed.
The payment_id is not a valid UUID. Fix the format.
The Authorization header is missing or wrong. It must be sent as Authorization: Bearer {secret_key}.
No payment with this payment_id is visible to your API key — wrong id, or a payment of a different store / API key.
Call 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.
The url is not a valid, publicly reachable HTTPS URL. Fix the URL.
A webhook with the same URL is already registered for this merchant. List the registered webhooks with GET /api/v1/webhooks instead of re-creating.
The merchant already has the maximum number of registered webhooks. Remove stale ones with DELETE /api/v1/webhooks/{id} before adding new.
The request body is empty or not valid JSON, or the webhook id in the path is not a valid UUID.
The Authorization header is missing or carries a wrong key (api key not found / invalid secret key). It must be sent as Authorization: Bearer {secret_key}.
No webhook with this id exists for your merchant. List the registered webhooks with GET /api/v1/webhooks.
Retry per your retry policy. If 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.
The request body is malformed or a parameter is invalid (e.g. wrong dispute_id format, missing required fields). Read the error field for the specific cause.
The Authorization header is missing or wrong. It must be sent as Authorization: Bearer {secret_key}.
No dispute with this disputeId is visible to your API key. Check the id against GET /api/v1/disputes.
The dispute is not in a state that accepts this operation — e.g. evidence was already submitted or the dispute has been resolved. Re-fetch it with GET /api/v1/disputes/{disputeId} and check its status.
Retry per your retry policy. If 500 persists, contact the Tabby Integrations team and check tabby-status.com.