Error Codes
When a request fails, Mazad returns a consistent JSON error body with a machine-readable error code, an HTTP status, and a human-readable message. Use the error code (not the HTTP status) to determine what went wrong and how to recover.
Error Response Structure
Every error response follows this shape. The code field is the stable identifier you should match against in your code.
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient available balance for this transaction."
}
}Authentication Errors
Returned when Gateway HMAC credentials are missing, invalid, or expired.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| HMAC_HEADERS_MISSING | 401 | X-Api-Key, X-Api-Signature, or X-Api-Timestamp header is absent. | Include all three headers on every gateway request. |
| HMAC_TIMESTAMP_EXPIRED | 401 | The X-Api-Timestamp is more than 90 seconds old. | Regenerate a fresh timestamp and recompute the signature. Ensure server clock is NTP-synced. |
| HMAC_KEY_INVALID | 401 | The X-Api-Key does not exist or has been revoked. | Check the key ID. If revoked, rotate keys in the admin panel. |
| HMAC_SIGNATURE_INVALID | 401 | The HMAC signature does not match. Canonical string mismatch. | Verify the signing formula: HMAC-SHA256(secret, timestamp.METHOD.path.body) |
| MERCHANT_NOT_FOUND | 403 | The API key is not linked to any merchant account. | Ensure the merchant account is fully provisioned. |
| MERCHANT_NOT_APPROVED | 403 | Merchant account is Pending, Rejected, or Suspended. | Contact support to approve or reactivate the merchant account. |
| FORBIDDEN | 403 | The authenticated user does not have permission for this action. | Verify your account has the required role or permission. |
| TOKEN_EXPIRED | 401 | The authentication token (JWT) has expired. | Refresh the token or re-authenticate. |
PIN Errors
Returned during financial operations that require PIN verification (transfers, FX, cashout).
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| INVALID_PIN | 401 | The PIN provided is incorrect. The error message includes attempts remaining. | Show attempts remaining from the error message. Do not auto-retry — each attempt counts toward the 5-attempt block. |
| PIN_BLOCKED | 401 | Five consecutive wrong PINs. Account is blocked for financial operations. | Direct the user to Security settings to reset PIN via OTP. Do not prompt for PIN again until reset. |
| PIN_NOT_SET | 401 | The user has not set a transaction PIN yet. | Redirect user to the PIN setup flow before allowing financial operations. |
| PIN_REQUIRED | 422 | A PIN is required for this operation but was not provided in the request body. | Include the pin field in the request body. |
Balance & Wallet Errors
Returned when a wallet or account cannot fulfil the requested operation.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| INSUFFICIENT_BALANCE | 422 | The account does not have enough available balance. | Check the available balance before initiating the transaction. The details object includes available and required amounts. |
| WALLET_NOT_FOUND | 404 | No wallet exists for the given user or wallet ID. | Call POST /users/sync first to auto-create the wallet. |
| ACCOUNT_NOT_FOUND | 404 | The specified currency account was not found in the wallet. | Ensure the user has an account in the requested currency. |
| WALLET_BUSY | 409 | Another transaction on this wallet is still being processed. | Retry the request after a short delay (1-2 seconds). |
| CURRENCY_MISMATCH | 422 | The transaction currency does not match the account currency. | Send the correct currency code or use the FX endpoint for cross-currency transfers. |
| UNSUPPORTED_CURRENCY | 422 | The specified currency is not supported by Mazad. | Use one of the supported currencies: IQD, USD. |
| ACCOUNT_FROZEN | 403 | The account has been frozen by an admin or compliance action. | Contact support to resolve the account freeze. |
Payment & State Errors
Returned when a payment operation is invalid for the current payment state.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| PAYMENT_NOT_FOUND | 404 | No payment exists with the given ID. | Verify the payment ID. Payment IDs start with pay_. |
| PAYMENT_ALREADY_CAPTURED | 409 | The payment has already been captured. | This is typically safe to ignore -- the payment was already processed. |
| PAYMENT_ALREADY_VOIDED | 409 | The payment has already been voided. | The hold has been released. Create a new checkout session if needed. |
| PAYMENT_EXPIRED | 422 | The payment checkout session has expired (default: 30 minutes). The customer did not complete payment in time. | Create a new payment and send the customer a fresh checkout_url. |
| INVALID_PAYMENT_STATUS | 422 | The payment is not in the correct status for this operation. | Check the current status via GET /payments/:id before operating. |
| REFUND_EXCEEDS_AMOUNT | 422 | The refund amount exceeds the captured amount. | Ensure total refunds do not exceed the captured amount. |
| MERCHANT_NOT_FOUND | 404 | The specified merchant was not found. | Verify your merchant ID or check that your account is fully onboarded. |
Transfer Errors
Returned when wallet-to-wallet transfers fail.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| TRANSFER_LIMIT_EXCEEDED | 422 | The transfer amount exceeds the per-transaction limit. | Split the transfer into smaller amounts or request a higher limit. |
| DAILY_LIMIT_EXCEEDED | 422 | The daily transaction limit has been exceeded. | Wait until the next day or request a limit increase. |
| SELF_TRANSFER_NOT_ALLOWED | 422 | The sender and recipient are the same account. | Provide a different recipient external_user_id. |
| RECIPIENT_NOT_FOUND | 404 | The recipient account was not found. | Sync the recipient via POST /users/sync before transferring. |
| RECIPIENT_UNAVAILABLE | 422 | The recipient account is suspended or closed. | Contact the recipient to resolve their account status. |
Rate Limiting
Mazad enforces rate limits to protect the platform. When you exceed a limit, back off using the Retry-After header.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| RATE_LIMIT_EXCEEDED | 429 | Too many requests in a short period. | Implement exponential backoff. Check the Retry-After header for the wait time in seconds. |
| VELOCITY_LIMIT_EXCEEDED | 422 | Too many transactions for this user in a short period. | This is a per-user safety limit. Wait a few minutes before retrying. |
| OTP_RATE_LIMITED | 422 | Too many OTP requests for this phone number. | Wait at least 60 seconds before requesting another OTP. |
Rate limit headers
X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers so you can proactively throttle before hitting the limit.Idempotency Errors
Returned when the idempotency key header is missing or conflicts with a previous request.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| IDEMPOTENCY_KEY_REQUIRED | 422 | A POST/PATCH request was made without an Idempotency-Key header. | Generate a UUID v4 and include it in the Idempotency-Key header. |
| IDEMPOTENCY_KEY_REUSED | 409 | The idempotency key was previously used with a different request body. | Generate a new UUID for each unique request. Retries must use the same body. |
KYC & Compliance Errors
Returned when identity verification is needed or has failed.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| KYC_REQUIRED | 422 | The operation requires identity verification. | Direct the user to complete KYC before retrying. |
| KYC_PENDING | 422 | KYC verification is still being reviewed. | Wait for the kyc.approved or kyc.rejected webhook event. |
| KYC_REJECTED | 422 | The KYC submission was rejected. | Ask the user to resubmit with valid documents. |
| TIER_UPGRADE_REQUIRED | 422 | The operation requires a higher wallet tier. | Guide the user through additional verification to upgrade their tier. |
General Errors
Catch-all errors for validation failures, missing resources, and server issues.
| Code | HTTP | Description | Developer Action |
|---|---|---|---|
| VALIDATION_ERROR | 422 | One or more request fields are invalid. | Check the details object for field-level errors and fix the request body. |
| NOT_FOUND | 404 | The requested resource does not exist. | Verify the URL path and resource ID. |
| SERVER_ERROR | 500 | An unexpected internal error occurred. | Retry with exponential backoff. If persistent, contact support with the request_id. |
| SERVICE_UNAVAILABLE | 503 | The service is temporarily down for maintenance. | Retry after a short delay. Check status.mazad.com for incident updates. |
| FEATURE_DISABLED | 422 | The requested feature is not enabled for your account. | Contact sales to enable this feature on your merchant account. |
Error Handling Best Practices
Always match on error.code, not HTTP status. Multiple error codes can share the same HTTP status (e.g., 422). The code gives you precise context.
Log the request_id. Every error includes a unique request ID. Include it when contacting support for fastest resolution.
Retry with exponential backoff for 429 and 5xx errors. Start with a 1-second delay and double it on each retry, up to a maximum of 60 seconds.
Use idempotency keys for safe retries. If a request times out, you can safely retry with the same Idempotency-Key without risk of duplicate processing.
Show friendly messages to end users. Map error codes to localized user-facing messages. Never show raw error codes or technical details to customers.