Developers

Errors and troubleshooting

Every error code the WonderFatoora API returns today, what causes it and how to fix it, with answers to the most common integration problems.

Error format

Errors use one envelope, whichever layer refused the call. Branch on error.code, never on the message: messages are written for people and may change.

{
  "ok": false,
  "error": {
    "code": "insufficient_scope",
    "message": "This API key has not been granted the permission this request needs.",
    "details": { "required_ability": "invoices:issue" }
  }
}

Language of messages

Messages are in Arabic unless the request's Accept-Language header prefers English (Accept-Language: en, en-US and so on); Arabic is used for anything else, or when the header is absent. Every answer names its language in the Content-Language header. Codes, field names and data never change with the language. The MCP server follows the opposite default for AI agents: English unless the client asks for Arabic.

Error codes

HTTP Code Cause and fix
400 bad_request The request could not be read, for example a JSON body that does not parse. details says what was wrong.
400 unsupported_api_version The path names an API version other than v1 (or a malformed one). Use /api/v1/….
401 unauthorized The key is missing, unknown or revoked, or its creator no longer belongs to the company. Check the header and the key in Settings → API keys.
401 invalid_token The OAuth access token is invalid, expired or revoked, or the company disconnected the app. Refresh it, or send the person through the consent screen again.
401 token_expired The key expired on the date in the message. details.renew_url is the page where an admin creates a new key or rotates this one (same scopes); details.expired_at is the exact time.
403 insufficient_scope The credential lacks a scope; details.required_scope names it (details.required_ability carries its original name for older clients). Use a credential that has it.
403 forbidden The person the credential acts for is not allowed to do this, for example to issue invoices. Change the user's role, or use a credential of someone who may.
403 plan_upgrade_required The company's plan does not include API access (Business or Enterprise).
403 mode_mismatch A sandbox credential met a live company, or the reverse. Credentials are always made for their own kind of company, so this does not happen in normal use; such a credential is refused as 401 unauthorized.
404 not_found No such endpoint, or no such record (a webhook endpoint or delivery) in the credential's company. Check the path and the id.
404 invoice_not_found There is no invoice with that id in the credential's company.
404 customer_not_found There is no customer with that id in the credential's company. On POST /invoices, a customer.id that is not yours answers it with 422.
404 note_not_found There is no credit or debit note with that id in the credential's company.
404 xml_not_applicable The company is not linked to ZATCA, so its documents have no legal XML. Download the PDF instead.
405 method_not_allowed The endpoint exists but not with this HTTP method; the Allow header lists the methods it takes.
409 invoice_not_draft The action needs a draft (validating or issuing it), and the document is not one, for example a draft that was cancelled. Read its status first.
409 invoice_immutable An issued document cannot be changed. Correct it with a credit or debit note.
409 document_not_available The PDF or XML is not ready yet, usually because ZATCA has not processed the document. Try again shortly.
409 idempotency_in_progress A request with the same Idempotency-Key is still running. Wait Retry-After seconds and send it again unchanged.
413 payload_too_large The request body is too large.
415 unsupported_media_type The body is not application/json. Send Content-Type: application/json.
422 validation_failed The body is invalid; details maps each field to its messages.
422 invalid_reference A line's tax_rate_id or product_id is not one of yours; details.field names it.
422 invoice_requirements_missing Your company settings lack something every invoice needs; details.missing lists it. Complete it in the dashboard.
422 invoice_not_issuable The invoice cannot be issued as it stands; the message says why. Nothing was created.
422 customer_vat_invalid The buyer's Saudi VAT number is malformed.
422 credit_exceeds_invoice The credit note is larger than what is left on the invoice after earlier credit notes.
422 note_reason_required A credit or debit note needs a reason.
409 invoice_not_payable A payment is recorded on an issued invoice only, never on a draft, cancelled or refunded one; details.status says where it stands.
422 payment_exceeds_balance The payment is more than the invoice still owes; details.balance and details.currency say how much. Overpayment can only be recorded by a person in WonderFatoora. On POST /invoices nothing was created.
422 plan_limit_reached The company has used up an allowance of its plan; details.resource and details.limit name it. Upgrade the plan or wait for the next period.
422 idempotency_key_reused This Idempotency-Key was already used with a different request. Use a new key for a new request.
429 rate_limited Too many requests. Wait Retry-After seconds. See Rate limits.
500 server_error Something failed on our side and was reported to us automatically; details.request_id identifies the call. Retry with the same external_reference or Idempotency-Key.
503 service_unavailable This endpoint is switched off for now. Try again later.

Validation errors

HTTP/1.1 422 Unprocessable Content

{
  "ok": false,
  "error": {
    "code": "validation_failed",
    "message": "The submitted data is invalid.",
    "details": {
      "issue_date": ["The issue date field is required."],
      "items.0.unit_price": ["The items.0.unit_price field is required."]
    }
  }
}

Troubleshooting

I get an HTML page instead of JSON

Send Accept: application/json on every request. Without it, a mistyped URL can answer with the website's HTML error page instead of a JSON error.

401 although the key looks right

  • The header must be Authorization: Bearer <key> (one space, no quotes) or X-Api-Key: <key>.
  • Keys in the URL are refused.
  • The key may have been rotated: the old key stops working the moment it is rotated. An expired key answers token_expired instead.
  • The user who created the key may have left the company. Create a new key.

403 forbidden when issuing

The key needs invoices:issue, and its creator needs the permission to issue invoices. A user with a view-only or limited role cannot issue through a key either.

Issuing fails with invoice_not_issuable

Read error.message; it names the problem. The most common causes: the company VAT number or address is incomplete; a business buyer of a standard invoice has no complete national address; a zero-rated or exempt line has no vat_exemption_code. Create a draft ("issue": false) to keep the data while you fix it.

The invoice is issued but deliverable is false

A standard invoice waits for ZATCA clearance. Read it again after a short wait. If its status becomes rejected, errors holds ZATCA's reasons.

A retry created a second invoice

Send external_reference. Without it, every call is a new invoice.

Getting help

Write to us with the time of the call, the HTTP status and error.code. Never send the API key itself.

Ready to build?

API access is part of the Business and Enterprise plans. Create a key in Settings → API keys and make your first call in minutes.