Protocol 01
Spec v0.1An identity your agent can prove.
A namespace, a keypair, and a JWKS. Any server verifies the agent's tokens by fetching a public key — no registration on your side, no shared secret, no account for the agent to create.
$npx interagentic init acme-worker generating Ed25519 keypair… POST /acme-worker/register → challenge signing challenge with private key✓ acme-worker registered key key_V1StGXR8 · EdDSA private ~/.interagentic/private.pem (0600) public https://id.interagentic.dev/acme-worker/jwks.jsonAnatomy
The whole identity is one URI.
It names the broker that vouches for the agent and the namespace the agent holds there. Nothing else is needed to verify a token.
interagentic://id.interagentic.dev/brave-fox-a3f2
The broker is swappable
id.interagentic.dev is the default, not the protocol. Any domain that serves the three endpoints is a broker, and a service decides for itself which brokers it trusts.
The namespace is a handle
Lowercase, hyphenated, 1–63 characters, matching ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$. Claimed once, by whoever asks first — like a domain.
The private key never moves
It is generated on the machine and written to ~/.interagentic/private.pem with mode 0600. The broker only ever sees the public half and a signature proving you hold the other one.
Registration
Claiming a namespace takes two requests.
A challenge proves the agent holds the private key for the public key it just submitted. Nothing is issued until that signature checks out.
- 1agentbroker
Submit the public key
If the namespace is taken by a different key, the broker answers409and the agent picks another name.POST /acme-worker/register { "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA…" }
- 2brokeragent
Receive a challenge
A single-use nonce, valid for five minutes.200 OK { "challenge": "3f9c1e…", "expiresIn": 300 }
- 3agentbroker
Sign it back
The signature is over the raw challenge bytes, using the key just submitted.POST /acme-worker/register/verify { "challenge": "3f9c1e…", "signature": "MEUCIQ…" }
- 4brokereveryone
The public key goes live
From this moment the JWKS is world-readable, and any service can verify the agent's tokens without ever talking to us about it.201 Created GET https://id.interagentic.dev/acme-worker/jwks.json
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"alg": "EdDSA",
"use": "sig",
"kid": "key_V1StGXR8",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
}
]
}Why a challenge at all
Without it, anyone could claim a namespace by pasting someone else's public key — and then that person's signatures would validate against a name they do not control.Authentication
A signed token, and a key you fetch yourself.
The agent signs a short-lived JWT. Your server resolves the broker from the issuer, fetches the JWKS, and verifies. That is the entire integration.
{
"alg": "EdDSA",
"kid": "key_V1StGXR8"
}
.
{
"iss": "interagentic://id.interagentic.dev/brave-fox-a3f2",
"sub": "interagentic://id.interagentic.dev/brave-fox-a3f2",
"aud": "api.example.com",
"iat": 1774051200,
"exp": 1774051500,
"jti": "01JQ8F3K…"
}import { jwtVerify, createRemoteJWKSet } from "jose";
// interagentic://<broker>/<namespace>
const { host, pathname } = new URL(token.iss.replace("interagentic:", "https:"));
const jwks = createRemoteJWKSet(
new URL(`https://${host}${pathname}/jwks.json`),
);
const { payload } = await jwtVerify(token, jwks, {
audience: "api.example.com",
maxTokenAge: "5m",
});
// payload.sub is the caller. There is nothing to look up.Claims
issstringrequired- The agent's full identity URI. The broker host is taken from here, so a service never has to be told where to look.
substringrequired- The acting identity. Equal to iss unless the agent is acting on behalf of a group it belongs to.
audstringrequired- The service the token is for. Audience-scoping is what stops a token captured by one service from being replayed against another.
expnumberrequired- At most five minutes out. Tokens are cheap to mint, so there is no reason for a long-lived one to exist.
jtistringrequired- Unique per token, so a service that cares can reject a replay within the expiry window.
Human linkage
Is there a person behind this agent?
Often that is the only thing your service needs to know. The broker will tell you — but it tells you about your relationship with that human, and nobody else's.
- 1servicebroker
Ask, signed as your domain
The request is authenticated with a JWT signed by your domain key and expiring within five minutes. That signature is what scopes the answer to you.GET /brave-fox-a3f2/metadata.json Authorization: Bearer <jwt signed by api.example.com>
- 2brokerservice
Get a per-service view
humanIdis derived from your root domain plus the broker's internal user id. The same person is a different, stable id at every service — so two services comparing notes cannot tell they are talking about the same human. - 3serviceagent
No human yet? Send the agent a link
The URL is deterministic, so the agent can build it without asking anyone. Permissions you want are declared right in the query string.403 Forbidden Link: <https://id.interagentic.dev/brave-fox-a3f2/link?service=api.example.com &permissions=orders:read,orders:write>; rel="approve"
- 4humanbroker
One approval, once
The human signs in, sees exactly which permissions are being requested and by whom, and approves. The agent repeats its original call and it goes through.
{
"namespace": "brave-fox-a3f2",
"linked": true,
"humanId": "h_9c3a1f7e5b2d8046",
"permissions": ["orders:read", "orders:write"],
"keyRotatedAt": "2026-09-17T08:14:02Z"
}What you cannot see
Not the person's name, email, or broker id. Not the permissions they granted to anyone else. Not which other services they use. You get a stable handle for your own relationship, and that is all.Optimisation
Or collapse it into one token.
Two extra round-trips per call adds up. A broker may let an agent pre-fetch an audience-scoped token that already carries the human id and the active permissions.
The agent asks its broker once; the service verifies one signature and reads everything it needs from the claims. Because the token is audience-scoped and short-lived, it is worth nothing to anyone else — and because the broker signs it, the service does not have to trust the agent's account of its own permissions.
This one is optional
A broker that does not implement it is still a conforming broker. Services should treat the token as a fast path and keep the metadata call as the fallback.{
"alg": "EdDSA",
"kid": "broker_2f91"
}
.
{
"iss": "https://id.interagentic.dev",
"sub": "interagentic://id.interagentic.dev/brave-fox-a3f2",
"aud": "api.example.com",
"human": "h_9c3a1f7e5b2d8046",
"permissions": ["orders:read", "orders:write"],
"exp": 1774051500
}Key rotation
A stolen key should stop working on its own.
Rotation is not a fixed treadmill. A service states how fresh a key it is willing to accept, and the agent rotates on demand.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Interagentic realm="api.example.com",
error="key_too_old", max_key_age=900A standard WWW-Authenticate challenge, so it survives contact with ordinary HTTP tooling. The agent reads max_key_age, rotates, and retries — one extra round trip, no new status code to learn, and no global policy forcing every agent on the network to churn keys it does not need to.
{
"keyId": "key_8Hq2Lm4P",
"fingerprint": "SHA256:xK9c…",
"previous": "SHA256:aB3f…",
"rotatedAt": "2026-09-17T08:14:02Z"
}Every key records the fingerprint it replaced. If a token ever arrives signed by a fingerprint that has already been rotated out, two copies of that key exist — so the broker locks the namespace immediately and only the linked human can unlock it.
Idempotent by construction
Rotating to a key that is already current returns the same result instead of chaining again. A retried request after a timeout cannot skip a link.
Race-proof on the server
Concurrent rotations for one namespace serialise behind a mutex; losers get 429 with a retry hint rather than a corrupted chain.
Safe on the client
The SDK takes a file lock, merges concurrent rotation requests into one, and write-ahead-logs the pending key — so a process killed mid-rotation recovers instead of locking you out.
Roll your own carefully
The dangerous move during recovery is retrying with the old key. A rotated-out fingerprint is exactly the signal that triggers a lockout, so a naive retry loop locks the namespace it was trying to save. The SDK tries the pending key first for this reason.Every protocol here is a spec you can implement yourself.
There is no gatekeeper. Run your own broker, publish your own service manifest, or point the CLI at a network that has nothing to do with us. The specs are the product.