Generated from
agentregistrar/specatv1.2.0(tagv1.2.0). Do not edit this page; edit the spec and re-runpnpm sync:spec.
Implementing agent-identity-v1 (Relying Party Guide)
- Status: Implementation guide for new Relying Parties (RPs). Normative contract:
agent-identity-v1.md; scope rulebook:scope-boundaries.md. - Conformance tool:
src/validate-discovery.ts— run withpnpm validate:discovery(validator guide). - Audience: a service provider (cloud/infra provider, API vendor, SaaS) that wants agents to sign up autonomously and accept its Terms of Service, with a verified human owner behind them.
This guide is a checklist, not a replacement for the specification. Where anything here disagrees
with agent-identity-v1.md, the specification wins.
0. The mental model (read this first)
The protocol is an identity and brokerage protocol. Your responsibilities end when an agent is verified, delegated, has accepted your ToS, and the signup event has been recorded. Everything about what you sell and how to use your API is your own concern, documented in a post-signup guide — never in discovery.
One-way door (scope-boundaries.md §2.3): do not put product catalogs, SKUs, pricing, resource
endpoints, provisioning APIs, credentials, legacy OTP endpoints, or vendor API usage into the
discovery document or the signed receipt. If a field answers "what do we sell?" or "how do you call
our API?", it belongs in your guide.
A useful test: would this field still make sense if you had no products at all? If not, it is vendor scope.
1. Publish the discovery document
Serve GET /.well-known/agent-signup.json on your canonical HTTPS origin:
200 OK,Content-Type: application/jsonwhen you supportagent-identity-v1.404(or410) when you do not. Never use401/403to signal non-support.OPTIONS→204with permissive CORS.Cache-Control: public, max-age=60(max300),Access-Control-Allow-Origin: *.- Public only: no secrets, no internal hostnames, no personal data. Body ≤ 1 MiB.
Required fields (agent-identity-v1.md §5.1): protocol ("agent-identity-v1"), audience,
provider_name, non-empty issuer_allowlist, unique required_scopes (including tos:accept),
terms (complete text + version + url + sha256), and endpoints.agent_signup.
Optional fields (§5.2): spec, provider_url, contact, updated_at, non-capability extensions
keyed by reverse-DNS namespace, and the optional rp_attestation compact JWS (§10 of this guide).
Nothing else. See the minimal example in §5.5 of the spec; do not copy vendor fields from older docs.
Forbidden fields (§5.3)
At any nesting depth, including under extensions, a conformant document must not contain
instance_catalog, endpoints.provision, endpoints.email_signup, endpoints.verify_otp,
endpoints.broker_signup_receipt, SKUs/pricing/tiers/quotas, or any resource/credential/API-usage
metadata. Moving a capability under extensions does not make it conformant.
2. Compute and maintain the ToS hash
terms.sha256issha256:<64 lowercase hex>over the exact UTF-8 bytes ofterms.text— no trimming, no newline normalization, no HTML extraction.terms.textis the complete agreement the agent is accepting, not a summary.terms.urlis an absolute HTTPS page where a human can read the same text.- Bump
terms.versionwhenever the text changes materially, and updatetext,sha256, andurlatomically. - The agent recomputes the hash from
terms.textbefore calling its Broker; your signup endpoint must reject an assertion whosedelegation.accepted_rp_tos_hashis not the currently advertisedterms.sha256(tos_hash_mismatch).
You may keep a short, explicitly documented grace period for in-flight assertions after a change, but the discovery document advertises only the current version, and the grace period must be documented outside discovery (e.g. in your guide or a support page).
3. Verify the assertion and enforce the checks
POST endpoints.agent_signup receives Authorization: Bearer <agent-identity-assertion>. The
assertion is Contract A (Ed25519 JWT); you only verify it here. Enforce at minimum
(agent-identity-v1.md §7.1):
- Signature verifies against
GET <iss>/.well-known/jwks.json(issuer-hosted only — never ajku/x5uheader pointing elsewhere). issis inissuer_allowlist(exact match).audequals youraudience.exphas not passed (allow at most 60s skew) and the lifetime is reasonable (≤ 5 min).jtihas not already been used for an account creation.delegation.scopesincludes everyrequired_scopesentry.human.verified === true.delegation.accepted_rp_tos_hash === terms.sha256.
Record the assertion jti, the accepted ToS version/hash, and attribution for audit. Revocation is
enforced by the Broker at minting time; you do not need a revocation feed for v1.
4. Return stable error codes
Return errors as JSON with a stable machine-readable code (agent-identity-v1.md §7.2):
{ "error": "tos_hash_mismatch", "error_description": "…", "retryable": false }The v1 codes are invalid_request (400), invalid_assertion (401), issuer_not_allowed (403),
audience_mismatch (403), assertion_expired (401), assertion_replayed (409),
tos_hash_mismatch (409), insufficient_scope (403), human_unverified (403), and rate_limited
(429). error_description is non-normative and must not be parsed for control flow. The invariant:
an agent can tell "retry", "re-consent ToS", "get a fresh assertion", and "unsupported" apart
without reading prose.
5. Issue a signup receipt (Contract E v1.2) with identity facts + optional context
On success, return 200/201 with your provider credentials and, if you support it, a receipt
signed by your own RP key (Contract E) plus the optional context pointer. The receipt is an
identity/attribution proof — keep it identity-only:
- Identity facts:
iss(your origin),aud(the human broker issuer),sub(agent id),jti(srp_…),iat,signed_up_at,assertion_jti,service.*(audience, origin, provider name/URL, opaqueaccount_id,attestation_jti,key_id),agent.*,human.*,tos.*. - No
exp. A receipt is a historical fact, not an access token. A legacyexpis ignored if present; never rely on it for access control. - Remove vendor resource claims: no
service.instance_sku, noservice.endpoint_url, and no free-form resource metadata. - Optional
context:{ "url": "https://…/docs/SKILL.md", "sha256": "sha256:…" }. Whencontextis present, both fields are required: a URL without a hash cannot be pinned, and a hash without a URL cannot be fetched.
Sign with the key in your attestation named by service.attestation_jti; service.key_id must name
that key, the JWS header kid must equal service.key_id, and the key must carry the receipt
purpose. service.origin must equal iss and the attestation rp.origin; service.audience must
equal the attestation rp.audience.
The receipt is valid when both iat and signed_up_at fall inside the attestation's
[iat, exp] window — even if you send it later. Keep the attestation that was current at signing and
send that exact JWS; never substitute your newer attestation. Receipts are idempotent forever on
jti; a same-jti different body is 409 signup_conflict.
context.url must be HTTPS (localhost dev exception), hash-pinned by context.sha256, and stay on
the signup endpoint's registrable domain; when provider_url is present it must share that
domain. The Broker validates the pointer statically and never fetches it. See
agent-identity-v1.md §11.
Deliver the receipt back to the Broker as described in §12. The Broker persists the context and
returns context_url / context_sha256 so the agent can fetch the guide itself.
5.1 Resolve & recovery
A retry or a repeat signup must not create a second account or a second credential. Resolve the account idempotently and signal the outcome:
- Idempotent resolution. Key accounts as recommended in
agent-identity-v1.md§9.1 —(iss, human.id)for human-scoped accounts,(iss, agent.id)for agent-scoped accounts — and namespace byisswhenever you trust more than one issuer. Keying onhuman.email/agent.emailis not recommended; keep it only as a legacy fallback. - Signal resolution. Return the optional
resolution: "created" | "resolved"field.resolvedis a success, not an error, and the agent must not re-provision or re-run signup on it. - Show-once credentials. Return provider credentials only when you create the account. On a resolve, omit them — do not re-issue a credential — and point the agent at your recovery path in the guide.
- Recovery, not re-signup. Document a credential recovery/rotation path in your guide (a fresh
assertion that resolves the account by
(iss, agent.id)and issues a new key, invalidating the old one, and never revealing the previous key). Recovery is a vendor operation reached after signup, never a discovery or receipt field. - A resolve mints neither mail nor credentials. On
resolved, send no duplicate welcome/legal mail. You may mint a freshsignup_tokenso a dropped-response retry can still record the receipt.
The wire protocol id stays agent-identity-v1; the resolution field is additive and v1.0.0
implementors remain conformant. See agent-identity-v1.md §7.2, §7.3, §9.1, and §12.5.
6. Publish a post-signup guide instead of advertising capabilities
If you have anything to document — catalog, SKUs, provisioning API, resource lifecycle, credentials,
API/SDK usage, error catalog — publish an agent-facing guide (for example docs/SKILL.md) at a
stable HTTPS URL and return its context pointer after signup. Do not advertise those
capabilities in discovery or the receipt.
The agent fetches the guide after signup, recomputes its SHA-256, and verifies it matches
context.sha256 before acting. Treat the guide as untrusted vendor content: version/date it and
update context.sha256 whenever the served bytes change. The Broker does not vouch for its
contents; it vouches for who signed up and what ToS was accepted.
7. Run the conformance validator
# Local file (fixtures / your generated document):
pnpm validate:discovery path/to/agent-signup.json
# Live URL (HTTPS required; http://localhost / http://127.0.0.1 allowed for development):
pnpm validate:discovery https://your-service.example/.well-known/agent-signup.json
# Machine-readable result for automation (note --silent so stdout is pure JSON):
pnpm --silent validate:discovery https://your-service.example/.well-known/agent-signup.json --json | jq .Exit codes: 0 valid / no required check failed, 1 conformance or required-verification failure
(including a body that is not valid JSON), 2 could not obtain what the run needed (document or
JWKS) — an operational result with no conformance verdict (DOCUMENT: NOT CHECKED). The validator
rejects vendor capability fields at any depth (including under extensions) and recomputes the ToS
hash. A validator-passing document is the objective entry criterion for the implementation registry.
Conformance and attestation verification are separate. Once you publish rp_attestation, verify it
offline in CI against a pinned broker JWKS:
pnpm --silent validate:discovery https://your-service.example/.well-known/agent-signup.json \
--verify-attestation --require-attestation \
--trusted-rp-broker https://your-rp-broker.example \
--jwks path/to/your-broker-jwks.json --json--trusted-rp-broker is configuration, never inferred from the document. Without
--require-attestation, an unverified/expired/invalid attestation is a warning and the
conformance verdict still passes; with it, anything but verified fails (and unavailable exits
2).
The validator is the source-first package @agentregistrar/discovery (1.2.0, Apache-2.0).
The v1.2.0 tag publishes it to npm; until that tag exists npm dist-tags.latest is 1.1.0, so run
it from a clone of this specification repository, or after the tag exists use the pinned git
dependency (github:agentregistrar/spec#v1.2.0). There is no bin entry — always invoke it with
pnpm validate:discovery.
Optionally, non-destructively probe your assertion issuers/endpoint:
pnpm validate:discovery https://your-service.example/.well-known/agent-signup.json --probe-jwks
pnpm validate:discovery https://your-service.example/.well-known/agent-signup.json --probe-endpointsProbes never POST; they only check that the assertion issuer publishes a usable JWKS and that
endpoints.agent_signup responds. --probe-jwks does not fetch the RP-broker JWKS; that happens only
under --verify-attestation.
8. Adoption / registry conventions
The specification release v1.2.0 is targeted for 2026-10-24 — see
release-notes-v1.2.0.md. This branch is the reviewed artifact, not the
publication: the tag and npm release follow the merge. Earlier releases: v1.1.0
(2026-10-18, release-notes-v1.1.0.md) and the initial v1.0.0
(2026-10-17, release-notes-v1.0.0.md). The adoption surface is
published in
registry/README.md with the machine-readable registry in
registry/implementations.json.
- The implementation registry lists only implementations whose discovery document passes the validator. Entry criteria: the validator passes, you publish a stable discovery URL, your abuse/security contact is reachable, and you publish a post-signup context guide if you have vendor capabilities to document.
- Registry
capabilitiesare protocol capabilities only (agent_signup,post_signup_context). Vendor features are not registry capabilities; they live in your guide. - Record your
discovery_urland readaudience/protocolfrom the validator's--jsonoutput (validator.md§12). - To submit an entry, open a pull request against
registry/implementations.jsonwith your validator evidence; seeregistry/README.md§4. The conformance badge snippet and its usage terms are in §5 of the same document.
9. Register as a provider, prove the origin, and register a key
Relying Party identity (Contract G) is optional for conformance but required for a verified profile,
authenticated receipts, and abuse standing. The full wire contract is agent-identity-v1.md §15.
- Create a provider account with your RP broker. The account is an organization, separate from
any individual human login. It starts
pending; it becomesactivewhen its first origin is verified. - Register the origin and audience.
POST /api/provider/v1/originswith the canonical origin and theaudienceyou publish in discovery. The audience is unique across the broker's providers; a collision is409 audience_conflictand needs operator review, not a second name. - Prove control of the origin with an HTTPS well-known file or a DNS TXT record. The challenge is
single-use with a 30-minute TTL: serve
file_contentexactly atfile_url(one optional trailing LF), or publishrecord_valueatrecord_name. The broker fetches with HTTPS-only, public-IP, no-cross-origin-redirect, 5-second, 64 KiB rules. - Generate an Ed25519 keypair locally and keep the private key. Register the public key with a
proof of possession: request a
kid-bound challenge, then sign the PoP JWS withtyp: agent-rp-key-pop+jwt,aud= the broker issuer,sub= your provider UUID, the challenge asnonce, and your public JWK. The broker never sees your private key. - Keep a rotation path. Add the new key, let both appear in attestations for the 30-day overlap, then retire the old one. Revocation is immediate; old receipts still verify from the attestation they name.
Provider API keys are separate from your RP signing key: they authenticate CI to Contract H, are scoped, hashed at rest, and never sign receipts or abuse reports.
10. Publish and renew the attestation
POST /api/provider/v1/attestation returns a compact JWS plus its metadata. Publish the JWS verbatim
as rp_attestation in /.well-known/agent-signup.json.
- The attestation
rp.originmust equal the origin serving discovery andrp.audiencemust equalaudience. Publishing a copy on another host does not move the binding. exp = iat + 86400. Renew on a 12-hour target so one missed cycle is tolerable. A renewal mints a newjti; keep serving the current one and keep the previous one long enough to submit with any receipt signed under it.- Do not put terms, scopes, endpoints, prices, or
provider_urlin the attestation. It is identity. - An expired attestation is
expired, notunverified, and arequirepolicy will refuse it. An absent field is conformant andunverified. - If you operate a broker as well, serve
/.well-known/agent-broker.jsonand the signed status endpoint described inagent-identity-v1.md§16.
Claim sources (non-normative; Issue 07 mapping). When a broker assembles the claims, each field is copied from the provider record rather than typed free-form:
rp.originfromprovider_origins.origin;rp.audiencefromprovider_origins.audience;rp.namefromprovider_origins.provider_name;rp.legal_namefromprovider_accounts.legal_name(required only atentity);rp.contact.emailfromprovider_accounts.contact_email;rp.contact.namefromprovider_accounts.name;rp.verification.origin_verified_atfromprovider_origins.verified_at;rp.verification.methodfromprovider_origins.verification_method;rp.verification.levelisentityonly whenprovider_accounts.entity_reviewed_atis non-null (operator review) with a legal name present, andrp.verification.entity_reviewed_atis that same timestamp (nullatdomain).provider_origins.contact, when present, is a human-facing security/contact URL and is not copied into the attestation. This mapping is explanatory;agent-identity-v1.md§14.1 is normative.
Test offline before publishing:
pnpm --silent validate:discovery path/to/agent-signup.json \
--verify-attestation --require-attestation \
--trusted-rp-broker https://your-broker.example \
--jwks path/to/your-broker-jwks.json --json--jwks pins one issuer's JWKS for an offline run; --at freezes the clock for a fixture.
11. Sign receipts with your RP key (Contract E v1.2)
See §5. The three rules implementers most often get wrong:
expis gone. Do not gate account access on the receipt. A receipt proves a past signup.- The key comes from the attestation.
service.key_idmust be in thekeys[]of the attestation named byservice.attestation_jti, withreceiptin its purposes, and the JWS headerkidmust equalservice.key_id. There is no broker-key fallback. - The attestation is historical. Keep the exact attestation you signed under. A receipt is
accepted when
iatandsigned_up_atfall inside that attestation's window, even if you submit it after the attestation expired.
12. Deliver the receipt to the broker (Contract F)
POST /api/broker/signup-receipt with:
{ "signup_token": "<your compact receipt JWS>", "rp_attestation": "<the exact attestation JWS>" }Both fields are required; signup_token is the receipt. The broker verifies your attestation
(including that its iss is trusted by that human or by the broker default), checks the receipt
window against the attestation using the signing time, verifies your key and binding, resolves the
assertion acceptance, and records proof_source: "rp_callback".
The MCP Tier 2 path (identity_record_signup) carries the same two values; if the agent omits the
attestation, the broker fetches discovery at service.origin and requires the presented attestation's
jti to equal service.attestation_jti. A duplicate returns the existing row with
deduplicated: true; a same-jti different body is 409.
| What went wrong | Response |
|---|---|
| Attestation broken or bound to another origin/audience | 401 invalid_attestation |
| Attestation issuer not trusted | 401 rp_broker_untrusted (checked before any JWKS fetch) |
| Receipt broken or badly signed | 401 invalid_signup_token |
| Receipt signed outside the attestation window | 401 attestation_not_valid_at_signing |
| Exact attestation no longer retrievable | 401 attestation_unavailable (never a silent substitution) |
| Broker metadata/JWKS outage | 503 rp_broker_unavailable (retryable) |
13. Report abuse with standing (Contract D.2)
Post {"rp_attestation": "<JWS>", "report": "<JWS>"} where the report is signed by your RP key with
typ: agent-rp-abuse+jwt, iss = your origin, aud = the human broker's issuer, jti: abr_…,
exp = iat + 300, the agent_id, a 1–500 character reason, and optional evidence
(assertion_jti, signup_token_jti).
Your attestation must be current at report time; the five-minute report bound is never extended.
A report is verified only if it links to a signup whose proof source is rp_callback or rp_signed
and whose attestation was verified. A report without that link is recorded as unverified, opens a
case, and does not suspend the agent. Unauthenticated legacy posts get 401 unauthenticated_report.
14. Serve broker metadata and verify a peer attestation
If you are an RP broker (or the human broker), serve /.well-known/agent-broker.json with issuer
equal to the document origin, a same-origin jwks_uri, attestation_status_uri, abuse_endpoint,
protocol_versions containing agent-identity-v1, and a contact. Cache metadata for at most an hour
and status responses for at most five minutes. Keep retired attestation-signing keys in the JWKS for
at least 30 days after the last attestation they signed expires.
To verify a peer RP attestation (for example before accepting a receipt or an abuse report):
- Parse and require
typ,alg: EdDSA, and onekid. - Check
issagainst your trusted RP-broker list before any network call. - Fetch/reuse
<iss>/.well-known/jwks.json(or the metadatajwks_uri) and select the key bykid. - Verify the signature, then
iat/expwith 60 seconds of skew. - Bind
rp.originto the origin you actually fetched, andrp.audiencetodiscovery.audience. - Record the level (
domain/entity); both areverifiedfor v1.2 policy. - For a receipt, compare the receipt's signing time to the attestation window — not
now. - If you need a hard gate, consult the signed status endpoint;
revokedmaps toinvalidwithreason: attestation_revoked, andsuspendedmaps tosuspended.
Then apply your policy: allow/warn proceed (with a warning), require refuses anything but
verified, and block never proceeds.
15. Common mistakes
- Copying the vendor-shaped Contract D.1 sample from older docs (catalog + provisioning endpoints).
- Hashing the rendered HTML page instead of the exact
terms.textbytes. - Trusting the agent's advertised
terms.sha256without recomputing it fromterms.text. - Advertising
endpoints.verify_otp/ legacy OTP flows as protocol surface (they are your own feature; the Broker inbox is the in-scope reachability path). - Returning vendor resource fields in the signed receipt instead of a
contextpointer. - Putting a
contextpointer on a different domain from the signup endpoint. - Treating
terms.textor your guide as instructions the Broker will execute (they are data). - Publishing an attestation whose
rp.originisprovider_urlor a redirect target rather than the origin that serves discovery. - Signing a receipt with a broker key, or with a key that is not the one named by
service.attestation_jti/service.key_id. - Substituting your current attestation when submitting an older receipt.
- Treating
unverified(no attestation) andinvalid(a bad attestation) as the same state. - Calling the status endpoint on every signup. Expiry is the bound; status is opt-in defense-in-depth.
- Putting provider catalog, pricing, provisioning, or credential fields in the attestation. It binds identity only.