How to send a request
Send a POST request with a JSON body and Content-Type: application/json. Use one endpoint per transaction, or the batch endpoint for up to 100 at once.
POST /api/logTransaction — log a single transaction
POST /api/logTransactions — log multiple transactions (see Batch upload)
Authorization
Each facility has its own API key (facility.api_key). Send it as a Bearer token in the Authorization header. The server uses the key to identify which facility is posting data — you do not send a separate global API key.
Authorization: Bearer <facility-api-key>
Only facilities with api_key_status = active can submit transactions. Assign and rotate keys per facility in the admin panel or database.
cURL example
curl -X POST https://your-domain.com/api/logTransaction \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-facility-api-key" \
-d '{"referenceId":"REF-001","patient":{...},"bill":{...}}'
Or send a file:
curl -X POST https://your-domain.com/api/logTransaction \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-facility-api-key" \
-d @path/to/request.json
Required fields
referenceId— unique transaction referencepatient.name— patient full name- Facility is determined from your Bearer token —
facilityIdandfacilityare optional; if sent,facilityIdmust match your token's facility - Optional catalog fields on the transaction root:
region,zone,woreda,facilityType,facilityStatus,operationalStatus,ownership(updates the facility record when present) bill.billId,bill.billNumber,bill.totals(grossAmount, discountAmount, netAmount)bill.payments— at least one payment; each needspaymentID,paymentMethod(CASH, CBHI, or DIGITAL),amount, and method-specific details (cash,cbhi, ordigital)
Batch upload
Send multiple billing transactions in one HTTP request. Each item uses the same JSON shape as /api/logTransaction, wrapped in a transactions array.
POST /api/logTransactions
Request body
{
"transactions": [
{
"referenceId": "REF-BATCH-001",
"patient": { "name": "Abebe Kebede", ... },
"bill": { "billId": "BILL-BATCH-001", ... }
},
{
"referenceId": "REF-BATCH-002",
...
}
]
}
Rules
- Maximum 100 transactions per request.
- Each item is processed independently — one failure does not roll back the others.
- Duplicate
referenceIdvalues within the same batch are rejected for the later item. - Same authorization as single upload:
Authorization: Bearer <token>.
cURL example
curl -X POST https://your-domain.com/api/logTransactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-facility-api-key" \
-d @docs/samples/request_batch.json
A full two-transaction sample is in docs/samples/request_batch.json in the project repository.
HTTP status codes
- 201 — all items succeeded
- 207 — partial success (some succeeded, some failed)
- 422 — all items failed
- 400 — invalid JSON, missing or empty
transactions, or batch over the limit
Batch success (201)
{
"success": true,
"message": "All 2 transactions logged successfully.",
"summary": { "total": 2, "succeeded": 2, "failed": 0 },
"results": [
{
"index": 0,
"success": true,
"message": "Transaction logged successfully.",
"data": {
"referenceId": "REF-BATCH-001",
"billId": "BILL-BATCH-001",
"billingReferenceId": 1,
"billDbId": 1
}
},
{
"index": 1,
"success": true,
"message": "Transaction logged successfully.",
"data": {
"referenceId": "REF-BATCH-002",
"billId": "BILL-BATCH-002",
"billingReferenceId": 2,
"billDbId": 2
}
}
]
}
Partial success (207)
{
"success": false,
"message": "1 of 2 transactions logged successfully.",
"summary": { "total": 2, "succeeded": 1, "failed": 1 },
"results": [
{
"index": 0,
"success": true,
"message": "Transaction logged successfully.",
"data": { "referenceId": "REF-BATCH-001", "billId": "BILL-BATCH-001", ... }
},
{
"index": 1,
"success": false,
"error": "DUPLICATE_REFERENCE_ID",
"message": "A transaction with this reference ID already exists.",
"referenceId": "REF-BATCH-002"
}
]
}
Per-item errors use the same error codes as the single endpoint (VALIDATION_ERROR, DUPLICATE_REFERENCE_ID, etc.). Check results[].index to match each outcome to the item in your request array.
Web reports access
The Reports and Transactions pages require a portal session. Sign in at /portal/login with a username and password issued by your administrator.
Each portal user is assigned one or more regions, zones, and/or woredas. Reports and transactions only include facilities that match any assigned scope (for example, a user scoped to a single woreda sees only that woreda).
After migration 008, a development account exists: username portal, password changeme — assign scopes in the admin panel under Portal users.
Click your name in the navigation header to change your password or sign out.
Responses
Applies to /api/logTransaction unless noted. Batch responses are described in Batch upload.
Unauthorized (401)
Returned when the Authorization header is missing, malformed, or the token is invalid.
{
"success": false,
"error": "UNAUTHORIZED",
"message": "Missing or invalid Authorization header. Use: Authorization: Bearer <facility-api-key>"
}
{
"success": false,
"error": "UNAUTHORIZED",
"message": "Invalid or revoked facility API key."
}
Success (201)
{
"success": true,
"message": "Transaction logged successfully.",
"data": {
"referenceId": "REF-20260304-001",
"billId": "BILL-20260304-001",
"billingReferenceId": 1,
"billDbId": 1
}
}
Duplicate reference (409)
{
"success": false,
"error": "DUPLICATE_REFERENCE_ID",
"message": "A transaction with this reference ID already exists.",
"referenceId": "REF-20260304-001"
}
Duplicate bill (409)
{
"success": false,
"error": "DUPLICATE_BILL_ID",
"message": "A bill with this bill ID already exists.",
"billId": "BILL-20260304-001"
}
Duplicate payment (409)
{
"success": false,
"error": "DUPLICATE_PAYMENT_ID",
"message": "A payment with this payment ID already exists.",
"paymentID": "PAY-001"
}
Facility mismatch (403)
{
"success": false,
"error": "FACILITY_MISMATCH",
"message": "facilityId does not match the authenticated facility.",
"facilityId": "FAC-OTHER"
}
Validation error (400)
{
"success": false,
"error": "VALIDATION_ERROR",
"message": "Validation failed.",
"fields": {
"referenceId": "referenceId is required",
"bill.totals.netAmount": "bill.totals.netAmount must be a number"
}
}
Sample: Cash payment
Single payment with paymentMethod: "CASH". Include cash.receiptNumber, receivedBy, and receivedAt.
{
"referenceId": "REF-20260304-001",
"patient": {
"name": "Abebe Kebede",
"phone": "+251911223344",
"gender": "M",
"age": 35
},
"bill": {
"billId": "BILL-20260304-001",
"billNumber": "INV-2026-0001",
"billType": "OUTPATIENT",
"totals": {
"grossAmount": 1000.00,
"discountAmount": 200.00,
"netAmount": 800.00
},
"serviceLines": [
{
"lineID": "LINE-001",
"purpose": "Consultation",
"code": "CONS-01",
"subCode": "GEN",
"name": "General Consultation",
"quantity": 1,
"unitPrice": 500.00,
"lineAmount": 500.00
},
{
"lineID": "LINE-002",
"purpose": "Lab",
"code": "LAB-01",
"subCode": "CBC",
"name": "Complete Blood Count",
"quantity": 1,
"unitPrice": 500.00,
"lineAmount": 500.00
}
],
"payments": [
{
"paymentID": "PAY-001",
"paymentMethod": "CASH",
"amount": 800.00,
"currency": "ETB",
"cash": {
"receiptNumber": "RCPT-20260304-0001",
"receivedBy": "USR-001",
"receivedAt": "2026-03-04T12:05:00+03:00"
},
"status": "SUCCESS"
}
]
}
}
Sample: CBHI payment
Single payment with paymentMethod: "CBHI". Include cbhi.scheme, memberID, authorizationID, claimID, and approvedAt.
{
"referenceId": "REF-20260304-002",
"patient": {
"name": "Tigist Hailu",
"phone": "+251922334455",
"gender": "F",
"age": 28
},
"bill": {
"billId": "BILL-20260304-002",
"billNumber": "INV-2026-0002",
"billType": "OUTPATIENT",
"totals": {
"grossAmount": 1200.00,
"discountAmount": 400.00,
"netAmount": 800.00
},
"serviceLines": [
{
"lineID": "LINE-003",
"purpose": "Consultation",
"code": "CONS-01",
"subCode": "SPEC",
"name": "Specialist Consultation",
"quantity": 1,
"unitPrice": 800.00,
"lineAmount": 800.00
},
{
"lineID": "LINE-004",
"purpose": "Pharmacy",
"code": "PHARM-01",
"subCode": "RX",
"name": "Prescription",
"quantity": 1,
"unitPrice": 400.00,
"lineAmount": 400.00
}
],
"payments": [
{
"paymentID": "PAY-002",
"paymentMethod": "CBHI",
"amount": 800.00,
"currency": "ETB",
"cbhi": {
"scheme": "CBHI",
"memberID": "CBHI-778899",
"authorizationID": "AUTH-112233",
"claimID": "CLM-445566",
"approvedAt": "2026-03-04T11:55:00+03:00"
},
"status": "APPROVED"
}
]
}
}
Sample: Digital (Bank)
paymentMethod: "DIGITAL" with bank transfer. Include digital.provider (e.g. SHEBELLE_BANK), instrument, fromAccountMasked, toAccountMasked, providerTransactionID, completedAt.
{
"referenceId": "REF-20260304-003",
"patient": {
"name": "Dawit Mekonnen",
"phone": "+251933445566",
"gender": "M",
"age": 42
},
"bill": {
"billId": "BILL-20260304-003",
"billNumber": "INV-2026-0003",
"billType": "OUTPATIENT",
"totals": {
"grossAmount": 800.00,
"discountAmount": 0.00,
"netAmount": 800.00
},
"serviceLines": [
{
"lineID": "LINE-005",
"purpose": "Consultation",
"code": "CONS-02",
"subCode": "GEN",
"name": "Follow-up Consultation",
"quantity": 1,
"unitPrice": 800.00,
"lineAmount": 800.00
}
],
"payments": [
{
"paymentID": "PAY-005",
"paymentMethod": "DIGITAL",
"amount": 800.00,
"currency": "ETB",
"digital": {
"provider": "SHEBELLE_BANK",
"instrument": "TRANSFER",
"fromAccountMasked": "1000****8899",
"toAccountMasked": "2000****1122",
"providerTransactionID": "SB-22334455",
"completedAt": "2026-03-04T12:00:00+03:00"
},
"status": "SUCCESS"
}
]
}
}
Sample: Digital (Telebirr)
paymentMethod: "DIGITAL" with mobile wallet (e.g. Telebirr). Include digital.provider, instrument (e.g. QR), qrType, payerWallet, providerTransactionID, completedAt.
{
"referenceId": "REF-20260304-004",
"patient": {
"name": "Sara Ahmed",
"phone": "+251944556677",
"gender": "F",
"age": 31
},
"bill": {
"billId": "BILL-20260304-004",
"billNumber": "INV-2026-0004",
"billType": "OUTPATIENT",
"totals": {
"grossAmount": 600.00,
"discountAmount": 0.00,
"netAmount": 600.00
},
"serviceLines": [
{
"lineID": "LINE-006",
"purpose": "Pharmacy",
"code": "PHARM-02",
"subCode": "OTC",
"name": "Over the counter",
"quantity": 1,
"unitPrice": 600.00,
"lineAmount": 600.00
}
],
"payments": [
{
"paymentID": "PAY-004",
"paymentMethod": "DIGITAL",
"amount": 600.00,
"currency": "ETB",
"digital": {
"provider": "TELEBIRR",
"instrument": "QR",
"qrType": "STATIC",
"payerWallet": "+2519XXXXXXX",
"providerTransactionID": "TB-55667788",
"completedAt": "2026-03-04T12:01:00+03:00"
},
"status": "SUCCESS"
}
]
}
}
Sample: Mixed payments
One bill with multiple payments (e.g. CASH + DIGITAL). Send an array of payment objects in bill.payments.
{
"referenceId": "REF-20260304-005",
"patient": {
"name": "Yonas Tesfaye",
"phone": "+251955667788",
"gender": "M",
"age": 50
},
"bill": {
"billId": "BILL-20260304-005",
"billNumber": "INV-2026-0005",
"billType": "OUTPATIENT",
"totals": {
"grossAmount": 1500.00,
"discountAmount": 300.00,
"netAmount": 1200.00
},
"serviceLines": [
{
"lineID": "LINE-007",
"purpose": "Consultation",
"code": "CONS-01",
"subCode": "GEN",
"name": "General Consultation",
"quantity": 1,
"unitPrice": 500.00,
"lineAmount": 500.00
},
{
"lineID": "LINE-008",
"purpose": "Lab",
"code": "LAB-02",
"subCode": "URINE",
"name": "Urine Analysis",
"quantity": 1,
"unitPrice": 700.00,
"lineAmount": 700.00
},
{
"lineID": "LINE-009",
"purpose": "Pharmacy",
"code": "PHARM-01",
"subCode": "RX",
"name": "Prescription",
"quantity": 1,
"unitPrice": 300.00,
"lineAmount": 300.00
}
],
"payments": [
{
"paymentID": "PAY-006",
"paymentMethod": "CASH",
"amount": 500.00,
"currency": "ETB",
"cash": {
"receiptNumber": "RCPT-20260304-0006",
"receivedBy": "USR-001",
"receivedAt": "2026-03-04T12:10:00+03:00"
},
"status": "SUCCESS"
},
{
"paymentID": "PAY-007",
"paymentMethod": "DIGITAL",
"amount": 700.00,
"currency": "ETB",
"digital": {
"provider": "TELEBIRR",
"instrument": "QR",
"qrType": "STATIC",
"payerWallet": "+2519YYYYYYY",
"providerTransactionID": "TB-66778899",
"completedAt": "2026-03-04T12:11:00+03:00"
},
"status": "SUCCESS"
}
]
}
}
Sample: Batch upload
Two transactions (CASH and CBHI) in one request. Each object in transactions is a full transaction payload. Facility is determined from your Bearer token — facilityId and facility are omitted below (same as docs/samples/request_batch.json).
{
"transactions": [
{
"referenceId": "REF-BATCH-001",
"patient": {
"name": "Abebe Kebede",
"phone": "+251911223344",
"gender": "M",
"age": 35
},
"bill": {
"billId": "BILL-BATCH-001",
"billNumber": "INV-BATCH-0001",
"billType": "OUTPATIENT",
"totals": {
"grossAmount": 1000.00,
"discountAmount": 200.00,
"netAmount": 800.00
},
"serviceLines": [
{
"lineID": "LINE-BATCH-001",
"purpose": "Consultation",
"code": "CONS-01",
"subCode": "GEN",
"name": "General Consultation",
"quantity": 1,
"unitPrice": 800.00,
"lineAmount": 800.00
}
],
"payments": [
{
"paymentID": "PAY-BATCH-001",
"paymentMethod": "CASH",
"amount": 800.00,
"currency": "ETB",
"cash": {
"receiptNumber": "RCPT-BATCH-0001",
"receivedBy": "USR-001",
"receivedAt": "2026-03-04T12:05:00+03:00"
},
"status": "SUCCESS"
}
]
}
},
{
"referenceId": "REF-BATCH-002",
"patient": {
"name": "Tigist Hailu",
"phone": "+251922334455",
"gender": "F",
"age": 28
},
"bill": {
"billId": "BILL-BATCH-002",
"billNumber": "INV-BATCH-0002",
"billType": "OUTPATIENT",
"totals": {
"grossAmount": 1200.00,
"discountAmount": 400.00,
"netAmount": 800.00
},
"serviceLines": [
{
"lineID": "LINE-BATCH-002",
"purpose": "Consultation",
"code": "CONS-01",
"subCode": "SPEC",
"name": "Specialist Consultation",
"quantity": 1,
"unitPrice": 800.00,
"lineAmount": 800.00
}
],
"payments": [
{
"paymentID": "PAY-BATCH-002",
"paymentMethod": "CBHI",
"amount": 800.00,
"currency": "ETB",
"cbhi": {
"scheme": "CBHI",
"memberID": "CBHI-778899",
"authorizationID": "AUTH-BATCH-002",
"claimID": "CLM-BATCH-002",
"approvedAt": "2026-03-04T11:55:00+03:00"
},
"status": "APPROVED"
}
]
}
}
]
}