Odit Verify

POST /api/verify

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

No receipt URL? See POST /api/verify-image — extracts a receipt from a screenshot via AI OCR and chains back here for the upstream confirmation when the screenshot is from a supported provider.

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"
}

Awash Bank (awashpay.awashbank.com:8225)

receipt.source === "awash-html". Scraped from a server-rendered static HTML receipt page. The static company-info block (TIN, VAT Reg, etc.) is dropped — it's identical on every receipt — and we surface customer + transaction sub-objects. Amounts are strings with a ETB suffix (Awash ships them that way; we keep verbatim).

Awash issues at least nine receipt layouts depending on transactionType (IPS Bank Transfer, Telebirr Transfer, Send Money, Bill Payment, Ethiotelecom Postpaid Bill, Bulk Payment, WITHDRAW, DEPOSIT, School Fees). The parser maps each variant's synonym labels (Sender Name / Customer Name, Beneficiary Account / Phone Number / CA Number, etc.) into the same typed fields below. Labels we don't have first-class names for — Branch, CBS Trans ID, Student Code, Invoice Number, etc. — appear inside transaction.extra. See Awash Bank provider page for the full variant matrix.

{
  "source": "awash-html",
  "customer": {
    "customerName": "<customer name>",
    "accountNo": "XXXXX******XXXX/BANK",
    "city": "Addis Ababa",
    "vatRegNo": "-",
    "vatRegDate": "-",
    "tinTaxId": "-",
    "branch": "<branch>"
  },
  "transaction": {
    "transactionTime": "2026-01-01 00:00:00 AM",
    "transactionType": "IPS Bank Transfer",
    "amount": "100 ETB",
    "vat": "0.09 ETB",
    "charge": "1 ETB",
    "senderName": "<sender name>",
    "senderAccount": "XXXXX*******XXX",
    "beneficiaryName": "<beneficiary name>",
    "beneficiaryAccount": "1000000000000",
    "beneficiaryBank": "Commercial Bank of Ethiopia",
    "reason": "Transfer",
    "transactionId": "000000000000000",
    "extra": { }
  }
}

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.