The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Inferrail listing page.
Know what your AI work costs.
Inferrail turns supported OpenAI chat-completion traffic into local,
attributable economic receipts. Give related requests a customer-defined
work_id, declare an outcome when your application knows one, and inspect the
known inference economics associated with that work without storing prompts,
responses, or tool payloads in Inferrail's own records.
For the supported chat-completions surface, Inferrail records known cost when
measured usage and a verified price are available. Otherwise it reports
unknown, never a fabricated $0.
Current main / upcoming Work Economics release. Work Economics was added
after the current PyPI release. To try the current product before the next
release, install from main:
The demo needs no API key, no network call, and no provider billing. It runs
canned requests through Inferrail's real engine with made-up prices labeled
DEMO, then shows receipts, attribution, work-level economics, and explicit
unknown evidence.
Stable PyPI release. pip install inferrail currently installs 0.1.2.
It includes the gateway, receipts, reports, and TaskTransaction, but not the
new work commands. It remains the stable released install until the next
package publication.
work_id that your application
defines.You decide what a unit of work means: a contract review, support resolution, coding task, research run, or document-processing job. Inferrail associates economic evidence with the identifier your application supplies; it does not interpret the business meaning of that identifier or its outcome.
Request economics: what known inference economics belong to one request?
Work Economics: what known inference economics belonged to the customer-defined unit of work those requests were performing?
This is not a full cost of work, COGS, margin, or business-value calculation.
The following uses real provider requests and requires OPENAI_API_KEY:
For a gateway client, the equivalent generic attribution header is:
The deterministic offline demo includes this synthetic example:
resolved is only the demo application's own outcome meaning. Inferrail does
not treat any outcome status as universally successful.
If Inferrail cannot verify the price for an observed inference event, its cost
remains unknown rather than being treated as zero. No receipt evidence is
also not the same thing as known zero cost.
inferrail try is the shortest route to one real receipt. It uses your
existing OPENAI_API_KEY; if it is not set, Inferrail prints what is required.
It prints the response, receipt, measured tokens, known cost or unknown, the
local receipt path, and the next report command.
One payload-free JSON receipt per supported request:
(Trimmed — the full record also carries pricing provenance, status, route, timestamp, latency, and retry count. See Privacy boundary below for the complete shape.)
One task is rarely one call. Tag every request belonging to one unit of work with the same attribution value, then ask Inferrail what the task cost:
This TaskTransaction example uses real provider requests and a task_id.
The offline demo instead correlates requests with work_id and shows Work
Economics. Over HTTP, an
X-Inferrail-Attribute-Task-Id: bug_9281 header does the same thing;
inferrail.track_task(task_id=...) (see Attribute spend
below) attaches it automatically to every nested call in an agent run, no
header-threading required. See
docs/adr/0008.
For a long-running application, start the separate gateway process. The gateway process must have access to the provider credential through the configured environment variable; a key held only inside application memory is not automatically transferred to the gateway.
The response is standard OpenAI choices/usage plus a non-standard
inferrail block (route, provider, latency, retries) any OpenAI client
already ignores. X-Inferrail-Attribute-* headers are optional
attribution — never forwarded upstream. See
examples/basic_chat_request.py for a
minimal Python client, or point a supported OpenAI-compatible chat client at
http://127.0.0.1:8000/v1. An OpenAI SDK client that does not set base_url
can use its existing OPENAI_BASE_URL environment mechanism instead.
The default receipt is one JSONL line per supported request in
./inferrail-receipts.jsonl, relative to the gateway's working directory.
Treat that file as machine/audit evidence; use inferrail report for the
human aggregate, inferrail transaction <task-id> for receipt-only task
grouping, and inferrail work <work-id> for work-attributed inference
economics plus a customer-declared outcome.
"model" normally selects a named route from inferrail.yaml (e.g.
"default"), which maps to a provider + underlying model. If
default_provider is set in your config, a model that matches no route
is instead forwarded to that provider unchanged — so "model": "gpt-5.6-sol" works with no route pre-registered for it. Named routes
always take priority. This passthrough is on by default for the
zero-config quickstart path, off by default otherwise. Full design:
docs/adr/0007.
Three ways to attach business context to a request, all landing in the
same attributes: dict[str, str] on its receipt:
X-Inferrail-Attribute-<Name>: <value>, e.g.
X-Inferrail-Attribute-Task-Id: bug_9281.inferrail try): --customer/--workflow shorthand, or
generic -a <name>=<value> for anything else, including task_id.inferrail.track_task attaches
X-Inferrail-Attribute-Task-Id to every outgoing request for the
duration of a with block or decorated function — no threading a
task_id parameter through nested function signatures by hand.with inferrail.track_task(task_id="..."): works the same way. Sync and
async are both supported (attributed_async_http_client(base_url=...) for
AsyncOpenAI/async frameworks); concurrent tasks never cross-contaminate.
This is a small client-side convenience over the HTTP header above — no
gateway or schema change, task_id only, no public API stability
commitment yet. See
docs/adr/0009.
Once tagged, inferrail report shows the all-up aggregate, while
inferrail report --by <provider|model|route|attribute-name>
aggregates receipts by any of these dimensions —
customer, workflow, task_id, or anything else you've attached.
Referral access is opening soon. Planned early-access rewards are based on verified routed usage, not signup:
Program terms will be published when referral access opens.
See the current program presentation at tryinferrail.com.
InferenceEngine normalizes the request, resolves model to a route in
inferrail.yaml (a pure config lookup — no cost/latency-aware
selection in v0.1), calls the one provider adapter in this version
(OpenAIProvider, generic over base_url — OpenAI itself, Azure
OpenAI's compatible surface, vLLM, llama.cpp-server, or anything else
speaking the same wire format), and emits a telemetry event and a
receipt for every supported request, success or failure. Full lifecycle, package
layout, and the streaming/retry boundaries:
docs/ARCHITECTURE.md.
Inferrail's own local receipt, telemetry, and outcome records contain
economic metadata and caller-supplied identifiers, not persisted prompts,
responses, tool payloads, or free-form business outcome payloads.
Structurally, the receipt and telemetry schemas have no field capable of
holding message content, and
test_inference_receipt_has_no_payload_fields enforces it. This is a
claim about Inferrail's own local records, not about the request path as
a whole — your configured provider still receives the real prompt either
way; Inferrail is a pass-through gateway to it, not a privacy boundary
against the provider.
Inferrail currently measures supported OpenAI chat-completions traffic. It is not a background monitor: it records while requests pass through the running process and serves nothing when that process is stopped. It does not enforce budgets or control provider spend.
inferrail try says this in its own output too, not just in the schema:
The full receipt shape, all fields:
If Inferrail can't verify a price for the (provider, model) pair,
pricing and estimated_cost_usd are null — never a guessed or
fabricated cost. You can check the no-payload claim yourself against a
running gateway, not just take it on faith:
docs/PRODUCT.md's verification walkthrough.
Design rationale:
docs/adr/0005.
An MCP server (inferrail-mcp), published on the MCP registry as
io.github.domondi1/inferrail,
exposes Inferrail's local receipt ledger to any MCP-aware agent (Claude
Code, Claude Desktop, Cursor, ...) as two read-only tools — neither
executes inference nor spends provider budget:
| Tool | What it does |
|---|---|
get_spend | Aggregates local receipts by provider/model/route/attribute (including task_id), optional time window |
get_health | Checks gateway reachability + most recent local receipt |
Claude Code: claude mcp add inferrail -- inferrail-mcp. Full contract:
inferrail-mcp/README.md.
POST /v1/chat/completions: streaming (stream: true, real SSE
passthrough) and tool/function calling, single string message content,
no n != 1GET /healthinferrail report, grouped reports, and
inferrail transaction <task-id>work_id attribution, append-only outcome declarations,
and derived Work Economics via inferrail work outcome, inferrail work <work-id>, and inferrail work --allinferrail demo, try, serve (--quickstart), config check,
report, transaction, workHonest edges, not silent gaps — full list in docs/PRODUCT.md:
/v1/chat/completions and
/health exist; no embeddings, assistants, batch, images, or audioINFERRAIL_GATEWAY_TOKEN is one shared secret, not a user systemTaskTransaction — its only event type today is inferenceTaskTransaction — it aggregates cost onlySingle node. The receipt ledger is a local append-only JSONL file, so every process that should appear in one report must write to one file on one filesystem.
O_APPEND write, so threads and multiple
processes on the same host can share one ledger without interleaving or
losing records.inferrail report and inferrail transaction read the whole file into
memory. That is fine for the millions-of-bytes range a developer
preview produces; it is not a query engine, and there is no retention,
rotation, or compaction. Rotate the file yourself if it grows.Anything beyond one host is out of scope for v0.x — see docs/PRODUCT.md.
Inferrail Work Economics is Inferrail's first hosted, paid capability: given caller-declared economic events for a unit of AI work, it returns a normalized cost receipt — known cost, a breakdown by resource class and supplier, and unit economics for the work — paid for over the x402 protocol by any agent with its own wallet — no Inferrail account required. Base Sepolia testnet only right now, not mainnet, not real money.
This is unrelated code, in hosted/, not part of the inferrail package —
running the gateway above never requires it and never talks to it.
https://work.tryinferrail.com (manifest)examples/work_economics_purchase.pyFor a real deployment instead of quickstart defaults:
inferrail.yaml only ever holds the name of an environment variable
for a secret, never the secret itself. Full shape (providers, routes,
telemetry, receipts, pricing overrides):
inferrail.example.yaml.
By default the gateway binds to 127.0.0.1:8000 with no auth. Set
INFERRAIL_GATEWAY_TOKEN to require callers to send Authorization: Bearer <token> — see SECURITY.md.
pytest needs no API key or network access — see
CONTRIBUTING.md.
Apache License 2.0 — see LICENSE.