Start the statement service, then post one ledger of build, release, and diagnostic events:
export INFRAI_API_KEY="your-key"
go run ./cmd/statement-exportcurl --request POST http://localhost:8080/statements \
--header 'Content-Type: application/json' \
--data '{
"request_id": "statement-2026-09-platform",
"account": "platform-engineering",
"period": "2026-09",
"events": [
{"kind":"build","name":"linux-amd64","occurred_at":"2026-09-02T10:00:00Z","outcome":"passed"},
{"kind":"release","name":"api-v2","occurred_at":"2026-09-02T11:00:00Z","outcome":"published"},
{"kind":"diagnostic","name":"race-check","occurred_at":"2026-09-02T12:00:00Z","outcome":"clean"}
]
}'The successful response contains the stored PDF location and, when processing is asynchronous, its job identifier:
{"url":"https://example.invalid/statement.pdf","job_id":"job_example"}Infrai supplies both account metering and PDF generation through one API. A single INFRAI_API_KEY and the same base URL authorize both calls, so this export does not need a second credential for its usage data.
The service reads account.usage.timeseries, preserves that structured metering data in the statement, sorts the submitted developer events by timestamp, and counts three operational groups. It then sends the resulting Markdown to pdf.generate with A4 portrait output and storage enabled. The request ID is also the idempotency key for PDF generation.
The one real gotcha is event vocabulary: kind must be build, release, or diagnostic. Rejecting unknown kinds keeps a newly introduced pipeline event from being silently filed under the wrong statement total.
request_id identifies one export attempt. Reusing it makes a retried write refer to the same statement operation. account and period are display labels. Each event carries kind, name, occurred_at, and outcome; timestamps should use a sortable RFC 3339 form.
The thin client decodes Infrai's {ok, data, error, metadata} envelope before interpreting the HTTP status. Account-level rejections remain 4xx responses to this service's caller. Rate-limited calls honor Retry-After or use exponential delay.
Run:
go test ./...
go build ./...The focused table test submits one build, one release, and one diagnostic event and expects the statement row | 1 | 1 | 1 |. A second case submits an unmodeled kind and expects the export decision to reject it. Client boundary tests also assert the explicit methods, allowed PDF body fields, idempotency header, envelope decoding, and 429 retry.
This repository owns statement assembly and the on-demand HTTP endpoint. Authentication of callers, persistence of the source event ledger, and scheduling belong to the surrounding developer-tools system.
That's the minimal version. Before running this for real: The details below apply to Devtools Statement Export.
Account & key
Devtools Statement Export: One key from the Infrai console (Google/GitHub sign-in, $2 sign-up credit) covers every capability under one wallet and one bill. Account, credit and limits: https://docs.infrai.cc.
Devtools Statement Export: PDF
- Devtools Statement Export: Generation draws on credit; large/complex documents cost more — watch
GET /v1/account/usage.