{"info":{"name":"Sente Rails API","description":"Sente Rails is the public API for the Sente Rails revenue collection\nrail \u2014 citizen identification, service catalog, multi-MDA assessment,\naggregator-level payment splitting, fiscal receipting, and end-of-\nshift reconciliation.\n\nSente Rails NEVER holds public money (PFMA \u00a743): split rules live on\nPayment Intent; the aggregator (MoMo / Airtel / Pesapal / Flutterwave)\nexecutes the split directly into each MDA's collection account.\nSente Rails records the event with proof and propagates downstream\nwebhooks.\n\n## Three interaction modes\n\nEndpoints serve three classes of consumer (per ARCHITECTURE.md \u00a73):\n  - **Mode A \u2014 System of Record.** MDAs without an existing digital\n    revenue system. Clerks transact directly through these endpoints.\n  - **Mode B \u2014 Integration.** External MDA systems (URA EFRIS, URSB\n    OBRS, NIRA via UGHub) are *called* by Sente Rails through internal\n    adapters. They push events back via `/v1/webhooks/{provider}`.\n  - **Mode C \u2014 Oversight.** Read-only consumers (OAG, MoFPED, UBOS)\n    will consume `/v1/oversight/*` (planned, not yet shipped).\n\n## URL scheme\n\nAll endpoints are versioned under `/v1/`. Canonical URLs work both\nin development (against the local bench) and in production (where\nnginx serves them directly to the same handlers).\n\n## Auth\n\nBearer key \u2014 issued at `/signup` and managed at `/dashboard/keys`:\n  `Authorization: Bearer <key>`\n\nEach key carries a set of scopes (`citizens.read`, `assessments.write`,\netc.). Calls that don't carry a key are rejected with `401`; calls that\ncarry a key without the required scope are rejected with `403`. Per-MDA\nscoped JWT tokens are a v2 hardening.\n\n## Response shape\n\nAll successful responses use:\n  `{ \"data\": <body> }`\n\nAll error responses use:\n  `{ \"error\": { \"code\": \"validation_failed\", \"message\": \"...\" } }`\n\nRFC 7807 problem-details migration is a v1 follow-up.\n","version":"1.1.0","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]},"item":[{"name":"Identity","description":"Citizens, identity lookup, NIRA cascade.","item":[{"name":"List / search citizens","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens?q=string&nin=string&phone=string&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","citizens"],"query":[{"key":"q","value":"string","description":"substring across full_name + nin + phone"},{"key":"nin","value":"string","description":"exact NIN match (uppercased server-side)"},{"key":"phone","value":"string","description":"exact phone match"},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"response":[{"name":"List of citizens (summary fields only).","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens?q=string&nin=string&phone=string&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","citizens"],"query":[{"key":"q","value":"string","description":"substring across full_name + nin + phone"},{"key":"nin","value":"string","description":"exact NIN match (uppercased server-side)"},{"key":"phone","value":"string","description":"exact phone match"},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"CITIZEN-2026-000002\",\n      \"nin\": \"CM78001234ABCD\",\n      \"full_name\": \"Mukasa John Patrick\",\n      \"phone\": \"+256772123456\",\n      \"email\": \"you@example.com\",\n      \"district\": \"Gulu\",\n      \"status\": \"Active\",\n      \"verified\": false,\n      \"modified\": \"2026-05-28T12:00:00Z\"\n    }\n  ]\n}"}]},{"name":"Create a new citizen","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens","host":["{{baseUrl}}"],"path":["v1","citizens"]},"description":"Persist a citizen into the local rail registry. **This does not\noriginate a legal identity** \u2014 only NIRA (the National\nIdentification and Registration Authority) does that. This\nendpoint records, on the rail, a citizen the rail needs to\ntransact with (an assessment payer, licence holder, counter\nwalk-in), linked back to NIRA by `nin`. New records are created\n`verified: false` until confirmed against NIRA.\n\n**Privileged scope.** Requires `citizens.write`, which is granted\nto counter-station and admin actors \u2014 **not** to integrator API\nkeys (which hold `citizens.read` only). Integrators *read*\nidentity and *resolve* it via NIRA cascade\n(`GET /v1/citizens/search`); they do not mint citizens. A key\nwithout the scope receives `403 forbidden`. This is the intended\ngovernance boundary, not an error.\n\nTypical flow: `GET /v1/citizens/search?nin=\u2026` returns\n`source: \"nira\"` for someone known to NIRA but not yet on the\nrail \u2192 a privileged caller POSTs that record here so future\nlookups resolve `source: \"local\"` with no NIRA round-trip.\n\n**If you click \"Send\" with a standard sandbox key:** expect\n`403 forbidden` \u2014 the sandbox key holds `citizens.read`, not\n`citizens.write`. That is the governance boundary working, not a\nbug. Prefer `POST /v1/citizens/register` (resolve-by-NIN) over\nraw create for real flows.\n"},"response":[{"name":"Citizen created.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens","host":["{{baseUrl}}"],"path":["v1","citizens"]},"description":"Persist a citizen into the local rail registry. **This does not\noriginate a legal identity** \u2014 only NIRA (the National\nIdentification and Registration Authority) does that. This\nendpoint records, on the rail, a citizen the rail needs to\ntransact with (an assessment payer, licence holder, counter\nwalk-in), linked back to NIRA by `nin`. New records are created\n`verified: false` until confirmed against NIRA.\n\n**Privileged scope.** Requires `citizens.write`, which is granted\nto counter-station and admin actors \u2014 **not** to integrator API\nkeys (which hold `citizens.read` only). Integrators *read*\nidentity and *resolve* it via NIRA cascade\n(`GET /v1/citizens/search`); they do not mint citizens. A key\nwithout the scope receives `403 forbidden`. This is the intended\ngovernance boundary, not an error.\n\nTypical flow: `GET /v1/citizens/search?nin=\u2026` returns\n`source: \"nira\"` for someone known to NIRA but not yet on the\nrail \u2192 a privileged caller POSTs that record here so future\nlookups resolve `source: \"local\"` with no NIRA round-trip.\n\n**If you click \"Send\" with a standard sandbox key:** expect\n`403 forbidden` \u2014 the sandbox key holds `citizens.read`, not\n`citizens.write`. That is the governance boundary working, not a\nbug. Prefer `POST /v1/citizens/register` (resolve-by-NIN) over\nraw create for real flows.\n"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"CITIZEN-2026-000002\",\n    \"nin\": \"CM78001234ABCD\",\n    \"full_name\": \"Mukasa John Patrick\",\n    \"phone\": \"+256772123456\",\n    \"email\": \"you@example.com\",\n    \"district\": \"Gulu\",\n    \"status\": \"Active\",\n    \"verified\": false,\n    \"modified\": \"2026-05-28T12:00:00Z\",\n    \"tin\": \"string\",\n    \"first_name\": \"string\",\n    \"middle_name\": \"string\",\n    \"surname\": \"string\",\n    \"dob\": \"2026-05-28\",\n    \"alternate_phone\": \"string\",\n    \"sub_county\": \"string\",\n    \"parish\": \"string\",\n    \"village\": \"string\",\n    \"address_line\": \"string\",\n    \"consent_data_sharing\": false,\n    \"consent_recorded_on\": \"2026-05-28T12:00:00Z\",\n    \"consent_recorded_by\": \"string\",\n    \"linked_customer\": \"string\",\n    \"photo\": \"string\"\n  }\n}"}]},{"name":"Get one citizen by docname","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens/:name","host":["{{baseUrl}}"],"path":["v1","citizens",":name"],"variable":[{"key":"name","value":"CITIZEN-2026-000002","description":"[required]"}]}},"response":[{"name":"The citizen.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens/:name","host":["{{baseUrl}}"],"path":["v1","citizens",":name"],"variable":[{"key":"name","value":"CITIZEN-2026-000002","description":"[required]"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"CITIZEN-2026-000002\",\n    \"nin\": \"CM78001234ABCD\",\n    \"full_name\": \"Mukasa John Patrick\",\n    \"phone\": \"+256772123456\",\n    \"email\": \"you@example.com\",\n    \"district\": \"Gulu\",\n    \"status\": \"Active\",\n    \"verified\": false,\n    \"modified\": \"2026-05-28T12:00:00Z\",\n    \"tin\": \"string\",\n    \"first_name\": \"string\",\n    \"middle_name\": \"string\",\n    \"surname\": \"string\",\n    \"dob\": \"2026-05-28\",\n    \"alternate_phone\": \"string\",\n    \"sub_county\": \"string\",\n    \"parish\": \"string\",\n    \"village\": \"string\",\n    \"address_line\": \"string\",\n    \"consent_data_sharing\": false,\n    \"consent_recorded_on\": \"2026-05-28T12:00:00Z\",\n    \"consent_recorded_by\": \"string\",\n    \"linked_customer\": \"string\",\n    \"photo\": \"string\"\n  }\n}"}]},{"name":"Register (find-or-create) a citizen from a NIN","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/citizens/register","host":["{{baseUrl}}"],"path":["v1","citizens","register"]},"description":"Write-scoped companion to `GET /v1/citizens/search`. Resolves a\nNIN (local first, then NIRA) and persists a NIRA hit into the\nlocal registry so it can anchor an assessment; an\nalready-registered NIN returns the existing record (idempotent).\nRequires the privileged `citizens.write` scope \u2014 integrator keys\nhold `citizens.read` only and receive 403.\n\n**Where it sits in the flow:** the first step of a counter or\nbilling pipeline \u2014 `**register (here)** \u2192 create assessment \u2192\n:assess \u2192 pay`. Persisting the NIRA hit gives you the local\n`CITIZEN-\u2026` docname that the assessment's `citizen` field\nrequires.\n\n**If you click \"Send\" with a standard sandbox key:** expect\n`403 forbidden` (no `citizens.write`). At a real counter this is\ncalled server-side under a staff session via\n`POST /v1/work/citizens`, which is where the clerk's in-person\naction authorises it.\n","body":{"mode":"raw","raw":"{\n  \"nin\": \"CM78001234ABCD\",\n  \"mda\": \"GULU\"\n}","options":{"raw":{"language":"json"}}}},"response":[{"name":"Citizen registered (or already on the rail).","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/citizens/register","host":["{{baseUrl}}"],"path":["v1","citizens","register"]},"description":"Write-scoped companion to `GET /v1/citizens/search`. Resolves a\nNIN (local first, then NIRA) and persists a NIRA hit into the\nlocal registry so it can anchor an assessment; an\nalready-registered NIN returns the existing record (idempotent).\nRequires the privileged `citizens.write` scope \u2014 integrator keys\nhold `citizens.read` only and receive 403.\n\n**Where it sits in the flow:** the first step of a counter or\nbilling pipeline \u2014 `**register (here)** \u2192 create assessment \u2192\n:assess \u2192 pay`. Persisting the NIRA hit gives you the local\n`CITIZEN-\u2026` docname that the assessment's `citizen` field\nrequires.\n\n**If you click \"Send\" with a standard sandbox key:** expect\n`403 forbidden` (no `citizens.write`). At a real counter this is\ncalled server-side under a staff session via\n`POST /v1/work/citizens`, which is where the clerk's in-person\naction authorises it.\n","body":{"mode":"raw","raw":"{\n  \"nin\": \"CM78001234ABCD\",\n  \"mda\": \"GULU\"\n}","options":{"raw":{"language":"json"}}}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"citizen\": {\n      \"name\": \"CITIZEN-2026-000002\",\n      \"nin\": \"CM78001234ABCD\",\n      \"full_name\": \"Mukasa John Patrick\",\n      \"phone\": \"+256772123456\",\n      \"email\": \"you@example.com\",\n      \"district\": \"Gulu\",\n      \"status\": \"Active\",\n      \"verified\": false,\n      \"modified\": \"2026-05-28T12:00:00Z\",\n      \"tin\": \"string\",\n      \"first_name\": \"string\",\n      \"middle_name\": \"string\",\n      \"surname\": \"string\",\n      \"dob\": \"2026-05-28\",\n      \"alternate_phone\": \"string\",\n      \"sub_county\": \"string\",\n      \"parish\": \"string\",\n      \"village\": \"string\",\n      \"address_line\": \"string\",\n      \"consent_data_sharing\": false,\n      \"consent_recorded_on\": \"2026-05-28T12:00:00Z\",\n      \"consent_recorded_by\": \"string\",\n      \"linked_customer\": \"string\",\n      \"photo\": \"string\"\n    },\n    \"created\": false,\n    \"source\": \"local\",\n    \"consent_event\": \"string\"\n  }\n}"}]},{"name":"Resolve a citizen by NIN","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens/search?nin=CM78001234ABCD","host":["{{baseUrl}}"],"path":["v1","citizens","search"],"query":[{"key":"nin","value":"CM78001234ABCD","description":"[required]"}]},"description":"Lookup order: local Citizen registry first, then NIRA via the\ncountry's identity adapter. Response carries `source`\n(`local` | `nira` | `not_found`) and `stub: true` when the NIRA\nadapter is still in stub mode.\n"},"response":[{"name":"Lookup result.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/citizens/search?nin=CM78001234ABCD","host":["{{baseUrl}}"],"path":["v1","citizens","search"],"query":[{"key":"nin","value":"CM78001234ABCD","description":"[required]"}]},"description":"Lookup order: local Citizen registry first, then NIRA via the\ncountry's identity adapter. Response carries `source`\n(`local` | `nira` | `not_found`) and `stub: true` when the NIRA\nadapter is still in stub mode.\n"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"source\": \"local\",\n    \"stub\": false,\n    \"citizen\": {\n      \"name\": \"CITIZEN-2026-000002\",\n      \"nin\": \"CM78001234ABCD\",\n      \"full_name\": \"Mukasa John Patrick\",\n      \"phone\": \"+256772123456\",\n      \"email\": \"you@example.com\",\n      \"district\": \"Gulu\",\n      \"status\": \"Active\",\n      \"verified\": false,\n      \"modified\": \"2026-05-28T12:00:00Z\",\n      \"tin\": \"string\",\n      \"first_name\": \"string\",\n      \"middle_name\": \"string\",\n      \"surname\": \"string\",\n      \"dob\": \"2026-05-28\",\n      \"alternate_phone\": \"string\",\n      \"sub_county\": \"string\",\n      \"parish\": \"string\",\n      \"village\": \"string\",\n      \"address_line\": \"string\",\n      \"consent_data_sharing\": false,\n      \"consent_recorded_on\": \"2026-05-28T12:00:00Z\",\n      \"consent_recorded_by\": \"string\",\n      \"linked_customer\": \"string\",\n      \"photo\": \"string\"\n    }\n  }\n}"}]}]},{"name":"Catalog","description":"MDAs (Ministries / Departments / Agencies / LGs) and the services they offer.","item":[{"name":"List MDAs","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/mdas?mode=A&country=UG&mda_type=City Authority&status=Active&start=0&limit=100","host":["{{baseUrl}}"],"path":["v1","mdas"],"query":[{"key":"mode","value":"A","description":"interaction mode"},{"key":"country","value":"UG","description":"Country Profile code"},{"key":"mda_type","value":"City Authority","description":""},{"key":"status","value":"Active","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"100","description":""}]}},"response":[{"name":"List of MDAs.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/mdas?mode=A&country=UG&mda_type=City Authority&status=Active&start=0&limit=100","host":["{{baseUrl}}"],"path":["v1","mdas"],"query":[{"key":"mode","value":"A","description":"interaction mode"},{"key":"country","value":"UG","description":"Country Profile code"},{"key":"mda_type","value":"City Authority","description":""},{"key":"status","value":"Active","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"100","description":""}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"GULU\",\n      \"short_code\": \"string\",\n      \"full_name\": \"Gulu City Authority\",\n      \"mda_type\": \"Ministry\",\n      \"country\": \"UG\",\n      \"mode\": \"A\",\n      \"status\": \"Active\",\n      \"parent_authority\": \"string\",\n      \"treasury_account\": \"string\",\n      \"integration_endpoint\": \"string\",\n      \"push_webhook_url\": \"string\",\n      \"api_credentials_ref\": \"string\",\n      \"oversight_scopes\": \"string\",\n      \"contact_email\": \"you@example.com\",\n      \"contact_phone\": \"string\"\n    }\n  ]\n}"}]},{"name":"Get one MDA by short_code","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/mdas/:name","host":["{{baseUrl}}"],"path":["v1","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]}},"response":[{"name":"The MDA.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/mdas/:name","host":["{{baseUrl}}"],"path":["v1","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"GULU\",\n    \"short_code\": \"string\",\n    \"full_name\": \"Gulu City Authority\",\n    \"mda_type\": \"Ministry\",\n    \"country\": \"UG\",\n    \"mode\": \"A\",\n    \"status\": \"Active\",\n    \"parent_authority\": \"string\",\n    \"treasury_account\": \"string\",\n    \"integration_endpoint\": \"string\",\n    \"push_webhook_url\": \"string\",\n    \"api_credentials_ref\": \"string\",\n    \"oversight_scopes\": \"string\",\n    \"contact_email\": \"you@example.com\",\n    \"contact_phone\": \"string\"\n  }\n}"}]},{"name":"List services in the catalog","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/services?mda=GULU&sector=Revenue&family=Trading Licenses&code=TL-RENEW&q=string&status=Active&start=0&limit=100","host":["{{baseUrl}}"],"path":["v1","services"],"query":[{"key":"mda","value":"GULU","description":""},{"key":"sector","value":"Revenue","description":""},{"key":"family","value":"Trading Licenses","description":""},{"key":"code","value":"TL-RENEW","description":""},{"key":"q","value":"string","description":"substring across service_name + code"},{"key":"status","value":"Active","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"100","description":""}]}},"response":[{"name":"List of services.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/services?mda=GULU&sector=Revenue&family=Trading Licenses&code=TL-RENEW&q=string&status=Active&start=0&limit=100","host":["{{baseUrl}}"],"path":["v1","services"],"query":[{"key":"mda","value":"GULU","description":""},{"key":"sector","value":"Revenue","description":""},{"key":"family","value":"Trading Licenses","description":""},{"key":"code","value":"TL-RENEW","description":""},{"key":"q","value":"string","description":"substring across service_name + code"},{"key":"status","value":"Active","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"100","description":""}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"mda\": \"GULU\",\n      \"code\": \"TL-RENEW\",\n      \"service_name\": \"Trading License Renewal\",\n      \"status\": \"Active\",\n      \"sector\": \"Revenue\",\n      \"service_family\": \"Trading Licenses\",\n      \"fee_amount\": 50000,\n      \"fee_currency\": \"UGX\",\n      \"fee_basis\": \"Flat\",\n      \"fee_schedule_ref\": \"Trade Licensing Act 2015 \\u00a712(1)\",\n      \"efris_taxable\": false,\n      \"vat_applicable\": false,\n      \"vat_rate\": 0,\n      \"gl_account_credit\": \"string\",\n      \"linked_item\": \"string\",\n      \"description\": \"string\"\n    }\n  ]\n}"}]},{"name":"Get one service by docname","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/services/:name","host":["{{baseUrl}}"],"path":["v1","services",":name"],"variable":[{"key":"name","value":"SVC-2026-000004","description":"[required]"}]}},"response":[{"name":"The service.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/services/:name","host":["{{baseUrl}}"],"path":["v1","services",":name"],"variable":[{"key":"name","value":"SVC-2026-000004","description":"[required]"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"mda\": \"GULU\",\n    \"code\": \"TL-RENEW\",\n    \"service_name\": \"Trading License Renewal\",\n    \"status\": \"Active\",\n    \"sector\": \"Revenue\",\n    \"service_family\": \"Trading Licenses\",\n    \"fee_amount\": 50000,\n    \"fee_currency\": \"UGX\",\n    \"fee_basis\": \"Flat\",\n    \"fee_schedule_ref\": \"Trade Licensing Act 2015 \\u00a712(1)\",\n    \"efris_taxable\": false,\n    \"vat_applicable\": false,\n    \"vat_rate\": 0,\n    \"gl_account_credit\": \"string\",\n    \"linked_item\": \"string\",\n    \"description\": \"string\"\n  }\n}"}]}]},{"name":"Transactions","description":"Assessments \u2014 multi-line, multi-MDA, lifecycle-managed.","item":[{"name":"List / filter assessments","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/assessments?citizen=string&status=Draft&shift=string&from_date=2026-05-28&to_date=2026-05-28&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","assessments"],"query":[{"key":"citizen","value":"string","description":""},{"key":"status","value":"Draft","description":""},{"key":"shift","value":"string","description":""},{"key":"from_date","value":"2026-05-28","description":""},{"key":"to_date","value":"2026-05-28","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"response":[{"name":"List of assessments (summary fields).","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/assessments?citizen=string&status=Draft&shift=string&from_date=2026-05-28&to_date=2026-05-28&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","assessments"],"query":[{"key":"citizen","value":"string","description":""},{"key":"status","value":"Draft","description":""},{"key":"shift","value":"string","description":""},{"key":"from_date","value":"2026-05-28","description":""},{"key":"to_date","value":"2026-05-28","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"citizen\": \"string\",\n      \"transaction_date\": \"2026-05-28\",\n      \"status\": \"Draft\",\n      \"mda_default\": \"string\",\n      \"shift\": \"string\",\n      \"total_amount\": 0,\n      \"gross_amount\": 0,\n      \"discount_amount\": 0,\n      \"discount_reason\": \"string\",\n      \"currency\": \"string\",\n      \"payment_status\": \"Pending\",\n      \"payment_channel\": \"string\",\n      \"payment_reference\": \"string\",\n      \"paid_at\": \"2026-05-28T12:00:00Z\"\n    }\n  ]\n}"}]},{"name":"Create a new Draft assessment (multi-line, multi-MDA)","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/assessments","host":["{{baseUrl}}"],"path":["v1","assessments"]},"description":"**Who calls this:** a back-office integrator system (billing\nengine, MDA portal) holding an API key with the\n`assessments.write` scope. **Not** an interactive \"click Send\"\naction \u2014 it is a programmatic step in a billing pipeline.\n\n**Where it sits in the flow:**\n`resolve citizen \u2192 **create assessment (here)** \u2192 :assess (lock\ntotals) \u2192 create payment-intent \u2192 settle`. The created assessment\nstarts in `Draft`; nothing is owed until `:assess` locks it.\n\n**Server is authoritative on price.** You send *which* services\nand *how many* \u2014 the rail prices each line from the official fee\nschedule (`Service.fee_amount`); a `rate` you pass is ignored.\n`citizen` must already exist on the rail (resolve/register first).\n\n**If you click \"Send\" in this explorer:** with a sandbox key that\ncarries `assessments.write` it will genuinely create a Draft\nassessment \u2014 *provided* `citizen` and each `lines[].service` are\nreal docnames. The placeholder example values resolve against the\nlive sandbox, so swap in IDs from `GET /v1/citizens` and\n`GET /v1/services` first, or you'll get `422 \u2014 Could not find\nCitizen/Service`.\n","body":{"mode":"raw","raw":"{\n  \"citizen\": \"CITIZEN-2026-000002\",\n  \"mda_default\": \"GULU\",\n  \"currency\": \"UGX\",\n  \"transaction_date\": \"2026-05-28\",\n  \"notes\": \"string\",\n  \"lines\": [\n    {\n      \"mda\": \"GULU\",\n      \"service\": \"SVC-2026-000004\",\n      \"quantity\": 1,\n      \"rate\": 0,\n      \"notes\": \"string\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}},"response":[{"name":"Assessment created in Draft status.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/assessments","host":["{{baseUrl}}"],"path":["v1","assessments"]},"description":"**Who calls this:** a back-office integrator system (billing\nengine, MDA portal) holding an API key with the\n`assessments.write` scope. **Not** an interactive \"click Send\"\naction \u2014 it is a programmatic step in a billing pipeline.\n\n**Where it sits in the flow:**\n`resolve citizen \u2192 **create assessment (here)** \u2192 :assess (lock\ntotals) \u2192 create payment-intent \u2192 settle`. The created assessment\nstarts in `Draft`; nothing is owed until `:assess` locks it.\n\n**Server is authoritative on price.** You send *which* services\nand *how many* \u2014 the rail prices each line from the official fee\nschedule (`Service.fee_amount`); a `rate` you pass is ignored.\n`citizen` must already exist on the rail (resolve/register first).\n\n**If you click \"Send\" in this explorer:** with a sandbox key that\ncarries `assessments.write` it will genuinely create a Draft\nassessment \u2014 *provided* `citizen` and each `lines[].service` are\nreal docnames. The placeholder example values resolve against the\nlive sandbox, so swap in IDs from `GET /v1/citizens` and\n`GET /v1/services` first, or you'll get `422 \u2014 Could not find\nCitizen/Service`.\n","body":{"mode":"raw","raw":"{\n  \"citizen\": \"CITIZEN-2026-000002\",\n  \"mda_default\": \"GULU\",\n  \"currency\": \"UGX\",\n  \"transaction_date\": \"2026-05-28\",\n  \"notes\": \"string\",\n  \"lines\": [\n    {\n      \"mda\": \"GULU\",\n      \"service\": \"SVC-2026-000004\",\n      \"quantity\": 1,\n      \"rate\": 0,\n      \"notes\": \"string\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"citizen\": \"string\",\n    \"transaction_date\": \"2026-05-28\",\n    \"status\": \"Draft\",\n    \"mda_default\": \"string\",\n    \"shift\": \"string\",\n    \"total_amount\": 0,\n    \"gross_amount\": 0,\n    \"discount_amount\": 0,\n    \"discount_reason\": \"string\",\n    \"currency\": \"string\",\n    \"payment_status\": \"Pending\",\n    \"payment_channel\": \"string\",\n    \"payment_reference\": \"string\",\n    \"paid_at\": \"2026-05-28T12:00:00Z\",\n    \"clerk\": \"string\",\n    \"assessment_lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"string\",\n        \"fee_basis\": \"string\",\n        \"quantity\": 1,\n        \"rate\": 0,\n        \"amount\": 0,\n        \"efris_taxable\": false,\n        \"fee_schedule_ref\": \"string\",\n        \"ura_prn\": \"string\",\n        \"efris_fdn\": \"string\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"idempotency_key\": \"string\",\n    \"linked_journal_entry\": \"string\",\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Get one assessment with its lines","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/assessments/:name","host":["{{baseUrl}}"],"path":["v1","assessments",":name"],"variable":[{"key":"name","value":"ASMT-2026-05-000022","description":"[required]"}]}},"response":[{"name":"The assessment.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/assessments/:name","host":["{{baseUrl}}"],"path":["v1","assessments",":name"],"variable":[{"key":"name","value":"ASMT-2026-05-000022","description":"[required]"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"citizen\": \"string\",\n    \"transaction_date\": \"2026-05-28\",\n    \"status\": \"Draft\",\n    \"mda_default\": \"string\",\n    \"shift\": \"string\",\n    \"total_amount\": 0,\n    \"gross_amount\": 0,\n    \"discount_amount\": 0,\n    \"discount_reason\": \"string\",\n    \"currency\": \"string\",\n    \"payment_status\": \"Pending\",\n    \"payment_channel\": \"string\",\n    \"payment_reference\": \"string\",\n    \"paid_at\": \"2026-05-28T12:00:00Z\",\n    \"clerk\": \"string\",\n    \"assessment_lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"string\",\n        \"fee_basis\": \"string\",\n        \"quantity\": 1,\n        \"rate\": 0,\n        \"amount\": 0,\n        \"efris_taxable\": false,\n        \"fee_schedule_ref\": \"string\",\n        \"ura_prn\": \"string\",\n        \"efris_fdn\": \"string\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"idempotency_key\": \"string\",\n    \"linked_journal_entry\": \"string\",\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Transition Draft \u2192 Assessed","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/assessments/:name:assess","host":["{{baseUrl}}"],"path":["v1","assessments",":name:assess"],"variable":[{"key":"name","value":"ASMT-2026-05-000022","description":"[required]"}]},"description":"**Who calls this:** the same `assessments.write` integrator\nsystem, immediately after `POST /v1/assessments`. Programmatic \u2014\nit locks the bill so a payment can be raised against a fixed\ntotal.\n\n**Where it sits in the flow:**\n`create assessment \u2192 **:assess (here)** \u2192 create payment-intent`.\nFreezes line amounts + grand total; an `Assessed` assessment is\nthe only state a payment-intent can attach to. Re-running on an\nalready-`Assessed` assessment is a safe no-op.\n\n**If you click \"Send\":** supply the `name` of a real `Draft`\nassessment (one you just created). A sandbox key with\n`assessments.write` will succeed; a stale/paid/cancelled id is\nrejected by the status-transition guard, and a missing id returns\n`404`.\n"},"response":[{"name":"Assessment now in Assessed status.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/assessments/:name:assess","host":["{{baseUrl}}"],"path":["v1","assessments",":name:assess"],"variable":[{"key":"name","value":"ASMT-2026-05-000022","description":"[required]"}]},"description":"**Who calls this:** the same `assessments.write` integrator\nsystem, immediately after `POST /v1/assessments`. Programmatic \u2014\nit locks the bill so a payment can be raised against a fixed\ntotal.\n\n**Where it sits in the flow:**\n`create assessment \u2192 **:assess (here)** \u2192 create payment-intent`.\nFreezes line amounts + grand total; an `Assessed` assessment is\nthe only state a payment-intent can attach to. Re-running on an\nalready-`Assessed` assessment is a safe no-op.\n\n**If you click \"Send\":** supply the `name` of a real `Draft`\nassessment (one you just created). A sandbox key with\n`assessments.write` will succeed; a stale/paid/cancelled id is\nrejected by the status-transition guard, and a missing id returns\n`404`.\n"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"citizen\": \"string\",\n    \"transaction_date\": \"2026-05-28\",\n    \"status\": \"Draft\",\n    \"mda_default\": \"string\",\n    \"shift\": \"string\",\n    \"total_amount\": 0,\n    \"gross_amount\": 0,\n    \"discount_amount\": 0,\n    \"discount_reason\": \"string\",\n    \"currency\": \"string\",\n    \"payment_status\": \"Pending\",\n    \"payment_channel\": \"string\",\n    \"payment_reference\": \"string\",\n    \"paid_at\": \"2026-05-28T12:00:00Z\",\n    \"clerk\": \"string\",\n    \"assessment_lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"string\",\n        \"fee_basis\": \"string\",\n        \"quantity\": 1,\n        \"rate\": 0,\n        \"amount\": 0,\n        \"efris_taxable\": false,\n        \"fee_schedule_ref\": \"string\",\n        \"ura_prn\": \"string\",\n        \"efris_fdn\": \"string\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"idempotency_key\": \"string\",\n    \"linked_journal_entry\": \"string\",\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Cancel an assessment","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/assessments/:name:cancel","host":["{{baseUrl}}"],"path":["v1","assessments",":name:cancel"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"**Who calls this:** a privileged actor holding the separate\n`assessments.cancel` scope \u2014 deliberately **not** bundled with\n`assessments.write`. Voiding an obligation is a higher-trust act\nthan raising one (clerk error, citizen walks away), so it is\ngated apart and typically held by supervisors/admin, not the\nbilling integrator.\n\n**Where it sits in the flow:** off the happy path \u2014 a corrective\naction from `Draft` or `Assessed` (and `Paid`, for refund-style\nreversals). The status-transition guard enforces which states may\ncancel.\n\n**If you click \"Send\" with a standard sandbox key:** expect\n`403 forbidden` \u2014 the default sandbox key carries\n`assessments.write` but **not** `assessments.cancel`. That 403 is\nthe governance boundary working, not a bug; it demonstrates that\ncreating and voiding revenue obligations are separately\nauthorised.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"Citizen left counter\"\n}","options":{"raw":{"language":"json"}}}},"response":[{"name":"Assessment moved to Cancelled.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/assessments/:name:cancel","host":["{{baseUrl}}"],"path":["v1","assessments",":name:cancel"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"**Who calls this:** a privileged actor holding the separate\n`assessments.cancel` scope \u2014 deliberately **not** bundled with\n`assessments.write`. Voiding an obligation is a higher-trust act\nthan raising one (clerk error, citizen walks away), so it is\ngated apart and typically held by supervisors/admin, not the\nbilling integrator.\n\n**Where it sits in the flow:** off the happy path \u2014 a corrective\naction from `Draft` or `Assessed` (and `Paid`, for refund-style\nreversals). The status-transition guard enforces which states may\ncancel.\n\n**If you click \"Send\" with a standard sandbox key:** expect\n`403 forbidden` \u2014 the default sandbox key carries\n`assessments.write` but **not** `assessments.cancel`. That 403 is\nthe governance boundary working, not a bug; it demonstrates that\ncreating and voiding revenue obligations are separately\nauthorised.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"Citizen left counter\"\n}","options":{"raw":{"language":"json"}}}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"citizen\": \"string\",\n    \"transaction_date\": \"2026-05-28\",\n    \"status\": \"Draft\",\n    \"mda_default\": \"string\",\n    \"shift\": \"string\",\n    \"total_amount\": 0,\n    \"gross_amount\": 0,\n    \"discount_amount\": 0,\n    \"discount_reason\": \"string\",\n    \"currency\": \"string\",\n    \"payment_status\": \"Pending\",\n    \"payment_channel\": \"string\",\n    \"payment_reference\": \"string\",\n    \"paid_at\": \"2026-05-28T12:00:00Z\",\n    \"clerk\": \"string\",\n    \"assessment_lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"string\",\n        \"fee_basis\": \"string\",\n        \"quantity\": 1,\n        \"rate\": 0,\n        \"amount\": 0,\n        \"efris_taxable\": false,\n        \"fee_schedule_ref\": \"string\",\n        \"ura_prn\": \"string\",\n        \"efris_fdn\": \"string\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"idempotency_key\": \"string\",\n    \"linked_journal_entry\": \"string\",\n    \"notes\": \"string\"\n  }\n}"}]}]},{"name":"Payments","description":"Payment Intents (with split rules) and Payment Events (proof of receipt).","item":[{"name":"Create a Payment Intent (auto-derives splits from Assessment lines if omitted)","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/payment-intents","host":["{{baseUrl}}"],"path":["v1","payment-intents"]},"description":"Create a Payment Intent for an assessed Assessment. Splits are\nauto-derived per MDA from the assessment lines unless explicitly\nprovided. **Sente Rails never holds funds** \u2014 the aggregator\nexecutes the split directly into each MDA collection account\n(PFMA \u00a743).\n\n**Who calls this:** a `payments.initiate`-scoped integrator system,\nright after the assessment is `:assess`-ed. A programmatic step, not\na click \u2014 it's how a billing front-end turns a fixed bill into a\npayable charge.\n\n**Where it sits in the flow:**\n`create assessment \u2192 :assess \u2192 **create payment-intent (here)** \u2192\n:initiate \u2192 :confirm (or provider webhook) \u2192 settle`. The intent\nopens `Pending`; `amount` comes from the assessment total and cannot\nbe set here.\n\n**If you click \"Send\":** the standard sandbox key *does* carry\n`payments.initiate`, so a valid body creates a Pending intent.\n`assessment` must be a real **assessed** docname \u2014 a draft /\ncancelled / missing one is rejected. An empty body returns a\nvalidation error (`assessment` + `channel` are required).\n","body":{"mode":"raw","raw":"{\n  \"assessment\": \"ASMT-2026-05-000022\",\n  \"channel\": \"MTN MoMo\",\n  \"citizen_msisdn\": \"+256772123456\",\n  \"aggregator\": \"MTN\",\n  \"splits\": [\n    {\n      \"mda\": \"GULU\",\n      \"amount\": 50000,\n      \"destination_account\": \"GULU-COLLECTION-001\",\n      \"destination_account_type\": \"Bank\",\n      \"notes\": \"string\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}},"response":[{"name":"Payment Intent created in Pending status.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/payment-intents","host":["{{baseUrl}}"],"path":["v1","payment-intents"]},"description":"Create a Payment Intent for an assessed Assessment. Splits are\nauto-derived per MDA from the assessment lines unless explicitly\nprovided. **Sente Rails never holds funds** \u2014 the aggregator\nexecutes the split directly into each MDA collection account\n(PFMA \u00a743).\n\n**Who calls this:** a `payments.initiate`-scoped integrator system,\nright after the assessment is `:assess`-ed. A programmatic step, not\na click \u2014 it's how a billing front-end turns a fixed bill into a\npayable charge.\n\n**Where it sits in the flow:**\n`create assessment \u2192 :assess \u2192 **create payment-intent (here)** \u2192\n:initiate \u2192 :confirm (or provider webhook) \u2192 settle`. The intent\nopens `Pending`; `amount` comes from the assessment total and cannot\nbe set here.\n\n**If you click \"Send\":** the standard sandbox key *does* carry\n`payments.initiate`, so a valid body creates a Pending intent.\n`assessment` must be a real **assessed** docname \u2014 a draft /\ncancelled / missing one is rejected. An empty body returns a\nvalidation error (`assessment` + `channel` are required).\n","body":{"mode":"raw","raw":"{\n  \"assessment\": \"ASMT-2026-05-000022\",\n  \"channel\": \"MTN MoMo\",\n  \"citizen_msisdn\": \"+256772123456\",\n  \"aggregator\": \"MTN\",\n  \"splits\": [\n    {\n      \"mda\": \"GULU\",\n      \"amount\": 50000,\n      \"destination_account\": \"GULU-COLLECTION-001\",\n      \"destination_account_type\": \"Bank\",\n      \"notes\": \"string\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"assessment\": \"string\",\n    \"channel\": \"MTN MoMo\",\n    \"status\": \"Pending\",\n    \"currency\": \"string\",\n    \"amount\": 0,\n    \"citizen_msisdn\": \"string\",\n    \"aggregator\": \"string\",\n    \"aggregator_reference\": \"string\",\n    \"sent_at\": \"2026-05-28T12:00:00Z\",\n    \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n    \"failed_at\": \"2026-05-28T12:00:00Z\",\n    \"failure_reason\": \"string\",\n    \"fiscal_status\": \"Not Fiscalised\",\n    \"fdn\": \"string\",\n    \"fiscal_verification_code\": \"string\",\n    \"fiscal_qr_payload\": \"string\",\n    \"fiscalised_at\": \"2026-05-28T12:00:00Z\",\n    \"refunded_at\": \"2026-05-28T12:00:00Z\",\n    \"refund_reason\": \"string\",\n    \"idempotency_key\": \"string\",\n    \"split_rules\": [\n      {\n        \"mda\": \"GULU\",\n        \"amount\": 50000,\n        \"destination_account\": \"GULU-COLLECTION-001\",\n        \"destination_account_type\": \"Bank\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Get one Payment Intent","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name"],"variable":[{"key":"name","value":"PI-2026-05-000023","description":"[required]"}]}},"response":[{"name":"The Payment Intent.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name"],"variable":[{"key":"name","value":"PI-2026-05-000023","description":"[required]"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"assessment\": \"string\",\n    \"channel\": \"MTN MoMo\",\n    \"status\": \"Pending\",\n    \"currency\": \"string\",\n    \"amount\": 0,\n    \"citizen_msisdn\": \"string\",\n    \"aggregator\": \"string\",\n    \"aggregator_reference\": \"string\",\n    \"sent_at\": \"2026-05-28T12:00:00Z\",\n    \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n    \"failed_at\": \"2026-05-28T12:00:00Z\",\n    \"failure_reason\": \"string\",\n    \"fiscal_status\": \"Not Fiscalised\",\n    \"fdn\": \"string\",\n    \"fiscal_verification_code\": \"string\",\n    \"fiscal_qr_payload\": \"string\",\n    \"fiscalised_at\": \"2026-05-28T12:00:00Z\",\n    \"refunded_at\": \"2026-05-28T12:00:00Z\",\n    \"refund_reason\": \"string\",\n    \"idempotency_key\": \"string\",\n    \"split_rules\": [\n      {\n        \"mda\": \"GULU\",\n        \"amount\": 50000,\n        \"destination_account\": \"GULU-COLLECTION-001\",\n        \"destination_account_type\": \"Bank\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Send the payment instruction via the channel-specific adapter","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name:initiate","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name:initiate"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Calls the country's payment adapter for the intent's channel\n(e.g. MTN MoMo STK push). On stub adapters returns a stub-shaped\naggregator_reference. Transitions intent: Pending \u2192 Sent.\n\n**Who calls this:** the same `payments.initiate` integrator,\nimmediately after creating the intent. This is the step that\nactually reaches out to the mobile-money / card aggregator and\ntriggers the citizen's payment prompt.\n\n**Where it sits in the flow:**\n`create payment-intent \u2192 **:initiate (here)** \u2192 :confirm`.\nOnly valid from `Pending`; calling it on an already-Sent/Confirmed\nintent is rejected by the state guard (`422`).\n\n**If you click \"Send\":** supply the `name` of a real **Pending**\nintent. The sandbox runs against MoMo sandbox creds, so this\ngenuinely calls the aggregator and stamps the intent `Sent`.\n"},"response":[{"name":"Initiation result with intent + adapter response.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name:initiate","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name:initiate"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Calls the country's payment adapter for the intent's channel\n(e.g. MTN MoMo STK push). On stub adapters returns a stub-shaped\naggregator_reference. Transitions intent: Pending \u2192 Sent.\n\n**Who calls this:** the same `payments.initiate` integrator,\nimmediately after creating the intent. This is the step that\nactually reaches out to the mobile-money / card aggregator and\ntriggers the citizen's payment prompt.\n\n**Where it sits in the flow:**\n`create payment-intent \u2192 **:initiate (here)** \u2192 :confirm`.\nOnly valid from `Pending`; calling it on an already-Sent/Confirmed\nintent is rejected by the state guard (`422`).\n\n**If you click \"Send\":** supply the `name` of a real **Pending**\nintent. The sandbox runs against MoMo sandbox creds, so this\ngenuinely calls the aggregator and stamps the intent `Sent`.\n"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"intent\": {\n      \"name\": \"string\",\n      \"assessment\": \"string\",\n      \"channel\": \"MTN MoMo\",\n      \"status\": \"Pending\",\n      \"currency\": \"string\",\n      \"amount\": 0,\n      \"citizen_msisdn\": \"string\",\n      \"aggregator\": \"string\",\n      \"aggregator_reference\": \"string\",\n      \"sent_at\": \"2026-05-28T12:00:00Z\",\n      \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n      \"failed_at\": \"2026-05-28T12:00:00Z\",\n      \"failure_reason\": \"string\",\n      \"fiscal_status\": \"Not Fiscalised\",\n      \"fdn\": \"string\",\n      \"fiscal_verification_code\": \"string\",\n      \"fiscal_qr_payload\": \"string\",\n      \"fiscalised_at\": \"2026-05-28T12:00:00Z\",\n      \"refunded_at\": \"2026-05-28T12:00:00Z\",\n      \"refund_reason\": \"string\",\n      \"idempotency_key\": \"string\",\n      \"split_rules\": [\n        {\n          \"mda\": \"GULU\",\n          \"amount\": 50000,\n          \"destination_account\": \"GULU-COLLECTION-001\",\n          \"destination_account_type\": \"Bank\",\n          \"notes\": \"string\"\n        }\n      ],\n      \"notes\": \"string\"\n    },\n    \"adapter_response\": {}\n  }\n}"}]},{"name":"Verify + confirm settlement; materialise Payment Events per split","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name:confirm","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name:confirm"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Synchronous confirmation path (production uses\n`/v1/webhooks/{provider}` for the same materialisation).\nVerifies via the channel adapter, on success creates one Payment\nEvent per split rule, marks parent Assessment Paid.\n\n**Who calls this \u2014 and the important nuance:** in production this is\nrarely called directly. The aggregator's **webhook**\n(`/v1/webhooks/{provider}`) drives the same materialisation\nautomatically when the citizen completes payment. This endpoint is\nthe **manual / poll-confirm fallback** (a cash counter, or any flow\nthat reconciles without a webhook) for a `payments.initiate`-scoped\ncaller.\n\n**Where it sits in the flow:**\n`:initiate \u2192 [citizen pays] \u2192 **:confirm (here) OR provider webhook**\n\u2192 Payment Events created, Assessment marked Paid`. Only valid from\n`Sent`; on success it is idempotent against the parent assessment's\npaid state.\n\n**If you click \"Send\":** use a real **Sent** intent. The adapter is\nre-queried; if the sandbox reports the payment complete, events are\nmaterialised and the assessment flips to Paid. A non-Sent intent is\nrejected (`422`).\n"},"response":[{"name":"Confirmation result with intent, events, assessment.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name:confirm","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name:confirm"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Synchronous confirmation path (production uses\n`/v1/webhooks/{provider}` for the same materialisation).\nVerifies via the channel adapter, on success creates one Payment\nEvent per split rule, marks parent Assessment Paid.\n\n**Who calls this \u2014 and the important nuance:** in production this is\nrarely called directly. The aggregator's **webhook**\n(`/v1/webhooks/{provider}`) drives the same materialisation\nautomatically when the citizen completes payment. This endpoint is\nthe **manual / poll-confirm fallback** (a cash counter, or any flow\nthat reconciles without a webhook) for a `payments.initiate`-scoped\ncaller.\n\n**Where it sits in the flow:**\n`:initiate \u2192 [citizen pays] \u2192 **:confirm (here) OR provider webhook**\n\u2192 Payment Events created, Assessment marked Paid`. Only valid from\n`Sent`; on success it is idempotent against the parent assessment's\npaid state.\n\n**If you click \"Send\":** use a real **Sent** intent. The adapter is\nre-queried; if the sandbox reports the payment complete, events are\nmaterialised and the assessment flips to Paid. A non-Sent intent is\nrejected (`422`).\n"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"intent\": {\n      \"name\": \"string\",\n      \"assessment\": \"string\",\n      \"channel\": \"MTN MoMo\",\n      \"status\": \"Pending\",\n      \"currency\": \"string\",\n      \"amount\": 0,\n      \"citizen_msisdn\": \"string\",\n      \"aggregator\": \"string\",\n      \"aggregator_reference\": \"string\",\n      \"sent_at\": \"2026-05-28T12:00:00Z\",\n      \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n      \"failed_at\": \"2026-05-28T12:00:00Z\",\n      \"failure_reason\": \"string\",\n      \"fiscal_status\": \"Not Fiscalised\",\n      \"fdn\": \"string\",\n      \"fiscal_verification_code\": \"string\",\n      \"fiscal_qr_payload\": \"string\",\n      \"fiscalised_at\": \"2026-05-28T12:00:00Z\",\n      \"refunded_at\": \"2026-05-28T12:00:00Z\",\n      \"refund_reason\": \"string\",\n      \"idempotency_key\": \"string\",\n      \"split_rules\": [\n        {\n          \"mda\": \"GULU\",\n          \"amount\": 50000,\n          \"destination_account\": \"GULU-COLLECTION-001\",\n          \"destination_account_type\": \"Bank\",\n          \"notes\": \"string\"\n        }\n      ],\n      \"notes\": \"string\"\n    },\n    \"events\": [\n      {\n        \"name\": \"string\",\n        \"payment_intent\": \"string\",\n        \"mda\": \"string\",\n        \"amount\": 0,\n        \"currency\": \"string\",\n        \"aggregator\": \"string\",\n        \"aggregator_txn_id\": \"string\",\n        \"destination_account\": \"string\",\n        \"received_at\": \"2026-05-28T12:00:00Z\",\n        \"proof_payload\": \"string\",\n        \"linked_journal_entry\": \"string\"\n      }\n    ],\n    \"assessment\": {\n      \"name\": \"string\",\n      \"citizen\": \"string\",\n      \"transaction_date\": \"2026-05-28\",\n      \"status\": \"Draft\",\n      \"mda_default\": \"string\",\n      \"shift\": \"string\",\n      \"total_amount\": 0,\n      \"gross_amount\": 0,\n      \"discount_amount\": 0,\n      \"discount_reason\": \"string\",\n      \"currency\": \"string\",\n      \"payment_status\": \"Pending\",\n      \"payment_channel\": \"string\",\n      \"payment_reference\": \"string\",\n      \"paid_at\": \"2026-05-28T12:00:00Z\",\n      \"clerk\": \"string\",\n      \"assessment_lines\": [\n        {\n          \"mda\": \"GULU\",\n          \"service\": \"SVC-2026-000004\",\n          \"service_name\": \"string\",\n          \"fee_basis\": \"string\",\n          \"quantity\": 1,\n          \"rate\": 0,\n          \"amount\": 0,\n          \"efris_taxable\": false,\n          \"fee_schedule_ref\": \"string\",\n          \"ura_prn\": \"string\",\n          \"efris_fdn\": \"string\",\n          \"notes\": \"string\"\n        }\n      ],\n      \"idempotency_key\": \"string\",\n      \"linked_journal_entry\": \"string\",\n      \"notes\": \"string\"\n    }\n  }\n}"}]},{"name":"Unified audit-trail timeline for a Payment Intent","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name/trace","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name","trace"],"variable":[{"key":"name","value":"PI-2026-05-000051","description":"[required]"}]},"description":"Assembles the assessment context, intent lifecycle, split rules,\npayment events, and verbatim adapter request/response payloads\ninto a single document. Designed for OAG-grade audit evidence\nand on-demand reconciliation.\n\nEvery state change and every adapter call is rendered as one\nrow in the `timeline` array, sorted chronologically. Adapter\nrequest/response payloads are inlined for inspection (Bearer\ntokens stripped).\n"},"response":[{"name":"Trace bundle.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name/trace","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name","trace"],"variable":[{"key":"name","value":"PI-2026-05-000051","description":"[required]"}]},"description":"Assembles the assessment context, intent lifecycle, split rules,\npayment events, and verbatim adapter request/response payloads\ninto a single document. Designed for OAG-grade audit evidence\nand on-demand reconciliation.\n\nEvery state change and every adapter call is rendered as one\nrow in the `timeline` array, sorted chronologically. Adapter\nrequest/response payloads are inlined for inspection (Bearer\ntokens stripped).\n"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"assessment\": {},\n    \"intent\": {},\n    \"events\": [\n      {}\n    ],\n    \"timeline\": [\n      {\n        \"at\": \"2026-05-28T12:00:00Z\",\n        \"kind\": \"assessment.update\",\n        \"actor\": \"string\",\n        \"summary\": \"string\",\n        \"doc\": \"string\",\n        \"detail\": {}\n      }\n    ]\n  }\n}"}]},{"name":"Re-query the aggregator LIVE for current status","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name/live-status","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name","live-status"],"variable":[{"key":"name","value":"PI-2026-05-000051","description":"[required]"}]},"description":"Read-only \u2014 polls the aggregator (MTN MoMo, Airtel, etc.) right\nnow and returns its response next to what Sente Rails has stored.\nDoes NOT mutate any Sente Rails state. Built for on-demand\n\"ask the aggregator live\" reconciliation.\n\nReturns a `match: bool` field so consumers can immediately tell\nwhether stored and live agree.\n"},"response":[{"name":"Live status comparison.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name/live-status","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name","live-status"],"variable":[{"key":"name","value":"PI-2026-05-000051","description":"[required]"}]},"description":"Read-only \u2014 polls the aggregator (MTN MoMo, Airtel, etc.) right\nnow and returns its response next to what Sente Rails has stored.\nDoes NOT mutate any Sente Rails state. Built for on-demand\n\"ask the aggregator live\" reconciliation.\n\nReturns a `match: bool` field so consumers can immediately tell\nwhether stored and live agree.\n"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"queried_at\": \"2026-05-28T12:00:00Z\",\n    \"aggregator\": \"MTN\",\n    \"aggregator_reference\": \"string\",\n    \"stored\": {\n      \"status\": \"string\",\n      \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n      \"amount\": 0,\n      \"currency\": \"string\"\n    },\n    \"live\": {\n      \"status\": \"string\",\n      \"txn_id\": \"string\",\n      \"amount\": 0,\n      \"currency\": \"string\",\n      \"settled_at\": \"2026-05-28T12:00:00Z\",\n      \"stub\": false,\n      \"raw_response\": {}\n    },\n    \"match\": false\n  }\n}"}]},{"name":"List Payment Events","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-events?intent=string&mda=string&from_date=2026-05-28T12:00:00Z&to_date=2026-05-28T12:00:00Z&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","payment-events"],"query":[{"key":"intent","value":"string","description":""},{"key":"mda","value":"string","description":""},{"key":"from_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"to_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"response":[{"name":"List of Payment Events.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-events?intent=string&mda=string&from_date=2026-05-28T12:00:00Z&to_date=2026-05-28T12:00:00Z&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","payment-events"],"query":[{"key":"intent","value":"string","description":""},{"key":"mda","value":"string","description":""},{"key":"from_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"to_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"payment_intent\": \"string\",\n      \"mda\": \"string\",\n      \"amount\": 0,\n      \"currency\": \"string\",\n      \"aggregator\": \"string\",\n      \"aggregator_txn_id\": \"string\",\n      \"destination_account\": \"string\",\n      \"received_at\": \"2026-05-28T12:00:00Z\",\n      \"proof_payload\": \"string\",\n      \"linked_journal_entry\": \"string\"\n    }\n  ]\n}"}]},{"name":"Citizen-facing receipt verifier","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name/public-summary","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name","public-summary"],"variable":[{"key":"name","value":"PI-2026-05-000087","description":"[required]"}]},"description":"Powers the public `/verify/{ref}` page \u2014 anyone with a printed\nreceipt's QR code can land here and confirm the payment is on\nfile. Returns only non-sensitive fields: status, citizen\n**display name** only (no NIN / phone / msisdn), MDA full names,\nservice line descriptions, amount + currency, channel,\naggregator reference, confirmation timestamp, split summary\nwith MDA names only (no destination account numbers).\n\nUnknown references return 404 \u2014 never reveal whether the ref\nexisted via response shape.\n","auth":{"type":"noauth"}},"response":[{"name":"Public-safe payment summary.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/payment-intents/:name/public-summary","host":["{{baseUrl}}"],"path":["v1","payment-intents",":name","public-summary"],"variable":[{"key":"name","value":"PI-2026-05-000087","description":"[required]"}]},"description":"Powers the public `/verify/{ref}` page \u2014 anyone with a printed\nreceipt's QR code can land here and confirm the payment is on\nfile. Returns only non-sensitive fields: status, citizen\n**display name** only (no NIN / phone / msisdn), MDA full names,\nservice line descriptions, amount + currency, channel,\naggregator reference, confirmation timestamp, split summary\nwith MDA names only (no destination account numbers).\n\nUnknown references return 404 \u2014 never reveal whether the ref\nexisted via response shape.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"status\": \"Confirmed\",\n    \"citizen_display_name\": \"Mukasa John P.\",\n    \"currency\": \"UGX\",\n    \"amount\": 50000,\n    \"channel\": \"MTN MoMo\",\n    \"aggregator_reference\": \"MOMO-87xyz\",\n    \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n    \"lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"mda_name\": \"Gulu City Council\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"Trading License Renewal\",\n        \"amount\": 50000\n      }\n    ],\n    \"split_summary\": [\n      {\n        \"mda\": \"string\",\n        \"mda_name\": \"string\",\n        \"amount\": 0\n      }\n    ]\n  }\n}"}]}]},{"name":"Settlement","description":"Counter Shifts \u2014 clerk open / transact / close with cash variance.","item":[{"name":"List shifts","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts?clerk=string&mda=string&status=Open&from_date=2026-05-28T12:00:00Z&to_date=2026-05-28T12:00:00Z&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","counter-shifts"],"query":[{"key":"clerk","value":"string","description":""},{"key":"mda","value":"string","description":""},{"key":"status","value":"Open","description":""},{"key":"from_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"to_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"response":[{"name":"List of shifts (summary fields).","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts?clerk=string&mda=string&status=Open&from_date=2026-05-28T12:00:00Z&to_date=2026-05-28T12:00:00Z&start=0&limit=50","host":["{{baseUrl}}"],"path":["v1","counter-shifts"],"query":[{"key":"clerk","value":"string","description":""},{"key":"mda","value":"string","description":""},{"key":"status","value":"Open","description":""},{"key":"from_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"to_date","value":"2026-05-28T12:00:00Z","description":""},{"key":"start","value":"0","description":""},{"key":"limit","value":"50","description":""}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"clerk\": \"string\",\n      \"mda\": \"string\",\n      \"counter_label\": \"string\",\n      \"status\": \"Open\",\n      \"opened_at\": \"2026-05-28T12:00:00Z\",\n      \"closed_at\": \"2026-05-28T12:00:00Z\",\n      \"opening_float\": 0,\n      \"assessment_count\": 0,\n      \"total_collected\": 0,\n      \"cash_collected\": 0,\n      \"momo_collected\": 0,\n      \"airtel_collected\": 0,\n      \"cash_expected\": 0,\n      \"cash_counted\": 0,\n      \"cash_variance\": 0\n    }\n  ]\n}"}]},{"name":"Open a new shift","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/counter-shifts","host":["{{baseUrl}}"],"path":["v1","counter-shifts"]},"description":"Open a counter shift before collecting at an MDA window \u2014 the\ncash-handling day's first step. Assessments + payments are scoped\nto the open shift, and end-of-day reconciliation (cash expected vs\ncounted, variance) is computed against it.\n\n**Who calls this:** an `assessments.write`-scoped integrator, or a\nclerk via the counter station. Single-open-shift-per-(clerk, mda)\nis enforced \u2014 close any existing open shift first.\n\n**Where it sits in the flow:**\n`**open shift (here)** \u2192 assess + collect (many) \u2192 close shift`.\n\n**If you click \"Send\":** the sandbox key carries\n`assessments.write`, so this opens a real shift \u2014 unless the clerk\nalready has one open at that MDA, which returns a validation error.\n","body":{"mode":"raw","raw":"{\n  \"mda\": \"GULU\",\n  \"counter_label\": \"Counter 3 \\u2014 City Hall\",\n  \"opening_float\": 100000,\n  \"currency\": \"UGX\",\n  \"opening_notes\": \"string\"\n}","options":{"raw":{"language":"json"}}}},"response":[{"name":"Shift opened.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/counter-shifts","host":["{{baseUrl}}"],"path":["v1","counter-shifts"]},"description":"Open a counter shift before collecting at an MDA window \u2014 the\ncash-handling day's first step. Assessments + payments are scoped\nto the open shift, and end-of-day reconciliation (cash expected vs\ncounted, variance) is computed against it.\n\n**Who calls this:** an `assessments.write`-scoped integrator, or a\nclerk via the counter station. Single-open-shift-per-(clerk, mda)\nis enforced \u2014 close any existing open shift first.\n\n**Where it sits in the flow:**\n`**open shift (here)** \u2192 assess + collect (many) \u2192 close shift`.\n\n**If you click \"Send\":** the sandbox key carries\n`assessments.write`, so this opens a real shift \u2014 unless the clerk\nalready has one open at that MDA, which returns a validation error.\n","body":{"mode":"raw","raw":"{\n  \"mda\": \"GULU\",\n  \"counter_label\": \"Counter 3 \\u2014 City Hall\",\n  \"opening_float\": 100000,\n  \"currency\": \"UGX\",\n  \"opening_notes\": \"string\"\n}","options":{"raw":{"language":"json"}}}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Get the current user's open shift on a given MDA (or null)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/active?mda=GULU","host":["{{baseUrl}}"],"path":["v1","counter-shifts","active"],"query":[{"key":"mda","value":"GULU","description":"[required]"}]},"description":"Used by the Clerk UI as the \"is there an open shift?\" gate at counter-app load."},"response":[{"name":"The active shift or null.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/active?mda=GULU","host":["{{baseUrl}}"],"path":["v1","counter-shifts","active"],"query":[{"key":"mda","value":"GULU","description":"[required]"}]},"description":"Used by the Clerk UI as the \"is there an open shift?\" gate at counter-app load."},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Get one shift","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/:name","host":["{{baseUrl}}"],"path":["v1","counter-shifts",":name"],"variable":[{"key":"name","value":"SHIFT-2026-05-21-021","description":"[required]"}]}},"response":[{"name":"The shift.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/:name","host":["{{baseUrl}}"],"path":["v1","counter-shifts",":name"],"variable":[{"key":"name","value":"SHIFT-2026-05-21-021","description":"[required]"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Close a shift with a counted-cash value","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/:name:close","host":["{{baseUrl}}"],"path":["v1","counter-shifts",":name:close"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Triggers `refresh_aggregates()` then transitions Open \u2192 Closed.\nIf |variance| > 0 and no `variance_reason` is provided, returns 422.\n\n**Who calls this:** the `assessments.write` actor (clerk / counter\nstation) at end of day \u2014 the reconciliation step. It freezes the\nshift's collected totals and records any cash discrepancy; a\nmaterial variance raises an Anomaly Flag for supervisor review.\n\n**Where it sits in the flow:**\n`open shift \u2192 assess + collect (many) \u2192 **close shift (here)**`.\nOnly valid on an `Open` shift.\n\n**If you click \"Send\":** supply the `name` of a real Open shift and\na `cash_counted` total. If counted \u2260 expected you must also pass\n`variance_reason`, or the close is rejected \u2014 that forced\nexplanation is the audit control, not an error.\n","body":{"mode":"raw","raw":"{\n  \"cash_counted\": 100000,\n  \"variance_reason\": \"Customer paid in coins, drawer over by 500\",\n  \"closing_notes\": \"string\"\n}","options":{"raw":{"language":"json"}}}},"response":[{"name":"Shift closed with reconciliation aggregates filled.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/:name:close","host":["{{baseUrl}}"],"path":["v1","counter-shifts",":name:close"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Triggers `refresh_aggregates()` then transitions Open \u2192 Closed.\nIf |variance| > 0 and no `variance_reason` is provided, returns 422.\n\n**Who calls this:** the `assessments.write` actor (clerk / counter\nstation) at end of day \u2014 the reconciliation step. It freezes the\nshift's collected totals and records any cash discrepancy; a\nmaterial variance raises an Anomaly Flag for supervisor review.\n\n**Where it sits in the flow:**\n`open shift \u2192 assess + collect (many) \u2192 **close shift (here)**`.\nOnly valid on an `Open` shift.\n\n**If you click \"Send\":** supply the `name` of a real Open shift and\na `cash_counted` total. If counted \u2260 expected you must also pass\n`variance_reason`, or the close is rejected \u2014 that forced\nexplanation is the audit control, not an error.\n","body":{"mode":"raw","raw":"{\n  \"cash_counted\": 100000,\n  \"variance_reason\": \"Customer paid in coins, drawer over by 500\",\n  \"closing_notes\": \"string\"\n}","options":{"raw":{"language":"json"}}}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Recompute aggregates without closing","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/:name:refresh","host":["{{baseUrl}}"],"path":["v1","counter-shifts",":name:refresh"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Useful as a Clerk-UI \"refresh stats\" button on an open shift."},"response":[{"name":"Shift with refreshed aggregates.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/counter-shifts/:name:refresh","host":["{{baseUrl}}"],"path":["v1","counter-shifts",":name:refresh"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Useful as a Clerk-UI \"refresh stats\" button on an open shift."},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]}]},{"name":"Integrations","description":"Adapter dispatch status \u2014 per-country, per-domain.","item":[{"name":"Per-country integration status snapshot","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/integrations","host":["{{baseUrl}}"],"path":["v1","integrations"]},"description":"Per-country integration status: `country \u2192 capability \u2192 status`,\nwhere status is `live` (real credentials wired), `sandbox`\n(adapter present, running in STUB mode), or `unavailable`\n(referenced in a Country Profile but not yet built / MoU-pending).\nSingle-adapter capabilities (`identity`, `fiscal`) are one\n`{status}` object; `payment` is a LIST of provider entries, each\n`{status, channels}`. Internal adapter class paths are never\nexposed.\n\n**Who calls this:** any integrator key (`catalogue.read` scope) \u2014\ndiscovery metadata, like the catalogue. The operator view with\nimportability + class-path detail is `/v1/ops/adapters`.\n\nSurfaces, at a glance, which integrations are live vs sandbox vs\nMoU-pending across the rail.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Per-country integration status map.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/integrations","host":["{{baseUrl}}"],"path":["v1","integrations"]},"description":"Per-country integration status: `country \u2192 capability \u2192 status`,\nwhere status is `live` (real credentials wired), `sandbox`\n(adapter present, running in STUB mode), or `unavailable`\n(referenced in a Country Profile but not yet built / MoU-pending).\nSingle-adapter capabilities (`identity`, `fiscal`) are one\n`{status}` object; `payment` is a LIST of provider entries, each\n`{status, channels}`. Internal adapter class paths are never\nexposed.\n\n**Who calls this:** any integrator key (`catalogue.read` scope) \u2014\ndiscovery metadata, like the catalogue. The operator view with\nimportability + class-path detail is `/v1/ops/adapters`.\n\nSurfaces, at a glance, which integrations are live vs sandbox vs\nMoU-pending across the rail.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"UG\": {\n      \"identity\": {\n        \"status\": \"sandbox\"\n      },\n      \"fiscal\": {\n        \"status\": \"sandbox\"\n      },\n      \"payment\": [\n        {\n          \"status\": \"live\",\n          \"channels\": [\n            \"MTN MoMo\"\n          ]\n        },\n        {\n          \"status\": \"sandbox\",\n          \"channels\": [\n            \"Airtel Money\"\n          ]\n        },\n        {\n          \"status\": \"live\",\n          \"channels\": [\n            \"Cash\"\n          ]\n        }\n      ]\n    }\n  }\n}"}]}]},{"name":"Sign up","description":"Self-serve sandbox account creation with email OTP verification.\nThree calls \u2014 `POST /v1/signup` \u2192 `POST /v1/signup/verify` \u2192 plaintext\nsandbox key. No human in the loop.\n","item":[{"name":"Start a sandbox signup","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/signup","host":["{{baseUrl}}"],"path":["v1","signup"]},"description":"Step 1 of the self-serve sandbox flow. Creates a `PendingEmail`\nintegrator row + dispatches a 6-digit OTP to the supplied email.\n`tos_accepted_version` must equal the value from `GET /v1/signup/tos`\n(currently `sandbox-tos-v1-2026-05-25`) \u2014 anything else is a 422.\n\nAn email already registered to an integrator is rejected with 409\n(`duplicate_email`) \u2014 sign in via the magic-link flow instead. Use\n`POST /v1/signup/resend-otp` to re-issue an OTP for a pending signup.\n\nRate limits: at most one OTP per 60 seconds per integrator,\n5 OTPs per UTC day total across initial signup + resends.\n","body":{"mode":"raw","raw":"{\n  \"full_name\": \"Jane Builder\",\n  \"email\": \"jane@acmefintech.test\",\n  \"organisation\": \"Acme Fintech\",\n  \"tos_accepted_version\": \"sandbox-tos-v1-2026-05-25\",\n  \"intended_use\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"OTP dispatched. Carry `integrator_id` to `/v1/signup/verify`.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/signup","host":["{{baseUrl}}"],"path":["v1","signup"]},"description":"Step 1 of the self-serve sandbox flow. Creates a `PendingEmail`\nintegrator row + dispatches a 6-digit OTP to the supplied email.\n`tos_accepted_version` must equal the value from `GET /v1/signup/tos`\n(currently `sandbox-tos-v1-2026-05-25`) \u2014 anything else is a 422.\n\nAn email already registered to an integrator is rejected with 409\n(`duplicate_email`) \u2014 sign in via the magic-link flow instead. Use\n`POST /v1/signup/resend-otp` to re-issue an OTP for a pending signup.\n\nRate limits: at most one OTP per 60 seconds per integrator,\n5 OTPs per UTC day total across initial signup + resends.\n","body":{"mode":"raw","raw":"{\n  \"full_name\": \"Jane Builder\",\n  \"email\": \"jane@acmefintech.test\",\n  \"organisation\": \"Acme Fintech\",\n  \"tos_accepted_version\": \"sandbox-tos-v1-2026-05-25\",\n  \"intended_use\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"integrator_id\": \"ACME-FINTECH-XXXXXX\",\n    \"email\": \"you@example.com\",\n    \"message\": \"string\",\n    \"expires_at_iso\": \"2026-05-28T12:00:00Z\",\n    \"tos_version\": \"string\"\n  }\n}"}]},{"name":"Submit the OTP and receive the sandbox key plaintext","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/signup/verify","host":["{{baseUrl}}"],"path":["v1","signup","verify"]},"description":"Step 2 of signup. On success: the integrator flips to\n`status=Active` + `email_verified=1`, OTP fields clear, the first\nsandbox key is issued. **The plaintext is shown exactly once** \u2014\nstore it before the response leaves the network.\n\nFailed OTP entries increment `otp_attempts_today`; after the\nconfigured cap the integrator must wait or contact ops.\n","body":{"mode":"raw","raw":"{\n  \"integrator_id\": \"ACME-FINTECH-XXXXXX\",\n  \"otp\": \"123456\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Verified. Plaintext key in the response \u2014 copy now.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/signup/verify","host":["{{baseUrl}}"],"path":["v1","signup","verify"]},"description":"Step 2 of signup. On success: the integrator flips to\n`status=Active` + `email_verified=1`, OTP fields clear, the first\nsandbox key is issued. **The plaintext is shown exactly once** \u2014\nstore it before the response leaves the network.\n\nFailed OTP entries increment `otp_attempts_today`; after the\nconfigured cap the integrator must wait or contact ops.\n","body":{"mode":"raw","raw":"{\n  \"integrator_id\": \"ACME-FINTECH-XXXXXX\",\n  \"otp\": \"123456\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"integrator\": {\n      \"code\": \"ACME-FINTECH-XXXXXX\",\n      \"display_name\": \"string\",\n      \"contact_email\": \"you@example.com\",\n      \"tier\": \"Registered\",\n      \"pricing_tier\": \"Free\",\n      \"email_verified\": true\n    },\n    \"key\": {\n      \"name\": \"string\",\n      \"prefix\": \"sk_sandbox_2026\",\n      \"last4\": \"kKr2\",\n      \"scopes\": [\n        \"string\"\n      ],\n      \"expires_at\": \"2026-05-28T12:00:00Z\"\n    },\n    \"plaintext\": \"sk_sandbox_2026_kKr2RPJu84wJjWspIZfQx5XcMK7bjWKS\",\n    \"plaintext_warning\": \"string\",\n    \"next_steps\": [\n      \"string\"\n    ]\n  }\n}"}]},{"name":"Re-issue an OTP for a pending signup","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/signup/resend-otp","host":["{{baseUrl}}"],"path":["v1","signup","resend-otp"]},"description":"Rate-limited: at most one send per 60 seconds, 5 sends per UTC\nday across initial signup + resends. Each call resets the\n15-minute TTL on the active OTP.\n","body":{"mode":"raw","raw":"{\n  \"integrator_id\": \"ACME-FINTECH-XXXXXX\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"New OTP dispatched.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/signup/resend-otp","host":["{{baseUrl}}"],"path":["v1","signup","resend-otp"]},"description":"Rate-limited: at most one send per 60 seconds, 5 sends per UTC\nday across initial signup + resends. Each call resets the\n15-minute TTL on the active OTP.\n","body":{"mode":"raw","raw":"{\n  \"integrator_id\": \"ACME-FINTECH-XXXXXX\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"integrator_id\": \"string\",\n    \"message\": \"string\",\n    \"expires_at_iso\": \"2026-05-28T12:00:00Z\",\n    \"sends_remaining_today\": 0\n  }\n}"}]},{"name":"Get the current Sandbox Terms of Service version","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/signup/tos","host":["{{baseUrl}}"],"path":["v1","signup","tos"]},"description":"Returns the version + summary + a URL to the full text. The signup\nform pins `tos_version` to the value returned here when the user\naccepts; spec changes that bump the version invalidate signups\nin flight only after a deliberate rollover.\n","auth":{"type":"noauth"}},"response":[{"name":"Current Sandbox ToS metadata.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/signup/tos","host":["{{baseUrl}}"],"path":["v1","signup","tos"]},"description":"Returns the version + summary + a URL to the full text. The signup\nform pins `tos_version` to the value returned here when the user\naccepts; spec changes that bump the version invalidate signups\nin flight only after a deliberate rollover.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"version\": \"sandbox-tos-v1-2026-05-25\",\n    \"summary\": \"string\",\n    \"document_url\": \"https://example.com\"\n  }\n}"}]}]},{"name":"Authentication","description":"Magic-link sign-in to the integrator dashboard. The link is single-use\nand lives 15 minutes. A successful consume sets a `sente_session`\ncookie that authenticates `/v1/me/*` calls from the browser; the same\nsession also accepts a Bearer key for headless tooling.\n","item":[{"name":"Send a magic-link to an active integrator","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/login/request","host":["{{baseUrl}}"],"path":["v1","login","request"]},"description":"Step 1 of magic-link sign-in. The response is **deliberately\nuniform** \u2014 whether the email is registered, registered-but-\nunverified, or suspended, the caller sees the same 200 body. This\ndenies account-existence enumeration via timing or shape side\nchannels.\n\nRate limits: at most one link per 60 seconds per integrator,\n5 links per UTC day.\n\n**Dev-only convenience**: when the site has\n`sente_dev_reveal_magic_link: 1` in `site_config.json`, the\nresponse data additionally includes a `dev_consume_url` field for\nreal-email-less testing. Real deployments leave this off.\n","body":{"mode":"raw","raw":"{\n  \"email\": \"jane@acmefintech.test\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Always returned. Body is uniform regardless of match.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/login/request","host":["{{baseUrl}}"],"path":["v1","login","request"]},"description":"Step 1 of magic-link sign-in. The response is **deliberately\nuniform** \u2014 whether the email is registered, registered-but-\nunverified, or suspended, the caller sees the same 200 body. This\ndenies account-existence enumeration via timing or shape side\nchannels.\n\nRate limits: at most one link per 60 seconds per integrator,\n5 links per UTC day.\n\n**Dev-only convenience**: when the site has\n`sente_dev_reveal_magic_link: 1` in `site_config.json`, the\nresponse data additionally includes a `dev_consume_url` field for\nreal-email-less testing. Real deployments leave this off.\n","body":{"mode":"raw","raw":"{\n  \"email\": \"jane@acmefintech.test\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"message\": \"string\",\n    \"dev_consume_url\": \"https://example.com\"\n  }\n}"}]},{"name":"Exchange a magic-link token for a session cookie","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/login/consume?token=ACME-FINTECH-XXXXXX.A7F3GHKL...","host":["{{baseUrl}}"],"path":["v1","login","consume"],"query":[{"key":"token","value":"ACME-FINTECH-XXXXXX.A7F3GHKL...","description":"[required]"}]},"description":"Step 2 of magic-link sign-in. Validates the single-use token\nencoded in the URL. On success: sets the `sente_session` cookie\n(HttpOnly, Secure, SameSite=Lax, 14-day life) and redirects to\n`/dashboard`. On any failure (bad token, expired, already used,\nintegrator suspended, email unverified): redirects to\n`/signin/expired` with no cookie set \u2014 failure paths share a\ntarget so attackers can't probe which tokens existed.\n\nThis endpoint is typically not called directly \u2014 it's the target\nof the URL inside the magic-link email.\n","auth":{"type":"noauth"}},"response":[]},{"name":"Clear the active session","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/logout","host":["{{baseUrl}}"],"path":["v1","logout"]},"description":"Idempotent \u2014 calling without a session set returns the same 200.\nThe cookie is cleared on every path so a malformed/stale cookie\nalso gets wiped from the browser.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Session cleared (or nothing to clear).","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/logout","host":["{{baseUrl}}"],"path":["v1","logout"]},"description":"Idempotent \u2014 calling without a session set returns the same 200.\nThe cookie is cleared on every path so a malformed/stale cookie\nalso gets wiped from the browser.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"message\": \"Signed out.\"\n  }\n}"}]},{"name":"Probe the current session","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/session","host":["{{baseUrl}}"],"path":["v1","session"]},"description":"Returns `{authenticated: true, integrator: {code, display_name,\ncontact_email, tier, pricing_tier, last_login_at}}` when a valid\nsession cookie OR Bearer key is present, else `{authenticated:\nfalse}`. Used by the workbench to hide/show signed-in chrome\nwithout falling over on logged-out visitors.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Session probe result.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/session","host":["{{baseUrl}}"],"path":["v1","session"]},"description":"Returns `{authenticated: true, integrator: {code, display_name,\ncontact_email, tier, pricing_tier, last_login_at}}` when a valid\nsession cookie OR Bearer key is present, else `{authenticated:\nfalse}`. Used by the workbench to hide/show signed-in chrome\nwithout falling over on logged-out visitors.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"authenticated\": true,\n    \"integrator\": {\n      \"code\": \"ACME-FINTECH-XXXXXX\",\n      \"display_name\": \"string\",\n      \"contact_email\": \"you@example.com\",\n      \"tier\": \"Registered\",\n      \"pricing_tier\": \"Free\",\n      \"last_login_at\": \"2026-05-28T12:00:00Z\"\n    }\n  }\n}"}]}]},{"name":"Account","description":"Integrator self-service \u2014 profile read/write, contact info, anticipated\nvolume. The signed-in integrator only ever sees their own row.\n","item":[{"name":"Get the signed-in integrator's profile","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/me","host":["{{baseUrl}}"],"path":["v1","me"]},"description":"Returns the profile + live counters (active key count, total key\ncount, last-7-day request count). The endpoint reads the\nrequest-local integrator identity set by the auth pipeline \u2014 so the\ncaller can only ever see their own row.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"The signed-in integrator's profile.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/me","host":["{{baseUrl}}"],"path":["v1","me"]},"description":"Returns the profile + live counters (active key count, total key\ncount, last-7-day request count). The endpoint reads the\nrequest-local integrator identity set by the auth pipeline \u2014 so the\ncaller can only ever see their own row.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"ACME-FINTECH-XXXXXX\",\n    \"display_name\": \"Acme Fintech\",\n    \"type\": \"Developer\",\n    \"tier\": \"Registered\",\n    \"pricing_tier\": \"Free\",\n    \"status\": \"PendingEmail\",\n    \"contact_email\": \"you@example.com\",\n    \"technical_lead_user\": \"string\",\n    \"webhook_endpoint\": \"https://example.com\",\n    \"mou_status\": \"string\",\n    \"kyc_status\": \"string\",\n    \"ip_allowlist\": \"string\",\n    \"tos_accepted_on\": \"2026-05-28T12:00:00Z\",\n    \"tos_accepted_version\": \"string\",\n    \"signup_source\": \"string\",\n    \"email_verified\": false,\n    \"last_login_at\": \"2026-05-28T12:00:00Z\",\n    \"anticipated_volume_daily\": 5000,\n    \"anticipated_volume_monthly\": 150000,\n    \"keys\": {\n      \"total\": 2,\n      \"active\": 1\n    },\n    \"requests_last_7d\": 1284\n  }\n}"}]},{"name":"Patch a writable subset of the profile","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me","host":["{{baseUrl}}"],"path":["v1","me"]},"description":"Only the listed fields are writable here. `contact_email` changes\nare deliberately out of scope \u2014 rotating the email requires re-\nrunning OTP verification against the new address, which is an ops\nflow until self-serve email change ships.\n","body":{"mode":"raw","raw":"{\n  \"display_name\": \"string\",\n  \"webhook_endpoint\": \"https://example.com\",\n  \"ip_allowlist\": \"10.0.0.0/8, 203.0.113.42/32\",\n  \"anticipated_volume_daily\": 0,\n  \"anticipated_volume_monthly\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Updated profile.","originalRequest":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me","host":["{{baseUrl}}"],"path":["v1","me"]},"description":"Only the listed fields are writable here. `contact_email` changes\nare deliberately out of scope \u2014 rotating the email requires re-\nrunning OTP verification against the new address, which is an ops\nflow until self-serve email change ships.\n","body":{"mode":"raw","raw":"{\n  \"display_name\": \"string\",\n  \"webhook_endpoint\": \"https://example.com\",\n  \"ip_allowlist\": \"10.0.0.0/8, 203.0.113.42/32\",\n  \"anticipated_volume_daily\": 0,\n  \"anticipated_volume_monthly\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"ACME-FINTECH-XXXXXX\",\n    \"display_name\": \"Acme Fintech\",\n    \"type\": \"Developer\",\n    \"tier\": \"Registered\",\n    \"pricing_tier\": \"Free\",\n    \"status\": \"PendingEmail\",\n    \"contact_email\": \"you@example.com\",\n    \"technical_lead_user\": \"string\",\n    \"webhook_endpoint\": \"https://example.com\",\n    \"mou_status\": \"string\",\n    \"kyc_status\": \"string\",\n    \"ip_allowlist\": \"string\",\n    \"tos_accepted_on\": \"2026-05-28T12:00:00Z\",\n    \"tos_accepted_version\": \"string\",\n    \"signup_source\": \"string\",\n    \"email_verified\": false,\n    \"last_login_at\": \"2026-05-28T12:00:00Z\",\n    \"anticipated_volume_daily\": 5000,\n    \"anticipated_volume_monthly\": 150000,\n    \"keys\": {\n      \"total\": 2,\n      \"active\": 1\n    },\n    \"requests_last_7d\": 1284\n  }\n}"}]},{"name":"Alias for PATCH /v1/me","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me","host":["{{baseUrl}}"],"path":["v1","me"]},"description":"Some HTTP clients can't issue PATCH. POST against `/v1/me` with\nthe same body produces the same effect.\n","body":{"mode":"raw","raw":"{\n  \"display_name\": \"string\",\n  \"webhook_endpoint\": \"https://example.com\",\n  \"ip_allowlist\": \"10.0.0.0/8, 203.0.113.42/32\",\n  \"anticipated_volume_daily\": 0,\n  \"anticipated_volume_monthly\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Updated profile.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me","host":["{{baseUrl}}"],"path":["v1","me"]},"description":"Some HTTP clients can't issue PATCH. POST against `/v1/me` with\nthe same body produces the same effect.\n","body":{"mode":"raw","raw":"{\n  \"display_name\": \"string\",\n  \"webhook_endpoint\": \"https://example.com\",\n  \"ip_allowlist\": \"10.0.0.0/8, 203.0.113.42/32\",\n  \"anticipated_volume_daily\": 0,\n  \"anticipated_volume_monthly\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"ACME-FINTECH-XXXXXX\",\n    \"display_name\": \"Acme Fintech\",\n    \"type\": \"Developer\",\n    \"tier\": \"Registered\",\n    \"pricing_tier\": \"Free\",\n    \"status\": \"PendingEmail\",\n    \"contact_email\": \"you@example.com\",\n    \"technical_lead_user\": \"string\",\n    \"webhook_endpoint\": \"https://example.com\",\n    \"mou_status\": \"string\",\n    \"kyc_status\": \"string\",\n    \"ip_allowlist\": \"string\",\n    \"tos_accepted_on\": \"2026-05-28T12:00:00Z\",\n    \"tos_accepted_version\": \"string\",\n    \"signup_source\": \"string\",\n    \"email_verified\": false,\n    \"last_login_at\": \"2026-05-28T12:00:00Z\",\n    \"anticipated_volume_daily\": 5000,\n    \"anticipated_volume_monthly\": 150000,\n    \"keys\": {\n      \"total\": 2,\n      \"active\": 1\n    },\n    \"requests_last_7d\": 1284\n  }\n}"}]}]},{"name":"API keys","description":"Per-integrator key lifecycle \u2014 list, rotate (with grace window),\nrevoke. Plaintext is shown exactly once at issue or rotate; lose it\nand the only path forward is rotate.\n","item":[{"name":"List every key the caller owns","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/me/keys","host":["{{baseUrl}}"],"path":["v1","me","keys"]},"description":"Returns keys newest-first, including revoked and rolling ones, so\nthe dashboard can render the full lifecycle. Plaintext is never\nin this response \u2014 only `prefix`, `last4`, and metadata.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"All keys owned by the signed-in integrator.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/me/keys","host":["{{baseUrl}}"],"path":["v1","me","keys"]},"description":"Returns keys newest-first, including revoked and rolling ones, so\nthe dashboard can render the full lifecycle. Plaintext is never\nin this response \u2014 only `prefix`, `last4`, and metadata.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"KEY-2026-000002\",\n      \"prefix\": \"sk_sandbox_2026\",\n      \"last4\": \"kKr2\",\n      \"environment\": \"sandbox\",\n      \"key_type\": \"sk\",\n      \"status\": \"active\",\n      \"scopes\": [\n        \"citizens.read\",\n        \"citizens.write\",\n        \"assessments.write\",\n        \"payment-intents.write\"\n      ],\n      \"created_at\": \"2026-05-28T12:00:00Z\",\n      \"expires_at\": \"2026-05-28T12:00:00Z\",\n      \"last_used_at\": \"2026-05-28T12:00:00Z\",\n      \"last_used_ip\": \"string\",\n      \"usage_count\": 0,\n      \"revoked_at\": \"2026-05-28T12:00:00Z\",\n      \"revoked_by\": \"string\",\n      \"revoked_reason\": \"string\",\n      \"rolling_until\": \"2026-05-28T12:00:00Z\",\n      \"rolled_to\": \"string\",\n      \"description\": \"string\"\n    }\n  ]\n}"}]},{"name":"Rotate a key","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me/keys/:name:rotate","host":["{{baseUrl}}"],"path":["v1","me","keys",":name:rotate"],"variable":[{"key":"name","value":"KEY-2026-000002","description":"[required]"}]},"description":"Mints a fresh key with the same scopes. The old key flips to\n`rolling` and remains live for `grace_hours` (1\u2013168, default 24),\ngiving callers a window to update applications. After the window\nthe old key expires and only the new one accepts traffic.\n\n**The plaintext of the new key is shown exactly once** in this\nresponse. Lose it, and the only remedy is to rotate again.\n","body":{"mode":"raw","raw":"{\n  \"grace_hours\": 24\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Rotated. Plaintext returned once \u2014 store immediately.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me/keys/:name:rotate","host":["{{baseUrl}}"],"path":["v1","me","keys",":name:rotate"],"variable":[{"key":"name","value":"KEY-2026-000002","description":"[required]"}]},"description":"Mints a fresh key with the same scopes. The old key flips to\n`rolling` and remains live for `grace_hours` (1\u2013168, default 24),\ngiving callers a window to update applications. After the window\nthe old key expires and only the new one accepts traffic.\n\n**The plaintext of the new key is shown exactly once** in this\nresponse. Lose it, and the only remedy is to rotate again.\n","body":{"mode":"raw","raw":"{\n  \"grace_hours\": 24\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"old_key\": {\n      \"name\": \"string\",\n      \"status\": \"rolling\",\n      \"rolling_until\": \"2026-05-28T12:00:00Z\"\n    },\n    \"new_key\": {\n      \"name\": \"KEY-2026-000002\",\n      \"prefix\": \"sk_sandbox_2026\",\n      \"last4\": \"kKr2\",\n      \"environment\": \"sandbox\",\n      \"key_type\": \"sk\",\n      \"status\": \"active\",\n      \"scopes\": [\n        \"citizens.read\",\n        \"citizens.write\",\n        \"assessments.write\",\n        \"payment-intents.write\"\n      ],\n      \"created_at\": \"2026-05-28T12:00:00Z\",\n      \"expires_at\": \"2026-05-28T12:00:00Z\",\n      \"last_used_at\": \"2026-05-28T12:00:00Z\",\n      \"last_used_ip\": \"string\",\n      \"usage_count\": 0,\n      \"revoked_at\": \"2026-05-28T12:00:00Z\",\n      \"revoked_by\": \"string\",\n      \"revoked_reason\": \"string\",\n      \"rolling_until\": \"2026-05-28T12:00:00Z\",\n      \"rolled_to\": \"string\",\n      \"description\": \"string\"\n    },\n    \"plaintext\": \"string\",\n    \"plaintext_warning\": \"string\"\n  }\n}"}]},{"name":"Revoke a key permanently","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me/keys/:name:revoke","host":["{{baseUrl}}"],"path":["v1","me","keys",":name:revoke"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Immediate, non-reversible. The key flips to `status=revoked` and\nevery subsequent request carrying it receives `401`. The supplied\n`reason` is recorded in the audit log; it shows up later under\nthe `revoked_reason` field on key listings.\n\nTo restore service, rotate a *different* key (or contact ops if\nthe revoked one was your last).\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"Suspected leak in CI logs\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Revoked. Subsequent requests with the key receive 401.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/me/keys/:name:revoke","host":["{{baseUrl}}"],"path":["v1","me","keys",":name:revoke"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Immediate, non-reversible. The key flips to `status=revoked` and\nevery subsequent request carrying it receives `401`. The supplied\n`reason` is recorded in the audit log; it shows up later under\nthe `revoked_reason` field on key listings.\n\nTo restore service, rotate a *different* key (or contact ops if\nthe revoked one was your last).\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"Suspected leak in CI logs\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"KEY-2026-000002\",\n    \"prefix\": \"sk_sandbox_2026\",\n    \"last4\": \"kKr2\",\n    \"environment\": \"sandbox\",\n    \"key_type\": \"sk\",\n    \"status\": \"active\",\n    \"scopes\": [\n      \"citizens.read\",\n      \"citizens.write\",\n      \"assessments.write\",\n      \"payment-intents.write\"\n    ],\n    \"created_at\": \"2026-05-28T12:00:00Z\",\n    \"expires_at\": \"2026-05-28T12:00:00Z\",\n    \"last_used_at\": \"2026-05-28T12:00:00Z\",\n    \"last_used_ip\": \"string\",\n    \"usage_count\": 0,\n    \"revoked_at\": \"2026-05-28T12:00:00Z\",\n    \"revoked_by\": \"string\",\n    \"revoked_reason\": \"string\",\n    \"rolling_until\": \"2026-05-28T12:00:00Z\",\n    \"rolled_to\": \"string\",\n    \"description\": \"string\"\n  }\n}"}]}]},{"name":"Audit logs","description":"90-day hot window over every `/v1` request your account made. Filter\nby endpoint, event class (`api.auth.granted` / `api.auth.denied` /\n`api.handler.error`), and minimum HTTP status. Cold-storage retention\nruns to 7 years per Tax Procedures Code Act \u00a773A\u2013B.\n","item":[{"name":"List the caller's recent /v1 audit events","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/me/logs?endpoint=/v1/citizens&event=api.auth.granted&min_status=0&limit=50","host":["{{baseUrl}}"],"path":["v1","me","logs"],"query":[{"key":"endpoint","value":"/v1/citizens","description":"Substring match on the endpoint path."},{"key":"event","value":"api.auth.granted","description":""},{"key":"min_status","value":"0","description":"Minimum HTTP status to include (e.g. 400 \u2192 errors only)."},{"key":"limit","value":"50","description":""}]},"description":"90-day hot window \u2014 newest first. Filter by substring on\n`endpoint`, exact `event` class, and minimum HTTP status. Each\nrow carries a `request_id` you can quote when reporting issues.\n\nEvents emitted today:\n- `api.auth.granted` \u2014 request authenticated + scoped successfully.\n- `api.auth.denied` \u2014 auth or scope check refused (4xx).\n- `api.handler.error` \u2014 handler raised (5xx).\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Matching audit rows.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/me/logs?endpoint=/v1/citizens&event=api.auth.granted&min_status=0&limit=50","host":["{{baseUrl}}"],"path":["v1","me","logs"],"query":[{"key":"endpoint","value":"/v1/citizens","description":"Substring match on the endpoint path."},{"key":"event","value":"api.auth.granted","description":""},{"key":"min_status","value":"0","description":"Minimum HTTP status to include (e.g. 400 \u2192 errors only)."},{"key":"limit","value":"50","description":""}]},"description":"90-day hot window \u2014 newest first. Filter by substring on\n`endpoint`, exact `event` class, and minimum HTTP status. Each\nrow carries a `request_id` you can quote when reporting issues.\n\nEvents emitted today:\n- `api.auth.granted` \u2014 request authenticated + scoped successfully.\n- `api.auth.denied` \u2014 auth or scope check refused (4xx).\n- `api.handler.error` \u2014 handler raised (5xx).\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"ts\": \"2026-05-28T12:00:00Z\",\n      \"event\": \"api.auth.granted\",\n      \"request_id\": \"00000000-0000-0000-0000-000000000000\",\n      \"http_method\": \"POST\",\n      \"endpoint\": \"/v1/payment-intents\",\n      \"http_status\": 200,\n      \"error_code\": \"string\",\n      \"api_key\": \"string\",\n      \"source_ip\": \"string\",\n      \"required_scopes\": [\n        \"string\"\n      ],\n      \"granted_scopes\": [\n        \"string\"\n      ],\n      \"latency_ms\": 47\n    }\n  ]\n}"}]}]},{"name":"Counter Stations","description":"Kiosk-style endpoints used by MDA counter clerks at physical\nterminals. The full clerk workflow lives here: shift open \u2192\ncitizen lookup \u2192 multi-line assessment \u2192 payment intent \u2192 close\nwith cash variance. Mirrors the /v1 core surface but auth'd via\nPlatform `sid` cookie + the `Sente Rails Clerk`, `Sente Rails\nSupervisor`, or `Sente Rails Admin` role (never via Bearer key).\n","item":[{"name":"Return the signed-in staff user's identity + role flags","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/whoami","host":["{{baseUrl}}"],"path":["v1","work","whoami"]},"description":"Allow-guest endpoint that returns `{authenticated: false}` when no\nvalid platform session is present. When authenticated, returns the\nuser row plus role booleans the kiosk uses to render its chrome.\n\nThis is the only `/v1/work/*` endpoint that doesn't 401 for\nunauthenticated callers \u2014 needed so the kiosk's auth-check flow\ncan probe state without bouncing the user to `/login`.\n","auth":{"type":"noauth"}},"response":[{"name":"Authentication probe result.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/whoami","host":["{{baseUrl}}"],"path":["v1","work","whoami"]},"description":"Allow-guest endpoint that returns `{authenticated: false}` when no\nvalid platform session is present. When authenticated, returns the\nuser row plus role booleans the kiosk uses to render its chrome.\n\nThis is the only `/v1/work/*` endpoint that doesn't 401 for\nunauthenticated callers \u2014 needed so the kiosk's auth-check flow\ncan probe state without bouncing the user to `/login`.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"authenticated\": true,\n    \"user\": {\n      \"name\": \"clerk.gulu@sente-rails.space\",\n      \"full_name\": \"Akello Susan\",\n      \"email\": \"you@example.com\"\n    },\n    \"roles\": [\n      \"Sente Rails Clerk\",\n      \"Guest\"\n    ],\n    \"is_clerk\": false,\n    \"is_supervisor\": false,\n    \"is_admin\": false,\n    \"has_work_access\": false\n  }\n}"}]},{"name":"List MDAs available at the kiosk","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/mdas","host":["{{baseUrl}}"],"path":["v1","work","mdas"]},"description":"Mirrors `/v1/mdas` but gated by Counter Stations auth. The kiosk's\nMDA picker reads from here on shift-open. No filters today \u2014\nreturns the full active catalogue.\n","auth":{"type":"noauth"}},"response":[{"name":"Active MDAs.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/mdas","host":["{{baseUrl}}"],"path":["v1","work","mdas"]},"description":"Mirrors `/v1/mdas` but gated by Counter Stations auth. The kiosk's\nMDA picker reads from here on shift-open. No filters today \u2014\nreturns the full active catalogue.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"GULU\",\n      \"short_code\": \"GULU\",\n      \"full_name\": \"Gulu City Council\",\n      \"mda_type\": \"City Authority\",\n      \"status\": \"Active\",\n      \"country\": \"UG\",\n      \"mode\": \"A\",\n      \"sector\": \"string\",\n      \"integration_status\": \"Live\",\n      \"target_endpoint_count\": 0,\n      \"endpoint_count\": 0,\n      \"display_endpoint_count\": 0,\n      \"contact_email\": \"you@example.com\",\n      \"contact_phone\": \"string\"\n    }\n  ]\n}"}]},{"name":"List active services for an MDA","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/services?mda=GULU","host":["{{baseUrl}}"],"path":["v1","work","services"],"query":[{"key":"mda","value":"GULU","description":"[required]"}]},"description":"Powers the kiosk's service picker mid-assessment. The `mda` query\nparam is required \u2014 services are scoped to their owning MDA.\n","auth":{"type":"noauth"}},"response":[{"name":"Active services for the given MDA.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/services?mda=GULU","host":["{{baseUrl}}"],"path":["v1","work","services"],"query":[{"key":"mda","value":"GULU","description":"[required]"}]},"description":"Powers the kiosk's service picker mid-assessment. The `mda` query\nparam is required \u2014 services are scoped to their owning MDA.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"mda\": \"GULU\",\n      \"code\": \"TL-RENEW\",\n      \"service_name\": \"Trading License Renewal\",\n      \"status\": \"Active\",\n      \"sector\": \"Revenue\",\n      \"service_family\": \"Trading Licenses\",\n      \"fee_amount\": 50000,\n      \"fee_currency\": \"UGX\",\n      \"fee_basis\": \"Flat\",\n      \"fee_schedule_ref\": \"Trade Licensing Act 2015 \\u00a712(1)\",\n      \"efris_taxable\": false,\n      \"vat_applicable\": false,\n      \"vat_rate\": 0,\n      \"gl_account_credit\": \"string\",\n      \"linked_item\": \"string\",\n      \"description\": \"string\"\n    }\n  ]\n}"}]},{"name":"Register a citizen at the counter","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/citizens","host":["{{baseUrl}}"],"path":["v1","work","citizens"]},"description":"Find-or-create a local Citizen from a NIN, resolving via NIRA.\nThe counter companion to `GET /v1/work/citizens/search`: a\nNIRA-only hit has no local docname and cannot anchor an\nassessment, so this persists it into the local registry and\ncaptures an Identity Verification consent event (the clerk\nregistering a citizen who is present at the counter is the\nin-person consent gesture). Idempotent \u2014 an already-registered\nNIN returns the existing record. Auth: staff session\n(Clerk / Supervisor).\n\n**Who calls this:** a signed-in clerk at a counter station \u2014 via\na browser **session cookie** (`sid`), not an API key. It is\ndriven by the workbench `/work/assess` screen, not by integrators.\n\n**If you click \"Send\" in this explorer:** expect `401` unless you\nare signed in as staff \u2014 this endpoint takes a staff session, and\nthe API key on the explorer page does not satisfy it. That is by\ndesign: only counter staff, with the citizen present, may register\nthem.\n","body":{"mode":"raw","raw":"{\n  \"nin\": \"CM78001234ABCD\",\n  \"mda\": \"GULU\"\n}","options":{"raw":{"language":"json"}}}},"response":[{"name":"Citizen registered (or already on the rail).","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/citizens","host":["{{baseUrl}}"],"path":["v1","work","citizens"]},"description":"Find-or-create a local Citizen from a NIN, resolving via NIRA.\nThe counter companion to `GET /v1/work/citizens/search`: a\nNIRA-only hit has no local docname and cannot anchor an\nassessment, so this persists it into the local registry and\ncaptures an Identity Verification consent event (the clerk\nregistering a citizen who is present at the counter is the\nin-person consent gesture). Idempotent \u2014 an already-registered\nNIN returns the existing record. Auth: staff session\n(Clerk / Supervisor).\n\n**Who calls this:** a signed-in clerk at a counter station \u2014 via\na browser **session cookie** (`sid`), not an API key. It is\ndriven by the workbench `/work/assess` screen, not by integrators.\n\n**If you click \"Send\" in this explorer:** expect `401` unless you\nare signed in as staff \u2014 this endpoint takes a staff session, and\nthe API key on the explorer page does not satisfy it. That is by\ndesign: only counter staff, with the citizen present, may register\nthem.\n","body":{"mode":"raw","raw":"{\n  \"nin\": \"CM78001234ABCD\",\n  \"mda\": \"GULU\"\n}","options":{"raw":{"language":"json"}}}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"citizen\": {\n      \"name\": \"CITIZEN-2026-000002\",\n      \"nin\": \"CM78001234ABCD\",\n      \"full_name\": \"Mukasa John Patrick\",\n      \"phone\": \"+256772123456\",\n      \"email\": \"you@example.com\",\n      \"district\": \"Gulu\",\n      \"status\": \"Active\",\n      \"verified\": false,\n      \"modified\": \"2026-05-28T12:00:00Z\",\n      \"tin\": \"string\",\n      \"first_name\": \"string\",\n      \"middle_name\": \"string\",\n      \"surname\": \"string\",\n      \"dob\": \"2026-05-28\",\n      \"alternate_phone\": \"string\",\n      \"sub_county\": \"string\",\n      \"parish\": \"string\",\n      \"village\": \"string\",\n      \"address_line\": \"string\",\n      \"consent_data_sharing\": false,\n      \"consent_recorded_on\": \"2026-05-28T12:00:00Z\",\n      \"consent_recorded_by\": \"string\",\n      \"linked_customer\": \"string\",\n      \"photo\": \"string\"\n    },\n    \"created\": false,\n    \"source\": \"local\",\n    \"consent_event\": \"string\"\n  }\n}"}]},{"name":"Resolve a citizen by NIN","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/citizens/search?nin=CM78001234ABCD","host":["{{baseUrl}}"],"path":["v1","work","citizens","search"],"query":[{"key":"nin","value":"CM78001234ABCD","description":"National Identification Number (case-insensitive \u2014 uppercased server-side).  [required]"}]},"description":"Two-layer lookup: local Citizen registry first, falling through to\nthe NIRA adapter (and caching the result locally). Response\n`source` tells the caller which layer answered.\n\nReturns `{source: \"miss\", citizen: null}` when no match is found \u2014\nthe kiosk falls back to its manual entry flow.\n","auth":{"type":"noauth"}},"response":[{"name":"Citizen lookup result.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/citizens/search?nin=CM78001234ABCD","host":["{{baseUrl}}"],"path":["v1","work","citizens","search"],"query":[{"key":"nin","value":"CM78001234ABCD","description":"National Identification Number (case-insensitive \u2014 uppercased server-side).  [required]"}]},"description":"Two-layer lookup: local Citizen registry first, falling through to\nthe NIRA adapter (and caching the result locally). Response\n`source` tells the caller which layer answered.\n\nReturns `{source: \"miss\", citizen: null}` when no match is found \u2014\nthe kiosk falls back to its manual entry flow.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"source\": \"local\",\n    \"citizen\": {\n      \"name\": \"CITIZEN-2026-000002\",\n      \"nin\": \"CM78001234ABCD\",\n      \"full_name\": \"Mukasa John Patrick\",\n      \"phone\": \"+256772123456\",\n      \"email\": \"you@example.com\",\n      \"district\": \"Gulu\",\n      \"status\": \"Active\",\n      \"verified\": false,\n      \"modified\": \"2026-05-28T12:00:00Z\",\n      \"tin\": \"string\",\n      \"first_name\": \"string\",\n      \"middle_name\": \"string\",\n      \"surname\": \"string\",\n      \"dob\": \"2026-05-28\",\n      \"alternate_phone\": \"string\",\n      \"sub_county\": \"string\",\n      \"parish\": \"string\",\n      \"village\": \"string\",\n      \"address_line\": \"string\",\n      \"consent_data_sharing\": false,\n      \"consent_recorded_on\": \"2026-05-28T12:00:00Z\",\n      \"consent_recorded_by\": \"string\",\n      \"linked_customer\": \"string\",\n      \"photo\": \"string\"\n    }\n  }\n}"}]},{"name":"Get the clerk's currently open shift at an MDA","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/shift/active?mda=GULU","host":["{{baseUrl}}"],"path":["v1","work","shift","active"],"query":[{"key":"mda","value":"GULU","description":"[required]"}]},"description":"Returns the open `Counter Shift` row for the signed-in clerk\nscoped to the given MDA, or `null` if no shift is open. The\nkiosk hits this on every page load to know whether to show the\n\"open shift\" CTA or the active workflow.\n","auth":{"type":"noauth"}},"response":[{"name":"The open shift, or null.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/shift/active?mda=GULU","host":["{{baseUrl}}"],"path":["v1","work","shift","active"],"query":[{"key":"mda","value":"GULU","description":"[required]"}]},"description":"Returns the open `Counter Shift` row for the signed-in clerk\nscoped to the given MDA, or `null` if no shift is open. The\nkiosk hits this on every page load to know whether to show the\n\"open shift\" CTA or the active workflow.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"List the clerk's shifts (newest first)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/shifts?status=Open&limit=20","host":["{{baseUrl}}"],"path":["v1","work","shifts"],"query":[{"key":"status","value":"Open","description":""},{"key":"limit","value":"20","description":""}]},"description":"History view for the kiosk's \"previous shifts\" tab. Filter by\nstatus (Open / Closed / Variance) if needed.\n","auth":{"type":"noauth"}},"response":[{"name":"Shifts owned by the signed-in clerk.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/shifts?status=Open&limit=20","host":["{{baseUrl}}"],"path":["v1","work","shifts"],"query":[{"key":"status","value":"Open","description":""},{"key":"limit","value":"20","description":""}]},"description":"History view for the kiosk's \"previous shifts\" tab. Filter by\nstatus (Open / Closed / Variance) if needed.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"clerk\": \"string\",\n      \"mda\": \"string\",\n      \"counter_label\": \"string\",\n      \"status\": \"Open\",\n      \"opened_at\": \"2026-05-28T12:00:00Z\",\n      \"closed_at\": \"2026-05-28T12:00:00Z\",\n      \"opening_float\": 0,\n      \"assessment_count\": 0,\n      \"total_collected\": 0,\n      \"cash_collected\": 0,\n      \"momo_collected\": 0,\n      \"airtel_collected\": 0,\n      \"cash_expected\": 0,\n      \"cash_counted\": 0,\n      \"cash_variance\": 0,\n      \"currency\": \"string\",\n      \"opening_notes\": \"string\",\n      \"card_collected\": 0,\n      \"bank_collected\": 0,\n      \"voucher_collected\": 0,\n      \"variance_reason\": \"string\",\n      \"closing_notes\": \"string\"\n    }\n  ]\n}"}]},{"name":"Open a new shift","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/shift","host":["{{baseUrl}}"],"path":["v1","work","shift"]},"description":"Creates a `Counter Shift` row in `Open` state owned by the signed-in\nclerk. Rejects if the clerk already has an open shift at this MDA \u2014\nonly one open shift per (clerk, MDA) pair at a time.\n","body":{"mode":"raw","raw":"{\n  \"mda\": \"GULU\",\n  \"counter_label\": \"Window 2\",\n  \"opening_cash\": 50000\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Shift opened.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/shift","host":["{{baseUrl}}"],"path":["v1","work","shift"]},"description":"Creates a `Counter Shift` row in `Open` state owned by the signed-in\nclerk. Rejects if the clerk already has an open shift at this MDA \u2014\nonly one open shift per (clerk, MDA) pair at a time.\n","body":{"mode":"raw","raw":"{\n  \"mda\": \"GULU\",\n  \"counter_label\": \"Window 2\",\n  \"opening_cash\": 50000\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Close a shift with a cash count","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/shift/:name:close","host":["{{baseUrl}}"],"path":["v1","work","shift",":name:close"],"variable":[{"key":"name","value":"SHIFT-2026-05-28-204","description":"[required]"}]},"description":"Submits the clerk's end-of-shift cash count. The server computes\nvariance against expected (`opening_cash + cash receipts -\ncash refunds`) and writes it to the shift row. If non-zero, the\nshift flips to `Variance` state and surfaces in the supervisor\nqueue; otherwise it flips to `Closed`.\n\nOnly the shift's owning clerk (or an admin) may close it.\n","body":{"mode":"raw","raw":"{\n  \"cash_counted\": 1450000,\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Shift closed. Inspect `status` for `Closed` vs `Variance`.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/shift/:name:close","host":["{{baseUrl}}"],"path":["v1","work","shift",":name:close"],"variable":[{"key":"name","value":"SHIFT-2026-05-28-204","description":"[required]"}]},"description":"Submits the clerk's end-of-shift cash count. The server computes\nvariance against expected (`opening_cash + cash receipts -\ncash refunds`) and writes it to the shift row. If non-zero, the\nshift flips to `Variance` state and surfaces in the supervisor\nqueue; otherwise it flips to `Closed`.\n\nOnly the shift's owning clerk (or an admin) may close it.\n","body":{"mode":"raw","raw":"{\n  \"cash_counted\": 1450000,\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Create a draft assessment","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/assessments","host":["{{baseUrl}}"],"path":["v1","work","assessments"]},"description":"Builds an `Assessment` in `Draft` state with one or more\n`Assessment Line` rows. Each line resolves its rate from the\nService catalogue; line `quantity` and `explicit_amount` overrides\nare honoured. The assessment is scoped to the clerk's currently\nopen shift \u2014 open one via `POST /v1/work/shift` first.\n","body":{"mode":"raw","raw":"{\n  \"citizen\": \"CITIZEN-2026-000002\",\n  \"lines\": [\n    {\n      \"service\": \"SVC-2026-000004\",\n      \"quantity\": 1,\n      \"explicit_amount\": 0,\n      \"notes\": \"string\"\n    }\n  ],\n  \"mda_default\": \"GULU\",\n  \"notes\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Assessment created in Draft.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/assessments","host":["{{baseUrl}}"],"path":["v1","work","assessments"]},"description":"Builds an `Assessment` in `Draft` state with one or more\n`Assessment Line` rows. Each line resolves its rate from the\nService catalogue; line `quantity` and `explicit_amount` overrides\nare honoured. The assessment is scoped to the clerk's currently\nopen shift \u2014 open one via `POST /v1/work/shift` first.\n","body":{"mode":"raw","raw":"{\n  \"citizen\": \"CITIZEN-2026-000002\",\n  \"lines\": [\n    {\n      \"service\": \"SVC-2026-000004\",\n      \"quantity\": 1,\n      \"explicit_amount\": 0,\n      \"notes\": \"string\"\n    }\n  ],\n  \"mda_default\": \"GULU\",\n  \"notes\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"citizen\": \"string\",\n    \"transaction_date\": \"2026-05-28\",\n    \"status\": \"Draft\",\n    \"mda_default\": \"string\",\n    \"shift\": \"string\",\n    \"total_amount\": 0,\n    \"gross_amount\": 0,\n    \"discount_amount\": 0,\n    \"discount_reason\": \"string\",\n    \"currency\": \"string\",\n    \"payment_status\": \"Pending\",\n    \"payment_channel\": \"string\",\n    \"payment_reference\": \"string\",\n    \"paid_at\": \"2026-05-28T12:00:00Z\",\n    \"clerk\": \"string\",\n    \"assessment_lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"string\",\n        \"fee_basis\": \"string\",\n        \"quantity\": 1,\n        \"rate\": 0,\n        \"amount\": 0,\n        \"efris_taxable\": false,\n        \"fee_schedule_ref\": \"string\",\n        \"ura_prn\": \"string\",\n        \"efris_fdn\": \"string\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"idempotency_key\": \"string\",\n    \"linked_journal_entry\": \"string\",\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Fetch an assessment by docname","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/assessments/:name","host":["{{baseUrl}}"],"path":["v1","work","assessments",":name"],"variable":[{"key":"name","value":"ASMT-2026-05-000123","description":"[required]"}]},"description":"Returns the full assessment including all lines, totals,\ncurrency, payment status, and links to the issuing clerk + shift.\n","auth":{"type":"noauth"}},"response":[{"name":"The assessment.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/assessments/:name","host":["{{baseUrl}}"],"path":["v1","work","assessments",":name"],"variable":[{"key":"name","value":"ASMT-2026-05-000123","description":"[required]"}]},"description":"Returns the full assessment including all lines, totals,\ncurrency, payment status, and links to the issuing clerk + shift.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"citizen\": \"string\",\n    \"transaction_date\": \"2026-05-28\",\n    \"status\": \"Draft\",\n    \"mda_default\": \"string\",\n    \"shift\": \"string\",\n    \"total_amount\": 0,\n    \"gross_amount\": 0,\n    \"discount_amount\": 0,\n    \"discount_reason\": \"string\",\n    \"currency\": \"string\",\n    \"payment_status\": \"Pending\",\n    \"payment_channel\": \"string\",\n    \"payment_reference\": \"string\",\n    \"paid_at\": \"2026-05-28T12:00:00Z\",\n    \"clerk\": \"string\",\n    \"assessment_lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"string\",\n        \"fee_basis\": \"string\",\n        \"quantity\": 1,\n        \"rate\": 0,\n        \"amount\": 0,\n        \"efris_taxable\": false,\n        \"fee_schedule_ref\": \"string\",\n        \"ura_prn\": \"string\",\n        \"efris_fdn\": \"string\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"idempotency_key\": \"string\",\n    \"linked_journal_entry\": \"string\",\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Lock totals on a draft assessment","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/work/assessments/:name:assess","host":["{{baseUrl}}"],"path":["v1","work","assessments",":name:assess"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Transitions a `Draft` assessment to `Assessed`, freezing line\ntotals + the grand total. Required before a payment intent can\nbe created against the assessment. Re-running on an already-\nassessed assessment is a no-op.\n","auth":{"type":"noauth"}},"response":[{"name":"Assessment locked.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/work/assessments/:name:assess","host":["{{baseUrl}}"],"path":["v1","work","assessments",":name:assess"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Transitions a `Draft` assessment to `Assessed`, freezing line\ntotals + the grand total. Required before a payment intent can\nbe created against the assessment. Re-running on an already-\nassessed assessment is a no-op.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"citizen\": \"string\",\n    \"transaction_date\": \"2026-05-28\",\n    \"status\": \"Draft\",\n    \"mda_default\": \"string\",\n    \"shift\": \"string\",\n    \"total_amount\": 0,\n    \"gross_amount\": 0,\n    \"discount_amount\": 0,\n    \"discount_reason\": \"string\",\n    \"currency\": \"string\",\n    \"payment_status\": \"Pending\",\n    \"payment_channel\": \"string\",\n    \"payment_reference\": \"string\",\n    \"paid_at\": \"2026-05-28T12:00:00Z\",\n    \"clerk\": \"string\",\n    \"assessment_lines\": [\n      {\n        \"mda\": \"GULU\",\n        \"service\": \"SVC-2026-000004\",\n        \"service_name\": \"string\",\n        \"fee_basis\": \"string\",\n        \"quantity\": 1,\n        \"rate\": 0,\n        \"amount\": 0,\n        \"efris_taxable\": false,\n        \"fee_schedule_ref\": \"string\",\n        \"ura_prn\": \"string\",\n        \"efris_fdn\": \"string\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"idempotency_key\": \"string\",\n    \"linked_journal_entry\": \"string\",\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Create a payment intent for an assessed assessment","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents","host":["{{baseUrl}}"],"path":["v1","work","payment-intents"]},"description":"Spawns a `Payment Intent` linked to the named assessment, carrying\nthe chosen channel + (for mobile money) the citizen's MSISDN.\nSplit rules are inherited from the assessment's per-line MDA\nbreakdown; Sente Rails never holds public money \u2014 the aggregator\nexecutes splits directly into each MDA's collection account.\n\nThe intent is `Pending` until `:initiate` fires.\n","body":{"mode":"raw","raw":"{\n  \"assessment\": \"ASMT-2026-05-000123\",\n  \"channel\": \"MTN MoMo\",\n  \"citizen_msisdn\": \"+256772123456\",\n  \"notes\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Payment intent created in Pending.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents","host":["{{baseUrl}}"],"path":["v1","work","payment-intents"]},"description":"Spawns a `Payment Intent` linked to the named assessment, carrying\nthe chosen channel + (for mobile money) the citizen's MSISDN.\nSplit rules are inherited from the assessment's per-line MDA\nbreakdown; Sente Rails never holds public money \u2014 the aggregator\nexecutes splits directly into each MDA's collection account.\n\nThe intent is `Pending` until `:initiate` fires.\n","body":{"mode":"raw","raw":"{\n  \"assessment\": \"ASMT-2026-05-000123\",\n  \"channel\": \"MTN MoMo\",\n  \"citizen_msisdn\": \"+256772123456\",\n  \"notes\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"assessment\": \"string\",\n    \"channel\": \"MTN MoMo\",\n    \"status\": \"Pending\",\n    \"currency\": \"string\",\n    \"amount\": 0,\n    \"citizen_msisdn\": \"string\",\n    \"aggregator\": \"string\",\n    \"aggregator_reference\": \"string\",\n    \"sent_at\": \"2026-05-28T12:00:00Z\",\n    \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n    \"failed_at\": \"2026-05-28T12:00:00Z\",\n    \"failure_reason\": \"string\",\n    \"fiscal_status\": \"Not Fiscalised\",\n    \"fdn\": \"string\",\n    \"fiscal_verification_code\": \"string\",\n    \"fiscal_qr_payload\": \"string\",\n    \"fiscalised_at\": \"2026-05-28T12:00:00Z\",\n    \"refunded_at\": \"2026-05-28T12:00:00Z\",\n    \"refund_reason\": \"string\",\n    \"idempotency_key\": \"string\",\n    \"split_rules\": [\n      {\n        \"mda\": \"GULU\",\n        \"amount\": 50000,\n        \"destination_account\": \"GULU-COLLECTION-001\",\n        \"destination_account_type\": \"Bank\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Dispatch the payment intent to the aggregator","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name:initiate","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name:initiate"],"variable":[{"key":"name","value":"PI-2026-05-000087","description":"[required]"}]},"description":"Fires the adapter for the chosen channel (MoMo / Airtel / Pesapal\n/ etc.) and stores the `aggregator_reference`. The intent moves\nfrom `Pending` \u2192 `Sent`. The aggregator pushes confirmation back\nvia `/v1/webhooks/{provider}`; the kiosk can poll\n`/v1/work/payment-intents/{name}/live-status` while waiting.\n\nFor `Cash` channel intents, this transitions straight to\n`Confirmed`.\n","auth":{"type":"noauth"}},"response":[{"name":"Adapter dispatched. Inspect `status` + `aggregator_reference`.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name:initiate","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name:initiate"],"variable":[{"key":"name","value":"PI-2026-05-000087","description":"[required]"}]},"description":"Fires the adapter for the chosen channel (MoMo / Airtel / Pesapal\n/ etc.) and stores the `aggregator_reference`. The intent moves\nfrom `Pending` \u2192 `Sent`. The aggregator pushes confirmation back\nvia `/v1/webhooks/{provider}`; the kiosk can poll\n`/v1/work/payment-intents/{name}/live-status` while waiting.\n\nFor `Cash` channel intents, this transitions straight to\n`Confirmed`.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"assessment\": \"string\",\n    \"channel\": \"MTN MoMo\",\n    \"status\": \"Pending\",\n    \"currency\": \"string\",\n    \"amount\": 0,\n    \"citizen_msisdn\": \"string\",\n    \"aggregator\": \"string\",\n    \"aggregator_reference\": \"string\",\n    \"sent_at\": \"2026-05-28T12:00:00Z\",\n    \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n    \"failed_at\": \"2026-05-28T12:00:00Z\",\n    \"failure_reason\": \"string\",\n    \"fiscal_status\": \"Not Fiscalised\",\n    \"fdn\": \"string\",\n    \"fiscal_verification_code\": \"string\",\n    \"fiscal_qr_payload\": \"string\",\n    \"fiscalised_at\": \"2026-05-28T12:00:00Z\",\n    \"refunded_at\": \"2026-05-28T12:00:00Z\",\n    \"refund_reason\": \"string\",\n    \"idempotency_key\": \"string\",\n    \"split_rules\": [\n      {\n        \"mda\": \"GULU\",\n        \"amount\": 50000,\n        \"destination_account\": \"GULU-COLLECTION-001\",\n        \"destination_account_type\": \"Bank\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Manually confirm a payment intent","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name:confirm","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name:confirm"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Operator-side confirmation \u2014 used when the aggregator's webhook\nis delayed or missing (e.g. cash, or a manual aggregator backfill).\nTransitions the intent to `Confirmed` + the assessment's\n`payment_status` to `Confirmed`, fires the success-side hooks\n(receipt, EFRIS, downstream webhook).\n","auth":{"type":"noauth"}},"response":[{"name":"Intent confirmed.","originalRequest":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name:confirm","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name:confirm"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Operator-side confirmation \u2014 used when the aggregator's webhook\nis delayed or missing (e.g. cash, or a manual aggregator backfill).\nTransitions the intent to `Confirmed` + the assessment's\n`payment_status` to `Confirmed`, fires the success-side hooks\n(receipt, EFRIS, downstream webhook).\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"assessment\": \"string\",\n    \"channel\": \"MTN MoMo\",\n    \"status\": \"Pending\",\n    \"currency\": \"string\",\n    \"amount\": 0,\n    \"citizen_msisdn\": \"string\",\n    \"aggregator\": \"string\",\n    \"aggregator_reference\": \"string\",\n    \"sent_at\": \"2026-05-28T12:00:00Z\",\n    \"confirmed_at\": \"2026-05-28T12:00:00Z\",\n    \"failed_at\": \"2026-05-28T12:00:00Z\",\n    \"failure_reason\": \"string\",\n    \"fiscal_status\": \"Not Fiscalised\",\n    \"fdn\": \"string\",\n    \"fiscal_verification_code\": \"string\",\n    \"fiscal_qr_payload\": \"string\",\n    \"fiscalised_at\": \"2026-05-28T12:00:00Z\",\n    \"refunded_at\": \"2026-05-28T12:00:00Z\",\n    \"refund_reason\": \"string\",\n    \"idempotency_key\": \"string\",\n    \"split_rules\": [\n      {\n        \"mda\": \"GULU\",\n        \"amount\": 50000,\n        \"destination_account\": \"GULU-COLLECTION-001\",\n        \"destination_account_type\": \"Bank\",\n        \"notes\": \"string\"\n      }\n    ],\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Poll the aggregator for current payment status","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name/live-status","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name","live-status"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Calls the aggregator's status endpoint live (no caching) and\nreturns the latest known state. The kiosk polls this every\nfew seconds while a payment is in flight to drive the spinner.\n","auth":{"type":"noauth"}},"response":[{"name":"Live status snapshot.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name/live-status","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name","live-status"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Calls the aggregator's status endpoint live (no caching) and\nreturns the latest known state. The kiosk polls this every\nfew seconds while a payment is in flight to drive the spinner.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"status\": \"string\",\n    \"aggregator_reference\": \"string\",\n    \"raw\": {}\n  }\n}"}]},{"name":"Get the full event trace for a payment intent","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name/trace","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name","trace"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Returns the chronological list of `Payment Event` rows attached\nto the intent \u2014 adapter dispatch, webhook receipt, status flips,\nEFRIS issuance, downstream webhook to the integrator. Useful for\ndebugging stuck payments.\n","auth":{"type":"noauth"}},"response":[{"name":"Event trace, newest-last.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/payment-intents/:name/trace","host":["{{baseUrl}}"],"path":["v1","work","payment-intents",":name","trace"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Returns the chronological list of `Payment Event` rows attached\nto the intent \u2014 adapter dispatch, webhook receipt, status flips,\nEFRIS issuance, downstream webhook to the integrator. Useful for\ndebugging stuck payments.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"payment_intent\": \"string\",\n      \"mda\": \"string\",\n      \"amount\": 0,\n      \"currency\": \"string\",\n      \"aggregator\": \"string\",\n      \"aggregator_txn_id\": \"string\",\n      \"destination_account\": \"string\",\n      \"received_at\": \"2026-05-28T12:00:00Z\",\n      \"proof_payload\": \"string\",\n      \"linked_journal_entry\": \"string\"\n    }\n  ]\n}"}]}]},{"name":"Counter Stations \u2014 Supervisor","description":"Variance management surface \u2014 approve, reject, or escalate a\nclosed shift's cash variance. Requires `Sente Rails Supervisor`\n(or admin) role on top of the platform session.\n","item":[{"name":"Supervisor variance queue + counters","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/dashboard?mda=string","host":["{{baseUrl}}"],"path":["v1","work","supervisor","dashboard"],"query":[{"key":"mda","value":"string","description":"Optional scope to one MDA. Omit for fleet-wide view."}]},"description":"Returns the pending-variance queue plus daily counters surfaced\non `/work/supervisor`. Requires the `Sente Rails Supervisor` or\nadmin role on top of the session.\n\nThe exact shape is intentionally open \u2014 downstream code should\ntreat unknown fields as forward-compatible additions.\n","auth":{"type":"noauth"}},"response":[{"name":"Dashboard snapshot.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/dashboard?mda=string","host":["{{baseUrl}}"],"path":["v1","work","supervisor","dashboard"],"query":[{"key":"mda","value":"string","description":"Optional scope to one MDA. Omit for fleet-wide view."}]},"description":"Returns the pending-variance queue plus daily counters surfaced\non `/work/supervisor`. Requires the `Sente Rails Supervisor` or\nadmin role on top of the session.\n\nThe exact shape is intentionally open \u2014 downstream code should\ntreat unknown fields as forward-compatible additions.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"pending_variance\": 0,\n    \"approved_today\": 0,\n    \"escalated_today\": 0,\n    \"queue\": [\n      {\n        \"shift\": \"string\",\n        \"clerk\": \"string\",\n        \"mda\": \"string\",\n        \"variance\": 0,\n        \"closed_at\": \"2026-05-28T12:00:00Z\"\n      }\n    ]\n  }\n}"}]},{"name":"Approve a closed shift's variance","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/shifts/:name:approve-variance","host":["{{baseUrl}}"],"path":["v1","work","supervisor","shifts",":name:approve-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Marks the shift's variance as approved (recorded under the\nsupervisor's user id). Closes the variance loop without\nfurther action \u2014 used when the difference is within tolerance\nor has a documented explanation.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"Counted bank deposit matches; difference is small-change loss.\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Variance approved.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/shifts/:name:approve-variance","host":["{{baseUrl}}"],"path":["v1","work","supervisor","shifts",":name:approve-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Marks the shift's variance as approved (recorded under the\nsupervisor's user id). Closes the variance loop without\nfurther action \u2014 used when the difference is within tolerance\nor has a documented explanation.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"Counted bank deposit matches; difference is small-change loss.\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Reject a closed shift's variance","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/shifts/:name:reject-variance","host":["{{baseUrl}}"],"path":["v1","work","supervisor","shifts",":name:reject-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Records a rejected variance \u2014 the clerk owes a re-count or an\nexplanation. The shift stays in `Variance` state until the\nclerk resolves and re-submits, or the supervisor escalates.\nRecommended `note` explains what the clerk needs to do next.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"Counted bank deposit matches; difference is small-change loss.\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Variance rejected.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/shifts/:name:reject-variance","host":["{{baseUrl}}"],"path":["v1","work","supervisor","shifts",":name:reject-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Records a rejected variance \u2014 the clerk owes a re-count or an\nexplanation. The shift stays in `Variance` state until the\nclerk resolves and re-submits, or the supervisor escalates.\nRecommended `note` explains what the clerk needs to do next.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"Counted bank deposit matches; difference is small-change loss.\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]},{"name":"Escalate a variance to MDA finance","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/shifts/:name:escalate-variance","host":["{{baseUrl}}"],"path":["v1","work","supervisor","shifts",":name:escalate-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Flags the shift's variance for review beyond the supervisor \u2014\ncreates a row in the escalation queue that MDA finance / audit\nsees. Used for material discrepancies or repeated issues with\nthe same clerk.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"Counted bank deposit matches; difference is small-change loss.\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Variance escalated.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/work/supervisor/shifts/:name:escalate-variance","host":["{{baseUrl}}"],"path":["v1","work","supervisor","shifts",":name:escalate-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Flags the shift's variance for review beyond the supervisor \u2014\ncreates a row in the escalation queue that MDA finance / audit\nsees. Used for material discrepancies or repeated issues with\nthe same clerk.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"Counted bank deposit matches; difference is small-change loss.\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"clerk\": \"string\",\n    \"mda\": \"string\",\n    \"counter_label\": \"string\",\n    \"status\": \"Open\",\n    \"opened_at\": \"2026-05-28T12:00:00Z\",\n    \"closed_at\": \"2026-05-28T12:00:00Z\",\n    \"opening_float\": 0,\n    \"assessment_count\": 0,\n    \"total_collected\": 0,\n    \"cash_collected\": 0,\n    \"momo_collected\": 0,\n    \"airtel_collected\": 0,\n    \"cash_expected\": 0,\n    \"cash_counted\": 0,\n    \"cash_variance\": 0,\n    \"currency\": \"string\",\n    \"opening_notes\": \"string\",\n    \"card_collected\": 0,\n    \"bank_collected\": 0,\n    \"voucher_collected\": 0,\n    \"variance_reason\": \"string\",\n    \"closing_notes\": \"string\"\n  }\n}"}]}]},{"name":"Ops Console","description":"Sente Rails staff administration \u2014 MDAs + services catalog\nmanagement, integrator + key lifecycle, audit log, fleet-wide\nshifts view, adapter health, system status. Auth'd via the platform\n`sid` cookie. Read endpoints accept `Sente Rails Admin`,\n`System Manager`, or `Sente Rails OAG` roles; write endpoints\n(PATCH / suspend / reactivate / revoke) require Admin or\nSystem Manager.\n","item":[{"name":"Return the signed-in operator's identity + role flags","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/whoami","host":["{{baseUrl}}"],"path":["v1","ops","whoami"]},"description":"Allow-guest probe \u2014 returns `{authenticated: false}` when no\nvalid platform session is present. Authenticated callers receive\ntheir user row plus the booleans the workbench uses to render\nchrome: `has_ops_access`, `can_write` (admin-only writes),\n`can_read_oversight`.\n","auth":{"type":"noauth"}},"response":[{"name":"Authentication probe result.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/whoami","host":["{{baseUrl}}"],"path":["v1","ops","whoami"]},"description":"Allow-guest probe \u2014 returns `{authenticated: false}` when no\nvalid platform session is present. Authenticated callers receive\ntheir user row plus the booleans the workbench uses to render\nchrome: `has_ops_access`, `can_write` (admin-only writes),\n`can_read_oversight`.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"authenticated\": true,\n    \"user\": {\n      \"name\": \"admin@sente-rails.space\",\n      \"full_name\": \"Asiimwe K.\",\n      \"email\": \"you@example.com\"\n    },\n    \"roles\": [\n      \"Sente Rails Admin\",\n      \"System Manager\"\n    ],\n    \"has_ops_access\": false,\n    \"can_write\": false,\n    \"can_read_oversight\": false\n  }\n}"}]},{"name":"System health snapshot","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/system","host":["{{baseUrl}}"],"path":["v1","ops","system"]},"description":"Returns a snapshot used by the Ops Console homepage: audit-log\ntable state (row count + oldest/newest timestamps), the last\ndaily key-expiry sweep, the live/sandbox adapter tally, schema\ncounters (integrators / MDAs / services / keys), and the deployed\nbuild's git head. The exact field set is treated as forward-\ncompatible \u2014 unknown fields should be ignored.\n","auth":{"type":"noauth"}},"response":[{"name":"Health snapshot.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/system","host":["{{baseUrl}}"],"path":["v1","ops","system"]},"description":"Returns a snapshot used by the Ops Console homepage: audit-log\ntable state (row count + oldest/newest timestamps), the last\ndaily key-expiry sweep, the live/sandbox adapter tally, schema\ncounters (integrators / MDAs / services / keys), and the deployed\nbuild's git head. The exact field set is treated as forward-\ncompatible \u2014 unknown fields should be ignored.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"audit_log\": {\n      \"row_count\": 0,\n      \"oldest_ts\": \"2026-05-28T12:00:00Z\",\n      \"newest_ts\": \"2026-05-28T12:00:00Z\"\n    },\n    \"scheduler\": {\n      \"last_daily_expiry_sweep\": \"2026-05-28T12:00:00Z\"\n    },\n    \"adapters\": {\n      \"live\": 0,\n      \"stub\": 0\n    },\n    \"counts\": {\n      \"integrators\": 0,\n      \"mdas\": 0,\n      \"services\": 0,\n      \"keys_active\": 0,\n      \"keys_total\": 0\n    },\n    \"build\": {\n      \"git_head\": \"string\"\n    }\n  }\n}"}]},{"name":"List every MDA (including inactive)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/mdas?status=Active","host":["{{baseUrl}}"],"path":["v1","ops","mdas"],"query":[{"key":"status","value":"Active","description":""}]},"description":"Returns up to 500 MDA rows. Unlike the public `/v1/mdas` which\nfilters to `Active` by default, this includes pending/inactive\nrows so the console can render the full management view.\n","auth":{"type":"noauth"}},"response":[{"name":"All MDAs matching the filter (or all when none given).","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/mdas?status=Active","host":["{{baseUrl}}"],"path":["v1","ops","mdas"],"query":[{"key":"status","value":"Active","description":""}]},"description":"Returns up to 500 MDA rows. Unlike the public `/v1/mdas` which\nfilters to `Active` by default, this includes pending/inactive\nrows so the console can render the full management view.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"GULU\",\n      \"short_code\": \"GULU\",\n      \"full_name\": \"Gulu City Council\",\n      \"mda_type\": \"City Authority\",\n      \"status\": \"Active\",\n      \"country\": \"UG\",\n      \"mode\": \"A\",\n      \"sector\": \"string\",\n      \"integration_status\": \"Live\",\n      \"target_endpoint_count\": 0,\n      \"endpoint_count\": 0,\n      \"display_endpoint_count\": 0,\n      \"contact_email\": \"you@example.com\",\n      \"contact_phone\": \"string\"\n    }\n  ]\n}"}]},{"name":"Get a single MDA's full record","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/mdas/:name","host":["{{baseUrl}}"],"path":["v1","ops","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]},"description":"Returns the unredacted MDA row \u2014 including the integration config\nthe public read hides: `treasury_account`, `integration_endpoint`,\n`push_webhook_url`, `api_credentials_ref`, `oversight_scopes`, and\nthe `contact_email` / `contact_phone` roster.\n","auth":{"type":"noauth"}},"response":[{"name":"The MDA.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/mdas/:name","host":["{{baseUrl}}"],"path":["v1","ops","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]},"description":"Returns the unredacted MDA row \u2014 including the integration config\nthe public read hides: `treasury_account`, `integration_endpoint`,\n`push_webhook_url`, `api_credentials_ref`, `oversight_scopes`, and\nthe `contact_email` / `contact_phone` roster.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"GULU\",\n    \"short_code\": \"GULU\",\n    \"full_name\": \"Gulu City Council\",\n    \"mda_type\": \"string\",\n    \"status\": \"Active\",\n    \"mode\": \"A\",\n    \"country\": \"UG\",\n    \"sector\": \"string\",\n    \"integration_status\": \"Live\",\n    \"parent_authority\": \"string\",\n    \"treasury_account\": \"GULU-TREASURY-001\",\n    \"integration_endpoint\": \"string\",\n    \"push_webhook_url\": \"string\",\n    \"api_credentials_ref\": \"string\",\n    \"oversight_scopes\": \"string\",\n    \"contact_email\": \"you@example.com\",\n    \"contact_phone\": \"string\",\n    \"target_endpoint_count\": 0\n  }\n}"}]},{"name":"Update MDA configuration","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/mdas/:name","host":["{{baseUrl}}"],"path":["v1","ops","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]},"description":"Writes the allowed subset of MDA fields. Requires `Sente Rails\nAdmin` or `System Manager` role \u2014 `Sente Rails OAG` cannot\nwrite. Touches the audit log with the caller's user id.\n","body":{"mode":"raw","raw":"{\n  \"full_name\": \"string\",\n  \"mda_type\": \"string\",\n  \"mode\": \"A\",\n  \"status\": \"Active\",\n  \"parent_authority\": \"string\",\n  \"treasury_account\": \"string\",\n  \"sector\": \"string\",\n  \"integration_status\": \"Live\",\n  \"target_endpoint_count\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Updated MDA row.","originalRequest":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/mdas/:name","host":["{{baseUrl}}"],"path":["v1","ops","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]},"description":"Writes the allowed subset of MDA fields. Requires `Sente Rails\nAdmin` or `System Manager` role \u2014 `Sente Rails OAG` cannot\nwrite. Touches the audit log with the caller's user id.\n","body":{"mode":"raw","raw":"{\n  \"full_name\": \"string\",\n  \"mda_type\": \"string\",\n  \"mode\": \"A\",\n  \"status\": \"Active\",\n  \"parent_authority\": \"string\",\n  \"treasury_account\": \"string\",\n  \"sector\": \"string\",\n  \"integration_status\": \"Live\",\n  \"target_endpoint_count\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"GULU\",\n    \"short_code\": \"GULU\",\n    \"full_name\": \"Gulu City Council\",\n    \"mda_type\": \"string\",\n    \"status\": \"Active\",\n    \"mode\": \"A\",\n    \"country\": \"UG\",\n    \"sector\": \"string\",\n    \"integration_status\": \"Live\",\n    \"parent_authority\": \"string\",\n    \"treasury_account\": \"GULU-TREASURY-001\",\n    \"integration_endpoint\": \"string\",\n    \"push_webhook_url\": \"string\",\n    \"api_credentials_ref\": \"string\",\n    \"oversight_scopes\": \"string\",\n    \"contact_email\": \"you@example.com\",\n    \"contact_phone\": \"string\",\n    \"target_endpoint_count\": 0\n  }\n}"}]},{"name":"Alias for PATCH /v1/ops/mdas/{name}","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/mdas/:name","host":["{{baseUrl}}"],"path":["v1","ops","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]},"description":"POST shape for clients that can't issue PATCH. Same body, same\nbehavior, same role gate.\n","body":{"mode":"raw","raw":"{\n  \"full_name\": \"string\",\n  \"mda_type\": \"string\",\n  \"mode\": \"A\",\n  \"status\": \"Active\",\n  \"parent_authority\": \"string\",\n  \"treasury_account\": \"string\",\n  \"sector\": \"string\",\n  \"integration_status\": \"Live\",\n  \"target_endpoint_count\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Updated MDA row.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/mdas/:name","host":["{{baseUrl}}"],"path":["v1","ops","mdas",":name"],"variable":[{"key":"name","value":"GULU","description":"[required]"}]},"description":"POST shape for clients that can't issue PATCH. Same body, same\nbehavior, same role gate.\n","body":{"mode":"raw","raw":"{\n  \"full_name\": \"string\",\n  \"mda_type\": \"string\",\n  \"mode\": \"A\",\n  \"status\": \"Active\",\n  \"parent_authority\": \"string\",\n  \"treasury_account\": \"string\",\n  \"sector\": \"string\",\n  \"integration_status\": \"Live\",\n  \"target_endpoint_count\": 0\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"GULU\",\n    \"short_code\": \"GULU\",\n    \"full_name\": \"Gulu City Council\",\n    \"mda_type\": \"string\",\n    \"status\": \"Active\",\n    \"mode\": \"A\",\n    \"country\": \"UG\",\n    \"sector\": \"string\",\n    \"integration_status\": \"Live\",\n    \"parent_authority\": \"string\",\n    \"treasury_account\": \"GULU-TREASURY-001\",\n    \"integration_endpoint\": \"string\",\n    \"push_webhook_url\": \"string\",\n    \"api_credentials_ref\": \"string\",\n    \"oversight_scopes\": \"string\",\n    \"contact_email\": \"you@example.com\",\n    \"contact_phone\": \"string\",\n    \"target_endpoint_count\": 0\n  }\n}"}]},{"name":"List services across the fleet","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/services?mda=GULU&status=Active","host":["{{baseUrl}}"],"path":["v1","ops","services"],"query":[{"key":"mda","value":"GULU","description":""},{"key":"status","value":"Active","description":""}]},"description":"Admin view over Service rows. Filter by `mda` or `status`.\nIncludes inactive/coming-soon entries that the public catalog\nhides.\n","auth":{"type":"noauth"}},"response":[{"name":"Matching services.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/services?mda=GULU&status=Active","host":["{{baseUrl}}"],"path":["v1","ops","services"],"query":[{"key":"mda","value":"GULU","description":""},{"key":"status","value":"Active","description":""}]},"description":"Admin view over Service rows. Filter by `mda` or `status`.\nIncludes inactive/coming-soon entries that the public catalog\nhides.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"mda\": \"GULU\",\n      \"code\": \"TL-RENEW\",\n      \"service_name\": \"Trading License Renewal\",\n      \"status\": \"Active\",\n      \"sector\": \"Revenue\",\n      \"service_family\": \"Trading Licenses\",\n      \"fee_amount\": 50000,\n      \"fee_currency\": \"UGX\",\n      \"fee_basis\": \"Flat\",\n      \"fee_schedule_ref\": \"Trade Licensing Act 2015 \\u00a712(1)\",\n      \"efris_taxable\": false,\n      \"vat_applicable\": false,\n      \"vat_rate\": 0,\n      \"gl_account_credit\": \"string\",\n      \"linked_item\": \"string\",\n      \"description\": \"string\"\n    }\n  ]\n}"}]},{"name":"Update service configuration","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/services/:name","host":["{{baseUrl}}"],"path":["v1","ops","services",":name"],"variable":[{"key":"name","value":"SVC-2026-000004","description":"[required]"}]},"description":"Writes the allowed subset of Service fields \u2014 service_name,\nsector, service_family, status, fee_amount, fee_currency,\nfee_basis, vat_rate, vat_applicable, efris_taxable. Admin or\nSystem Manager only.\n","body":{"mode":"raw","raw":"{\n  \"service_name\": \"string\",\n  \"sector\": \"string\",\n  \"service_family\": \"string\",\n  \"status\": \"Active\",\n  \"fee_amount\": 0,\n  \"fee_currency\": \"UGX\",\n  \"fee_basis\": \"Flat\",\n  \"vat_applicable\": false,\n  \"vat_rate\": 0,\n  \"efris_taxable\": false\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Updated service.","originalRequest":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/services/:name","host":["{{baseUrl}}"],"path":["v1","ops","services",":name"],"variable":[{"key":"name","value":"SVC-2026-000004","description":"[required]"}]},"description":"Writes the allowed subset of Service fields \u2014 service_name,\nsector, service_family, status, fee_amount, fee_currency,\nfee_basis, vat_rate, vat_applicable, efris_taxable. Admin or\nSystem Manager only.\n","body":{"mode":"raw","raw":"{\n  \"service_name\": \"string\",\n  \"sector\": \"string\",\n  \"service_family\": \"string\",\n  \"status\": \"Active\",\n  \"fee_amount\": 0,\n  \"fee_currency\": \"UGX\",\n  \"fee_basis\": \"Flat\",\n  \"vat_applicable\": false,\n  \"vat_rate\": 0,\n  \"efris_taxable\": false\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"mda\": \"GULU\",\n    \"code\": \"TL-RENEW\",\n    \"service_name\": \"Trading License Renewal\",\n    \"status\": \"Active\",\n    \"sector\": \"Revenue\",\n    \"service_family\": \"Trading Licenses\",\n    \"fee_amount\": 50000,\n    \"fee_currency\": \"UGX\",\n    \"fee_basis\": \"Flat\",\n    \"fee_schedule_ref\": \"Trade Licensing Act 2015 \\u00a712(1)\",\n    \"efris_taxable\": false,\n    \"vat_applicable\": false,\n    \"vat_rate\": 0,\n    \"gl_account_credit\": \"string\",\n    \"linked_item\": \"string\",\n    \"description\": \"string\"\n  }\n}"}]},{"name":"Alias for PATCH /v1/ops/services/{name}","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/services/:name","host":["{{baseUrl}}"],"path":["v1","ops","services",":name"],"variable":[{"key":"name","value":"SVC-2026-000004","description":"[required]"}]},"body":{"mode":"raw","raw":"{\n  \"service_name\": \"string\",\n  \"sector\": \"string\",\n  \"service_family\": \"string\",\n  \"status\": \"Active\",\n  \"fee_amount\": 0,\n  \"fee_currency\": \"UGX\",\n  \"fee_basis\": \"Flat\",\n  \"vat_applicable\": false,\n  \"vat_rate\": 0,\n  \"efris_taxable\": false\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Updated service.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/services/:name","host":["{{baseUrl}}"],"path":["v1","ops","services",":name"],"variable":[{"key":"name","value":"SVC-2026-000004","description":"[required]"}]},"body":{"mode":"raw","raw":"{\n  \"service_name\": \"string\",\n  \"sector\": \"string\",\n  \"service_family\": \"string\",\n  \"status\": \"Active\",\n  \"fee_amount\": 0,\n  \"fee_currency\": \"UGX\",\n  \"fee_basis\": \"Flat\",\n  \"vat_applicable\": false,\n  \"vat_rate\": 0,\n  \"efris_taxable\": false\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"mda\": \"GULU\",\n    \"code\": \"TL-RENEW\",\n    \"service_name\": \"Trading License Renewal\",\n    \"status\": \"Active\",\n    \"sector\": \"Revenue\",\n    \"service_family\": \"Trading Licenses\",\n    \"fee_amount\": 50000,\n    \"fee_currency\": \"UGX\",\n    \"fee_basis\": \"Flat\",\n    \"fee_schedule_ref\": \"Trade Licensing Act 2015 \\u00a712(1)\",\n    \"efris_taxable\": false,\n    \"vat_applicable\": false,\n    \"vat_rate\": 0,\n    \"gl_account_credit\": \"string\",\n    \"linked_item\": \"string\",\n    \"description\": \"string\"\n  }\n}"}]},{"name":"List integrators (admin view)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/integrators?status=PendingEmail&tier=Anonymous&signup_source=string&q=string&limit=200","host":["{{baseUrl}}"],"path":["v1","ops","integrators"],"query":[{"key":"status","value":"PendingEmail","description":""},{"key":"tier","value":"Anonymous","description":""},{"key":"signup_source","value":"string","description":""},{"key":"q","value":"string","description":"Substring search across display_name + contact_email + integrator code."},{"key":"limit","value":"200","description":""}]},"description":"Returns up to 500 integrator rows. Filter by `status`, `tier`,\nor `signup_source`; full-text-ish search via `q` matches against\ndisplay_name, contact_email, and integrator code. Sorted by\ncreation desc.\n","auth":{"type":"noauth"}},"response":[{"name":"Matching integrators.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/integrators?status=PendingEmail&tier=Anonymous&signup_source=string&q=string&limit=200","host":["{{baseUrl}}"],"path":["v1","ops","integrators"],"query":[{"key":"status","value":"PendingEmail","description":""},{"key":"tier","value":"Anonymous","description":""},{"key":"signup_source","value":"string","description":""},{"key":"q","value":"string","description":"Substring search across display_name + contact_email + integrator code."},{"key":"limit","value":"200","description":""}]},"description":"Returns up to 500 integrator rows. Filter by `status`, `tier`,\nor `signup_source`; full-text-ish search via `q` matches against\ndisplay_name, contact_email, and integrator code. Sorted by\ncreation desc.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"ACME-FINTECH-XXXXXX\",\n      \"display_name\": \"string\",\n      \"contact_email\": \"you@example.com\",\n      \"type\": \"MDA\",\n      \"status\": \"PendingEmail\",\n      \"tier\": \"Anonymous\",\n      \"pricing_tier\": \"Free\",\n      \"signup_source\": \"string\",\n      \"email_verified\": false,\n      \"mou_status\": \"string\",\n      \"kyc_status\": \"string\",\n      \"last_login_at\": \"2026-05-28T12:00:00Z\",\n      \"creation\": \"2026-05-28T12:00:00Z\"\n    }\n  ]\n}"}]},{"name":"Get a single integrator's full record","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/integrators/:name","host":["{{baseUrl}}"],"path":["v1","ops","integrators",":name"],"variable":[{"key":"name","value":"ASAT-LABS","description":"[required]"}]},"description":"Returns the integrator row plus live counters (active vs total\nkeys, requests in the last 7 days). Sensitive transient fields\n(`otp_hash`, `session_token_hash`, `login_link_hash`) are\nstripped before serialization \u2014 admins don't need plaintext\nhashes.\n","auth":{"type":"noauth"}},"response":[{"name":"The integrator with counters.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/integrators/:name","host":["{{baseUrl}}"],"path":["v1","ops","integrators",":name"],"variable":[{"key":"name","value":"ASAT-LABS","description":"[required]"}]},"description":"Returns the integrator row plus live counters (active vs total\nkeys, requests in the last 7 days). Sensitive transient fields\n(`otp_hash`, `session_token_hash`, `login_link_hash`) are\nstripped before serialization \u2014 admins don't need plaintext\nhashes.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"ACME-FINTECH-XXXXXX\",\n    \"display_name\": \"Acme Fintech\",\n    \"type\": \"Developer\",\n    \"tier\": \"Registered\",\n    \"pricing_tier\": \"Free\",\n    \"status\": \"PendingEmail\",\n    \"contact_email\": \"you@example.com\",\n    \"technical_lead_user\": \"string\",\n    \"webhook_endpoint\": \"https://example.com\",\n    \"mou_status\": \"string\",\n    \"kyc_status\": \"string\",\n    \"ip_allowlist\": \"string\",\n    \"tos_accepted_on\": \"2026-05-28T12:00:00Z\",\n    \"tos_accepted_version\": \"string\",\n    \"signup_source\": \"string\",\n    \"email_verified\": false,\n    \"last_login_at\": \"2026-05-28T12:00:00Z\",\n    \"anticipated_volume_daily\": 5000,\n    \"anticipated_volume_monthly\": 150000,\n    \"keys\": {\n      \"total\": 0,\n      \"active\": 0\n    },\n    \"requests_last_7d\": 0,\n    \"creation\": \"2026-05-28T12:00:00Z\",\n    \"modified\": \"2026-05-28T12:00:00Z\",\n    \"notes\": \"string\"\n  }\n}"}]},{"name":"Suspend an integrator","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/integrators/:name:suspend","host":["{{baseUrl}}"],"path":["v1","ops","integrators",":name:suspend"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Flips status to `Suspended`. All keys owned by the integrator\nimmediately fail with 401 (the auth middleware checks integrator\nstatus on every request, not just key status). Admin or System\nManager only. `reason` is required and recorded against the\nintegrator's audit notes.\n\nReturns 409 if the integrator is already Suspended.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Integrator suspended.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/integrators/:name:suspend","host":["{{baseUrl}}"],"path":["v1","ops","integrators",":name:suspend"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Flips status to `Suspended`. All keys owned by the integrator\nimmediately fail with 401 (the auth middleware checks integrator\nstatus on every request, not just key status). Admin or System\nManager only. `reason` is required and recorded against the\nintegrator's audit notes.\n\nReturns 409 if the integrator is already Suspended.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"status\": \"Suspended\"\n  }\n}"}]},{"name":"Reactivate a suspended integrator","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/integrators/:name:reactivate","host":["{{baseUrl}}"],"path":["v1","ops","integrators",":name:reactivate"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Restores status to `Active`. Existing keys resume accepting\ntraffic. `reason` is required.\n\nReturns 409 if the integrator is not currently suspended.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Integrator reactivated.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/integrators/:name:reactivate","host":["{{baseUrl}}"],"path":["v1","ops","integrators",":name:reactivate"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Restores status to `Active`. Existing keys resume accepting\ntraffic. `reason` is required.\n\nReturns 409 if the integrator is not currently suspended.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"status\": \"Suspended\"\n  }\n}"}]},{"name":"List API keys fleet-wide","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/keys?integrator=string&environment=sandbox&status=active&limit=200","host":["{{baseUrl}}"],"path":["v1","ops","keys"],"query":[{"key":"integrator","value":"string","description":""},{"key":"environment","value":"sandbox","description":""},{"key":"status","value":"active","description":""},{"key":"limit","value":"200","description":""}]},"description":"Returns API keys across all integrators. Filter by integrator,\nenvironment, or status. Plaintext is never returned.\n","auth":{"type":"noauth"}},"response":[{"name":"Matching keys.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/keys?integrator=string&environment=sandbox&status=active&limit=200","host":["{{baseUrl}}"],"path":["v1","ops","keys"],"query":[{"key":"integrator","value":"string","description":""},{"key":"environment","value":"sandbox","description":""},{"key":"status","value":"active","description":""},{"key":"limit","value":"200","description":""}]},"description":"Returns API keys across all integrators. Filter by integrator,\nenvironment, or status. Plaintext is never returned.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"KEY-2026-000002\",\n      \"prefix\": \"sk_sandbox_2026\",\n      \"last4\": \"kKr2\",\n      \"environment\": \"sandbox\",\n      \"key_type\": \"sk\",\n      \"status\": \"active\",\n      \"scopes\": [\n        \"citizens.read\",\n        \"citizens.write\",\n        \"assessments.write\",\n        \"payment-intents.write\"\n      ],\n      \"created_at\": \"2026-05-28T12:00:00Z\",\n      \"expires_at\": \"2026-05-28T12:00:00Z\",\n      \"last_used_at\": \"2026-05-28T12:00:00Z\",\n      \"last_used_ip\": \"string\",\n      \"usage_count\": 0,\n      \"revoked_at\": \"2026-05-28T12:00:00Z\",\n      \"revoked_by\": \"string\",\n      \"revoked_reason\": \"string\",\n      \"rolling_until\": \"2026-05-28T12:00:00Z\",\n      \"rolled_to\": \"string\",\n      \"description\": \"string\",\n      \"integrator\": \"ACME-FINTECH-XXXXXX\"\n    }\n  ]\n}"}]},{"name":"Force-revoke an API key","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/keys/:name:revoke","host":["{{baseUrl}}"],"path":["v1","ops","keys",":name:revoke"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Admin-issued revoke. Behaves identically to the integrator's\nown `/v1/me/keys/{name}:revoke` but bypasses the ownership\ncheck \u2014 used when an integrator can't (or won't) revoke a\nleaked key themselves. `reason` is required.\n\nReturns 409 if the key is already revoked.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Key revoked.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/ops/keys/:name:revoke","host":["{{baseUrl}}"],"path":["v1","ops","keys",":name:revoke"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Admin-issued revoke. Behaves identically to the integrator's\nown `/v1/me/keys/{name}:revoke` but bypasses the ownership\ncheck \u2014 used when an integrator can't (or won't) revoke a\nleaked key themselves. `reason` is required.\n\nReturns 409 if the key is already revoked.\n","body":{"mode":"raw","raw":"{\n  \"reason\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"string\",\n    \"status\": \"Suspended\"\n  }\n}"}]},{"name":"Query the audit log fleet-wide","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/audit?integrator=string&event=api.auth.granted&endpoint=string&min_status=0&since=2026-05-28T12:00:00Z&limit=100","host":["{{baseUrl}}"],"path":["v1","ops","audit"],"query":[{"key":"integrator","value":"string","description":""},{"key":"event","value":"api.auth.granted","description":""},{"key":"endpoint","value":"string","description":"Substring match on the endpoint path."},{"key":"min_status","value":"0","description":""},{"key":"since","value":"2026-05-28T12:00:00Z","description":"ISO datetime \u2014 only rows newer than this are returned."},{"key":"limit","value":"100","description":""}]},"description":"Same underlying `Sente API Audit Log` doctype as `/v1/me/logs`,\nbut unscoped \u2014 admins see every integrator's row. Filter by\n`integrator`, `event`, `min_status`, or `since` (ISO datetime).\n","auth":{"type":"noauth"}},"response":[{"name":"Matching audit rows.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/audit?integrator=string&event=api.auth.granted&endpoint=string&min_status=0&since=2026-05-28T12:00:00Z&limit=100","host":["{{baseUrl}}"],"path":["v1","ops","audit"],"query":[{"key":"integrator","value":"string","description":""},{"key":"event","value":"api.auth.granted","description":""},{"key":"endpoint","value":"string","description":"Substring match on the endpoint path."},{"key":"min_status","value":"0","description":""},{"key":"since","value":"2026-05-28T12:00:00Z","description":"ISO datetime \u2014 only rows newer than this are returned."},{"key":"limit","value":"100","description":""}]},"description":"Same underlying `Sente API Audit Log` doctype as `/v1/me/logs`,\nbut unscoped \u2014 admins see every integrator's row. Filter by\n`integrator`, `event`, `min_status`, or `since` (ISO datetime).\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"ts\": \"2026-05-28T12:00:00Z\",\n      \"event\": \"api.auth.granted\",\n      \"request_id\": \"00000000-0000-0000-0000-000000000000\",\n      \"http_method\": \"POST\",\n      \"endpoint\": \"/v1/payment-intents\",\n      \"http_status\": 200,\n      \"error_code\": \"string\",\n      \"api_key\": \"string\",\n      \"source_ip\": \"string\",\n      \"required_scopes\": [\n        \"string\"\n      ],\n      \"granted_scopes\": [\n        \"string\"\n      ],\n      \"latency_ms\": 47\n    }\n  ]\n}"}]},{"name":"Fleet-wide shifts list","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/shifts?status=Open&mda=string&limit=100","host":["{{baseUrl}}"],"path":["v1","ops","shifts"],"query":[{"key":"status","value":"Open","description":""},{"key":"mda","value":"string","description":""},{"key":"limit","value":"100","description":""}]},"description":"All Counter Shifts across MDAs and clerks. Filter by `status`\nor `mda`. Mirrors the supervisor surface but unscoped to the\nwhole fleet \u2014 used by admins to spot operational anomalies.\n","auth":{"type":"noauth"}},"response":[{"name":"Matching shifts.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/shifts?status=Open&mda=string&limit=100","host":["{{baseUrl}}"],"path":["v1","ops","shifts"],"query":[{"key":"status","value":"Open","description":""},{"key":"mda","value":"string","description":""},{"key":"limit","value":"100","description":""}]},"description":"All Counter Shifts across MDAs and clerks. Filter by `status`\nor `mda`. Mirrors the supervisor surface but unscoped to the\nwhole fleet \u2014 used by admins to spot operational anomalies.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"clerk\": \"string\",\n      \"mda\": \"string\",\n      \"counter_label\": \"string\",\n      \"status\": \"Open\",\n      \"opened_at\": \"2026-05-28T12:00:00Z\",\n      \"closed_at\": \"2026-05-28T12:00:00Z\",\n      \"opening_float\": 0,\n      \"assessment_count\": 0,\n      \"total_collected\": 0,\n      \"cash_collected\": 0,\n      \"momo_collected\": 0,\n      \"airtel_collected\": 0,\n      \"cash_expected\": 0,\n      \"cash_counted\": 0,\n      \"cash_variance\": 0,\n      \"currency\": \"string\",\n      \"opening_notes\": \"string\",\n      \"card_collected\": 0,\n      \"bank_collected\": 0,\n      \"voucher_collected\": 0,\n      \"variance_reason\": \"string\",\n      \"closing_notes\": \"string\"\n    }\n  ]\n}"}]},{"name":"Adapter registry + status","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/adapters","host":["{{baseUrl}}"],"path":["v1","ops","adapters"]},"description":"Reports the registered adapters (per country, per domain \u2014\nPayment / Identity / Lands / Companies / Fiscal / SMS) along\nwith their STUB-vs-live state, last call latency, and last\nerror if any. Used to spot when an aggregator's sandbox key\nrotated or a partner system went down.\n","auth":{"type":"noauth"}},"response":[{"name":"Adapter registry snapshot.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/adapters","host":["{{baseUrl}}"],"path":["v1","ops","adapters"]},"description":"Reports the registered adapters (per country, per domain \u2014\nPayment / Identity / Lands / Companies / Fiscal / SMS) along\nwith their STUB-vs-live state, last call latency, and last\nerror if any. Used to spot when an aggregator's sandbox key\nrotated or a partner system went down.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"UG\": {\n      \"identity\": {\n        \"status\": \"sandbox\"\n      },\n      \"fiscal\": {\n        \"status\": \"sandbox\"\n      },\n      \"payment\": [\n        {\n          \"status\": \"live\",\n          \"channels\": [\n            \"MTN MoMo\"\n          ]\n        },\n        {\n          \"status\": \"sandbox\",\n          \"channels\": [\n            \"Airtel Money\"\n          ]\n        }\n      ]\n    }\n  }\n}"}]}]},{"name":"Ops Console \u2014 Oversight","description":"Aggregated / anomaly views for oversight bodies (OAG, MoFPED,\nUBOS). Read-only. Role-gated to `Sente Rails OAG` and admins.\nThese power the OAG dashboards that surface anomaly flags,\npayment-event traces, citizen-consent timelines, and fleet\nstatistics for accountability oversight.\n","item":[{"name":"Per-MDA + fleet aggregate counters","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/aggregates","host":["{{baseUrl}}"],"path":["v1","ops","oversight","aggregates"]},"description":"Returns `{by_mda: [{mda, payments_confirmed, total_amount, ...}],\ntotals: {...}}`. Powers the OAG dashboard's revenue overview.\n","auth":{"type":"noauth"}},"response":[{"name":"Aggregate snapshot.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/aggregates","host":["{{baseUrl}}"],"path":["v1","ops","oversight","aggregates"]},"description":"Returns `{by_mda: [{mda, payments_confirmed, total_amount, ...}],\ntotals: {...}}`. Powers the OAG dashboard's revenue overview.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"by_mda\": [\n      {\n        \"mda\": \"GULU\",\n        \"total_amount\": 0,\n        \"event_count\": 0\n      }\n    ],\n    \"totals\": {\n      \"window_days\": 30,\n      \"total_amount\": 0,\n      \"event_count\": 0\n    }\n  }\n}"}]},{"name":"Recent anomaly-flagged rows","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/anomaly-flags?limit=100","host":["{{baseUrl}}"],"path":["v1","ops","oversight","anomaly-flags"],"query":[{"key":"limit","value":"100","description":""}]},"description":"Returns the latest entries from the `Anomaly Flag` doctype \u2014\nrows where one of the three detectors fired (large cash, large\namount, velocity spike). Newest first.\n","auth":{"type":"noauth"}},"response":[{"name":"Anomaly flag rows.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/anomaly-flags?limit=100","host":["{{baseUrl}}"],"path":["v1","ops","oversight","anomaly-flags"],"query":[{"key":"limit","value":"100","description":""}]},"description":"Returns the latest entries from the `Anomaly Flag` doctype \u2014\nrows where one of the three detectors fired (large cash, large\namount, velocity spike). Newest first.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"flag_type\": \"Cash Variance\",\n      \"severity\": \"Low\",\n      \"status\": \"Open\",\n      \"flagged_at\": \"2026-05-28T12:00:00Z\",\n      \"reference_doctype\": \"string\",\n      \"reference_name\": \"string\",\n      \"detection_rule\": \"string\",\n      \"signal_value\": 0,\n      \"threshold\": 0,\n      \"description\": \"string\",\n      \"flagged_by\": \"string\",\n      \"assigned_to\": \"string\",\n      \"resolved_at\": \"2026-05-28T12:00:00Z\"\n    }\n  ]\n}"}]},{"name":"Recent payment events (cross-MDA)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/payment-events?limit=100","host":["{{baseUrl}}"],"path":["v1","ops","oversight","payment-events"],"query":[{"key":"limit","value":"100","description":""}]},"description":"Returns the latest entries from the `Payment Event` doctype \u2014\nthe immutable trail of every aggregator callback + status\nflip + EFRIS issuance. Used by OAG to spot stuck or anomalous\npayment flows.\n","auth":{"type":"noauth"}},"response":[{"name":"Payment event rows.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/payment-events?limit=100","host":["{{baseUrl}}"],"path":["v1","ops","oversight","payment-events"],"query":[{"key":"limit","value":"100","description":""}]},"description":"Returns the latest entries from the `Payment Event` doctype \u2014\nthe immutable trail of every aggregator callback + status\nflip + EFRIS issuance. Used by OAG to spot stuck or anomalous\npayment flows.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"payment_intent\": \"string\",\n      \"mda\": \"string\",\n      \"amount\": 0,\n      \"currency\": \"string\",\n      \"aggregator\": \"string\",\n      \"aggregator_txn_id\": \"string\",\n      \"destination_account\": \"string\",\n      \"received_at\": \"2026-05-28T12:00:00Z\",\n      \"proof_payload\": \"string\",\n      \"linked_journal_entry\": \"string\"\n    }\n  ]\n}"}]},{"name":"Recent citizen-consent events","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/citizen-consent?limit=100","host":["{{baseUrl}}"],"path":["v1","ops","oversight","citizen-consent"],"query":[{"key":"limit","value":"100","description":""}]},"description":"Returns rows from the `Citizen Consent Event` doctype \u2014 the\nproof-of-consent trail for citizen-data access. Each MDA's\naccess is logged when a clerk pulls a citizen record via NIRA;\nOAG uses this for compliance review.\n","auth":{"type":"noauth"}},"response":[{"name":"Consent event rows.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/citizen-consent?limit=100","host":["{{baseUrl}}"],"path":["v1","ops","oversight","citizen-consent"],"query":[{"key":"limit","value":"100","description":""}]},"description":"Returns rows from the `Citizen Consent Event` doctype \u2014 the\nproof-of-consent trail for citizen-data access. Each MDA's\naccess is logged when a clerk pulls a citizen record via NIRA;\nOAG uses this for compliance review.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"string\",\n      \"citizen\": \"string\",\n      \"mda\": \"string\",\n      \"purpose\": \"string\",\n      \"granted\": false,\n      \"granted_at\": \"2026-05-28T12:00:00Z\",\n      \"expiry_at\": \"2026-05-28T12:00:00Z\",\n      \"revoked_at\": \"2026-05-28T12:00:00Z\",\n      \"evidence_type\": \"string\",\n      \"captured_by\": \"string\"\n    }\n  ]\n}"}]},{"name":"Fleet statistics snapshot","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/statistics","host":["{{baseUrl}}"],"path":["v1","ops","oversight","statistics"]},"description":"High-level counters used on the OAG home \u2014 total MDAs / active\nservices / clerks / shifts / settled volume / outstanding\nvariance \u2014 at this moment. Treat unknown fields as forward-\ncompatible additions.\n","auth":{"type":"noauth"}},"response":[{"name":"Statistics snapshot.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/ops/oversight/statistics","host":["{{baseUrl}}"],"path":["v1","ops","oversight","statistics"]},"description":"High-level counters used on the OAG home \u2014 total MDAs / active\nservices / clerks / shifts / settled volume / outstanding\nvariance \u2014 at this moment. Treat unknown fields as forward-\ncompatible additions.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"citizens_total\": 0,\n    \"integrators_total\": 0,\n    \"integrators_active\": 0,\n    \"mdas_total\": 0,\n    \"services_total\": 0,\n    \"keys_active\": 0,\n    \"audit_total\": 0,\n    \"audit_7d\": 0,\n    \"anomaly_flags_total\": 0,\n    \"anomaly_flags_open\": 0,\n    \"payment_events_total\": 0\n  }\n}"}]}]},{"name":"Webhooks","description":"Inbound provider callbacks. Money-movement aggregators (MoMo,\nAirtel, Pesapal) and fiscal partners (EFRIS) POST here with\nstatus updates on payment intents Sente Rails dispatched.\nSigned and verified \u2014 endpoints reject unsigned callbacks in\nlive mode. Idempotent: replays return 200 with the cached\ndecision.\n","item":[{"name":"MTN Mobile Money callback","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/momo","host":["{{baseUrl}}"],"path":["v1","webhooks","momo"]},"description":"MTN MoMo posts here when a payment intent's status changes\n(Pending \u2192 Sent \u2192 Confirmed / Failed). Sente Rails verifies the\nprovider signature (live mode), looks up the Payment Intent by\nthe aggregator reference, writes a Payment Event row, and\npropagates downstream events to the integrator's webhook.\n\nIdempotent \u2014 duplicate callbacks return `{status: \"IGNORED\",\nreason: \"duplicate\"}` with 200. Unknown references return\n`{status: \"IGNORED\", reason: \"unknown_reference\"}` with 200 so\nretries from the provider eventually stop.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Callback accepted (or idempotently ignored).","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/momo","host":["{{baseUrl}}"],"path":["v1","webhooks","momo"]},"description":"MTN MoMo posts here when a payment intent's status changes\n(Pending \u2192 Sent \u2192 Confirmed / Failed). Sente Rails verifies the\nprovider signature (live mode), looks up the Payment Intent by\nthe aggregator reference, writes a Payment Event row, and\npropagates downstream events to the integrator's webhook.\n\nIdempotent \u2014 duplicate callbacks return `{status: \"IGNORED\",\nreason: \"duplicate\"}` with 200. Unknown references return\n`{status: \"IGNORED\", reason: \"unknown_reference\"}` with 200 so\nretries from the provider eventually stop.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"status\": \"ACCEPTED\",\n    \"reason\": \"duplicate\",\n    \"payment_intent\": \"string\",\n    \"payment_event\": \"string\",\n    \"new_status\": \"Confirmed\"\n  }\n}"}]},{"name":"Airtel Money callback","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/airtel","host":["{{baseUrl}}"],"path":["v1","webhooks","airtel"]},"description":"Airtel Money posts here on payment status updates. Identical\nbehavior to `/v1/webhooks/momo` \u2014 signature-verified,\nidempotent, looks up the Payment Intent by aggregator reference.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Callback accepted (or idempotently ignored).","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/airtel","host":["{{baseUrl}}"],"path":["v1","webhooks","airtel"]},"description":"Airtel Money posts here on payment status updates. Identical\nbehavior to `/v1/webhooks/momo` \u2014 signature-verified,\nidempotent, looks up the Payment Intent by aggregator reference.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"status\": \"ACCEPTED\",\n    \"reason\": \"duplicate\",\n    \"payment_intent\": \"string\",\n    \"payment_event\": \"string\",\n    \"new_status\": \"Confirmed\"\n  }\n}"}]},{"name":"Pesapal callback","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/pesapal","host":["{{baseUrl}}"],"path":["v1","webhooks","pesapal"]},"description":"Pesapal posts here on payment status updates. Pesapal carries\nits own canonical-body signature scheme \u2014 already implemented\non Sente's side. Same idempotent behavior as MoMo / Airtel.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Callback accepted (or idempotently ignored).","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/pesapal","host":["{{baseUrl}}"],"path":["v1","webhooks","pesapal"]},"description":"Pesapal posts here on payment status updates. Pesapal carries\nits own canonical-body signature scheme \u2014 already implemented\non Sente's side. Same idempotent behavior as MoMo / Airtel.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"status\": \"ACCEPTED\",\n    \"reason\": \"duplicate\",\n    \"payment_intent\": \"string\",\n    \"payment_event\": \"string\",\n    \"new_status\": \"Confirmed\"\n  }\n}"}]},{"name":"EFRIS fiscal callback","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/efris","host":["{{baseUrl}}"],"path":["v1","webhooks","efris"]},"description":"EFRIS (Uganda's fiscal device platform) posts here when a\nfiscal receipt has been issued for a payment Sente Rails\ntriggered. The callback carries the FDN (Fiscal Document\nNumber); Sente Rails writes it to the Assessment Line so the\nprinted receipt + downstream webhook to the integrator both\ninclude it.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"response":[{"name":"Callback accepted (or idempotently ignored).","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/webhooks/efris","host":["{{baseUrl}}"],"path":["v1","webhooks","efris"]},"description":"EFRIS (Uganda's fiscal device platform) posts here when a\nfiscal receipt has been issued for a payment Sente Rails\ntriggered. The callback carries the FDN (Fiscal Document\nNumber); Sente Rails writes it to the Assessment Line so the\nprinted receipt + downstream webhook to the integrator both\ninclude it.\n","body":{"mode":"raw","raw":"{\n  \"referenceId\": \"string\",\n  \"reference\": \"string\",\n  \"invoiceId\": \"string\",\n  \"status\": \"SUCCESSFUL\",\n  \"financialTransactionId\": \"string\",\n  \"fdn\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"status\": \"ACCEPTED\",\n    \"reason\": \"duplicate\",\n    \"payment_intent\": \"string\",\n    \"payment_event\": \"string\",\n    \"new_status\": \"Confirmed\"\n  }\n}"}]}]},{"name":"Supervisor","description":"Bearer-key surface for integrators automating cash-variance\nreview across counter shifts. Mirrors the workbench's\nSupervisor surface but consumable by external finance / audit\nsystems. Scoped to `assessments.read` (dashboard) and\n`assessments.write` (approve / reject / escalate).\n","item":[{"name":"Variance-queue snapshot for finance/audit systems","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/supervisor/dashboard?mda=GULU&date=2026-05-28","host":["{{baseUrl}}"],"path":["v1","supervisor","dashboard"],"query":[{"key":"mda","value":"GULU","description":""},{"key":"date","value":"2026-05-28","description":"ISO date (YYYY-MM-DD). Defaults to today (server tz)."}]},"description":"Bearer-key mirror of the workbench's\n`/v1/work/supervisor/dashboard`. Returns variance summary +\nper-shift detail for a given MDA + date. Default `mda` is\ninferred from the integrator's roster when omitted; default\n`date` is today.\n\nScope: `assessments.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Dashboard payload.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/supervisor/dashboard?mda=GULU&date=2026-05-28","host":["{{baseUrl}}"],"path":["v1","supervisor","dashboard"],"query":[{"key":"mda","value":"GULU","description":""},{"key":"date","value":"2026-05-28","description":"ISO date (YYYY-MM-DD). Defaults to today (server tz)."}]},"description":"Bearer-key mirror of the workbench's\n`/v1/work/supervisor/dashboard`. Returns variance summary +\nper-shift detail for a given MDA + date. Default `mda` is\ninferred from the integrator's roster when omitted; default\n`date` is today.\n\nScope: `assessments.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"mda\": \"string\",\n    \"date\": \"2026-05-28\",\n    \"totals\": {\n      \"total_collected\": 0,\n      \"shift_count\": 0,\n      \"variance_count\": 0,\n      \"outstanding_variance\": 0\n    },\n    \"shifts\": [\n      {\n        \"name\": \"string\",\n        \"clerk\": \"string\",\n        \"mda\": \"string\",\n        \"counter_label\": \"string\",\n        \"status\": \"Open\",\n        \"opened_at\": \"2026-05-28T12:00:00Z\",\n        \"closed_at\": \"2026-05-28T12:00:00Z\",\n        \"opening_float\": 0,\n        \"assessment_count\": 0,\n        \"total_collected\": 0,\n        \"cash_collected\": 0,\n        \"momo_collected\": 0,\n        \"airtel_collected\": 0,\n        \"cash_expected\": 0,\n        \"cash_counted\": 0,\n        \"cash_variance\": 0,\n        \"currency\": \"string\",\n        \"opening_notes\": \"string\",\n        \"card_collected\": 0,\n        \"bank_collected\": 0,\n        \"voucher_collected\": 0,\n        \"variance_reason\": \"string\",\n        \"closing_notes\": \"string\"\n      }\n    ],\n    \"by_service\": [\n      {\n        \"service\": \"string\",\n        \"service_name\": \"string\",\n        \"count\": 0,\n        \"total\": 0\n      }\n    ],\n    \"by_channel\": [\n      {\n        \"channel\": \"string\",\n        \"count\": 0,\n        \"total\": 0\n      }\n    ]\n  }\n}"}]},{"name":"Approve a closed shift's variance","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/supervisor/shifts/:name:approve-variance","host":["{{baseUrl}}"],"path":["v1","supervisor","shifts",":name:approve-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Stamps the shift's variance as approved by the calling\nintegrator. Idempotent \u2014 repeating returns the same stamp.\nScope: `assessments.write`.\n\n**Who calls this:** a supervisor (or a finance/audit system on\ntheir behalf) clearing the variance queue surfaced by\n`GET /v1/supervisor/dashboard`. The chain-of-custody sign-off \u2014 it\nrecords *who* accepted the clerk's stated reason, in the shift's\naudit trail. **If you click \"Send\":** pass a real closed shift\n`name`; the sandbox key carries `assessments.write`, so it stamps a\nlive approval. Unknown shift \u2192 404.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Variance stamped approved.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/supervisor/shifts/:name:approve-variance","host":["{{baseUrl}}"],"path":["v1","supervisor","shifts",":name:approve-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Stamps the shift's variance as approved by the calling\nintegrator. Idempotent \u2014 repeating returns the same stamp.\nScope: `assessments.write`.\n\n**Who calls this:** a supervisor (or a finance/audit system on\ntheir behalf) clearing the variance queue surfaced by\n`GET /v1/supervisor/dashboard`. The chain-of-custody sign-off \u2014 it\nrecords *who* accepted the clerk's stated reason, in the shift's\naudit trail. **If you click \"Send\":** pass a real closed shift\n`name`; the sandbox key carries `assessments.write`, so it stamps a\nlive approval. Unknown shift \u2192 404.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"SHIFT-2026-05-28-204\",\n    \"action\": \"approved\",\n    \"stamped\": {\n      \"by\": \"string\",\n      \"at\": \"2026-05-28T12:00:00Z\",\n      \"note\": \"string\"\n    }\n  }\n}"}]},{"name":"Reject a closed shift's variance","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/supervisor/shifts/:name:reject-variance","host":["{{baseUrl}}"],"path":["v1","supervisor","shifts",":name:reject-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Reopens the shift (status \u2192 `Open`) and stamps the rejection\nnote for the clerk to act on. `note` is required.\nScope: `assessments.write`.\n\n**Who calls this:** a supervisor sending a drawer back for a\nre-count when the stated variance reason doesn't hold up. **If you\nclick \"Send\":** pass a real closed shift `name` plus a `note` \u2014 a\nmissing `note` returns a validation error (the reason the clerk\nsees is mandatory), not a server error.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Variance rejected, shift reopened.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/supervisor/shifts/:name:reject-variance","host":["{{baseUrl}}"],"path":["v1","supervisor","shifts",":name:reject-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Reopens the shift (status \u2192 `Open`) and stamps the rejection\nnote for the clerk to act on. `note` is required.\nScope: `assessments.write`.\n\n**Who calls this:** a supervisor sending a drawer back for a\nre-count when the stated variance reason doesn't hold up. **If you\nclick \"Send\":** pass a real closed shift `name` plus a `note` \u2014 a\nmissing `note` returns a validation error (the reason the clerk\nsees is mandatory), not a server error.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"SHIFT-2026-05-28-204\",\n    \"action\": \"approved\",\n    \"stamped\": {\n      \"by\": \"string\",\n      \"at\": \"2026-05-28T12:00:00Z\",\n      \"note\": \"string\"\n    }\n  }\n}"}]},{"name":"Escalate a variance to MDA finance","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/supervisor/shifts/:name:escalate-variance","host":["{{baseUrl}}"],"path":["v1","supervisor","shifts",":name:escalate-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Flags the shift for review beyond the supervisor \u2014 creates an\nescalation queue entry that MDA finance / audit consumers\ncan poll. `note` is required.\nScope: `assessments.write`.\n\n**Who calls this:** a supervisor raising a variance they can't\nresolve to the Treasurer / MDA finance tier. The top of the\nescalation ladder (approve = accept, reject = bounce to clerk,\nescalate = push upward). **If you click \"Send\":** real closed shift\n`name` + a `note`; a missing `note` returns a validation error.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Escalation stamped.","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"{{baseUrl}}/v1/supervisor/shifts/:name:escalate-variance","host":["{{baseUrl}}"],"path":["v1","supervisor","shifts",":name:escalate-variance"],"variable":[{"key":"name","value":"string","description":"[required]"}]},"description":"Flags the shift for review beyond the supervisor \u2014 creates an\nescalation queue entry that MDA finance / audit consumers\ncan poll. `note` is required.\nScope: `assessments.write`.\n\n**Who calls this:** a supervisor raising a variance they can't\nresolve to the Treasurer / MDA finance tier. The top of the\nescalation ladder (approve = accept, reject = bounce to clerk,\nescalate = push upward). **If you click \"Send\":** real closed shift\n`name` + a `note`; a missing `note` returns a validation error.\n","body":{"mode":"raw","raw":"{\n  \"note\": \"string\"\n}","options":{"raw":{"language":"json"}}},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"name\": \"SHIFT-2026-05-28-204\",\n    \"action\": \"approved\",\n    \"stamped\": {\n      \"by\": \"string\",\n      \"at\": \"2026-05-28T12:00:00Z\",\n      \"note\": \"string\"\n    }\n  }\n}"}]}]},{"name":"Oversight (Mode C)","description":"Read-only API for oversight bodies \u2014 Office of the Auditor\nGeneral (OAG), Ministry of Finance (MoFPED), Uganda Bureau of\nStatistics (UBOS). Bearer key + `oversight.read` scope. Returns\naggregated revenue, anomaly flags, citizen-consent counts,\npayment-event streams, and UBOS-shaped statistics. Strictly\nadditive \u2014 no row carries citizen NIN, msisdn, or other PII.\n","item":[{"name":"Aggregate revenue + transaction counts grouped by MDA or district","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/aggregates?mda=string&district=False&period_type=month&period_start=2026-05-28&period_end=2026-05-28","host":["{{baseUrl}}"],"path":["v1","oversight","aggregates"],"query":[{"key":"mda","value":"string","description":"Filter to one MDA."},{"key":"district","value":"False","description":"When true, groups by Citizen.district instead of MDA."},{"key":"period_type","value":"month","description":""},{"key":"period_start","value":"2026-05-28","description":"ISO date (inclusive). Required when `period_type=custom`."},{"key":"period_end","value":"2026-05-28","description":"ISO date (exclusive). Required when `period_type=custom`."}]},"description":"Returns per-group totals over a settable period. `district=1`\ngroups by Citizen.district; otherwise groups by MDA.\nAugments each row with the top 5 services contributing to\nthat group's revenue. Scope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Aggregate rows for the window.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/aggregates?mda=string&district=False&period_type=month&period_start=2026-05-28&period_end=2026-05-28","host":["{{baseUrl}}"],"path":["v1","oversight","aggregates"],"query":[{"key":"mda","value":"string","description":"Filter to one MDA."},{"key":"district","value":"False","description":"When true, groups by Citizen.district instead of MDA."},{"key":"period_type","value":"month","description":""},{"key":"period_start","value":"2026-05-28","description":"ISO date (inclusive). Required when `period_type=custom`."},{"key":"period_end","value":"2026-05-28","description":"ISO date (exclusive). Required when `period_type=custom`."}]},"description":"Returns per-group totals over a settable period. `district=1`\ngroups by Citizen.district; otherwise groups by MDA.\nAugments each row with the top 5 services contributing to\nthat group's revenue. Scope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"period_start\": \"2026-05-28\",\n    \"period_end\": \"2026-05-28\",\n    \"period_type\": \"day\",\n    \"grouped_by\": \"mda\",\n    \"mda_filter\": \"string\",\n    \"row_count\": 0,\n    \"rows\": [\n      {\n        \"group_key\": \"GULU\",\n        \"total_collected\": 0,\n        \"transaction_count\": 0,\n        \"average_amount\": 0,\n        \"distinct_assessments\": 0,\n        \"distinct_citizens\": 0,\n        \"top_services\": [\n          {\n            \"service\": \"string\",\n            \"service_name\": \"string\",\n            \"total\": 0\n          }\n        ]\n      }\n    ]\n  }\n}"}]},{"name":"Document version history (audit trail)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/audit-trail?doctype=Assessment&name=ASMT-2026-05-000123","host":["{{baseUrl}}"],"path":["v1","oversight","audit-trail"],"query":[{"key":"doctype","value":"Assessment","description":"[required]"},{"key":"name","value":"ASMT-2026-05-000123","description":"[required]"}]},"description":"Returns the change log for a named document \u2014 useful for\nproving who changed what when. Backed by the platform's `Version`\nrows. Common doctypes: `Assessment`, `Payment Intent`,\n`Counter Shift`, `MDA`, `Service`. Scope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Versions for the document.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/audit-trail?doctype=Assessment&name=ASMT-2026-05-000123","host":["{{baseUrl}}"],"path":["v1","oversight","audit-trail"],"query":[{"key":"doctype","value":"Assessment","description":"[required]"},{"key":"name","value":"ASMT-2026-05-000123","description":"[required]"}]},"description":"Returns the change log for a named document \u2014 useful for\nproving who changed what when. Backed by the platform's `Version`\nrows. Common doctypes: `Assessment`, `Payment Intent`,\n`Counter Shift`, `MDA`, `Service`. Scope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"doctype\": \"Assessment\",\n    \"name\": \"ASMT-2026-05-000123\",\n    \"version_count\": 0,\n    \"versions\": [\n      {\n        \"name\": \"string\",\n        \"owner\": \"string\",\n        \"creation\": \"2026-05-28T12:00:00Z\",\n        \"modified\": \"2026-05-28T12:00:00Z\",\n        \"data\": \"string\"\n      }\n    ]\n  }\n}"}]},{"name":"Paginated anomaly flags","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/anomaly-flags?status=Open&severity=Low&flag_type=cash_anomaly&limit=50&offset=0","host":["{{baseUrl}}"],"path":["v1","oversight","anomaly-flags"],"query":[{"key":"status","value":"Open","description":""},{"key":"severity","value":"Low","description":""},{"key":"flag_type","value":"cash_anomaly","description":""},{"key":"limit","value":"50","description":""},{"key":"offset","value":"0","description":""}]},"description":"Returns rows from the `Anomaly Flag` doctype with pagination\n(`limit` + `offset`). Filter by status, severity, or flag type.\nDetectors today: cash-anomaly, amount-anomaly, velocity-spike.\nScope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Page of anomaly flags.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/anomaly-flags?status=Open&severity=Low&flag_type=cash_anomaly&limit=50&offset=0","host":["{{baseUrl}}"],"path":["v1","oversight","anomaly-flags"],"query":[{"key":"status","value":"Open","description":""},{"key":"severity","value":"Low","description":""},{"key":"flag_type","value":"cash_anomaly","description":""},{"key":"limit","value":"50","description":""},{"key":"offset","value":"0","description":""}]},"description":"Returns rows from the `Anomaly Flag` doctype with pagination\n(`limit` + `offset`). Filter by status, severity, or flag type.\nDetectors today: cash-anomaly, amount-anomaly, velocity-spike.\nScope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"limit\": 0,\n    \"offset\": 0,\n    \"row_count\": 0,\n    \"has_more\": false,\n    \"rows\": [\n      {\n        \"name\": \"string\",\n        \"flag_type\": \"Cash Variance\",\n        \"severity\": \"Low\",\n        \"status\": \"Open\",\n        \"flagged_at\": \"2026-05-28T12:00:00Z\",\n        \"reference_doctype\": \"string\",\n        \"reference_name\": \"string\",\n        \"detection_rule\": \"string\",\n        \"signal_value\": 0,\n        \"threshold\": 0,\n        \"description\": \"string\",\n        \"flagged_by\": \"string\",\n        \"assigned_to\": \"string\",\n        \"resolved_at\": \"2026-05-28T12:00:00Z\"\n      }\n    ]\n  }\n}"}]},{"name":"Active citizen-consent counts grouped by (mda, purpose)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/citizen-consent?mda=string&purpose=string","host":["{{baseUrl}}"],"path":["v1","oversight","citizen-consent"],"query":[{"key":"mda","value":"string","description":""},{"key":"purpose","value":"string","description":""}]},"description":"Active = `granted=1 AND revoked_at IS NULL AND\n(expiry_at IS NULL OR expiry_at >= today)`. Used for\ncompliance review \u2014 OAG can spot MDAs / purposes with\nunusual consent volumes. No row carries citizen identity.\nScope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"Consent counts grouped by (mda, purpose).","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/citizen-consent?mda=string&purpose=string","host":["{{baseUrl}}"],"path":["v1","oversight","citizen-consent"],"query":[{"key":"mda","value":"string","description":""},{"key":"purpose","value":"string","description":""}]},"description":"Active = `granted=1 AND revoked_at IS NULL AND\n(expiry_at IS NULL OR expiry_at >= today)`. Used for\ncompliance review \u2014 OAG can spot MDAs / purposes with\nunusual consent volumes. No row carries citizen identity.\nScope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"mda_filter\": \"string\",\n    \"purpose_filter\": \"string\",\n    \"as_of\": \"2026-05-28\",\n    \"row_count\": 0,\n    \"rows\": [\n      {\n        \"mda\": \"string\",\n        \"purpose\": \"string\",\n        \"active_consents\": 0\n      }\n    ]\n  }\n}"}]},{"name":"Payment-event stream (cursor-paginated)","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/payment-events?after=string&limit=100","host":["{{baseUrl}}"],"path":["v1","oversight","payment-events"],"query":[{"key":"after","value":"string","description":"Opaque cursor from a previous response."},{"key":"limit","value":"100","description":""}]},"description":"Streams `Payment Event` rows newest-first with a stable\ncursor. Pass the previous response's `next_cursor` as\n`after` to fetch the next page. Used by OAG to tail the\nlive event feed without polling holes.\nScope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"One page of payment events.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/payment-events?after=string&limit=100","host":["{{baseUrl}}"],"path":["v1","oversight","payment-events"],"query":[{"key":"after","value":"string","description":"Opaque cursor from a previous response."},{"key":"limit","value":"100","description":""}]},"description":"Streams `Payment Event` rows newest-first with a stable\ncursor. Pass the previous response's `next_cursor` as\n`after` to fetch the next page. Used by OAG to tail the\nlive event feed without polling holes.\nScope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"limit\": 0,\n    \"after\": \"string\",\n    \"row_count\": 0,\n    \"next_cursor\": \"string\",\n    \"rows\": [\n      {\n        \"name\": \"string\",\n        \"payment_intent\": \"string\",\n        \"mda\": \"string\",\n        \"amount\": 0,\n        \"currency\": \"string\",\n        \"aggregator\": \"string\",\n        \"aggregator_txn_id\": \"string\",\n        \"destination_account\": \"string\",\n        \"received_at\": \"2026-05-28T12:00:00Z\",\n        \"proof_payload\": \"string\",\n        \"linked_journal_entry\": \"string\"\n      }\n    ]\n  }\n}"}]},{"name":"UBOS-shaped aggregate statistics","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/statistics?metric=revenue_by_sector&period_start=2026-05-28&period_end=2026-05-28&geography=string","host":["{{baseUrl}}"],"path":["v1","oversight","statistics"],"query":[{"key":"metric","value":"revenue_by_sector","description":"[required]"},{"key":"period_start","value":"2026-05-28","description":"[required]"},{"key":"period_end","value":"2026-05-28","description":"Exclusive \u2014 `period_end - 1 day` is the last day included.  [required]"},{"key":"geography","value":"string","description":"Optional district filter."}]},"description":"Three metrics today, scoped to a custom window: `revenue_by_sector`\n(totals per Service.sector), `transactions_by_district` (counts\nper Citizen.district), `taxpayer_count` (distinct citizens per\ndistrict). `geography` optionally restricts to a specific\ndistrict. Scope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"response":[{"name":"The requested metric over the window.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/oversight/statistics?metric=revenue_by_sector&period_start=2026-05-28&period_end=2026-05-28&geography=string","host":["{{baseUrl}}"],"path":["v1","oversight","statistics"],"query":[{"key":"metric","value":"revenue_by_sector","description":"[required]"},{"key":"period_start","value":"2026-05-28","description":"[required]"},{"key":"period_end","value":"2026-05-28","description":"Exclusive \u2014 `period_end - 1 day` is the last day included.  [required]"},{"key":"geography","value":"string","description":"Optional district filter."}]},"description":"Three metrics today, scoped to a custom window: `revenue_by_sector`\n(totals per Service.sector), `transactions_by_district` (counts\nper Citizen.district), `taxpayer_count` (distinct citizens per\ndistrict). `geography` optionally restricts to a specific\ndistrict. Scope: `oversight.read`.\n","auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {\n    \"metric\": \"revenue_by_sector\",\n    \"period_start\": \"2026-05-28\",\n    \"period_end\": \"2026-05-28\",\n    \"geography_filter\": \"string\",\n    \"rows\": [\n      {}\n    ]\n  }\n}"}]}]},{"name":"Meta","description":"Spec + discovery endpoints. Useful for tooling that wants to\nkeep its client SDK / Postman collection in lockstep with the\nlive API surface.\n","item":[{"name":"Return this OpenAPI 3.1 spec as JSON","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/openapi.json","host":["{{baseUrl}}"],"path":["v1","openapi.json"]},"description":"Returns the live OpenAPI 3.1 document the explorer reads. The\nserver parses `openapi.yaml` on disk once per worker (mtime-\nkeyed) and serves from RAM thereafter \u2014 re-parsing only when\nthe file changes between deploys. Use this URL as the spec\nsource for client-SDK generators, Postman collection\ngenerators, or anything else that wants to stay in lockstep\nwith the live surface.\n","auth":{"type":"noauth"}},"response":[{"name":"The OpenAPI 3.1 document.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/openapi.json","host":["{{baseUrl}}"],"path":["v1","openapi.json"]},"description":"Returns the live OpenAPI 3.1 document the explorer reads. The\nserver parses `openapi.yaml` on disk once per worker (mtime-\nkeyed) and serves from RAM thereafter \u2014 re-parsing only when\nthe file changes between deploys. Use this URL as the spec\nsource for client-SDK generators, Postman collection\ngenerators, or anything else that wants to stay in lockstep\nwith the live surface.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": {}\n}"}]},{"name":"Return the API as a Postman Collection v2.1","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/openapi.postman.json","host":["{{baseUrl}}"],"path":["v1","openapi.postman.json"]},"description":"The same surface as `/v1/openapi.json`, converted on the fly to a\nPostman Collection v2.1 document \u2014 folders per tag, one request per\noperation, example bodies pre-filled from the schemas.\n\nUnlike every other `/v1` response, this one is **not** wrapped in\nthe `{data}` envelope: Postman's importer rejects anything but the\nbare collection at the top level (`{info, item, ...}`). Point\nPostman's **Import \u2192 Link** at this URL, or save it to a file and\nimport that.\n","auth":{"type":"noauth"}},"response":[{"name":"Postman Collection v2.1 document (bare, no `{data}` envelope).","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/openapi.postman.json","host":["{{baseUrl}}"],"path":["v1","openapi.postman.json"]},"description":"The same surface as `/v1/openapi.json`, converted on the fly to a\nPostman Collection v2.1 document \u2014 folders per tag, one request per\noperation, example bodies pre-filled from the schemas.\n\nUnlike every other `/v1` response, this one is **not** wrapped in\nthe `{data}` envelope: Postman's importer rejects anything but the\nbare collection at the top level (`{info, item, ...}`). Point\nPostman's **Import \u2192 Link** at this URL, or save it to a file and\nimport that.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{}"}]}]},{"name":"Service notices","description":"Operator-curated public announcements: planned MDA downtime, new\nMDA onboarding, SDK breaking changes, security advisories. The\nlist endpoint is `allow_guest` and is consumed by the marketing\nlanding page + by the dashboard top-bar. Notices are managed in\nthe back-office Desk (`Service Notice` doctype) by System Manager\nor Sente Rails Admin.\n","item":[{"name":"List currently-active service notices","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/notices?active=1&mda=GULU&limit=50","host":["{{baseUrl}}"],"path":["v1","notices"],"query":[{"key":"active","value":"1","description":"1 = currently-displayable rows only. 0 = every row."},{"key":"mda","value":"GULU","description":"Optional MDA scope. Empty = all notices regardless of scope."},{"key":"limit","value":"50","description":""}]},"description":"Returns operator-curated announcements ordered by severity\n(Critical \u2192 Warning \u2192 Info), then newest-first by\n`effective_from`. Default behavior (`active=1`) returns only\nnotices that are simultaneously `active=1`, past their\n`effective_from`, and either open-ended or before their\n`effective_to`.\n\nPass `active=0` to see every row regardless of state \u2014 used by\noperators reviewing the queue. Pass `mda=<short_code>` to scope\nto that MDA's notices plus any platform-wide ones (rows with\n`mda IS NULL`).\n\nCurated in the back-office Desk by System Manager or Sente\nRails Admin (Service Notice doctype). No write surface on\n`/v1` today.\n","auth":{"type":"noauth"}},"response":[{"name":"Matching notices, ordered by severity then effective_from desc.","originalRequest":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/v1/notices?active=1&mda=GULU&limit=50","host":["{{baseUrl}}"],"path":["v1","notices"],"query":[{"key":"active","value":"1","description":"1 = currently-displayable rows only. 0 = every row."},{"key":"mda","value":"GULU","description":"Optional MDA scope. Empty = all notices regardless of scope."},{"key":"limit","value":"50","description":""}]},"description":"Returns operator-curated announcements ordered by severity\n(Critical \u2192 Warning \u2192 Info), then newest-first by\n`effective_from`. Default behavior (`active=1`) returns only\nnotices that are simultaneously `active=1`, past their\n`effective_from`, and either open-ended or before their\n`effective_to`.\n\nPass `active=0` to see every row regardless of state \u2014 used by\noperators reviewing the queue. Pass `mda=<short_code>` to scope\nto that MDA's notices plus any platform-wide ones (rows with\n`mda IS NULL`).\n\nCurated in the back-office Desk by System Manager or Sente\nRails Admin (Service Notice doctype). No write surface on\n`/v1` today.\n","auth":{"type":"noauth"}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"body":"{\n  \"data\": [\n    {\n      \"name\": \"SN-2026-05-000001\",\n      \"title\": \"GULU EFRIS sandbox refresh tomorrow 22:00\\u201323:00 UTC\",\n      \"body\": \"We're rotating the GULU EFRIS sandbox credentials at 22:00 UTC.\\nPayment intents created during the window may return PENDING\\nuntil the new credentials are accepted on our side. Expect\\nfull restoration within 60 minutes.\\n\",\n      \"severity\": \"Warning\",\n      \"mda\": \"GULU\",\n      \"effective_from\": \"2026-05-28T12:00:00Z\",\n      \"effective_to\": \"2026-05-28T12:00:00Z\",\n      \"active\": 0\n    }\n  ]\n}"}]}]}],"variable":[{"key":"baseUrl","value":"https://sente-rails.space","type":"string","description":"Sente Rails base URL. Swap to a self-hosted bench for offline testing."},{"key":"apiKey","value":"sk_sandbox_REPLACE_ME","type":"string","description":"Your sandbox API key from /signup. Bearer endpoints inherit `Authorization: Bearer {{apiKey}}` from this collection's auth."}]}