Private AI inference, paid per request. x402 · USDG · signed receipts · nothing stored. No API key, no account, no logs.

Joined October 2026
UPDATE · gateway 0.11.0 is live. Buybacks are back, on Uniswap v4. When UMBRA graduated from its launch curve to Uniswap v4, two things in the gateway paused by design: the automated buyback and paying in UMBRA. Both read their price from the curve, and the curve was gone. Today both read the v4 pool, and the first buyback on it is done. What shipped: • Price source: the ETH/UMBRA v4 pool, read straight from PoolManager storage, times the WETH/USDG pool for dollars. No oracle, no API. • Buyback route: USDG → WETH (v3) → ETH → UMBRA through the Universal Router (V4_SWAP: swap exact in, settle all, take all) → 0x…dEaD. • Exact quote: the output is found by simulation before sending, so the pool's hook fee (3% on output) and the price impact are inside the slippage floor. No surprises, no stuck runs. • Pay in UMBRA is available again: same Permit2 rail, same fee share destroyed with burn() right after settlement, same holder tiers. • The executor burns exactly what it bought. UMBRA received as payment stays in the fee wallet, like USDG does, and is subject to the same 50% policy. First run on the new route, 2026-10-21: • 0.2547 USDG → 0.00010375 ETH → 16,898.00 UMBRA bought on v4 → burned • burn tx robinhoodchain.blockscout.co… • buy tx robinhoodchain.blockscout.co… • dead address now 17,868,926 UMBRA, 1.787% of supply Policy unchanged: 50% of fees buy and burn, 50% fund operations, executed inside the enclave, parameters in the measured compose (0.11.0: 1e00ef0d…). The ledger is the chain, readable at /revenue. umbra-inference.app CA 0x4d3ace48b1590bc4c5dd46f05237c133be564eb3
27
2
3
45
4,470
Try the whole thing for free. Umbra has a testnet rail, and it is not a mock: Base Sepolia, test USDC, verified and settled by PayAI, the same gateway, the same models, the same signed receipts. 1. faucet.circle.com → test USDC to your wallet on Base Sepolia. No ETH needed: the exact scheme is a gasless signature, the facilitator pays the gas. 2. network: "eip155:84532" in the SDK, or pick the rail in the 402 your client receives. 3. Send a request. You get the answer, a PAYMENT-RESPONSE with a real Sepolia transaction, and a receipt signed by the production enclave. This morning, from the TypeScript SDK: a sealed model (DeepSeek, TDX + GPU), 0.0001 test USDC, settled in 8.9 s, receipt valid, facilitation: PayAI, network: eip155:84532, 0 ETH in the wallet before and after. When it works on Sepolia, change one string and it works on USDG or Base. The receipts look the same because they are the same. CA: 0x4d3ace48b1590bc4c5dd46f05237c133be564eb3
5
66
GM, What our logs say about you. All of it. The enclave writes a log, and that log is the one thing that leaves it: the platform shows it to us. So what we print is a privacy decision, not a debugging habit. Here is the complete trace of one paid request, copied from production this morning: [facilitator] verify on eip155:4663 [facilitator] settle on eip155:4663 [facilitator] settled on eip155:4663 {"success":true,"transaction":"0x29bf…9813","network":"eip155:4663","payer":"0x1Ca2…E194"} [facilitator] refund sent on eip155:4663 0xfe53…2552 4081 Four lines. A payer address, a transaction hash, an amount, a network. Every one of them is already public on the chain, in the same transaction. The log adds nothing the block explorer does not show. What is not there: the model, the token counts, the prompt hash, the receipt id, an IP address, a user agent, a request id. Not redacted. Never printed. The debug mode that would print request metadata exists in the code and is off in the measured compose. When the model fails, one more line: the error class, no content. When it works, nothing at all about what it said.
1
7
144
SDK 0.3.1: the prompt never touches the web host. umbra-inference.app is a static site: HTML, JSON, a wasm verifier. The API does not live there. Every /v1/* path answers with a 307 to the enclave, and clients follow it. Here is the hop we did not like. A 307 is answered after the request arrives. So a client following the redirect sent its first POST, prompt included, to the web host's TLS endpoint, which replied "not here" and never read it. No reason to log it. But "no reason to" is not "cannot", and we only claim the second kind. Since 0.3.1 both SDKs do it the other way round: one body-less GET to the public domain, not followed, just to read the Location header; then every request, paid or not, goes straight to the enclave. One extra round trip, once per client. Proof, logged from inside both SDKs during a paid request this morning: GET umbra-inference.app/.well-kn…, no body POST <enclave>/v1/chat/completions, 131 bytes POST <enclave>/v1/chat/completions, with the payment Bodies sent to umbra-inference.app: 0. Full path of a request now: your client, the enclave, the model runtime. On USDG the enclave settles the payment itself. On Base and Solana the enclave calls the facilitator, @PayAINetwork, with the payment payload: amounts, addresses, your signature. The request body never leaves the enclave in either case. The site's own demo already talked to the enclave directly from your browser. The MCP server and the CLI point at the enclave by name. If you use curl, use the enclave URL from /.well-known/umbra.json. Next: the API hostname itself inside the enclave, so the redirect disappears.
1
2
1
11
242
A header to choose where the transaction lives. Two payment flows, and since 0.16.0 they differ in one visible way. After-answer, the default for short requests: the answer is produced, the receipt is signed with the exact usage, then the authorization settles. The transaction hash rides in the PAYMENT-RESPONSE header. Fastest path. Upfront, the default for streams and answers of 2,048 tokens or more: the ceiling settles before the first token, the unused part comes back, and the receipt is signed last, with settlement_tx and refund_tx inside it. One signed JSON that names its own money. You can ask for the second one on any request: x-umbra-flow: upfront. What it costs: a refund transaction, gas on us, and a few seconds. What it buys: a receipt that carries its settlement, verifiable against the chain by the site and the SDKs without any other document. Pick per request. Agents that archive receipts tend to want the header; interactive clients tend to want the speed.
7
165
Two keys, two jobs. The gateway holds two private keys, and they never do each other's work. The money key, secp256k1. It settles your authorization on chain, sends refunds, runs the buybacks. It lives in the sealed environment the enclave decrypts at boot: it is never in the compose, never in the measurement, and it can be rotated by changing the environment, not the code. The receipt key, Ed25519. It signs receipts and nothing else. Nobody generated it: the dstack KMS derives it inside the enclave from the app id, it never exists in a file, and its hash is written into the hardware quote so a quote and a receipt can be matched. The curve was chosen so a browser can verify a receipt with WebCrypto, 64-byte signature, no library. Why two: a stolen money key could move funds, but could not sign a receipt. A receipt key cannot be stolen in the usual sense, it never leaves the enclave; and a receipt signed by a key outside its window in keys.json fails verification anyway. Compromise one, the other still means what it says.
1
8
196
What an unpaid request costs us: a signature check, at most. There is no API key on Umbra, so there is no key to leak, and also nothing to throttle by. What stops a flood? The price. A request without payment: the gateway parses it, picks the model, estimates the input tokens (characters ÷ 4, × 1.25, + 16), computes the ceiling for your holder tier and answers 402 with the price on every rail. No model call, no transaction, no state kept. Microseconds of CPU, then nothing. A request with a payment signature: the verifier recovers the signer, checks the validity window, checks the nonce is unused and the balance covers the ceiling. Read-only calls to the chain. Only when all four pass does the first write happen: the settlement. Then the model. So a flood of unpaid requests is a flood of 402s. A flood of signed-but-unfunded requests is a flood of read-only RPC calls. A flood of valid payments is revenue, and on the UMBRA rail, burns. The one place the gateway does say "slow down": fresh attestation quotes, cached 60 s per nonce, 500 in flight. Quotes are hardware work. Everything else is priced, not limited.
1
7
204
Hang up. The refund still lands. Test this morning: a stream, 3,000-token ceiling, settled before the first token (1,564 atomic, 0.001564 USDG). Three chunks in, the client closed the connection. Nobody listening, no receipt collected, no second request. 18 seconds later, a transfer from the gateway wallet back to the payer: the unused part of the ceiling. The model had finished the essay nobody was reading, the enclave counted the tokens, and sent back the difference. One atomic unit, this time, because the essay ran to the ceiling. The amount is not the point. The reflex is: the refund does not depend on the client being there, or on anything the client does next. What you pay when you hang up: the tokens the model produced. Stopping the model mid-answer on a hang-up is on the list; today the answer completes upstream. The two transactions are on chain either way, and the chain does not need you online to receive.
10
206
What a receipt says about everyone else: nothing. An invoice number is a leak. INV-00042 tells the customer you have done about 42 of these, and the next one tells them your pace. It is the oldest trick in estimating a supplier: the German tank problem, with a serial number. Umbra receipt ids are rcpt_ followed by 20 hex characters from a random UUID: 80 random bits, no counter, no sequence, no date prefix. Two receipts from the same minute look no closer than two from different months. What else the receipt leaves out: • no wallet address, no name, no IP: the payer is identified by the payment, on chain, not by the receipt • no request counter, no "your 12th request today" • no other customer: the prompt hash is yours, the balance tier is yours What it keeps is what you need to audit your own trade: model, token counts, the hash of your prompt, the amount, the enclave that signed. The volume we do publish, we publish on purpose: the buyback executions in revenue.json and the burns on chain. Aggregates we chose, not serials we leaked.
1
9
255
SDK 0.3.0, TypeScript and Python: parse, verify, anchor, settle. verifyReceipt was three checks in one call: shape against the published schema, Ed25519 over the canonical receipt, key anchored in keys.json. It is now four. For upfront receipts, streams and long answers, where the receipt names its settlement transaction since 0.16.0, the SDK reads the chain through the network's public RPC: • the settlement transaction exists and succeeded, and the transfer to the gateway equals the ceiling the receipt declares • the refund transaction, if any, returns refunded_atomic to the payer • settlement minus refund equals settled_atomic, to the atomic unit • and how long before the signature the settlement landed: 2.6 s on today's receipt valid is true only when all four agree. One result object tells you which one did not. Run this morning from the published archives, on a real receipt: a 4-token answer under a 9,000-token ceiling, 4,181 settled, 4,081 refunded, net 100, valid in both languages. The same receipt with settled edited to 1: signature invalid, chain disagrees, valid false in both. npm i umbra-inference.app/releases… pip install umbra-inference.app/releases… chain: false skips the RPC. After-answer receipts carry no settlement transaction and keep the three checks. CA: 0x4d3ace48b1590bc4c5dd46f05237c133be564eb3
2
10
322
18.5M payments. 30 days. ~$18,400 in network fees. with x402 batch settlement on @solana, that same flow costs less than $1. our bot breaks down how 👇
7
21
4
80
2,883
A float in a signed document. The receipt is signed over its canonical form: keys sorted, no whitespace, numbers written the way JavaScript writes them. Every byte counts. And one field is a float: cost.holder.balance_umbra, the paying wallet's UMBRA balance that set the fee tier. That one field is why the Python SDK has a function nobody wanted to write. Python's json.dumps and JavaScript's JSON.stringify disagree on how to print a number: • 1e-7 → Python "1e-07", JavaScript "1e-7" • 0.000001 → Python "1e-06", JavaScript "0.000001" Same value, different bytes, and Ed25519 does not care that they mean the same thing. A verifier using json.dumps would call an honest receipt forged the first time a wallet holds a dust balance. So the SDK re-implements Number#toString: shortest round-trip digits, fixed notation between 1e-7 and 1e21, exponent without the zero padding outside. Ten edge cases, from 5e-324 to 1.5e300, byte-identical to node. Tested on a real receipt, same verdict in both languages. Signatures over JSON are easy to design and easy to get subtly wrong. If you build a verifier in another language, this is the line to test first.
1
2
12
447
Which code, which config, which boot. Every receipt carries attestation.gateway, four values that name the enclave that signed it: • app_id: the application identity. The dstack KMS derives the receipt key for this id and no other. Change the app, the key changes. Ours changed once, on October 22, when we moved to a new CVM: 3675ce94… became 1f88acf1…, and keys.json records both. • compose_hash: the measured configuration, the 48-line compose with the release pinned inside, folded into the TDX config register. Changes with every release: 0.15.0 was bb7ba660…, 0.16.0 is 16a8b884…. • instance_id: the running VM instance. Same app, same compose, different machine: different id. • report_data: sha256 of the receipt key, written into the hardware quote. The link between a signature and a quote. So a receipt does not say "signed by Umbra". It says: signed by key K, held by app A, running configuration C, on instance I. Each of the four can be checked against something you do not get from us: keys.json for the key history, the published compose for C, Intel's signature on the quote for I and the report_data.
2
3
11
458
Website update: the receipt checker now reads the chain. Paste any receipt at umbra-inference.app/#verify and six checks run in your browser: 1. Shape, against the published JSON Schema. 2. Signature, Ed25519 over the canonical receipt. 3. Key, anchored in keys.json: the receipt's date inside that key's window, the app id matching the enclave. 4. Settlement: the transaction the receipt names, read from the chain through a public RPC. Success, a transfer to the gateway, the amount equal to the ceiling the receipt declares. 5. Refund: the transfer back to the payer, equal to refunded_atomic. 6. Net paid: settlement minus refund, equal to settled_atomic. The chain agrees with the receipt, or it does not. Nothing you paste leaves the page. Only the transaction hash goes to the RPC. Tried on a real receipt from today, a 4-token answer under a 9,000-token ceiling: 4,181 settled, 4,081 refunded three seconds later, net 100 atomic, the receipt's settled_atomic to the unit. Then the same receipt with settled edited to 1: the signature fails, and check 6 says the chain disagrees. Two independent witnesses, the enclave and the chain, and the page asks both. The demo button loads a real 0.16.0 receipt. Try it.
1
2
11
545
The whole machine is one file. The enclave's configuration is a 48-line compose. The hardware measures it (compose_hash, folded into the TDX config register), so reading the file is reading what runs. What it contains: • One image: node:20-alpine, public. • One release: a tarball pinned by URL and sha256, downloaded and checked at boot. Change one byte of the gateway and the measurement changes. • One mount: /var/run/dstack.sock, the guest agent that derives the receipt key and produces the quotes. No volume, no disk. Nothing survives a restart except what the chain remembers. • Policy as literals: the payment networks, the Solana receiving address, the holder tiers (100,000 UMBRA → 2%, 1,000,000 → 0%), the buyback rules (50% share, $100 threshold, $2,000 cap, 3% slippage, 28 days apart). In the clear, measured. We cannot change a tier without changing the hash. • Secrets as references: the model provider key, the fee wallet. They come from the sealed environment and are not part of the measurement. The hash proves the code and the policy, not the keys. Published with every release: umbra-inference.app/releases…. The site's enclave check hashes it and compares with the value inside the hardware quote.
4
1
13
688
Two dialects, one hash. Umbra has two doors: /v1/chat/completions speaks OpenAI, /v1/messages speaks Anthropic. The models behind them each speak one. The sealed open models run an OpenAI-compatible runtime; Claude speaks Anthropic. When the door and the runtime differ, the gateway translates inside the enclave: system prompt, messages, tools, tool calls, streaming deltas. The receipt hashes neither dialect. prompt_sha256 is computed over the normalized prompt (system, messages, tools), so the same question gives the same hash whichever door you came in by. Checked this morning, one prompt, four paid requests: • OpenAI route → Claude Haiku: 33e1a4f0…c3a2 • Anthropic route → Claude Haiku: 33e1a4f0…c3a2 • OpenAI route → DeepSeek, sealed, TDX + GPU: 33e1a4f0…c3a2 • Anthropic route → DeepSeek, sealed, TDX + GPU: 33e1a4f0…c3a2 Four signed receipts, one prompt hash. Keep your OpenAI client and point it at a sealed model, or keep your Anthropic client and do the same. The receipt does not care which SDK you used.
2
1
11
497
Update 0.16.0: the receipt points at the settlement. On streams and long answers Umbra settles before the first token, so the settlement is known before the receipt is signed. From this release the receipt carries the transaction itself: cost.settlement_tx and cost.settlement_network, next to refund_tx. One signed JSON now holds the whole trade: the amount the enclave says it charged, the transaction that moved it, the refund of the unused ceiling if any, the model, the token counts, the prompt hash and the measurements of the enclave that signed. Verify the signature offline, then open the transaction. Nothing to reconcile. The after-answer flow is unchanged: there the receipt is signed first and the transaction rides in the PAYMENT-RESPONSE header. The SDKs hand you both either way. First production receipt with the field, this morning: a 30-token stream on eip155:4663, 0.0001 USDG settled, settlement_tx 0xf7502e33…cdd47, the same hash as the header. Schema updated: umbra-inference.app/receipt.…
2
12
465
Thank you, that is exactly the model, and said better than we say it ourselves. Since this morning the receipt points at it literally: on the upfront flow (streams, long answers) the settlement lands before the receipt is signed, so the signed receipt now carries settlement_tx and settlement_network next to refund_tx. One JSON, the facilitator’s transaction inside it, signature and amount both checkable offline. Live on 0.16.0, schema updated.
Replying to @Umbra_Inference
Love this. Facilitator settles onchain, Umbra signs a receipt that points at it, anyone can verify both offline. MIT schema means every x402 service can copy the shape. More of this please.
2
3
1
28
944
UPDATE · enclave check v4 is live. Eight checks. Replay the boot. Land on the hardware's numbers. A TDX quote signs the final value of each measurement register. The event log, served next to the quote, says what went into them. Until today you could read that log and believe it. Now your browser replays it: event by event, SHA-384 chained from forty-eight zero bytes, and it must land on the exact register values Intel signed. If one event were missing, altered or reordered, the register would not match. Live run, this evening: • RTMR0, 13 events, = quote (firmware configuration, boot services) • RTMR1, 5 events, = quote (kernel, boot loader) • RTMR2, 2 events, = quote (kernel command line, initrd) • RTMR3, 10 events, = quote (dstack app events) • inside RTMR3: app-id = our app id, compose-hash = our measured compose, os-image-hash = dstack-0.5.12, the non-dev image For dstack's application events the served log omits the digest, so the page rebuilds it: SHA-384 of the event type (as little-endian u32), a colon, the event name, a colon, the payload bytes. Then the register is SHA-384 of the previous value followed by the digest. Thirty events, under a second, WebCrypto only. One thing worth saying: the first deploy of this check scored 7/8. A hex-decoding bug on the page turned every digest into zeros, and the replay disagreed with the quote exactly as it should when the input is wrong. Fixed, 8/8. A check is only worth something if it can fail. Checks 1 to 7 are unchanged: published compose, literal pin, tarball bytes, receipt key and your nonce in report_data, freshness, DCAP chain to Intel with TCB UpToDate, mr_config_id equal to 0x01 followed by the compose hash. 0x4d3ace48b1590bc4c5dd46f05237c133be564eb3
3
4
11
539
An agent can find Umbra without being told. Every x402 request to Umbra carries a discovery extension in the 402 itself: what the endpoint does, how to call it (input schema and example body), what comes back (an example answer with the signed receipt), tags, icon. No form, no listing request. Machine-readable by construction. @PayAINetwork indexes that into the x402 Bazaar, a public catalogue agents can query: 22,251 payable resources from 1,345 hosts today. Both Umbra routes are in it, listed after a real settlement through the facilitator, with the rails they actually settled on. No settlement, no listing. The part we like most: the catalogue is measured by someone else. PayAI keeps per-resource stats, uptime and response time. Our 23-minute outage on Wednesday shows up there as 99.28% over seven days. We would rather that number be public than polished. One detail in those stats: a settlement on Base mainnet that was not one of ours. Somebody found the endpoint and paid for an answer. That is the whole point. Query it: facilitator.payai.network/di… (and /discovery/resources/<url>/stats)
2
1
12
503
Thank you @PayAINetwork . The settlement half of that math is yours on Base and Solana: the receipt's settled_atomic is checked against the transaction your facilitator wrote on chain. The schema is MIT; any x402 service is welcome to copy the shape. And "parse, verify, anchor" is now one call in both SDKs (0.2.0, live today): shape against the schema, Ed25519 over the canonical receipt, key anchored in keys.json with the receipt's date inside that key's window. A tampered amount fails the signature; a wrong enum fails the shape before anything is read.
Replying to @Umbra_Inference
Receipts you can audit offline, tied to a real onchain settlement. More x402 services should ship this. Trust the math, not the API.
2
6
2
36
1,851