Skip to main content

Free, open-source Ethiopian bank/wallet receipt verification. Reverse-engineered public endpoints.

Project description

cheki Python SDK

Free, open-source Ethiopian bank/wallet receipt verification for Python.

cheki verifies that a payment receipt is real by fetching it directly from the bank's own public receipt endpoint — no API keys, no paywalls, no scraping with Selenium. This SDK mirrors the cheki project's other official SDKs (TypeScript, Go, PHP, Dart).

Two modes

The SDK offers two complementary ways to verify receipts:

Mode When to use How it works
API client (recommended) Production apps, servers, anything that wants reliability Calls the hosted cheki REST API, which handles geo-blocking, QR decryption, PDF parsing, and bank-endpoint rotation for you.
Direct verification (advanced) Self-hosting, no external dependency, research Fetches bank endpoints directly from your machine. No round-trip to cheki, but geo-blocked banks (telebirr, M-Pesa) will fail outside Ethiopia.

Both are importable from the top-level package:

from cheki import ChekiClient, verify, supported_banks

Quick start — API client

from cheki import ChekiClient

cheki = ChekiClient()  # uses https://cheki-pi.vercel.app by default

# CBE requires the receiving account number
result = cheki.verify("cbe", "FT26140P01YB", account_number="1000560536171")

if result.is_verified:
    print(f"{result.sender_name} sent {result.amount} {result.currency}")
    print(f"to {result.receiver_name} on {result.date}")
else:
    print(f"Not verified: {result.error}")

Batch verification

Verify up to 50 receipts in a single request:

results = cheki.verify_batch([
    {"bank": "cbe", "reference": "FT26140P01YB", "accountNumber": "1000560536171"},
    {"bank": "telebirr", "reference": "DET8FJGUJ4"},
    {"bank": "dashen", "reference": "B22WDTI261620001"},
])

print(f"{results.verified}/{results.total} verified")
for r in results.results:
    print(r.reference, r.is_verified, r.error)

Discover supported banks

for bank in cheki.get_banks():
    print(f"{bank.code:<12} {bank.name}  [{bank.status}]")

Health check

health = cheki.get_health()
print(health.status)        # "ok"
for check in health.checks:
    print(f"  {check.name}: {check.status} ({check.latency_ms}ms)")

Context manager

ChekiClient reuses a connection pool and can be used as a context manager:

with ChekiClient(timeout=10, max_retries=5) as cheki:
    result = cheki.verify("cbe", "FT26140P01YB", account_number="1000560536171")

Quick start — Direct verification (advanced)

Direct verification fetches the bank endpoint from your machine. It needs no cheki server but is subject to geo-blocking and bank-side changes.

from cheki import verify, supported_banks

# CBE needs the full receiving account number
result = verify("cbe", "FT26140P01YB", account_number="1000560536171")

print(result.status)          # VerificationStatus.VERIFIED
print(result.exists)          # True
print(result.amount)          # 20000.0
print(result.sender_name)

# telebirr / M-Pesa only work from Ethiopian IP addresses
print(supported_banks())

Direct verification returns a VerificationResult (different from the API client's ClientVerifyResult). See the API reference below.


Installation

pip install cheki

From source (development):

git clone https://github.com/1RB/cheki.git
cd cheki/python
pip install -e ".[dev]"

Dependencies: requests, beautifulsoup4, pdfplumber (the latter two are only needed for direct verification of PDF/HTML receipts).


API reference

ChekiClient

ChekiClient(
    base_url="https://cheki-pi.vercel.app",
    api_key=None,
    timeout=30,
    max_retries=3,
    session=None,
    user_agent=None,
)
Parameter Type Default Description
base_url str https://cheki-pi.vercel.app cheki API root URL.
api_key str | None None Optional bearer token. The public API does not require one.
timeout float 30 Per-request timeout in seconds.
max_retries int 3 Retries on HTTP 408/429/5xx (in addition to the first attempt).
session requests.Session | None None Reuse a custom session (connection pool, proxies, etc.).
user_agent str | None auto User-Agent header value.

Methods

verify(bank, reference, account_number=None, phone_number=None, qr_data=None) -> ClientVerifyResult

Verify a single receipt.

verify_batch(receipts) -> ClientBatchResult

Verify up to 50 receipts. Each receipt is a dict with bank, reference, and optional accountNumber / phoneNumber / qrData. Results are returned in input order with computed total / verified / failed counts.

get_banks() -> list[ClientBankInfo]

List supported banks/wallets.

get_health() -> ClientHealthStatus

Check service health (per-bank reachability).

get_receipt_url(bank, reference, account_number=None) -> str

Build a direct receipt-viewer URL (no network request).

close()

Close the underlying HTTP session.

Response types

ClientVerifyResult

Field Type Notes
success bool HTTP-level success.
verified bool | None Whether the receipt is legitimate.
bank, bank_code, reference str | None Identifiers.
source_url str | None Where the receipt was fetched from.
sender_name, sender_account str | None Sender details.
receiver_name, receiver_account str | None Receiver details.
amount, currency float | None, str | None Payment amount.
date str | None Transaction date (as reported by the bank).
branch, reason str | None Extra metadata.
duration_ms int | None Server-side processing time.
invoice_number, transaction_status str | None Wallet-specific.
settled_amount, stamp_duty, discount_amount float | None Wallet fees.
service_fee, service_fee_vat, total_paid float | None Wallet fees.
amount_in_words, payment_mode, payment_channel str | None Wallet metadata.
bank_account_number, bank_account_name str | None Wallet metadata.
error str | None Error message on failure.
fallback_url str | None Direct URL for geo-blocked banks.
index int | None Position within a batch.
raw dict The unparsed API payload.

Helper: result.is_verifiedTrue when success and verified are both true.

ClientBatchResult

success, total, verified, failed, results (list of ClientVerifyResult), error, raw.

ClientBankInfo

code, name, swift, type, status, requires_account, account_digits, requires_phone, endpoint, color, initials, raw. Helper: bank.is_live.

ClientHealthStatus

success, status, version, timestamp, checks (list of ClientHealthCheck), raw. Helper: health.is_ok.

ClientHealthCheck

name, status, latency_ms, raw.

Direct verification

verify(bank, reference, **kwargs) -> VerificationResult

Fetch and parse a receipt directly from the bank endpoint.

  • cbe, boa: pass account_number= (full receiving account).
  • cbebirr: pass phone_number= (payer phone, 2519XXXXXXXXX).

Returns a VerificationResult with status (VerificationStatus), exists, amount, sender_name, receiver_name, transaction_date, source_url, etc.

supported_banks() -> dict[str, str]

Map of bank code → human name for the banks with direct verifiers.


Error handling

API client errors

All API client errors derive from ChekiClientError:

from cheki import (
    ChekiClient, ChekiClientError, ChekiAPIError, ChekiNetworkError, ChekiTimeoutError,
)

cheki = ChekiClient()
try:
    result = cheki.verify("cbe", "FT26140P01YB", account_number="1000560536171")
except ChekiTimeoutError as exc:
    print("Request timed out:", exc)
except ChekiNetworkError as exc:
    print("Network error:", exc)
except ChekiAPIError as exc:
    print(f"API error (HTTP {exc.status_code}):", exc.message)
except ChekiClientError as exc:
    print("Client error:", exc)
Error Meaning
ChekiAPIError Non-2xx API response. Carries status_code, message, body.
ChekiNetworkError Connection failure.
ChekiTimeoutError Request timed out (subclass of ChekiNetworkError).

Note: a verification that finds no receipt is not an error — the API returns success: false with an error message in the ClientVerifyResult. Exceptions are reserved for transport/server failures.

Direct verification errors

from cheki import verify
from cheki.errors import (
    VerificationError, ReceiptNotFoundError, UpstreamError, UnsupportedBankError,
)
Error Meaning
UnsupportedBankError The bank code is not supported.
ReceiptNotFoundError The bank returned a "not found" response.
UpstreamError The bank endpoint is unreachable or returned an error.

Retries

ChekiClient automatically retries on HTTP 408, 429, and 5xx using exponential backoff with full jitter (up to max_retries extra attempts). A Retry-After header, when present, is honored. Network and timeout errors are also retried.


CLI

Install the package to get the ethio-verify command:

# API client (recommended)
ethio-verify cbe FT26140P01YB --account 1000560536171 --api
ethio-verify telebirr DET8FJGUJ4 --api --json

# Direct verification (advanced)
ethio-verify cbe FT26140P01YB --account 1000560536171

# Service health & supported banks (via API)
ethio-verify --health
ethio-verify --list-banks --api

Flags

Flag Description
bank Bank/wallet code (e.g. cbe, telebirr, boa, mpesa).
reference Transaction reference number.
--account Receiving account number (required for cbe, boa).
--phone Payer phone number (required for cbebirr).
--qr Raw QR payload (Bank of Abyssinia inter-bank receipts).
--api Use the hosted cheki REST API instead of direct verification.
--base-url cheki API base URL (default: https://cheki-pi.vercel.app).
--api-key Optional bearer token.
--timeout Per-request timeout in seconds (API mode, default: 30).
--json Output raw JSON.
--list-banks List supported banks and exit.
--health Check cheki API health and exit (implies --api).

Configuration

Custom base URL / self-hosting

cheki = ChekiClient(base_url="https://cheki.my-server.com")

Proxies & custom session

import requests

session = requests.Session()
session.proxies = {"https": "http://proxy.local:8080"}
cheki = ChekiClient(session=session)

Timeouts & retries

cheki = ChekiClient(timeout=10, max_retries=5)

Supported banks

cbe, telebirr, boa, mpesa, dashen, zemen, cbebirr, siinqee, kaafiebirr (and more — run cheki.get_banks() or ethio-verify --list-banks --api for the current list). Availability depends on the bank endpoint's status and geo-restrictions.


License

MIT © 1RB

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cheki-0.2.0.tar.gz (25.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cheki-0.2.0-py3-none-any.whl (30.6 kB view details)

Uploaded Python 3

File details

Details for the file cheki-0.2.0.tar.gz.

File metadata

  • Download URL: cheki-0.2.0.tar.gz
  • Upload date:
  • Size: 25.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for cheki-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c0f08f102625fe4d370e018b85261c87f7ced36800de7243fa50ee48b1ad4046
MD5 8d8e95a3bc66dd503901c413595f72c7
BLAKE2b-256 458cf6faf2f9b8a159cb5ee99da090c355801775bd3150cd0d1a1e2d48314e2f

See more details on using hashes here.

File details

Details for the file cheki-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: cheki-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 30.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for cheki-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4a073820ec31eec22259e72fb390cb71efdd71e1c6165836687ea06e2c131bc2
MD5 57f3e71e963d49757ac061df2e34fb65
BLAKE2b-256 6d124aef6bd50abb3c1b21be7c63b341702afcc0397f3dc35b9687a84e74f59d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page