Skip to main content

cheki Python SDK

Python client for the cheki receipt verification API: verify Ethiopian bank and wallet receipts (live: CBE, Telebirr, BOA, M-Pesa (beta), Dashen, Awash, Siinqee, eBirr, ZamZam (beta), CBE Birr (beta); get_banks() lists the current set). Python 3.10+.

Website: cheki.et · API reference: cheki.et/docs · Integration guide and every SDK: cheki.et/developers · Supported banks: cheki.et/banks

pip install cheki            # the API client: needs only requests
pip install 'cheki[direct]'  # only for the deprecated direct mode (adds beautifulsoup4, pdfplumber)

Quick start

from cheki import ChekiClient

with ChekiClient() as cheki:  # https://cheki.et, 65 s per attempt, at most 1 retry
    # CBE: the receipt link (or just its Receipt ID) from the CBE app. No account number needed.
    result = cheki.verify("cbe", "https://mbreciept.cbe.com.et/fHCxExampleRcpt01")

    if result.is_verified:
        print(f"{result.sender_name} sent {result.amount} {result.currency}")
    elif result.decoded_only:
        print("Decoded from a QR code, NOT confirmed with the bank")
    elif result.is_rate_limited:
        print(f"Rate limited, retry in {result.retry_after} s")
    else:
        print(f"Not verified ({result.http_status}): {result.error}")
        # result.fallback_url is set for geo-blocked banks; result.checked_url
        # on a 422 is the bank page cheki asked

is_verified is the only success signal

verify() returns a ClientVerifyResult for API errors too (it does not raise for a 4xx/5xx). success only means the request was processed: a BOA QR code that BOA could not confirm comes back with success=True, verified=False, decoded_only=True, and a QR code can be forged. /api/parse results (parsed=True) are never verified either.

QR-only (BOA): cheki.verify("boa", "", qr_data="KPHO0k4YlK/5s6...").

Batch (max 20)

batch = cheki.verify_batch([
    {"bank": "cbe", "reference": "https://mbreciept.cbe.com.et/fHCxExampleRcpt01"},
    {"bank": "telebirr", "reference": "DET0SAMPLE"},
    {"bank": "boa", "qr_data": "KPHO0k4YlK/5s6..."},
])
print(f"{batch.verified}/{batch.total} verified, {batch.failed} not, {batch.cached} from cache")
for r in batch.results:
    print(r.index, r.is_verified, r.http_status, r.error)

More than 20 receipts (or none, or an item without bank) raises ValueError without a request. Each receipt also counts against the per-IP verify limit (30 per minute), and a batch is never retried after a 5xx or network error, so send long lists in chunks of RECOMMENDED_BATCH_CHUNK (15):

from cheki import RECOMMENDED_BATCH_CHUNK

for i in range(0, len(receipts), RECOMMENDED_BATCH_CHUNK):
    batch = cheki.verify_batch(receipts[i:i + RECOMMENDED_BATCH_CHUNK])

A failed item has http_status and error, plus fallback_url when the server sends one for a geo-blocked bank, or checked_url (the bank page cheki asked) on a 422. Results carry order_id when the receipt has one (CBE Birr).

Banks and health

for bank in cheki.get_banks():
    print(bank.id, bank.name, bank.type, bank.status, bank.beta)   # bank.code is an alias of bank.id; beta: M-Pesa, ZamZam

health = cheki.get_health()   # cached up to 5 minutes server-side
print(health.status, health.checked_at, health.cached)
for b in health.banks:        # health.checks is a deprecated alias
    print(b.id, b.status, b.latency_ms)
cheki.get_receipt_url("cbe", "fHCxExampleRcpt01")
# https://cheki.et/api/receipt?bank=cbe&reference=fHCxExampleRcpt01

Only cbe, zemen, dashen and zamzam; anything else raises ValueError, and so does a ZamZam receipt id that is not digits only. CBE, Dashen and ZamZam links redirect to the bank's own receipt page; Zemen (and older CBE receipts) are served as the bank's PDF.

Older receipts

Older CBE receipts that only show an FT reference still work as a legacy format: pass the receiving account (at least the last 8 digits). CBE's older receipt service is often unreachable, so prefer the receipt link when you have it.

cheki.verify("cbe", "FT26SAMPLE01", account_number="1000000006171")
cheki.get_receipt_url("cbe", "FT26SAMPLE01", account_number="1000000006171")  # the bank's PDF

Errors

get_banks() and get_health() raise on failure; verify() and verify_batch() put the error on the result.

Error Meaning
ChekiAPIError Non-2xx response: status_code, message (the server's text), body.
ChekiRateLimitError 429, with retry_after seconds. Subclass of ChekiAPIError.
ChekiPayloadTooLargeError 413. Subclass of ChekiAPIError.
ChekiNetworkError Connection failure.
ChekiTimeoutError An attempt timed out (subclass of ChekiNetworkError).

Timeouts and retries

  • 65 s per attempt (timeout=). The server may spend up to 60 s on a slow bank.
  • At most one retry (max_retries is clamped to 0..1), and only where it is safe (the same rule in every cheki SDK):
    • GET (banks, health): a network error or a 5xx.
    • verify: a network error, or a 429/503 whose Retry-After is at most 60 s.
    • batch: only a 429/503 with a short Retry-After. Never a network error or a 5xx: a 504 means the batch ran out of server time, and replaying it would hit every bank again and charge every receipt against the rate limit.
    • Never: timeouts, other 4xx, a geo-blocked bank (502 with fallbackUrl), or any error the server marks retryable: false. A 500/502/504 on verify is not retried either: the server already ran the check, with its own 2 attempts per bank.
  • The retry waits for Retry-After; a network-error retry waits 1-2 s. Timeouts are never retried (the server may still be working on the request).
  • The server tries each bank at most 2 times, so one call reaches the bank at most 4 times.
  • ChekiClient(session=...) reuses your requests.Session without changing it: the User-Agent and Authorization headers go on each request, so your API key is never sent to other hosts that session talks to, and close() leaves your session open.

CLI

pip install cheki installs cheki-verify, which uses the hosted API:

cheki-verify cbe https://mbreciept.cbe.com.et/fHCxExampleRcpt01
cheki-verify telebirr DET0SAMPLE --json
cheki-verify boa --qr 'KPHO0k4YlK/5s6...'
cheki-verify --health
cheki-verify --list-banks

It exits 0 only when the bank confirmed the receipt. --api is accepted and ignored (it is the default).

Direct verification (deprecated)

from cheki import verify needs pip install 'cheki[direct]' (without it, it raises DirectModeUnavailableError). It fetches bank endpoints from your own machine with Python parsers that have drifted from the server's: they lack its host allowlist and ReDoS fixes and do not support eBirr/Siinqee. CBE (Receipt ID first, FT reference + account for older receipts) and Dashen (receipts.dashenbanksc.com) follow the current formats. It is kept only so existing code keeps working:

  • it emits a DeprecationWarning;
  • it rejects references that are not plain receipt numbers (no / ? # & %, .. or spaces);
  • it verifies TLS for every bank (1.5.0 removed the Awash exception, which let anyone on the network path forge an Awash receipt);
  • it applies a fail-closed port of the server's gate (positive amount, the requested reference unless it is an opaque share token such as CBE's Receipt ID, a payer or receiver, no failed/pending status), so a response that does not pass is FAILED, not VERIFIED;
  • cheki-verify --direct uses it (with a warning).

It will be removed in the next major version. If you cannot use the hosted API, self-host the cheki server (see the repo's Docker setup) and point ChekiClient(base_url=...) at it.

Development

pip install -e ".[dev]"
ruff check . && mypy && pytest

tests/test_contract.py decodes the shared fixtures in ../sdks/fixtures/api-contract.json, which the repo's tests/contract suite checks against the real route handlers.

License

MIT © 1RB

Metadata

Release files for cheki 1.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cheki 1.6.0
File Size Uploaded Provenance Actions
cheki-1.6.0.tar.gz 47.8 kB Without provenance Details for cheki-1.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for cheki 1.6.0
File Interpreter ABI Platform Actions
cheki-1.6.0-py3-none-any.whl Python 3 none any Details for cheki-1.6.0-py3-none-any.whl

Total release size: 96.2 kB

Release files / cheki-1.6.0.tar.gz

Download URL cheki-1.6.0.tar.gz
Size 47.8 kB
Tags Source
SHA-256 checksum
How to use checksums
e0153991186673d5ea0574a7e1aa9486f10628b26f3d404bf27c896ec5ba6830
BLAKE2b-256 checksum
How to use checksums
4d4a42b7706d0a15293af40eaa154434991ad46c12778bfff9726c7467f3aaba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / cheki-1.6.0-py3-none-any.whl

Download URL cheki-1.6.0-py3-none-any.whl
Size 48.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f6664776187ff230d75435130d042f86d884a23688aa0a02baff1b69bb7ab547
BLAKE2b-256 checksum
How to use checksums
75db08decbf445a90b6bfb8ff1a4f62c6d58f5001e19731ea3613393b9474d1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Security for cheki 1.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page