{"auth":{"apikey":[{"key":"key","type":"string","value":"X-Api-Key"},{"key":"value","type":"string","value":"{{apiKey}}"},{"key":"in","type":"string","value":"header"}],"type":"apikey"},"info":{"description":"Generated from https://dev-pos-backoffice.marshal.co.ls/v1/openapi.json. Set apiKey (a mk_test_ key to start), and terminalId from List terminals.\n\nSend an invoice; Marshal validates it against the taxpayer's own tax table, numbers and signs it on your fiscal device, files it with the tax authority and returns the fiscal details (receipt numbers, verification code, QR link).\n\n**Keys.** `mk_test_…` keys file for real at the Lekuka practice gateway, never at the tax authority. Each of your terminals 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 and PDF work) and carry the practice gateway's receipt numbers, verification code and QR link; the gateway answers with the authority's validation codes, so `flagged` documents can be tested too. Webhooks for them say `test: true`. They are never billed or counted as live use. Add `?dry_run=true` to a test filing to check a document without keeping anything. `mk_live_…` keys file for real.\n\n**Idempotency.** Every POST sends an `Idempotency-Key` header: a unique value per operation, e.g. a UUID (400 `idempotency_key_required` otherwise). Retry with the same key after a timeout and, within 24 hours, you get the first answer back (same status and body, header `Idempotent-Replay: true`); the work is never done twice. The same key with a different body is refused (422 `idempotency_key_reused`). Your `invoice_number` stays unique too: the same number with the same content returns the original document (200), with different content 409 `idempotency_conflict`.\n\n**Amounts** are decimal strings in every answer (`\"230.00\"`), never binary floats. In requests, send quantities and amounts as decimal strings (recommended) or numbers.\n\n**202 Accepted.** When the tax authority has not answered yet, a filed document comes back 202 with status `queued`; the result follows by webhook (`document.fiscalized`, `document.flagged`, `document.failed`) or a later read.\n\n**Scopes and allowed addresses.** A key holds scopes (`documents:write`, `documents:read`, `terminals:write`, `terminals:read`, `usage:read`; all of them by default) and may be limited to IP addresses or ranges. A call outside them is refused with 403 `insufficient_scope` or `ip_not_allowed`. Rotate a key from the developer area: the new key works at once and the old one keeps working for an overlap you choose (7 days by default, up to 30) before it expires.\n\n**Corrections.** An issued invoice can never be changed. Correct it with a `credit_note` or `debit_note` that names it in `original_invoice_number`.\n\n**Webhooks** are signed: `Marshal-Signature: t=\u003cunix\u003e,v1=\u003chex HMAC-SHA256(secret, \"\u003ct\u003e.\u003craw body\u003e\")\u003e`. For 24 hours after you rotate an endpoint's secret the header carries a `v1=` for the new secret and one for the old: accept the delivery when any `v1` matches.\n\n**Limits.** 600 requests a minute per key. Every response carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy`; over the limit the answer is 429 with `Retry-After`.\n\n**Requests.** Bodies are JSON with `Content-Type: application/json` (otherwise 415), at most 2 MB. Unknown fields are refused (400), never ignored: a misspelt field would otherwise file the wrong document.\n\n**Errors.** One envelope: `{\"error\": {\"code\", \"message\", \"details\", \"fields\", \"requestId\"}, \"request_id\"}` — our stated equivalent of RFC 9457 problem details. Send `X-Request-Id` to set the id; it is echoed back.\n\n**Environments.** The test address is `https://dev-pos-backoffice.marshal.co.ls`; documents filed there go to RSL's test gateway and do not count with RSL. The production address will be announced.","name":"Marshal Fiscal API 1.3.0","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"item":[{"item":[{"name":"List documents (of the key's mode)","request":{"description":"Requires the `documents:read` scope. A test key reads its test documents, a live key its live ones.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","invoices"],"query":[{"description":"Comma-separated: validated, queued, fiscalized, flagged, failed","disabled":true,"key":"status","value":""},{"description":"Comma-separated: sale, credit_note, debit_note","disabled":true,"key":"type","value":""},{"description":"","disabled":true,"key":"terminal_id","value":""},{"description":"","disabled":true,"key":"invoice_number","value":""},{"description":"Date (2026-09-01) or RFC 3339 time, inclusive","disabled":true,"key":"from","value":""},{"description":"Date or time, exclusive","disabled":true,"key":"to","value":""},{"description":"","disabled":true,"key":"limit","value":""},{"description":"","disabled":true,"key":"offset","value":""}],"raw":"{{baseUrl}}/v1/{{country}}/invoices"}}},{"name":"File a sale, credit note or debit note","request":{"body":{"mode":"raw","options":{"raw":{"language":"json"}},"raw":"{\n  \"buyer\": {\n    \"name\": \"Acme (Pty) Ltd\",\n    \"tin\": \"200012345-6\"\n  },\n  \"invoice_number\": \"INV-1002\",\n  \"lines\": [\n    {\n      \"description\": \"Consulting, September\",\n      \"is_service\": true,\n      \"quantity\": \"1\",\n      \"tax_category\": \"standard\",\n      \"unit_price\": \"5750.00\"\n    }\n  ]\n}"},"description":"Requires the `documents:write` scope.","header":[{"key":"Content-Type","value":"application/json"},{"description":"A new value for each operation; resend the same value to retry it safely.","key":"Idempotency-Key","value":"{{$guid}}"}],"method":"POST","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","invoices"],"query":[{"description":"Test keys only. `true` checks the document without keeping it: the real checks, numbering and signing run and are then rolled back, the receipt is signed with a sandbox key, nothing is filed and nothing is kept. The answer has status `validated` and `dry_run: true`. With a live key it is refused (400 `dry_run_needs_test_key`), so a check is never filed for real.","disabled":true,"key":"dry_run","value":""}],"raw":"{{baseUrl}}/v1/{{country}}/invoices"}}},{"name":"Counts and totals by status and type (same filters as the list)","request":{"description":"Requires the `documents:read` scope. A test key reads its test documents, a live key its live ones.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","invoices","stats"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/invoices/stats"}}},{"name":"Get a document","request":{"description":"Requires the `documents:read` scope. A test key reads its test documents, a live key its live ones.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","invoices","{{documentId}}"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/invoices/{{documentId}}"}}},{"name":"Printable fiscal document (80 mm)","request":{"description":"Requires the `documents:read` scope. A test key reads its test documents, a live key its live ones.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","invoices","{{documentId}}","pdf"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/invoices/{{documentId}}/pdf"}}}],"name":"Invoices"},{"item":[{"name":"List terminals","request":{"description":"Requires the `terminals:read` scope. With a test key, the terminal's test terminal at the Lekuka practice gateway: its device, enrolment and fiscal day, under this terminal's id, name and place.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals"}}},{"name":"Enrol a new fiscal device (live keys only)","request":{"body":{"mode":"raw","options":{"raw":{"language":"json"}},"raw":"{}"},"description":"Requires the `terminals:write` scope.","header":[{"key":"Content-Type","value":"application/json"},{"description":"A new value for each operation; resend the same value to retry it safely.","key":"Idempotency-Key","value":"{{$guid}}"}],"method":"POST","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals"}}},{"name":"Get a terminal","request":{"description":"Requires the `terminals:read` scope. With a test key, the terminal's test terminal at the Lekuka practice gateway: its device, enrolment and fiscal day, under this terminal's id, name and place.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals","{{terminalId}}"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals/{{terminalId}}"}}},{"name":"The current (or last) fiscal day","request":{"description":"Requires the `terminals:read` scope. With a test key: the test terminal's fiscal day.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals","{{terminalId}}","fiscal-day"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals/{{terminalId}}/fiscal-day"}}},{"name":"Close the fiscal day (Z report). On an Offline device this builds and sends the day file.","request":{"description":"Requires the `terminals:write` scope. With a test key: closes the test terminal's day at the practice gateway, never the real one.","header":[{"key":"Content-Type","value":"application/json"},{"description":"A new value for each operation; resend the same value to retry it safely.","key":"Idempotency-Key","value":"{{$guid}}"}],"method":"POST","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals","{{terminalId}}","fiscal-day","close"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals/{{terminalId}}/fiscal-day/close"}}},{"name":"Open a fiscal day (auto-day terminals do this themselves)","request":{"description":"Requires the `terminals:write` scope. With a test key: opens the test terminal's day at the practice gateway, never the real one.","header":[{"key":"Content-Type","value":"application/json"},{"description":"A new value for each operation; resend the same value to retry it safely.","key":"Idempotency-Key","value":"{{$guid}}"}],"method":"POST","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals","{{terminalId}}","fiscal-day","open"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals/{{terminalId}}/fiscal-day/open"}}},{"name":"Offline mode: the day files and how the authority processed them","request":{"description":"Requires the `terminals:read` scope.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals","{{terminalId}}","fiscal-days","{{dayNo}}","files"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals/{{terminalId}}/fiscal-days/{{dayNo}}/files"}}},{"name":"The taxpayer's tax table — the tax_id values a line may use","request":{"description":"Requires the `terminals:read` scope. With a test key: the practice gateway's table, which test documents are checked and filed against. `sandbox` is true only for a terminal not yet enrolled, on a server without the practice gateway.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","{{country}}","terminals","{{terminalId}}","tax-codes"],"query":[],"raw":"{{baseUrl}}/v1/{{country}}/terminals/{{terminalId}}/tax-codes"}}}],"name":"Terminals"},{"item":[{"name":"Changelog and deprecation policy","request":{"auth":{"type":"noauth"},"description":"Public, no key. Dated changes to the API, newest first, and the policy: additive changes only within /v1; anything retiring is announced 6 months ahead with Deprecation and Sunset headers (RFC 8594).","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","changelog"],"query":[],"raw":"{{baseUrl}}/v1/changelog"}}},{"name":"List countries","request":{"auth":{"type":"noauth"},"description":"","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","countries"],"query":[],"raw":"{{baseUrl}}/v1/countries"}}},{"name":"The key and account you are calling as","request":{"description":"","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","me"],"query":[],"raw":"{{baseUrl}}/v1/me"}}},{"name":"Service status","request":{"auth":{"type":"noauth"},"description":"Public, no key. Whether the API is up, whether RSL is answering (from the Suite's regular checks of real devices), whether anything is waiting in the filing queue, and when a document was last filed. Aggregate figures only. Cached for 30 seconds.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","status"],"query":[],"raw":"{{baseUrl}}/v1/status"}}},{"name":"This month's billable documents and allowance","request":{"description":"Requires the `usage:read` scope.","header":[],"method":"GET","url":{"host":["{{baseUrl}}"],"path":["v1","usage"],"query":[],"raw":"{{baseUrl}}/v1/usage"}}}],"name":"Account"}],"variable":[{"key":"baseUrl","value":"https://dev-pos-backoffice.marshal.co.ls"},{"key":"apiKey","value":""},{"key":"country","value":"ls"},{"key":"terminalId","value":""},{"key":"documentId","value":""},{"key":"dayNo","value":""}]}
