Odit Verify

POST /api/verify

Fetch + parse a single receipt URL across all supported providers.

Request

POST /api/verify HTTP/1.1
Host: v.odit.et
x-api-key: vk_live_...
content-type: application/json
 
{ "url": "<receipt URL>" }
FieldTypeDescription
urlstringFull receipt URL. Works for every supported provider.
referencestringShorthand — telebirr-only. Equivalent to transactioninfo.ethiotelecom.et/receipt/<ref>.

Exactly one of the two is required. URLs and references shown below are placeholders — substitute one from a receipt you control.

Response envelope

Every success returns the same envelope. The shape of receipt varies by provider; use the source field (or its absence) to dispatch.

{
  "ok": true,
  "providerKey": "telebirr" | "cbe",
  "resolvedUrl": "<upstream URL we actually fetched>",
  "httpStatus": 200,
  "fetchedAt": "2026-01-01T00:00:00.000Z",
  "rawHtmlLength": 25928,
  "error": null,
  "receipt": { /* provider-specific, see below */ }
}

Telebirr

receipt.source === "telebirr-html". Scraped from the bilingual HTML.

{
  "source": "telebirr-html",
  "payerName": "<payer name>",
  "payerTelebirrNo": "251********",
  "payerAccountType": "Individual Customer",
  "creditedPartyName": "<merchant or recipient name>",
  "creditedPartyAccountNo": "251********",
  "transactionStatus": "Completed",
  "receiptNo": "ABCD1234EF",
  "paymentDate": "01-01-2026 00:00:00",
  "settledAmount": "100 Birr",
  "serviceFee": "1.74 Birr",
  "serviceFeeVAT": "0.26 Birr",
  "totalPaidAmount": "102 Birr",
  "paymentReason": "Send Money to Registered Customer",
  "paymentMode": "telebirr",
  "paymentChannel": "API/App"
}

CBE PDF (apps.cbe.com.et and dashed mbreciept.cbe.com.et)

receipt.source === "cbe-pdf". Amounts are numbers, not strings.

{
  "source": "cbe-pdf",
  "payerName": "<payer name>",
  "payerAccount": "1****0000",
  "receiverName": "<receiver name>",
  "receiverAccount": "1****0000",
  "paymentDate": "1/1/2026, 00:00:00 AM",
  "reference": "FT00000000",
  "paymentReason": "Account Transfer",
  "transferredAmount": 100,
  "serviceCharge": 1.74,
  "vat": 0.26,
  "totalAmount": 102,
  "currency": "ETB",
  "amountInWords": "<amount in words>",
  "customerName": "<receipt-holder name>",
  "branch": "<branch>",
  "vatReceiptNo": "ABC0000",
  "vatRegistrationNo": "0000",
  "vatRegistrationDate": "1/1/2020"
}

Zemen Bank (share.zemenbank.com)

receipt.source === "zemen-pdf". Amounts are numbers; currency is always ETB on these receipts. The URL has the shape share.zemenbank.com/rt/<token>/pdf where <token> is 24 alphanumeric characters (an 8-char prefix followed by the 16-char reference).

{
  "source": "zemen-pdf",
  "invoiceNo": "000000000",
  "date": "1-Jan-2026",
  "payerName": "<payer name>",
  "payerAccount": "173****0000(923141XXXXXX0000)",
  "payerTin": "<payer TIN, if registered>",
  "payerVat": "<payer VAT, if registered>",
  "recipientName": "<recipient name>",
  "recipientAccount": "00****00",
  "paymentOrderNumber": "<if present>",
  "reference": "108ATWR0000000QA",
  "transactionStatus": "COMPLETED",
  "transactionDetail": "Remot On Us - ATM CASH WITHDRAWAL",
  "settledAmount": 100,
  "serviceCharge": 1,
  "vat": 0.15,
  "disasterRiskCharge": 0.05,
  "totalAmountPaid": 101.20,
  "currency": "ETB",
  "totalAmountInWord": "ONE HUNDRED ONE BIRR AND TWENTY CENT(S)",
  "paymentReason": "<if present>"
}

Bank of Abyssinia (cs.bankofabyssinia.com)

receipt.source === "boa-json". The public /slip/ page is a React SPA — we bypass it and hit the same /api/onlineSlip/getDetails/?id=<trx> endpoint the bundle does, then normalize the response. Amounts are numbers.

{
  "source": "boa-json",
  "transactionReference": "FT00000000",
  "paymentReference": "<if present>",
  "transactionDate": "01/01/26 00:00",
  "transactionType": "Account Transfer",
  "receiverName": "<receiver name>",
  "receiverAccount": "1******00",
  "transferredAmount": 10,
  "serviceCharge": 0,
  "vat": 0,
  "totalAmount": 10,
  "currency": "ETB",
  "transferredAmountInWord": "TEN BIRR",
  "narrative": "Transfer",
  "upstreamStatus": "Success"
}

CBE JSON (mb.cbe.com.et and plain mbreciept.cbe.com.et)

receipt.source === "mb-json". Richer than the PDF — includes commissions, taxes, and processing dates as an array.

{
  "source": "mb-json",
  "payerName": "<payer name>",
  "payerAccount": "1********0000",
  "receiverName": "<receiver name>",
  "receiverAccount": "1********0000",
  "paymentDate": "2026-01-01T00:00:00Z",
  "reference": "FT00000000",
  "paymentReason": "Mobile Banking Transfer",
  "transferredAmount": 260,
  "serviceCharge": 0.61,
  "vat": 0.08,
  "totalAmount": 260.61,
  "currency": "ETB",
  "vatReceiptNo": "FT00000000",
  "transactionType": "ACNX",
  "processingDate": "20260101",
  "chargeCode": "WAIVE",
  "commissions": [{ "type": "COMFTMB", "amount": 0.5, "currency": "ETB" }],
  "taxes":       [{ "type": "15",     "amount": 0.08, "currency": "ETB" }],
  "paymentDetails": ["Mobile Banking Transfer"],
  "creditAmount": 260
}

502 — parsed but invalid

The upstream answered with 200 but the body didn't carry enough to validate as a real receipt (no reference, or telebirr lacked receiptNo/payerName/transactionStatus). The partial receipt is still returned so you can inspect what was extracted.

{ "ok": false, "error": "parsed PDF but didn't look like a CBE receipt", "receipt": { "...": "..." } }

GET /api/verify

Returns { ok: true, ts }. No auth — used by uptime monitors.

On this page