KashierDevelopersKashier Developers
Dashboard API

Error responses

Error response format and common error scenarios

Kashier returns more than one error shape. Operational errors from the payment and processing services use the standard envelope below. Authentication, schema, and validation errors are produced before the request reaches a controller and return their own flat shapes instead. Branch your error handling on the HTTP status code first, then on which keys are present.

Standard error envelope (operational errors)

Most business-logic errors — invalid merchant configuration, invalid request objects, and similar — are emitted through a shared envelope, with the HTTP status the error itself carried:

{
   "error": {
       "cause": string       // The technical cause of the error
   },
   "messages": {
       "en": string,        // English error message
       "ar": string         // Arabic error message
   },
   "status": "FAILURE"      // Always "FAILURE" for this envelope
}

Not every error uses this envelope

Authentication, authorization, schema, and request-validation failures return a flat { "message": ... }, an { "error": ... } string, or a { "more_info": [...] } array — with no messages object and no status field. Do not require status to be present before treating a non-2xx response as an error.

Common error scenarios

Invalid merchant ID

Occurs when attempting to use an incorrect or non-existent merchant identifier. This is an operational error, so it uses the envelope:

{
    "error": {
        "cause": "The request object is invalid"
    },
    "messages": {
        "en": "This merchant is not found on kashier, example 'MID-XX-XX'",
        "ar": "This merchant is not found on kashier, example 'MID-XX-XX'"
    },
    "status": "FAILURE"
}

A separate check rejects a merchantId whose format is wrong — anything not matching MID-<digits>-<digits> — with HTTP 400 and an error string rather than the envelope:

{
  "error": "Invalid merchantId format. It should be in the format MID-XXX-XXX"
}

Missing or rejected authentication

These are returned by the authentication layer before your request reaches an endpoint, so they use flat shapes.

ScenarioStatusBody
No Authorization header sent403{ "message": "No auth token provided" }
Credential rejected (bad, expired, or wrong user type)401{ "error": "Authorization error", "message": "..." }
Secret key missing or malformed at the strategy level400{ "message": "Missing token" }
Secret key cannot be decrypted401{ "message": "Invalid secret key" }
Secret key caller IP not allow-listed403{ "message": "Unauthorized IP address" }
Dashboard session no longer valid401{ "message": "Please re-authenticate and try again" }
No user resolved for the credential401{ "message": "Unauthorized" }

Two of these are worth calling out because they are the most common real integration failures and neither returns the status people expect:

  • A missing Authorization header returns 403, not 401. Only an invalid credential returns 401. See Authentication.
  • IP allow-list rejection returns 403 { "message": "Unauthorized IP address" }. Every Secret Key call is checked against your merchant's IP allow-list after the credential itself has been validated. An empty allow-list permits all IPs, so this only bites once you have added at least one entry — and it looks identical to a permissions problem unless you read the message. Manage the list from API keys. Dashboard session (JWT) callers are not IP-checked.

Authorization failures — a valid credential whose role lacks the privilege the route requires — surface through the same 401 Authorization error shape, with a message such as Not authorized to perform this action.

Validation errors (400)

Two distinct validation layers can reject a request, and they return two distinct shapes. Check which one you're looking at before parsing.

Schema validation

Requests that fail request-schema validation (missing or malformed fields at the schema level) are caught before anything else and return only a list of the individual violations:

{
   "more_info": [
       { "message": "should have required property 'amount'" }
   ]
}
FieldTypeMeaning
more_infoarrayThe individual schema validation errors that were found.

HTTP status: 400. There is no message, messages, or status key on this shape.

Field-level (body/query/param) validation

Most other input validation — required fields, format checks on individual body, query, or param values — returns a flat, localized message instead:

{
  "message": "merchant ID is required"
}
FieldTypeMeaning
messagestringThe first validation error found, localized based on your request's language header.

HTTP status: 400. Only the first error is returned even when several fields are invalid, so fix and re-send rather than expecting the full list.

Non-operational / unknown errors

An unexpected server-side failure — an unhandled exception rather than a recognized business error — does not use the envelope. It returns a flat message only:

{
   "message": "Cannot read properties of undefined"
}

The HTTP status is the error's own statusCode when it has one, and 500 otherwise. A service outage or acquirer downtime (a rare occurrence) surfaces here rather than through the standard envelope.

Live-mode-only routes

Some routes only operate in live mode and reject calls made with test credentials:

{
  "message": "This API is only available on live mode"
}

HTTP status: 403.

Duplicate / conflicting records (409)

Returned when a request would create a record whose unique field already exists — for example reusing an invoice number, a category name, or a payment-request reference for the same merchant:

{
   "status": 409,
   "message": "The invoice number has already been used. Please enter another invoice number.",
   "code": "DUPLICATE_KEY_ERROR"
}

Match on code: "DUPLICATE_KEY_ERROR" rather than on the message text, which varies by the conflicting field:

Conflicting field(s)Message
Invoice referenceThe invoice number has already been used. Please enter another invoice number.
Category name (slug)The category name has already been used. Please enter another category name.
merchantId + referenceId (payment requests)A payment request with merchantId "..." and referenceId "..." already exists.
Any other duplicate keyA duplicate key error occurred. Please check your input and try again.

Retrying a 409 with the same payload will fail again. Change the conflicting value or treat the existing record as the result.

Other, non-duplicate database errors return a 400 instead. A Mongo-originated error carries an extra error: "MongoError" key alongside message; other downstream or generic errors return just { "message": "<description>" }.

Quick reference by source

The top-level keys are not uniform across error paths — some responses use error.cause / messages / status, others use a flat message, an error string, or a more_info array. Branch your client error handling on the HTTP status code together with whichever of these keys is present in the body.

Error sourceHTTP statusTop-level keys
Operational / business-logic errorvaries (error's own status)error.cause, messages.{en,ar}, status: "FAILURE"
Non-operational / unexpected errorerror's own status, or 500message
Schema validation400more_info[]
Field-level validation400message
Missing Authorization header403message
No user resolved from credentials401message
Missing/malformed secret-key token400message
Invalid secret key401message
Unauthorized IP address403message
Dashboard session expired401message
Other auth failure401error, message
Invalid merchantId format400error
Live-mode-only route called with test credentials403message
Mongo / DB error400error: "MongoError", message
Duplicate key / conflicting record409status, message, code: "DUPLICATE_KEY_ERROR"

Best practices

  1. Treat any non-2xx HTTP status as an error. Do not rely on a status field being present — several error paths omit it
  2. Read the message from messages.en when present, and fall back to the flat message or error key, then to more_info
  3. Handle both English and Arabic messages appropriately based on your user's locale
  4. Pay special attention to the error.cause field for debugging purposes
  5. For authorization errors, verify your API key, your hash generation process, and your IP allow-list
  6. Match 409 conflicts on code: "DUPLICATE_KEY_ERROR" and do not blind-retry them
  7. In live mode, ensure your account has the necessary credentials configured for each operation

On this page