Confidential LLM Access for Agents (x402)
Overview
SecretAI Confidential LLMs can be consumed directly by autonomous agents using the x402 payment protocol. Instead of registering for an API key, an agent authenticates each request with an EIP-191 signature from its EVM wallet and pays per request from its portal balance.
This gives agents a fully autonomous loop: fund the wallet balance once, then make signed, pay-as-you-go LLM calls with no human in the loop.
No API keys β the agent's wallet address is its identity
Pay-per-request β token usage is metered and charged against the agent's balance after every call
OpenAI-compatible API β standard
/v1/chat/completionsrequests work unchanged; only three extra headers are addedConfidential end-to-end β inference runs inside a SecretVM confidential virtual machine
This page covers calling Confidential LLMs as an agent. For creating and managing virtual machines with an agent wallet, see SecretVM REST API for Agents (x402).
How it works

The agent signs every request with its wallet key and sends it to the LLM endpoint.
The endpoint verifies the signature and timestamp. Invalid signatures are rejected with
401.For billable requests, the agent's balance is checked against the portal. If it is below the minimum, the endpoint returns
402 Payment Requiredwith a top-up challenge.The request is forwarded to the confidential LLM.
The agent receives a standard OpenAI-format response.
Token usage (input + output) is reported to the portal asynchronously and deducted from the agent's balance β it never adds latency to the response.
Authentication
Every request carries three headers:
X-Agent-Address
The agent's EVM wallet address (0xβ¦, checksummed β as returned by wallet.address)
X-Agent-Signature
EIP-191 signature of the request hash (see below)
X-Agent-Timestamp
Unix timestamp β seconds or milliseconds are both accepted
Computing the signature
The signature is an EIP-191 personal_sign over a SHA-256 hash of the request:
METHODβ uppercase HTTP method, e.g.POSTpathβ the request path only, without host or query string, e.g./v1/chat/completions(if you send a query string in the URL, still sign the bare path). The signed path must match the full path the server sees β if the endpoint URL includes a base path, include it.bodyβ the exact raw request body string (empty string for GET requests)timestampβ the same value sent inX-Agent-Timestamp
In JavaScript with ethers:
{% hint style="warning" %} Timestamp freshness: the timestamp must be within 30 seconds of the server's clock, and the signature covers the exact body bytes β any modification invalidates it. A signature remains valid (and billable) for its whole 30-second window, so sign each request freshly and keep request/signature pairs confidential in transit. {% endhint %}
Making Requests
The LLM endpoints expose the standard OpenAI-compatible API. A signed chat completion with curl:
The response is a standard OpenAI chat completion, including a usage object. Billing is based on these counts (when the backend reports cached prompt tokens, those are not charged):
Request bodies are subject to a deployment-configured size limit (on the order of a few megabytes) β very large prompts should be split.
Streaming ("stream": true) is supported and billed the same way. Note that the endpoint requests usage accounting from the model, so the SSE stream ends with a final chunk carrying usage and an empty choices array even if you did not set stream_options.include_usage β make sure your stream parser tolerates it.
Endpoint discovery: available LLM endpoints and models are listed in the SecretAI Portal. You can also query any endpoint's GET /v1/models β this call is free and works even with a zero balance.
If a signed request gets 401 with the body Missing Authorization header, the request was handled by the API-key path instead of the agent path β either the endpoint does not have x402 agent access enabled, or your X-Agent-Address header is missing (or was stripped by an intermediary). {% endhint %}
Free (non-billed) requests
Only JSON requests (Content-Type: application/json) that contain a "model" field in the body are billed. Discovery and health calls β GET /v1/models, GET /api/tags, health checks β still require a valid signature but skip the balance check entirely, so a freshly created wallet can explore the API before topping up.
Billing
Naming note: the standard x402 USDC payment flow is used for funding the agent's balance (via the portal's add-funds endpoint). LLM calls themselves authenticate with the signed X-Agent-* headers against that balance β off-the-shelf x402 client middleware (e.g. x402-fetch) is not used for inference requests.
Insufficient balance β 402
If the agent's balance is below the required minimum, the endpoint responds with 402 Payment Required and a machine-readable challenge:
The response also carries a Payment-Required: x402 header, the protocol marker agents can key on.
An autonomous agent should treat 402 as a signal to top up via the topup_url and retry. Funding uses USDC x402 payments β see SecretVM REST API for Agents (x402) for the add-funds and balance endpoints (the same balance funds both VM management and LLM usage).
{% hint style="warning" %} topup_amount_usd is the exact current deficit, not a recommended amount. Topping up by only that much leaves you roughly one request away from the next 402 β fund enough for your expected usage instead. {% endhint %}
Usage reporting
After each billable request, input and output tokens are reported to the portal and deducted from the agent's balance. Reporting is asynchronous, so it adds no latency to inference responses.
Billing normally uses the usage counts returned by the model. If a response contains no usage object, token counts are estimated with a tokenizer β so validate your requests (model name, parameters) before sending; a request the backend rejects can still incur an estimated charge.
Response Code
200
JSON
Success β response includes usage with token counts
400
plain text
Request body could not be read
401
plain text
Invalid or expired signature/timestamp β re-sign with a fresh timestamp
402
JSON
Insufficient balance β top up via the topup_url in the challenge body and retry
503
plain text
Billing service temporarily unreachable β retry with backoff
Other status codes (e.g. 404 for an unknown model, 500) are passed through unchanged from the model backend.
Last updated
Was this helpful?