Changelog
Versioning and deprecation
Within /v1 changes are additive only: new endpoints, new optional request fields, new response fields and new error codes. Anything that would break a working integration goes into a new version. A retiring endpoint or field is announced at least 6 months ahead: its responses carry a Deprecation header from the announcement and a Sunset header with the date it stops (RFC 8594), and the change is listed here.
31 October 2026 v1 (1.2.0)
- Test keys file for real at the Lekuka practice gateway, never at the tax authority. Each terminal has a test terminal of its own there, so a test document never takes a number from a live receipt sequence or fiscal day. Test documents are kept: list, get, stats and PDF work with a test key and name your terminal's id. They carry the practice gateway's receipt numbers, verification code and QR link (the QR opens business.marshal.co.ls/practice-receipt), and its RSL-style validation codes, so flagged documents can be tested. They are never billed or counted as live use.
- With a test key, a filed document comes back fiscalized, flagged or queued, like a live one, instead of validated. Its preview is the receipt as filed at the practice gateway.
- New: ?dry_run=true on POST /v1/{country}/invoices checks a document without keeping it — the previous test-key behaviour: the real checks, numbering and signing run and are rolled back, signed with a sandbox key. The answer has status validated and dry_run: true. Test keys only: with a live key it is 400 dry_run_needs_test_key.
- With a test key, terminals, tax-codes and the fiscal day show the test terminal (under your terminal's id), and fiscal-day open and close act on the test terminal's day, never the real one (previously 403 live_key_required).
- Every webhook payload carries test: true or false. Test documents and test terminals' fiscal days reach the business's endpoints whose mode is test or both.
- An Idempotency-Key now covers the query string too: a dry run and a real filing of the same body are different requests.
- New error codes: dry_run_needs_test_key (400) and test_terminal_unavailable (503, nothing filed; retry, or use ?dry_run=true).
30 October 2026 v1 (1.1.0)
- Breaking, made before any integration went live (the only exception to the additive rule): every POST must send an Idempotency-Key header (400 idempotency_key_required otherwise). A retry with the same key within 24 hours returns the first answer with Idempotent-Replay: true; the same key with a different body is 422 idempotency_key_reused; a retry while the first request runs is 409 idempotency_in_progress.
- Breaking, same exception: money in every answer is a decimal string with two places ("230.00"): a document's total, tax_total and taxes[].amount_with_tax / tax_amount, the stats totals, and usage's overage_price and overage_amount. Tax rates stay numbers.
- Requests accept quantities and amounts as decimal strings (recommended) or numbers; a malformed decimal string is refused (400).
- A filed document the tax authority has not answered yet comes back 202 Accepted (status queued) instead of 201; the result follows by webhook or a later read.
- Keys have scopes (documents:write, documents:read, terminals:write, terminals:read, usage:read; all by default) and an optional IP allowlist; a call outside them is 403 insufficient_scope or 403 ip_not_allowed.
- Keys can be rotated with an overlap (7 days by default, up to 30): the new key works at once and the old one until the overlap ends (then 401 key_expired).
- Webhook secrets rotate with a 24-hour overlap: Marshal-Signature then carries a v1= for each secret; accept a delivery when any v1 matches.
- New: GET /v1/postman.json (public), a Postman collection made from the specification: one request per operation, the key and environment as variables, a fresh Idempotency-Key on every POST.
30 October 2026 v1
- Errors carry the request id (error.requestId) and the field errors keyed by field (error.fields), as well as error.details.
- Every response carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy headers.
- A request body must be sent with Content-Type: application/json (415 unsupported_media_type otherwise).
- buyer.tin is checked against the Lesotho shape (123456789-0); ten digits written another way are reformatted.
- New: GET /v1/status (public) and GET /v1/changelog (public).
- /.well-known/security.txt published, with the disclosure contact.
24 September 2026 v1
- First release: terminals, fiscal days, invoices, credit and debit notes, PDFs, webhooks, usage, test and live keys.
Machine-readable: GET /v1/changelog (no key needed).