# Errors

> Status codes, error codes and how to handle them.

Source: /docs/reference/errors

Every error response has the same shape:

```json
{ "code": "E_VALIDATION", "statusCode": 400, "message": "buyer.trn must be 15 digits", "field": "buyer.trn", "extra": { } }
```

| HTTP | `code` | When |
| --- | --- | --- |
| 400 | `BAD_REQUEST` | Malformed JSON, invalid query parameter, nothing to update |
| 400 | `E_VALIDATION` | Structural validation of the payload (`field` names the input) or PINT AE business rules (`extra.errors[]` with `rule`, `field`, `message`, `severity`) or custom fields (`extra.fields[]`) |
| 401 | `UNAUTHORIZED` | Missing, malformed, unknown, revoked or expired key |
| 403 | `FORBIDDEN` | Key lacks the scope (for example `periods:manage` to reopen a VAT return), or `X-Organization-Id` is not a managed client |
| 404 | `NOT_FOUND` | Route or object not found in this organization |
| 405 | `METHOD_NOT_ALLOWED` | `Allow` header lists the methods |
| 409 | `CONFLICT` | Duplicate SKU, deleting an issued invoice, submission already in progress, accepting twice |
| 409 | `period_locked` | The document is dated in a locked period (filed VAT return or lock date); `extra` has `lockSource`, `periodStart`, `periodEnd`, `firstOpenDate` |
| 409 | `lock_date_covers_period` | Reopening a VAT return that the lock date for everyone covers |
| 409 | `fiscal_settings_conflict_locked_periods` | Changing the VAT period settings would cut through a filed return |
| 413 | `PAYLOAD_TOO_LARGE` | Import over 20 MB |
| 422 | `E_VALIDATION` | Incoming import recorded but failed validation (`extra` carries the import) |
| 429 | `RATE_LIMITED` | Over 600 requests per minute; `Retry-After` header |
| 501 | `NOT_IMPLEMENTED` | Feature not available yet |
| 502 / 503 | `SERVICE_UNAVAILABLE` | ASP unreachable, email provider failed, PDF of a document other than an invoice, credit or debit note or proforma requested where no PDF service is configured (`?format=html` still works) |
| 500 | `INTERNAL_SERVER_ERROR` | Unexpected; include `X-Request-Id` when reporting it |

## Validation errors, explained

Every PINT AE validation issue the API returns (`extra.errors[]` on a 400, and `errors[]` / `warnings[]` on the validation, status, create and dry-run responses) carries more than the rule and a message:

```json
{
  "rule": "IBR-190-AE",
  "field": "lines[0].tax_rate",
  "path": "items[0].taxRate",
  "scope": "document",
  "severity": "error",
  "message": "Line 1: Standard-rated lines carry 5 %",
  "explanation": "A line taxed at the standard rate must state the UAE standard rate of 5 %.",
  "fix": "Set the line's VAT rate to 5, or change its VAT category.",
  "explanationAr": "…",
  "fixAr": "…",
  "docsUrl": "https://alyabooks.ai/reference/rules/IBR-190-AE.html"
}
```

- `path` is the field as you sent it (`items[0].taxRate`, `buyer.trn`, `precedingInvoice.number`); `field` is the internal name, kept for compatibility. Input checks that run before the rules (a 14-digit TRN, a missing description) answer 400 with `field`, `path` and `scope` at the top level of the envelope.
- `scope` says where the fix is: `document` in the request, `organization` under Settings → Company (seller TRN, address, bank account, licence).
- `explanation` and `fix` are Alya Books's plain-language text; the Arabic pair is there for finance teams working in Arabic. Business-term codes (BT-…) carry a link to the field catalogue instead of an explanation.
- `docsUrl` is public and needs no login, so it can go straight into an ERP job note or a webhook receiver's log. The links are built on the deployment's public site (`PUBLIC_SITE_URL`, else `APP_URL`); the webhook sender rewrites the links in payloads to the same site before signing.

## Batch responses

`POST /invoices`, `/customers/import` and `/products/import` process elements independently. For invoices, a failure turns the whole response into an error whose `extra.results[]` holds per-element outcomes; created elements are marked `success: true`. Customer and product imports answer `207` with `results[]` and never roll back created elements.

## Validation rules you will meet

| Rule | Message | Fix |
| --- | --- | --- |
| BT-31 / BT-35 / BT-37 / BT-40 | Seller TRN, street, city or country missing | Complete the organization profile in the app |
| BT-44 | Buyer name is required | Provide `buyer.name` or `buyer.id` |
| BT-48 | Buyer TRN must be 15 digits | Correct `buyer.trn`, or omit it for unregistered buyers (warning only) |
| BT-25 | Preceding invoice reference required | Credit and debit notes need `precedingInvoice` |
| BT-129 / BT-146 | Quantity must be > 0, price cannot be negative | Fix the line |
| BT-152 | UAE standard VAT rate must be 5 % | Use `taxRate: 5` or another category |
| BT-106 / BT-110 / BT-112 | Totals mismatch | Only on `PUT` with hand-computed totals; let Alya Books compute them |
| BT-130 | Unit code not in UN/ECE Rec 20 | Warning; use `EA`, `HUR`, `KGM`, … |

The [PINT AE field reference](/docs/reference/pint-ae-fields) lists all business terms.

## Idempotency

The API has no idempotency keys yet. Use `externalId` on invoices (`GET /invoices?externalId=`) and `reference` on imports to detect repeats before you retry, and reconcile batches with `extra.results`.
