A professional, type-safe Python SDK for parsing Ethiopian bank transaction receipts
| Feature | Description |
|---|---|
| Auto-Detection | Automatically identifies the bank from any receipt URL |
| Type-Safe | Full Pydantic models with validation — no more raw dicts |
| Unified Output | Same Receipt model regardless of bank |
| REST API | Built-in FastAPI server for microservice deployment |
| Retry Logic | Exponential backoff with configurable retries |
| Batch Processing | Parse multiple receipts in one call |
| Export Formats | JSON, CSV, and dict export built-in |
| Beautiful CLI | Rich terminal output with colored tables |
| Plugin Architecture | Easy to add new banks without modifying core code |
| Bank | ID | Receipt Type | Auto-Detect |
|---|---|---|---|
| Commercial Bank of Ethiopia | cbe |
Yes | |
| Dashen Bank | dashen |
Yes | |
| Awash Bank | awash |
HTML | Yes |
| Bank of Abyssinia | boa |
HTML (JS) | Yes |
| Zemen Bank | zemen |
Yes | |
| Telebirr (Ethio Telecom) | telebirr |
HTML | Yes |
# Core SDK
pip install ethio-receipt-sdk
# With REST API support
pip install ethio-receipt-sdk[api]
# With Selenium (needed for BOA)
pip install ethio-receipt-sdk[selenium]
# Everything
pip install ethio-receipt-sdk[all]from ethio_receipt_sdk import ReceiptParser
parser = ReceiptParser()
receipt = parser.parse("https://apps.cbe.com.et:100/?id=FT25211G11JQ21827223")
print(receipt.sender.name) # "JOHN DOE"
print(receipt.amount) # ETB 1,250.75
print(receipt.reference) # "FT25211G11JQ"
print(receipt.status) # TransactionStatus.SUCCESSfrom ethio_receipt_sdk import ReceiptParser
parser = ReceiptParser()
# SDK automatically detects the bank from the URL
receipt = parser.parse("https://receipt.dashensuperapp.com/receipt/387ETAP2522000WK")
print(receipt.bank) # BankType.DASHEN
receipt = parser.parse("https://awashpay.awashbank.com:8225/-E41AE0D86FFA-21XYYW")
print(receipt.bank) # BankType.AWASH
# Even works with just a reference ID
receipt = parser.parse("CHQ0FJ403O") # Auto-detects Telebirrfrom ethio_receipt_sdk import ReceiptParser
parser = ReceiptParser()
# Just the transaction ID
receipt = parser.parse("CHQ0FJ403O", bank="telebirr")
# Or full URL
receipt = parser.parse("https://transactioninfo.ethiotelecom.et/receipt/CHQ0FJ403O")from ethio_receipt_sdk.parsers.cbe import parse_cbe_from_ft
receipt = parse_cbe_from_ft("FT25211G11JQ", "21827223")
print(receipt.sender.name)
print(receipt.amount)from ethio_receipt_sdk import ReceiptParser
urls = [
"https://apps.cbe.com.et:100/?id=FT25211G11JQ21827223",
"https://receipt.dashensuperapp.com/receipt/387ETAP2522000WK",
"https://awashpay.awashbank.com:8225/-E41AE0D86FFA-21XYYW",
]
with ReceiptParser() as parser:
receipts = parser.parse_batch(urls, ignore_errors=True)
for r in receipts:
if isinstance(r, dict): # Error case
print(f"Failed: {r['error']}")
else:
print(f"{r.bank.value}: {r.amount}")parser = ReceiptParser()
receipt = parser.parse(url)
# Structured JSON
json_str = parser.to_json(receipt, indent=2)
print(json_str){
"bank": "cbe",
"reference": "FT25211G11JQ",
"status": "success",
"timestamp": "2025-09-09T10:20:00",
"sender": {
"name": "JOHN DOE",
"account": "1000223344",
"phone": null,
"bank": null
},
"receiver": {
"name": "XYZ TRADING PLC",
"account": "1000556677",
"phone": null,
"bank": null
},
"amount": {
"value": "1250.75",
"currency": "ETB"
},
"fees": {
"service_charge": "5.00",
"vat": "0.75",
"total_fee": null,
"calculated_total": "5.75"
},
"reason": "Transfer"
}csv_output = parser.to_csv(receipts)
with open("receipts.csv", "w") as f:
f.write(csv_output)data = parser.to_dict(receipt) # Standard Python dict# Parse any receipt (auto-detects bank)
ethio-receipt parse "https://apps.cbe.com.et:100/?id=FT25211G11JQ21827223"
# Specify bank explicitly
ethio-receipt parse "CHQ0FJ403O" --bank telebirr
# Output as JSON
ethio-receipt parse "https://receipt.dashensuperapp.com/receipt/387ETAP2522000WK" --json
# List supported banks
ethio-receipt banks
# Show version
ethio-receipt versionCLI Output Example:
┌──────────────────────────────────────────────┐
│ CBE Receipt │
├──────────────────────┬───────────────────────┤
│ Field │ Value │
├──────────────────────┼───────────────────────┤
│ Reference │ FT25211G11JQ │
│ Status │ SUCCESS │
│ Timestamp │ 2025-09-09 10:20:00 │
│ │ │
│ Sender │ │
│ Name │ JOHN DOE │
│ Account │ 1000223344 │
│ │ │
│ Receiver │ │
│ Name │ XYZ TRADING PLC │
│ Account │ 1000556677 │
│ │ │
│ Amount │ ETB 1,250.75 │
│ Total Debited │ ETB 1,256.50 │
│ Service Charge │ ETB 5.00 │
│ VAT │ ETB 0.75 │
└──────────────────────┴───────────────────────┘
Start the API server:
# Install with API support
pip install ethio-receipt-sdk[api]
# Run the server
python -m ethio_receipt_sdk.api| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Health check & info |
GET |
/banks |
List supported banks |
POST |
/parse |
Parse a single receipt |
GET |
/parse?url=... |
Parse via query parameter |
POST |
/parse/batch |
Parse multiple receipts |
Parse a receipt:
curl -X POST http://localhost:8000/parse \
-H "Content-Type: application/json" \
-d '{"url": "https://apps.cbe.com.et:100/?id=FT25211G11JQ21827223"}'Parse with explicit bank:
curl -X POST http://localhost:8000/parse \
-H "Content-Type: application/json" \
-d '{"url": "CHQ0FJ403O", "bank": "telebirr"}'Batch parse:
curl -X POST http://localhost:8000/parse/batch \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://apps.cbe.com.et:100/?id=FT25211G11JQ21827223",
"https://receipt.dashensuperapp.com/receipt/387ETAP2522000WK"
],
"ignore_errors": true
}'Quick parse via GET:
curl "http://localhost:8000/parse?url=https://apps.cbe.com.et:100/?id=FT25211G11JQ21827223"Interactive API docs available at: http://localhost:8000/docs
from flask import Flask, request, jsonify
from ethio_receipt_sdk import ReceiptParser
app = Flask(__name__)
parser = ReceiptParser()
@app.route("/api/receipt", methods=["POST"])
def parse_receipt():
data = request.json
receipt = parser.parse(data["url"], bank=data.get("bank"))
return jsonify(parser.to_dict(receipt))# views.py
import json
from django.http import JsonResponse
from ethio_receipt_sdk import ReceiptParser
parser = ReceiptParser()
def parse_receipt(request):
body = json.loads(request.body)
receipt = parser.parse(body["url"])
return JsonResponse(parser.to_dict(receipt))const { execSync } = require('child_process');
function parseReceipt(url) {
const result = execSync(`ethio-receipt parse "${url}" --json`);
return JSON.parse(result.toString());
}FROM python:3.12-slim
WORKDIR /app
RUN pip install ethio-receipt-sdk[api]
EXPOSE 8000
CMD ["python", "-m", "ethio_receipt_sdk.api"]docker build -t ethio-receipt-api .
docker run -p 8000:8000 ethio-receipt-apiethio-receipt-sdk/
├── src/ethio_receipt_sdk/
│ ├── __init__.py # Public API exports
│ ├── client.py # ReceiptParser (main interface)
│ ├── models.py # Pydantic data models
│ ├── exceptions.py # Custom exception hierarchy
│ ├── detector.py # Auto-detect bank from URL
│ ├── http.py # HTTP client with retry logic
│ ├── cli.py # Rich CLI interface
│ ├── api.py # FastAPI REST server
│ └── parsers/
│ ├── base.py # Abstract BaseParser
│ ├── registry.py # Plugin registry (auto-discovery)
│ ├── cbe.py # CBE parser
│ ├── dashen.py # Dashen parser
│ ├── awash.py # Awash parser
│ ├── boa.py # BOA parser (Selenium)
│ ├── zemen.py # Zemen parser
│ └── telebirr.py # Telebirr parser
├── tests/
├── pyproject.toml
├── README.md
└── LICENSE
The plugin architecture makes it easy to add new banks:
import re
from ethio_receipt_sdk.parsers.base import BaseParser
from ethio_receipt_sdk.parsers.registry import ParserRegistry
from ethio_receipt_sdk.models import BankType, Receipt
# 1. Register your parser
@ParserRegistry.register(BankType.YOUR_BANK)
class YourBankParser(BaseParser):
def validate_url(self, url: str) -> bool:
return "yourbank.com" in url
def parse(self, url: str) -> Receipt:
response = self._http.get(url)
# ... extract data ...
return Receipt(bank=BankType.YOUR_BANK, ...)from ethio_receipt_sdk import ReceiptParser
from ethio_receipt_sdk.exceptions import (
BankNotSupportedError,
ParseError,
NetworkError,
ReceiptNotFoundError,
)
parser = ReceiptParser()
try:
receipt = parser.parse(url)
except BankNotSupportedError as e:
print(f"Unknown bank: {e.details}")
except NetworkError as e:
print(f"Network issue: {e.message} (status={e.details.get('status_code')})")
except ParseError as e:
print(f"Could not parse receipt: {e.message}")| Issue | Affected | Workaround |
|---|---|---|
| Geo-restriction | Telebirr | Use Ethiopian-hosted server |
| JavaScript rendering | BOA | Install ethio-receipt-sdk[selenium] |
| SSL certificate issues | Some banks | SDK disables SSL verification by default |
| Rate limiting | All banks | Built-in retry with exponential backoff |
# Clone
git clone https://github.com/A1ET/ethio-receipt-sdk.git
cd ethio-receipt-sdk
# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Type checking
mypy src/
# Lint
ruff check src/MIT License — see LICENSE
This is not an official product of any Ethiopian bank or Ethio Telecom.
Built for developer utility purposes. Use responsibly and in compliance with applicable terms of service.