# POST /api/verify

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

> No receipt URL? See [`POST /api/verify-image`](/docs/verify-image); it 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

```http
POST /api/verify HTTP/1.1
Host: links.et
x-api-key: vk_live_...
content-type: application/json

{ "url": "<receipt URL>" }
```

| Field | Type | Description |
|-------------|----------|--------------------------------------------------------------------------|
| `url` | string | Full receipt URL. Works for every supported provider. |
| `reference` | string | Shorthand, 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.

```json
{
  "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.

```json
{
  "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.

```json
{
  "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).

```json
{
  "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.

```json
{
  "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](/docs/providers/awash) for
the full variant matrix.

```json
{
  "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.

```json
{
  "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.

```json
{ "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.
