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 reference
  • patient.name — patient full name
  • Facility is determined from your Bearer token — facilityId and facility are optional; if sent, facilityId must 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 needs paymentID, paymentMethod (CASH, CBHI, or DIGITAL), amount, and method-specific details (cash, cbhi, or digital)

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 referenceId values 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"
          }
        ]
      }
    }
  ]
}