RPC access: open, owner and admin
How Tenzro nodes authorise calls: closed by default, three access classes, signatures from the paying account, OAuth 2.1 with DPoP and HTTP message signatures.
Tenzro Network 1 is closed by default. Every method a node serves is classified, and a method that is not classified is refused before it runs. Reading public state needs nothing. Anything that moves money or changes state needs a signature from the account that pays for it or owns what changes. Operator policy on a node needs that node's operator.
The network stays permissionless: any developer can call any node's RPC, MCP or A2A surface from anywhere, signing with their own hardware-rooted keys, with no per-node enrolment.
The three classes
| Class | Who can call | Credential | Examples |
|---|---|---|---|
| Open | Anyone | None | Blocks, balances, validators, model lists, DID resolution, verification |
| Owner | The account that pays or owns | A signature, a DID envelope, a DPoP-bound session, or an API key issued to that account | Transfers, user operations, passkey and recovery changes, payments, tasks, agent actions, tenant files and databases |
| Admin | The operator of the node you are calling | X-Tenzro-Admin-Token | Model lifecycle, pricing and schedules, API and service keys, rentals, the operator's own stake |
tenzro_listRpcMethods (and tenzro rpc methods in the CLI) returns every method with its class and any API-key scope, so a client can tell "I need to sign this" from "I need a scoped key" from "only the operator can do this" before it calls. The full grouping is in the API reference.
Open
Open methods read public state or verify something. They need no headers.
curl -s https://rpc.tenzro.xyz \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tenzro_getFinalizedBlock","params":[]}'Owner: proof from the paying account
An owner method changes something that belongs to an account. The node accepts proof only from that account. The operator's admin token is deliberately not accepted for owner methods: a node's host must not be able to move a tenant's funds, rewrite a developer's tool or change how a payee gets paid.
There are four kinds of proof.
Signed transactions and user operations. A transfer or contract call is a transaction signed with the account's composite hybrid signature: a classical signature and an ML-DSA-65 signature over the same canonical hash, non-separable, with the post-quantum key bound to the account. It is submitted with eth_sendRawTransaction; the node verifies both halves before admitting it. Smart accounts submit ERC-4337 user operations with eth_sendUserOperation, signed with a passkey through the account's validator module. See Hardware-rooted keys and Smart account policies.
DID envelopes. Actions that belong to an identity rather than a balance (updating a skill you published, assigning a task you posted, terminating an agent swarm you control, publishing a site) carry a DID envelope. The envelope is signed by the identity's key and commits to:
- the DID of the signer,
- the method it authorises,
- a SHA-256 hash of the canonical parameters,
- a timestamp and a random nonce.
Because the method and parameters are inside the signature, an envelope for one call cannot be replayed as another, and each nonce is accepted once. Over JSON-RPC the envelope goes in the did_envelope parameter; over HTTP it goes in the X-Tenzro-DID-Envelope header; A2A and MCP carry it in their message metadata. POST /verify/did-envelope on the Web API checks one for you.
Address signatures. Where a record names its owner by address instead of a DID, the call carries signature and public_key over a domain-separated message that names the method and binds the parameters.
API keys for tenant resources. Files and databases a tenant keeps on an RPC provider are authorised by an API key issued to that tenant, carrying the storage or database scope. The key's subject is the owner. See API keys.
The SDKs build all of these for you from a signer backed by your passkey or device key. The key never leaves your control; the node only ever sees signatures.
Sessions for agents: OAuth 2.1 with DPoP
Agents and services that make many calls use short-lived sessions instead of a signature per request. Onboarding (tenzro_onboardHuman, tenzro_onboardDelegatedAgent, tenzro_onboardAutonomousAgent) issues an OAuth 2.1 access token bound to a DPoP key (RFC 9449), with the agent's permissions expressed as Rich Authorization Requests (RFC 9396).
Every request then carries two headers:
Authorization: DPoP <access token>
DPoP: <proof JWT signed by the holder's key for this method and URL>A stolen token is useless without the key that signs the proofs. A delegated agent is bounded by its controller's delegation scope and spending policy; when it calls something the controller wants to approve, the node answers -32002 with an approval_id, and the call proceeds once the controller approves. Revoking a controller's DID revokes the tokens of every agent under it. Tokens are refreshed, introspected and revoked through /oauth/token, /oauth/introspect and /oauth/revoke.
The MCP server at https://mcp.tenzro.xyz/mcp uses the same scheme: read tools are open, and write tools need a DPoP-bound bearer token.
HTTP message signatures
Agents calling paid HTTP resources can sign the request itself with HTTP Message Signatures (RFC 9421), in the Signature-Input and Signature headers. Covered components typically include @method, @target-uri, @authority and content-digest, plus any payment headers. The keyid is a DID, optionally with a key fragment (did:tenzro:machine:...#key-1), so any verifier can resolve it.
Every node publishes the public keys needed to verify these signatures:
| Route | Contents |
|---|---|
GET /.well-known/jwks.json | JWK Set of agent signing keys |
GET /.well-known/jwks.json/{keyid} | One key by keyid |
GET /.well-known/http-message-signatures-directory | Signature directory for automated agents |
Payment credentials carried alongside the HTTP signature keep their own hybrid binding, so the payment stays quantum-safe whatever the HTTP-layer algorithm. See x402 and MPP.
Admin: operator only
Admin methods set the policy of one node: which models it serves, at what price and when, who holds API keys and service keys, which rentals are open, and the operator's own stake. They need X-Tenzro-Admin-Token, which exists only on that node and is not a network credential. No admin method can change network-wide state such as the validator set, fees or protocol parameters; those change only through governance.
Node listeners bind to loopback by default, so a fresh node exposes nothing off the machine until the operator chooses to.
Paying without an account
Paid HTTP routes such as /v1/chat/completions need no prior relationship. The route answers 402 Payment Required with its price and accepted methods; the client pays with x402 or MPP, in TNZO or stablecoins, and retries with the payment attached. The payment is signed by the paying account, like any other owner action. See Payments.
Service keys: private nodes
An operator can put their whole node behind a service key, sent as X-Tenzro-Service-Key. It is off by default. Adding the first key gates JSON-RPC, MCP, A2A and the Web API at once. It never gates consensus and peer-to-peer traffic, so a gated node still validates and gossips, and it never gates /health and /ready. A model the operator publishes to the network stays callable by anyone who pays, even on a gated node. Keys are stored as SHA-256 digests and compared in constant time.
Headers at a glance
| Header | Sent by | Purpose |
|---|---|---|
Authorization: DPoP ... and DPoP | Agent or app | OAuth 2.1 session bound to a DPoP key |
X-Tenzro-DID-Envelope | Identity owner | Signed authorisation for one call |
Signature-Input, Signature | Agent | RFC 9421 HTTP message signature |
X-Tenzro-Api-Key | Tenant | Scoped access issued by an RPC provider |
X-Tenzro-Service-Key | Renter or tenant | Reach a private node at all |
X-Tenzro-Admin-Token | Operator | Admin methods on their own node |
Several can be present on one request; each gate checks its own header.