Payment Verify API

—
Open this page through the server, not as a local file://. Browsers block API calls from file://. Start the stack (docker compose up -d) and open http://localhost:1111/docs.

About

Verification API for Telebirr and CBE (Commercial Bank of Ethiopia) receipts. Each call is independent — no duplicate-detection state is kept.

🔐 Authentication: POST /api/check/*, POST /api/verify/*, and legacy /check//verify require an API key. Paste yours in the API key field above to use the live "Try it" forms. Get one by signing up (free 1-day trial). Send via: Authorization: Bearer pk_live_...

GET/health

Liveness probe + deployed code version. Use code_version after every deploy to confirm the new code is live.

Request

(no body)

200 response

{
  "status": "ok",
  "timestamp": "2026-05-26T20:00:00.000Z",
  "domain": null,
  "code_version": "stateless-bank-split-node-2026-05-25",
  "version": "2.0.0",
  "telebirr_proxy": false
}

Try it

GET/banks

Lists the banks this build supports.

200 response

{ "supported_banks": ["telebirr", "cbe"] }

Try it

POST/api/check/telebirr

Pure parse — fetches the Telebirr receipt page and returns parsed fields. No receiver validation. Accepts a receipt id (DDQ18SQ4KF), a full receipt URL, or the full SMS text containing the link.

Request body

{ "url": "DDQ18SQ4KF" }

200 response (shape)

{
  "ok": true,
  "bank": "TELEBIRR",
  "receipt_id": "DDQ18SQ4KF",
  "payment_link": "https://transactioninfo.ethiotelecom.et/receipt/DDQ18SQ4KF",
  "amount": 247.5,                  // settled (what the receiver got)
  "total_paid_amount": 250,         // settled + service_fee + vat
  "settled_amount": 247.5,
  "service_fee": 2.5,
  "vat": 0,
  "name": "ABEBE KEBEDE",
  "phone": "251911****",
  "sender_name": "TIGIST ALEMU",
  "sender_phone": "251922****",
  "status": "Completed",
  "payment_date": "26-04-2026 19:08:24",
  "payment_date_iso": "2026-04-26T19:08:24",
  "amount_text": "247.5 Birr",
  "total_paid_amount_text": "250 Birr",
  "auto_eligible": true,
  "issues": []
}

Errors

400 missing fields · 502 upstream fetch/parse failed · 504 upstream timeout

Try it

POST/api/verify/telebirr

Parses the receipt and validates that it was paid to you by matching receiver_name and receiver_account. (For Telebirr, receiver_account = the receiver's phone number. The legacy field name receiver_phone is still accepted.) Returns 422 if the receipt parses but the receiver doesn't match.

Request body

{
  "url": "DDQ18SQ4KF",
  "receiver_name": "ABEBE KEBEDE",
  "receiver_account": "251911000000"
}

200 response

Same shape as /api/check/telebirr, with verified: true and a populated verification object.

422 response

{
  "status": "success",
  "bank": "telebirr",
  "data": { ... },
  "verified": false,
  "verification": {
    "verified": false,
    "issues": ["receiver_name_mismatch"]
  }
}

Try it

POST/api/check/cbe

Pure parse for CBE. url can be any of:

Request body

{ "url": "FT26121GRBSB12345678" }

200 response (shape)

{
  "ok": true,
  "bank": "CBE",
  "receipt_id": "FT26121GRBSB12345678",
  "payment_link": "https://mbreciept.cbe.com.et/FT26121GRBSB12345678",
  "amount": 1000,
  "amount_debited": 1000,
  "service_fee": 0,
  "vat": 0,
  "currency": "ETB",
  "status": "COMPLETED",
  "name": "ABEBE KEBEDE",
  "account": "1000***0778",
  "sender_name": "TIGIST ALEMU",
  "sender_account": "1000***1234",
  "payment_date": "2026-05-01T17:08:24+03:00",
  "auto_eligible": true,
  "issues": []
}

Try it

POST/api/verify/cbe

Parse + receiver match for CBE. receiver_account is required and is also used to complete bare FT… IDs (the API auto-appends the last 8 digits of receiver_account when the full reference isn't provided).

Request body

{
  "url": "FT26121GRBSB",
  "receiver_name": "ABEBE KEBEDE",
  "receiver_account": "1000000000778"
}

Responses

200 verified · 422 parsed but receiver mismatch · 400 missing fields · 502 upstream

Try it

GET/stats

Aggregate usage stats for the running process — total requests, error rate, per-bank counts. Resets on restart unless USAGE_TRACK_ENABLED=true and the JSONL log is persisted to a volume.

200 response (shape)

{
  "uptime_seconds": 1234,
  "total_requests": 0,
  "by_bank": { "telebirr": 0, "cbe": 0 },
  "by_status": { "200": 0, "400": 0, "422": 0, "502": 0 }
}

Try it

POST/check legacy

Old all-in-one endpoint. Kept for back-compat with existing integrations. Prefer /api/check/* and /api/verify/* in new code.

Request body

{
  "bank": "telebirr",
  "url": "DDQ18SQ4KF",
  "receiver_name": "ABEBE KEBEDE",
  "receiver_phone": "251911000000"
}

Try it