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.
| Property | trlay | wba |
|---|---|---|
| For | The seller's check and TollRelay | Any site at the edge |
tag | trlay-agent | web-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 |
| Window | created plus 30 seconds | created plus 30 seconds |
nonce | 16 random bytes, refused on repeat by TollRelay | 16 random bytes; the edge may not check it, so the window is the bound |
keyid | The key's fingerprint | The 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-directoryEach 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:
- Reads the
wbamember of the signature headers. - Requires the
Signature-Agentorigin to end in.agents.trlay.dev, or in the suffixes the site passes asoptions.suffixes(.agents.trlay.shfor staging). - Fetches the directory, cached under its cache headers.
- Verifies the signature over the site's own
@authorityand the signature window. A site behind a proxy that rewrites the host passes its own asoptions.authority. - 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
401carryingWWW-Authenticate: Bearer realm="tollrelay", whichgate()already sends, or with an x402402carrying 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: falsesends nowbasignature. - 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.