OpenAI-compatible completions.
Receipt on every response.
/v1/chat/completions speaks the OpenAI wire format exactly. Same request body, same response body, same streaming frame shape. Nothing in your existing client code changes except the base_url.
The difference is every response carries a receipt. The gateway hashes your canonical request and response, signs the pair plus model id, cost, and issue time with ed25519, and returns the receipt id in the X-Plumb-Receipt header. You can verify that signature client-side against the published signer key registry — no trust in the operator required.
Drop-in with the OpenAI Python SDK
Point base_url at the gateway, authenticate with a SIWE session token, and call chat.completions.create. The receipt id rides on the response headers; grab it if you want to verify later.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.plumbtech.xyz/v1",
api_key=os.environ["PLUMB_SESSION"], # SIWE bearer token
)
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=[{"role": "user", "content": "explain ed25519 briefly"}],
)
receipt_id = resp.response.headers.get("x-plumb-receipt")
print(resp.choices[0].message.content)
print("receipt →", receipt_id)The atomic debit+receipt guarantee
Plumb's hard rule: no response without a receipt, no receipt without a debit. The gateway runs the ledger debit and the receipt insert inside a single Postgres transaction. If either fails, the whole tx rolls back and the caller gets a 503 billing_unavailable. You never get a completion you didn't pay for, and you never get charged for a completion you didn't receive.
Upstream provider failures (OpenAI 5xx, Anthropic rate limit) never produce a receipt — the transaction aborts before signing. Only settled inferences leave an artifact.
I.03Verify a receipt end-to-end
The Python SDK ships a one-liner. Under the hood it rebuilds the canonical v1 payload, fetches the public key by signer_key_id, and runs ed25519.verify. If the math holds, the receipt came from a signer you trust — not from the gateway's word.
from plumb_sdk import Client
c = Client(base_url="https://api.plumbtech.xyz", session_token=os.environ["PLUMB_SESSION"])
rcpt = c.receipts.get(receipt_id)
assert c.verify_receipt(rcpt), "signature did not match canonical payload"
print("verified cost:", rcpt.cost_micro, "μPLMB")
print("settlement tx:", rcpt.settlement_tx_hash or "pending batch")Batches, Merkle roots, and settlement
Writing every receipt to chain would cost pennies per call and defeat the point. Instead, the Plumb worker sweeps settled receipts every two minutes (or on size overflow, whichever first), builds an OpenZeppelin StandardMerkleTree of (id, requestHash, responseHash, costMicro) leaves, and calls Settlement.checkpointReceipts(root, count, totalCost, sig) on Base Sepolia.
Each receipt in the batch is then stamped with its settlement_tx_hash. You can prove that a given receipt is in a given checkpoint by recomputing the leaf and walking the Merkle proof to the on-chain root — the explorer does this in-browser.
Streaming is fully supported
Pass stream=True and you get SSE frames in the standard OpenAI shape (chat.completion.chunk objects). The receipt is written after the stream closes, using the accumulated content and final usage counts. Client-side streaming integrations keep working unchanged.
↳ 5-minute Python quickstart
↳ Anatomy of a receipt
↳ Client-side verification deep dive
↳ Per-model pricing in micro-PLMB