# Errors

Every failure is a JSON body with the matching HTTP status:

```json
{
  "error": "payments 30000 != total 35400",
  "reason": "payments_mismatch",
  "hint": "Make the sum of payments[].amount equal the total including VAT, in agorot. With priceMode gross the total is the sum of quantity x unitPriceExVat as you sent them.",
  "param": "payments",
  "doc_url": "https://docs.billos.co.il/errors#payments_mismatch",
  "retryable": false,
  "request_id": "5b0c7a52-6f0e-4c0e-9d0e-2f7f1f0a9a11"
}
```

| Field | Meaning |
|---|---|
| `error` | What went wrong, in one sentence. For people and logs; do not branch on it. |
| `reason` | The stable machine code. Branch on this. |
| `hint` | What to change so the call succeeds. |
| `param` | The request field the refusal is about, when there is one (`lines`, `payments`, `party.taxId`). |
| `doc_url` | This page, at the reason. |
| `retryable` | `true` when sending the **same** request again can succeed (after `Retry-After` when the response carries one). `false` means change something first. |
| `request_id` | The id in the `X-Request-Id` header. Quote it to support. |

A create that fails with a retryable reason is safe to send again only with the same `Idempotency-Key`.

To learn whether a draft would issue without issuing it, call `POST /businesses/:bid/documents/:id/validate`: it runs every check issuing runs and takes no number.

| `reason` | HTTP | Retryable | Meaning |
|---|---|---|---|
| [`unauthorized`](#unauthorized) | 401 | no | No credential, or one we do not recognise: a missing, mistyped, revoked or expired key or token. |
| [`forbidden`](#forbidden) | 403 | no | The credential is real but may not do this: a missing scope, an address outside the key's allowlist, a route that belongs to an API key and was called with a session or a Connect token, a sandbox-only route called with a live key, or a business that is paused. |
| [`not_found`](#not_found) | 404 | no | No such resource under this credential. A business another key owns answers exactly like one that does not exist. |
| [`validation`](#validation) | 400 | no | The request is malformed or misses a field. The message says which; `param` names the field when there is one. |
| [`doc_type_not_allowed`](#doc_type_not_allowed) | 400 | no | This dealer type may not issue this document type: an exempt dealer (patur) issues receipts and proformas, never a tax invoice. |
| [`payments_mismatch`](#payments_mismatch) | 400 | no | On a receipt-bearing document the payments must add up to exactly the document total (net of withholding). |
| [`not_draft`](#not_draft) | 409 | no | The document or expense is already issued or recorded, and an issued record is immutable. |
| [`conflict`](#conflict) | 409 | no | The request collides with existing state: a duplicate unique value, or a race another request won. |
| [`pdf_pending`](#pdf_pending) | 409 | yes | The document is issued but its PDF has not finished rendering. |
| [`allocation_required`](#allocation_required) | 422 | no | The invoice is over the allocation-number threshold for an identified business buyer, the business requires a number, and none could be obtained. |
| [`fx_rate_required`](#fx_rate_required) | 422 | no | A foreign-currency document needs an exchange rate and the Bank of Israel published none for its date. |
| [`approval_required`](#approval_required) | 403 | no | The business account is not open for permanent records: pending approval, or suspended. |
| [`authorization_denied`](#authorization_denied) | 403 | no | The person who was asked to approve a sandbox key declined. |
| [`upgrade_required`](#upgrade_required) | 402 | no | The business is on a free plan and reached what that plan includes (its documents for the month, for example). |
| [`limit_reached`](#limit_reached) | 429 | no | A per-business ceiling was reached: the monthly document or expense ceiling, or a pending account's daily draft cap. |
| [`business_limit`](#business_limit) | 403 | no | This API client already holds as many open businesses as its owner allowed (5 unless changed). |
| [`payment_required`](#payment_required) | 402 | no | Opening a live business needs a card on file, and an unpaid month blocks new businesses until it is paid. |
| [`rate_limited`](#rate_limited) | 429 | yes | Too many requests in the window. |
| [`quota_exceeded`](#quota_exceeded) | 429 | no | The daily OCR quota is used up. |
| [`too_large`](#too_large) | 413 | no | The request body is over the size limit (JSON 2MB, a receipt image 5MB, an imported file 8MB). |
| [`unconfigured`](#unconfigured) | 503 | no | This server is missing configuration the request needs (for example, no sandbox database). |
| [`provider`](#provider) | 502 | yes | An internal dependency failed (rendering, signing, storage). Transient. |
| [`internal`](#internal) | 500 | no | An unexpected server error. |

## unauthorized

HTTP 401. No credential, or one we do not recognise: a missing, mistyped, revoked or expired key or token.

**What to do:** Send the key in the X-Api-Key header (or as Authorization: Bearer bk_...). No key yet? POST https://api.billos.co.il/v1/sandboxes with no credentials starts a request for a sandbox key that a person approves in the console.

## forbidden

HTTP 403. The credential is real but may not do this: a missing scope, an address outside the key's allowlist, a route that belongs to an API key and was called with a session or a Connect token, a sandbox-only route called with a live key, or a business that is paused.

**What to do:** The message names what is missing. A scope has a read half and a write half; GET /keys shows what this key holds.

## not_found

HTTP 404. No such resource under this credential. A business another key owns answers exactly like one that does not exist.

**What to do:** Check the id and the mode: a bk_test_ key sees only sandbox data and a bk_live_ key only live data (the X-BillOS-Mode response header says which answered). GET /businesses lists the ids this key holds.

## validation

HTTP 400. The request is malformed or misses a field. The message says which; `param` names the field when there is one.

**What to do:** Fix the field the message names and send again. Amounts are integer agorot, dates are YYYY-MM-DD, docType is the numeric code (300, 305, 320, 330, 400).

## doc_type_not_allowed

HTTP 400. This dealer type may not issue this document type: an exempt dealer (patur) issues receipts and proformas, never a tax invoice.

**What to do:** For a patur business use docType 400 (receipt) or 300 (proforma). GET /businesses/:bid returns dealerType.

## payments_mismatch

HTTP 400. On a receipt-bearing document the payments must add up to exactly the document total (net of withholding).

**What to do:** Make the sum of payments[].amount equal the total including VAT, in agorot. With priceMode gross the total is the sum of quantity x unitPriceExVat as you sent them.

## not_draft

HTTP 409. The document or expense is already issued or recorded, and an issued record is immutable.

**What to do:** Do not edit or delete it. Correct an issued document with POST /businesses/:bid/documents/:id/credit-note, and a recorded expense with its correction route.

## conflict

HTTP 409. The request collides with existing state: a duplicate unique value, or a race another request won.

**What to do:** Read the resource again and decide from its current state. Retrying a create with the same Idempotency-Key returns the row that already exists.

## pdf_pending

HTTP 409. The document is issued but its PDF has not finished rendering.

**What to do:** Wait a second or two and ask again, or subscribe to the document.pdf_ready webhook.

## allocation_required

HTTP 422. The invoice is over the allocation-number threshold for an identified business buyer, the business requires a number, and none could be obtained.

**What to do:** The business owner connects the Israel Tax Authority once in the BillOS console; after that the number is requested automatically at issue. See the Documents guide, "Allocation numbers".

## fx_rate_required

HTTP 422. A foreign-currency document needs an exchange rate and the Bank of Israel published none for its date.

**What to do:** Pass fxRate (ILS per 1 unit of the currency). GET /businesses/:bid/fx-rate?currency=USD&date=YYYY-MM-DD shows what would be used.

## approval_required

HTTP 403. The business account is not open for permanent records: pending approval, or suspended.

**What to do:** Drafts still work. Nothing in the request is wrong: the account owner contacts BillOS support. A sandbox business is never pending.

## authorization_denied

HTTP 403. The person who was asked to approve a sandbox key declined.

**What to do:** Do not ask again by yourself. Tell the person the request was declined and let them decide whether to start a new one (POST /sandboxes).

## upgrade_required

HTTP 402. The business is on a free plan and reached what that plan includes (its documents for the month, for example).

**What to do:** The way forward is a paid plan for the business; sending again will not help until it has one or the month turns over.

## limit_reached

HTTP 429. A per-business ceiling was reached: the monthly document or expense ceiling, or a pending account's daily draft cap.

**What to do:** Sending again will not help until the period turns over or BillOS raises the ceiling.

## business_limit

HTTP 403. This API client already holds as many open businesses as its owner allowed (5 unless changed).

**What to do:** Close a business you no longer use (POST /businesses/:bid/deactivate), or raise the limit in the console under שימוש וחיוב. Every open business is billed for the month.

## payment_required

HTTP 402. Opening a live business needs a card on file, and an unpaid month blocks new businesses until it is paid.

**What to do:** Add or replace the card in the console under שימוש וחיוב. Existing businesses keep working; only opening a new one waits.

## rate_limited

HTTP 429. Too many requests in the window.

**What to do:** Wait the number of seconds in the Retry-After header, then send the same request. X-RateLimit-Remaining on every response says how much is left.

## quota_exceeded

HTTP 429. The daily OCR quota is used up.

**What to do:** It resets at midnight Asia/Jerusalem. Create the expense with its fields typed instead of scanned.

## too_large

HTTP 413. The request body is over the size limit (JSON 2MB, a receipt image 5MB, an imported file 8MB).

**What to do:** Send a smaller body: downscale the image, or split the request.

## unconfigured

HTTP 503. This server is missing configuration the request needs (for example, no sandbox database).

**What to do:** Nothing in the request is wrong. Report it to BillOS support with the request_id.

## provider

HTTP 502. An internal dependency failed (rendering, signing, storage). Transient.

**What to do:** Send the same request again with the same Idempotency-Key: a retry can never create a second record.

## internal

HTTP 500. An unexpected server error.

**What to do:** Report it to BillOS support with the request_id. If you retry a create, keep the same Idempotency-Key.
