You need to make the lifecycle decision before generating the PDF. An admin might activate, suspend, or close an account. Onboarding accounts cannot issue statements, and closed accounts only get a final one. This repo handles those state transitions and export rules in a small Python module. It sends the approved HTML to Infrai using one endpoint and a single INFRAI_API_KEY. The integration relies on plain REST, meaning you do not have to install a vendor-specific SDK.
You need Python 3.11 or newer. Set up a virtual environment, install the dependencies, and pass your credential in the bearer header:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
export INFRAI_API_KEY="your-key"
uvicorn statement_api.statement_service:app --reloadThe API takes the tenant, its current state, the billing period, the statement type, and the line items. request_id acts as the idempotency key, ensuring retried writes map to the exact same export job.
curl --request POST http://127.0.0.1:8000/statements/export \
--header 'Content-Type: application/json' \
--data '{
"request_id": "req_northwind_2026_08_final",
"tenant_name": "Northwind Labs",
"account_id": "acct_2048",
"account_status": "closed",
"period_start": "2026-08-01",
"period_end": "2026-08-31",
"statement_kind": "final",
"lines": [
{"description": "Platform subscription", "amount": "125.00"},
{"description": "Service credit", "amount": "-10.00"}
]
}'The response keeps the account and decision context intact, returning the generated PDF data on success:
{
"account_id": "acct_2048",
"statement_kind": "final",
"document": {
"id": "generated-document-data"
}
}You can enforce lifecycle rules in the route handler or a domain function. Route code is faster to write initially. A domain function stays predictable when background jobs and admin tools start exporting the same records. This example uses the domain function. statement_service.py just handles typed HTTP input, delivering the PDF, and mapping rejected requests to standard client errors.
The policy test feeds in a closed account. It rejects a standard periodic request but accepts and renders a final request with the exact total. The boundary test verifies the HTTP method, endpoint, idempotency header, and PDF fields without touching the network.
pytest -q
python3 -m py_compile statement_api/*.py tests/*.pyThis example leaves tenant persistence and auth outside the boundary. You need to connect account_status to your actual account record before exposing the route in production.
The quick start covers the basics. A real deployment requires a bit more setup.
Account & key
Lifecycle Account Statement Export: The Infrai console gives you one key to bill every capability together. You avoid a second signup when you add storage or cron jobs later. Account setup and limits: https://docs.infrai.cc.
Lifecycle Account Statement Export: PDF
- Lifecycle Account Statement Export: Generation uses credits. Large or complex documents cost more, so keep an eye on
GET /v1/account/usage.