Reference
Cookbook
End-to-end recipes for the workflows you'll run most — from an everyday market-dues collection to a full cross-agency business registration. Every step is runnable against the live sandbox; citizens, mobile-money numbers, and expected payloads are wired up.
How to use these recipes
Each recipe is a complete, copy-pastable workflow against sente-rails.space/v1. Paste the curl commands in order and you'll drive the same state machines a counter clerk or an integrator app drives in production. They ramp in complexity — start with Recipe 1 and work down.
Test data reference
The sandbox is pre-seeded with forty-six agencies across twenty-six sectors, their service catalogues and fee schedules, and a set of citizens you can transact against. The three below appear throughout the recipes here.
| Persona | NIN | District | Use for |
|---|---|---|---|
| John Patrick Mukasa | CM78001234ABCD | Gulu | Counter-led collection & registration |
| Patrick Okello Akena | CM85042134GULU | Gulu | Cross-agency Lands scenarios |
| Robert Ssemakula Mukasa | CM81051712KLAA | Kampala | Kampala-side Mode B scenarios |
MoMo sandbox MSISDN: +256772123456. Any string ending with 123456 will resolve in the MTN sandbox. Other aggregator numbers are documented inside the Postman collection.
Recipe 1 — Everyday market-dues collection
The rail's highest-volume, most everyday transaction: a vegetable seller at Cereleno Market in Gulu pays her daily market dues — a small, fixed statutory fee — and a clerk rings it up in seconds. This is the bread-and-butter of local-government revenue, repeated thousands of times a day across the country. We use the pre-seeded Gulu citizen (CM78001234ABCD).
1. Open the counter shift
The clerk opens their till for the day with the cash already in the drawer (the opening float).
curl -X POST https://sente-rails.space/v1/counter-shifts/open \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "mda": "GULU", "opening_float": 100000 }'2. Resolve the citizen
curl https://sente-rails.space/v1/citizens/search-by-nin?nin=CM78001234ABCD \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY"3. Assess the daily dues
One line, the daily vegetables-market fee. The price is set by the agency — the server fetches it from the service's fee schedule, so the amount can never be entered or undercut by the caller.
curl -X POST https://sente-rails.space/v1/assessments \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"citizen": "CM78001234ABCD",
"lines": [{ "service": "MD-VEGETABLES", "mda": "GULU" }]
}'Assessment returns Assessed with a total of 1500 UGX — the statutory daily fee.
4. Take the payment
At a market counter this is usually paid in cash (channel Cash); here we use the MoMo sandbox so the whole recipe runs end-to-end. The flow is identical either way.
INTENT=$(curl -sS -X POST https://sente-rails.space/v1/payment-intents \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "assessment": "ASSESS-2026-000200", "payment_channel": "MTN MoMo" }' \
| jq -r '.data.name')
curl -X POST "https://sente-rails.space/v1/payment-intents/$INTENT/initiate" \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "payer_msisdn": "+256772123456" }'On confirmation the dues are recorded against the seller, the funds are routed to Gulu's collection account, and the amount lands in the clerk's shift totals for end-of-day reconciliation. A 1,500-shilling market payment is now as traceable and verifiable as a bank transfer.
Recipe 2 — Counter-led trading licence renewal
A clerk at Gulu City Hall renews John Mukasa's trading licence end-to-end: NIN cascade, single-line assessment with an EFRIS fiscal document number (FDN), MoMo payment, and a receipt with a QR code that points at the public verifier. A routine licensing interaction, start to finish.
Sequence diagram available in the repository at docs/diagrams/04-workflow-vertical-1-trading-licence.svg.
1. Open the counter shift
curl -X POST https://sente-rails.space/v1/counter-shifts/open \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "mda": "GULU", "opening_float": 100000 }'2. Resolve the citizen
curl https://sente-rails.space/v1/citizens/search-by-nin?nin=CM78001234ABCD \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY"3. Create the single-line assessment
curl -X POST https://sente-rails.space/v1/assessments \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"citizen": "CM78001234ABCD",
"lines": [{ "service": "TL-RENEW", "mda": "GULU" }]
}'Assessment status returns Assessed with a single FDN attached to the TL-RENEW line and total 50000 UGX.
4. Initiate the MoMo charge
# create the intent
INTENT=$(curl -sS -X POST https://sente-rails.space/v1/payment-intents \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "assessment": "ASSESS-2026-000123", "payment_channel": "MTN MoMo" }' \
| jq -r '.data.name')
# charge
curl -X POST "https://sente-rails.space/v1/payment-intents/$INTENT/initiate" \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "payer_msisdn": "+256772123456" }'5. Watch the trace
Either poll /trace or subscribe to the payment_intent.confirmed webhook (see Webhooks). On confirmation the trace carries the full state machine: initiated → confirmed → settled → propagated.
6. Verify the receipt as a citizen would
The printed receipt embeds a QR pointing at the public verifier. The citizen-facing summary at /verify/<payment-ref> is PII-safe and unauthenticated — anyone with the QR can confirm the payment is real:
curl https://sente-rails.space/v1/payment-intents/PI-2026-000045/public-summaryRecipe 3 — Lands title transfer with EFRIS PRN
An API-led, cross-agency transaction handled entirely via /v1: two agencies in one assessment (Ministry of Lands title transfer and URA stamp duty), an EFRIS payment-registration number (PRN) generated per taxable line, a single unified MoMo charge, aggregator-level split disbursement to both agencies, and a side-by-side reconciliation against MTN's own API.
Sequence diagram in the repository at docs/diagrams/05-workflow-vertical-2-lands-title.svg.
1. Build the two-line cross-agency assessment
curl -X POST https://sente-rails.space/v1/assessments \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"citizen": "CM85042134GULU",
"lines": [
{ "service": "TITLE-TRANSFER", "mda": "MOL" },
{ "service": "STAMP-DUTY", "mda": "URA" }
]
}'Response carries cross_mda: true, two FDNs (one per EFRIS-taxable line), and a total of 90000 UGX.
2. Initiate unified payment via MoMo
One MoMo charge for the whole 90,000. The split disbursement happens on the aggregator side after confirmation — Sente Rails never holds the money in between.
INTENT=$(curl -sS -X POST https://sente-rails.space/v1/payment-intents \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "assessment": "ASSESS-2026-000124", "payment_channel": "MTN MoMo" }' \
| jq -r '.data.name')
curl -X POST "https://sente-rails.space/v1/payment-intents/$INTENT/initiate" \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "payer_msisdn": "+256772123456" }'3. Side-by-side reconciliation
The /live-status endpoint queries the aggregator directly and returns both records side by side — the "our records match the aggregator's records" reconciliation proof, on demand:
curl "https://sente-rails.space/v1/payment-intents/$INTENT/live-status" \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY"{
"data": {
"rail": {
"status": "Confirmed",
"aggregator_reference": "MOMO-SBX-2026-7891",
"paid_at": "2026-05-25T08:30:09Z"
},
"aggregator": {
"status": "SUCCESSFUL",
"transaction_id": "MOMO-SBX-2026-7891",
"amount": 90000,
"currency": "UGX"
},
"match": true
}
}Recipe 4 — Cross-agency business registration
The rail's most ambitious flow: three agencies touched in a single transaction — URSB (name reservation + company registration), URA (TIN issuance), and Gulu City (new trading licence). Parallel propagation means all three artefacts are issued concurrently on payment confirmation, collapsing a multi-office errand into one interaction.
Sequence diagram in the repository at docs/diagrams/06-workflow-vertical-3-cross-mda.svg.
1. Resolve the prospective director
curl https://sente-rails.space/v1/citizens/search-by-nin?nin=CM78001234ABCD \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY"2. Build the four-line assessment across three agencies
curl -X POST https://sente-rails.space/v1/assessments \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"citizen": "CM78001234ABCD",
"lines": [
{ "service": "NAME-RESERVE", "mda": "URSB" },
{ "service": "COMPANY-REG", "mda": "URSB" },
{ "service": "TIN-REG", "mda": "URA" },
{ "service": "TL-NEW", "mda": "GULU" }
]
}'Total 350000 UGX. Three FDNs attached (TIN-REG is UGX 0, no FDN). Response includes cross_mda: true and mdas_count: 3.
3. Single MoMo charge, three propagation webhooks
INTENT=$(curl -sS -X POST https://sente-rails.space/v1/payment-intents \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "assessment": "ASSESS-2026-000125", "payment_channel": "MTN MoMo" }' \
| jq -r '.data.name')
curl -X POST "https://sente-rails.space/v1/payment-intents/$INTENT/initiate" \
-H "X-Sente-Authorization: Bearer $SENTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "payer_msisdn": "+256772123456" }'On confirmation: one payment_intent.confirmed webhook, three assessment.propagated webhooks (one per agency), and the rail's ledger reflects URSB ← 300k / URA ← 0 / Gulu ← 50k at the aggregator level.
4. The headline outcome
From a cold start (no prior citizen lookup) this recipe completes in roughly ninety seconds against the sandbox — under thirty minutes from idea to a formalised business once you allow for the human steps in between. What used to be days of queuing is now a single counter interaction: same legal artefacts, same statutory fees, a fraction of the operational distance.
What this replaces
The traditional path for a Ugandan business registration: three separate government offices, three separate queues, three separate payment windows, paper-based handoffs between offices, and a typical multi-day turnaround. This recipe collapses it to a single counter interaction — same legal artefacts, same statutory fees, dramatically shorter operational distance.