Admit

A site verifies an agent's edge signature against its company's public key directory and learns which company sent it, never what it may spend. Coming with the SDK.

The two signatures

Coming: the pass names the organisation's origin once the origin is served. The SDK sends the wba signature for a pass that names one, so until then it sends the trlay signature only.

Every request an agent sends carries two RFC 9421 signatures from one device key, under two labels in the same Signature-Input and Signature headers. A verifier reads one label and ignores the other.

The two signatures side by side
Propertytrlaywba
ForThe seller's check and TollRelayAny site at the edge
tagtrlay-agentweb-bot-auth
Covered@method, @authority, @path, @query, content-digest, trlay-requester when present@authority, @path, signature-agent;key="wba", content-digest when a body is sent
Windowcreated plus 30 secondscreated plus 30 seconds
nonce16 random bytes, refused on repeat by TollRelay16 random bytes; the edge may not check it, so the window is the bound
keyidThe key's fingerprintThe same fingerprint

The Signature-Agent header is wba="https://<handle>.agents.trlay.dev", the origin of the organisation's key directory. The keyid is the JWK SHA-256 thumbprint of the device key, so the same fingerprint names the key in both signatures, the directory and the agent's DID document.

The key directory

Coming: the origin and the directory are not served yet, so the address above does not answer today.

GET https://<handle>.agents.trlay.dev/.well-known/http-message-signatures-directory

Each organisation has one public origin, <handle>.agents.trlay.dev. It serves a JWK Set of the organisation's active agent keys at the standard's well-known path. Each key carries kty, crv, x, kid (the fingerprint), use and alg, and never a private member.

The directory lists one company's keys, so it stays small. Responses carry Cache-Control: public, max-age=300 and an ETag. A removed key leaves the directory within five minutes plus the verifier's own cache.

The directory is served unsigned in the beta. A verifier fetches it over TLS from the organisation's origin. A verifier that insists on a signed directory will not accept it yet.

A site that already runs a Web Bot Auth verifier can admit these agents with no TollRelay code: the signature, the directory and the key id are the standard's.

A sample entry. The values are fixed when the directory ships.

Content-Type: application/http-message-signatures-directory+json
Cache-Control: public, max-age=300

{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "x": "…", "kid": "<fingerprint>", "use": "sig", "alg": "…" } ] }

admit()

Coming: the trust list names each company's verification state once the API publishes it. Until then verified is false for every agent.

admit(request, options) is for a site that sells nothing. It answers who is asking, never how much they may spend.

import { admit } from "@tollrelay/sdk";

const answer = await admit(request);
if (!answer.ok) return refuse(answer.reason);
// answer: { ok: true, handle, fingerprint, verified }

It does these, in order:

  1. Reads the wba member of the signature headers.
  2. Requires the Signature-Agent origin to end in .agents.trlay.dev, or in the suffixes the site passes as options.suffixes (.agents.trlay.sh for staging).
  3. Fetches the directory, cached under its cache headers.
  4. Verifies the signature over the site's own @authority and the signature window. A site behind a proxy that rewrites the host passes its own as options.authority.
  5. Reads the organisation's verification state from the trust list.

It answers { ok: true, handle, fingerprint, verified } or { ok: false, reason }, where the reason is one of no_signature, malformed, origin_not_allowed, directory_unavailable, unknown_key, invalid or expired. It returns no problem code, because it answers the site and not the agent. verified is true only when the trust list says the company is verified.

It never reads a pass, a limit or a spend, and it never calls TollRelay's check. Admitting an agent says the company is known. It does not say what the agent may buy.

The pass, shown when asked

The first request to a host carries the wba signature only. The pass and the requester pseudonym travel to a host that asks for them.

  • A host asks with a 401 carrying WWW-Authenticate: Bearer realm="tollrelay", which gate() already sends, or with an x402 402 carrying the identity extension.
  • The SDK retries once with the pass and the requester header, and remembers the host for the life of the client.
  • The option showPass: "always" sends both on every request, as the SDK did before 0.9.0.
  • A pass that carries edge: false sends no wba signature.
  • Coming: an owner or admin will be able to switch the edge signature off for one agent, and a sandbox pass will always carry edge: false.

The agent lane

Coming: the sandbox desk is not live yet.

A site that wants agents but not all of them mounts gate() on a route that is set apart for them. The route has its own terms, its own rate limits and a price per call on x402. The agent lane lets agents in without blocking them, and the price and the limits are the site's to set.

The sandbox desk is the worked example. It will show an agent lane running end to end, with a sandbox pass.