# Changelog

What changed in the BillOS API, newest first. Additive changes (a new field, a new endpoint, a new event) land without notice; anything that would remove or rename what a client already reads is announced here first and kept working for at least six months (see "Versioning and deprecation" under Conventions). Dates are the day the change reached production.

## Unreleased

- **Branding themes: several looks for one business.** `/businesses/:bid/branding-themes` keeps alternatives to the main design (logo, colour, page design, tagline, footer, website, signature, and a bank account, phone and email of their own to print). A document prints in the theme it names (`brandingThemeId` on a draft, a quote or a recurring plan), else its customer's `defaultThemeId`, else the main design; `"main"` forces the main design. The issued document records the theme it used and freezes its look. A theme never changes who issues: the business's name and tax id head every document. `brandingThemes.list/create/get/update` in both SDKs. Guide: Businesses → Branding themes.
- **Connect a business to the Tax Authority from your app.** `POST /businesses/:bid/ita/connect { returnUrl, state, link }` answers an address for the business's owner: he consents on the רשות המסים page with his own credentials, and lands back on your `returnUrl` with `ita=connected|denied|error`, `business` and your `state`. From then on documents that need a מספר הקצאה get one at issue, as before. `GET /businesses/:bid/ita` reads the connection (`not_connected`, `active`, `expiring`, `expired`, with `expiresAt` and `daysLeft`); `DELETE` removes it. A consent lasts 90 days: new events `ita.connected`, `ita.expiring` (7 days before, once), `ita.expired` and `ita.disconnected` say when to ask the owner again. In the sandbox the address opens a simulated consent page, and `POST /businesses/:bid/ita/simulate { state: "expiring" | "expired" }` moves the connection to its end. Only the key that created the business starts or removes a connection. `ita.get/connect/disconnect/simulate` in both SDKs. Guide: Documents → 5.9.1.
- **`forbidden` and `upgrade_required` in the Errors table.** `forbidden` now also covers a sandbox-only route called with a live key and a paused business; `upgrade_required` (402, a free plan at what it includes) was answered before it was listed.

## 2026-10-13

- **Ask for a sandbox key with no key in hand.** `POST /sandboxes` takes no credentials and answers `202` with a `verifyUrl` for a person to open and a `deviceCode` to keep; they sign in on that page and approve or decline (a test key takes the sign-in alone: no SMS, no enrolment), and `POST /sandboxes/token` then hands over a `bk_test_` key, once (a refusal is `403 authorization_denied`, also once: tell the person, and do not ask again by yourself). It is how a script, a CI job or a coding agent starts: the agent shows its user the link and waits. The key is an ordinary sandbox key on that person's developer account, capped at 5,000 requests a day until the account verifies a phone. `createSandbox()` + `waitForSandbox()` in `billos-sdk`, `create_sandbox()` + `wait_for_sandbox()` in the Python package, `billos sandbox` in the CLI. Guide: Sandbox, and the new AI agents page. (A server may also enable anonymous sandboxes, which answer `201` with a key at once; a client reads `status`.)
- **Errors say how to fix themselves.** Every error body now carries `hint` (what to change), `param` (the field, when there is one), `doc_url`, `retryable` and `request_id` beside `error` and `reason`. Additive: nothing that read `error` or `reason` changes. A `401` now also carries `reason: "unauthorized"`. The new Errors page lists every reason and is generated from the table the API answers from. `BillOSError` in both SDKs exposes the new fields.
- **Check before the permanent act.** `POST /businesses/:bid/documents/:id/validate` runs every check issuing runs and takes no number: it answers what would be issued, or the exact refusal. `documents.validate` in both SDKs, `--dry-run` in the CLI.
- **A credit note takes an idempotency key.** `POST /documents/:id/credit-note` reads `Idempotency-Key` (or `externalRef`): the same key answers the credit note already issued with `deduped: true`, and a key that produced another document is `409 conflict`. Until now a retried call could issue a second partial credit. The MCP tool `cancel_document` takes `idempotencyKey`. Also fixed: `refund: { "method": "card" }` ignored the alias and recorded how the money first arrived; a code or an alias is now read like any payment method.
- **A key may be sent as a Bearer.** `Authorization: Bearer bk_...` is accepted wherever `X-Api-Key` is. The MCP guide said so before the server did.
- **MCP: thirteen tools, annotated.** New: `create_business`, `seed_sandbox` (sandbox keys), `search_docs` (this documentation, by section). `create_document` takes `dryRun` and `idempotencyKey`, and a refused call no longer leaves a draft behind. Every tool declares whether it only reads or writes a permanent record, results also arrive as structured content, and a refusal carries `reason`, `hint` and `param`.
- **Docs for agents.** A page answers markdown to `Accept: text/markdown` (the `.md` twin is unchanged); `GET /search?q=` on the docs host searches sections as JSON; `openapi.json` carries a request example on every operation, an example body on every error response, and declares Connect as an OAuth 2.0 security scheme. The example body for creating a document now issues as printed (its payment did not equal its total).
- **SDK 0.5.0 (Node and Python).** The additions above, and in the TypeScript types a field with a default (`currency`, `lang`, `priceMode`) is optional, as it always was on the wire; the issue response declares `warnings`. `PATCH` on a draft is now documented as it behaves: `docType` and `priceMode` are fixed at creation, and the customer changes by `partyId`.
- **BillOS on Zapier and on Make, in early access.** Two apps over Connect and webhooks, joined by invite link: four instant triggers (a document issued, an invoice paid in full, a quote accepted, an expense recorded) and actions that create a document, return its PDF link and find a document. Links and what each does: https://dev.billos.co.il/integrations/zapier/ and https://dev.billos.co.il/integrations/make/.
- **The Python SDK is on PyPI** (`pip install billos`, 0.5.0), and the kit for coding agents with three example apps is public: https://github.com/billos-dev/billos.
- **CLI 0.4.0.** English by default (`BILLOS_LANG=he` or a Hebrew locale keeps Hebrew), `billos sandbox` (prints a link, waits for the approval, stores the key; with `--json` it returns at once and is run again to collect), `billos seed`, `billos init` (prepares a repository for Claude Code, Cursor and Codex), `billos listen --forward-to` (events to a local server, signed like a delivery), and `--dry-run` on issuing.

## 2026-09-24

- **The request log names the client.** Every row of `GET /requests` now carries `userAgent`: the `User-Agent` the call sent (`billos-sdk/…`, `billos-python/…`, `billos-cli/…`, a browser, curl), up to 200 characters. Rows from before carry `null`. From CLI 0.3.1 the CLI sends `billos-cli/<version>` (earlier versions send node's default, `node`). Guide: Conventions → Request ids.

## 2026-09-16

- **The OCR daily cap counts per account.** A developer account's sandbox and live clients now share the one daily OCR quota (still 200 a day by default) instead of each holding its own; the sandbox scans you run in the morning count against the same day's live scans. Nothing changes for a partner client we minted for you. Guide: Conventions → Limits, and Expenses → 6.1.

## 2026-09-08

- **Connect: your app, acting for a business its owner connected.** OAuth 2.0 (authorization code + PKCE) for apps that act on businesses they did not open: register an app under your key (`POST /oauth-apps`, secret shown once; live apps approved by us, sandbox apps at once), send the owner to `GET /oauth/authorize`, exchange the code at `POST /oauth/token` for a per-business access token (1h) and refresh token (180d, rotated on use), and call `/v1` with `Authorization: Bearer bkat_…`. Your own key also sees every connected business with the grant's scopes (Stripe's Connect shape). Events fan out to your endpoints, filtered by the grant; new events `connection.created` / `connection.revoked`. The owner manages connections in the console (`DELETE /businesses/:bid/connections/:id`); you can `POST /oauth/deauthorize`. SDKs: `accessToken` / `access_token`, the `apps` and `connections` namespaces. Guide: section 15.
- **Shared access: the accountant signs in.** `POST /businesses/:bid/access { email, role }` opens a business to a person by email; whoever signs in to the console with that address finds it under "משותפים איתי" with exactly the scopes the owner named (`accountant`: read everything and generate the regulatory outputs; `bookkeeper`: plus recording expenses; `viewer`: read only; or an explicit list). Revoke with `DELETE`. Every business row now carries `access` (`via: owner | shared | connect`, `owner`, `scopes`). Guide: Businesses → Shared access.
- **CLI 0.3.0 and both SDKs 0.3.0 carry Connect.** `billos access` shares a business with an accountant by email and role, `billos connections` is the owner's view of the apps connected to it, and `billos apps create|update|rotate|delete` registers and manages a Connect app (the secret printed once, with a ready authorize URL). In the SDKs: `accessToken` / `access_token` for a per-business token, and the `access`, `connections` and `apps` namespaces. `npm install -g billos@0.3.0`, `npm install billos-sdk@0.3.0`, `pip install billos==0.3.0`.
- **Scopes have a read half and a write half.** A refusal names the fine scope the request needed (`missing scope: documents:write`). A key's coarse scope (`documents`) covers both halves, so no existing key changes; the fine grain is what a shared business, and soon a connected app, carries. Guide: Authentication.
- **CLI 0.2.0.** `billos receivables` (who owes you, aged), `billos vat [--file]` (the periodic figures, and the PCN874 file on disk), `billos archive` (the previous software's documents), `--settles` and `--due` on `billos invoice`, and `--amount` / `--refund` on `billos cancel` for a partial credit. `billos usage` also prints the response times. Guide: CLI.
- **MCP: `list_open_items`.** "מי חייב לי כסף" as a tool: every invoice with a balance, aged, filterable by customer or to the overdue only. Each row carries the `documentId` a receipt then names in `settles`, so closing an invoice needs no second lookup. Tenth tool. Guide: MCP.
- **`BillOS-Version` really is on every response**, refusals included: the current version is stamped beside the request id, and a pinned one overwrites it. Until now a 401 carried no version, which the Conventions section had promised. The endpoint index also gained the sixteen routes it had silently fallen behind on (webhook CRUD, `GET /businesses`, the keys pair, share, email, expenses/record, files/thumbs, parties/find-or-create, deleting an expense draft), and a test now holds the index equal to the router. Section numbering under Documents runs 5.1 to 5.11 in order.
- **PCN874 reconciles to the shekel.** The header's four figures are now summed from the entries' own rounded shekels instead of from the agorot, so the Tax Authority's intake reports 100% instead of a file that is 99.97% correct (a ₪2 gap over 24 sales entries, seen in their simulator). New warning `supplier_is_self`: an expense whose supplier number is the business's own, which the intake refuses (input VAT on your own invoice) and which otherwise surfaces only as a whole return that cannot be transmitted.
- **Company number validated.** `registrationNumber` on `POST /businesses` is checked for its check digit like `taxId` (`400 validation`); a wrong one used to pass and fail the ITA structure check in the מבנה אחיד header (A000 field 1015).
- **Request log and latency.** `GET /requests`: every call made with your key over the last 7 days, with status, server time in ms, request id and error reason; `GET /usage` adds `latency` (p50 / p95 / max). The console's פעילות page shows both. Guide: Conventions → Request ids.
- **Import the history.** `POST /businesses/:bid/legacy-documents` archives a document the business issued with its previous software (metadata as printed there, then the original file with `PUT …/file`), re-runnable by `source` + `docNumber`; list, read, download. Never a BillOS document: no number, no signature, absent from every export and report. With `startNumbers` on the business, this is the whole move. Console: מסמכים → ארכיון. Guide: Documents → 5.9.1.
- **Dated API versions.** `BillOS-Version: 2026-09-08` on a request pins the behaviour inside `/v1`; every response names the version that answered; an unknown date is `400 validation`. One version exists today, so nothing changes for any client; the header is how a future change inside `/v1` ships without breaking what you built. `apiVersion` / `api_version` in the SDKs. Guide: Conventions → Versioning.
- **Webhook replay by time range.** `POST /webhooks/:id/replay { from, to, events?, businessId?, includeDelivered? }` queues every event in the window the endpoint would have received, from the event log; what already arrived is skipped unless asked for; 1,000 per call with `nextFrom` to continue. The console's endpoint dialog has it too. Guide: Webhooks → Redelivery.
- **Detailed VAT return (PCN874).** `POST /businesses/:bid/exports/pcn874 { period, months }` writes the PCN874.TXT the Tax Authority takes with the periodic return, built from the ledger per שע"ם's spec; `GET /businesses/:bid/reports/vat` is the same as figures, entries and warnings without a file. Guide: Regulatory outputs.
- **Partial credit notes.** `POST /documents/:id/credit-note` takes `amount` (VAT included) or `lines` for a partial credit (הוראה 23א): the source stays open on the remainder, `reason` is required, the credits against a document are capped at its total, and `refund: true` on a 320 also issues the negative receipt. Every credit note and negative receipt now prints the document it corrects ("בגין חשבונית מס 1234"). The document view carries `credit: { creditedAmount, remaining }`.
- **Open items.** A receipt names the invoices it pays (`settles` on `POST /documents`, printed as "על חשבון"); a 305/300 carries `settlement` (balance, status `open` / `partial` / `paid` / `cancelled`, what paid it) and list rows carry `balance` / `paymentStatus`; `GET /businesses/:bid/receivables` is the aged open-items report; `POST /documents/:id/settlements` matches an issued receipt afterwards; `dueDate` on a 305/300 prints "לתשלום עד"; new event `document.paid`. Guide: Documents → Open items.

## 2026-09-07

- **Usage as a series.** `GET /usage` also answers `daily`: one row per day with traffic in the window, beside the per-endpoint sums. What the console's developers page charts.
- **Python SDK.** `billos` on PyPI: the same client for Python 3.9+, no dependencies, every operation as `namespace.method`. Guide: SDK.
- **Recurring documents.** `POST /businesses/:bid/recurring`: a plan (the fields of a document plus monthly / quarterly / yearly on a fixed day) that becomes a draft, or an issued document with `autoIssue`, every interval; `recurring.ran` webhook event; `/run` for now. Guide: Documents → 5.8.
- **Key IP allowlist.** `PATCH /keys/:id { allowedCidrs }` pins a key to your servers' addresses; requests from elsewhere answer `403 forbidden`. `GET /keys` shows it.
- **Sandbox fixtures.** `POST /sandbox/seed` on a test key creates a demo business with customers, issued documents (including a dollar one in English), a draft and expenses, idempotently. Guide: Sandbox.
- **Mixed VAT rates.** `vatRateBp: 0` on a line makes it VAT-exempt on an otherwise VAT-bearing document; VAT is computed per line and summed, the totals carry `vatRateBp: null` and `vatByRate`, the page tags the exempt lines and names the taxed base. A document-level discount is refused with mixed rates.
- **Proforma invoices.** `docType: 300` (חשבונית עסקה): a demand for payment, numbered in its own gapless series and immutable like every issued document, exported in the מבנה אחיד, but not a tax document: no VAT effect, never income, no credit note. Open to a עוסק פטור as well. In both PDF designs, the console, the MCP tool (`proforma`) and the CLI (`--type proforma`).

## 2026-09-06

- **SDK.** `billos-sdk` on npm: a typed Node.js / TypeScript client generated from this spec. Guide: SDK.
- **Foreign-currency documents.** `currency` (USD, EUR, GBP, CHF, AUD, CAD) on a draft; the ledger keeps recording shekels at `fxRate` (yours) or the Bank of Israel שער יציג of the document date. New: `GET /businesses/:bid/fx-rate`, `fxTotalIncVat` / `fxRate` / `fxRateDate` on documents, the C100 1217/1218 fields in the מבנה אחיד export, `fx_rate_required` (422). Guide: Documents → Foreign currency.
- **Zero-rated VAT.** `zeroVat: true` on a 305/320 (סעיף 30): 0% on the lines, the legal basis printed.
- **English documents.** `lang: "en"`: an LTR English page in both designs, bilingual on the statutory headings. The document email follows the language.
- **Request ids.** Every response carries `X-Request-Id` (yours echoed when valid); it is the `ref` inside an opaque error.
- **Rate-limit headers.** `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on every response.
- **Event log.** `GET /events` and `GET /events/:id`: everything that happened under your key, whether or not a webhook was subscribed. Deliveries carry `eventId`.
- **Webhook redelivery.** `POST /webhooks/:id/deliveries/:did/redeliver`.
- **Status page.** status.billos.co.il, with `/status.json`.
- **CLI 0.1.2.** `--currency`, `--rate`, `--zero-vat`, `--lang`.

## 2026-09-03

- **Two document designs.** `branding.template`: `classic` (the default) or `stripe`. A document keeps its design forever.
- **MCP endpoint.** `POST /mcp` for AI agents, nine business-verb tools, same key and limits as REST.
- **Per-business plans and ceilings.** `maxDocsPerMonth` / `maxExpensesPerMonth` on the business; `limit_reached` (429).
- **Account approval.** An open-signup business starts `pending` (drafts only) until approved; `approval_required` (403). `business.status_changed` webhook event.
- **CLI.** `billos` on npm.

## 2026-08-30

- **Webhooks.** Endpoints, signed deliveries (`X-BillOS-Signature`), retries with backoff, `webhook.test`, the deliveries list.
- **Sandbox.** `bk_test_` keys answer from a separate database; `X-BillOS-Mode` on every response.

## 2026-08-20

- **Public docs and the clean host.** docs.billos.co.il, `https://api.billos.co.il/v1` (the `/api/books/v1` alias keeps answering), `llms.txt`, `openapi.json`.
- **API keys.** One client, many keys: rotation without downtime, `GET /keys`, `GET /usage`.