skip to content

Provider quickstart

Go from a terms text file to a validator-passing agent-signup.json document, then optionally register a provider and publish a verification credential.

awaiting reviewupdated view markdown ->
for humans

This is the shortest conformant path. You write one terms file, hash its exact bytes, generate one JSON document, serve it over HTTP and validate it. The last step returns a stable error from a stub signup route. example.com and example-rp are synthetic values; replace them with your own origin and audience.

The base flow is free and needs no broker account, no SDK and no network call to the broker to accept an assertion. A provider account is optional and is required only to become a verified provider, issue authenticated receipts, or report abuse with standing.

Before you start

  • Node 22 or newer, with npm or pnpm.
  • An HTTPS origin for production. http://localhost and http://127.0.0.1 are the documented development exception.
  • A public page where a human can read the same terms you will hash.
  • No broker account, no SDK and no network call to the broker are required for the conformant path.

1. Write the terms as one text file

The complete agreement the agent accepts lives in terms.txt. This example is synthetic. The trailing newline is part of the file and therefore part of the hash.

mkdir provider-quickstart
cd provider-quickstart
cat > terms.txt <<'EOF'
Example RP Terms of Service
Version: 2026.1

These terms govern accounts created through a brokered agent signup. The agent
acts for a verified human owner, and that human owner is bound by these terms.

1. The service is provided as-is, with no uptime guarantee.
2. The human owner is responsible for the agent's actions.
3. Either party may terminate the account at any time.
EOF

The content rules are in ToS binding. Do not trim, re-wrap or extract the text from HTML when you hash it.

2. Generate the document and its hash

Save this as build-discovery.mjs. It reads the exact bytes of terms.txt, computes the digest and writes the document, so the text and hash cannot drift.

import { createHash } from "node:crypto";
import { readFileSync, writeFileSync } from "node:fs";

const text = readFileSync("terms.txt", "utf8");
const sha256 = "sha256:" + createHash("sha256").update(text, "utf8").digest("hex");

const document = {
  protocol: "agent-identity-v1",
  audience: "example-rp",
  provider_name: "Example RP",
  provider_url: "https://example.com",
  contact: "security@example.com",
  issuer_allowlist: ["https://agentregistrar.dev"],
  required_scopes: ["tos:accept"],
  terms: {
    version: "2026.1",
    url: "https://example.com/terms",
    sha256,
    text,
  },
  endpoints: {
    agent_signup: "https://example.com/api/signup/agent",
  },
};

writeFileSync("agent-signup.json", JSON.stringify(document, null, 2) + "\n");
console.log(sha256);
node build-discovery.mjs

Expected output:

sha256:6c18126f93de8d1972589ca94633ca82e76bd9ba1f0592618b0105753f3501d6

The generated document:

{
  "protocol": "agent-identity-v1",
  "audience": "example-rp",
  "provider_name": "Example RP",
  "provider_url": "https://example.com",
  "contact": "security@example.com",
  "issuer_allowlist": [
    "https://agentregistrar.dev"
  ],
  "required_scopes": [
    "tos:accept"
  ],
  "terms": {
    "version": "2026.1",
    "url": "https://example.com/terms",
    "sha256": "sha256:6c18126f93de8d1972589ca94633ca82e76bd9ba1f0592618b0105753f3501d6",
    "text": "Example RP Terms of Service\nVersion: 2026.1\n\nThese terms govern accounts created through a brokered agent signup. The agent\nacts for a verified human owner, and that human owner is bound by these terms.\n\n1. The service is provided as-is, with no uptime guarantee.\n2. The human owner is responsible for the agent's actions.\n3. Either party may terminate the account at any time.\n"
  },
  "endpoints": {
    "agent_signup": "https://example.com/api/signup/agent"
  }
}

Every field above is identity-only. The allowed and forbidden fields are in Discovery fields. Do not add product catalogs, SKUs, provisioning endpoints or OTP endpoints.

3. Serve it

Save this as serve.mjs. The discovery route returns JSON with no authentication and no redirect; the signup route is a stub that returns one stable error.

import { createServer } from "node:http";
import { readFileSync } from "node:fs";

const document = readFileSync("agent-signup.json", "utf8");

const server = createServer((req, res) => {
  if (req.url === "/.well-known/agent-signup.json" && req.method !== "GET") {
    res.writeHead(405, { allow: "GET" });
    return res.end();
  }
  if (req.url === "/.well-known/agent-signup.json") {
    res.writeHead(200, {
      "content-type": "application/json",
      "cache-control": "public, max-age=60",
      "access-control-allow-origin": "*",
    });
    return res.end(document);
  }
  if (req.url === "/api/signup/agent" && req.method !== "POST") {
    res.writeHead(405, { allow: "POST" });
    return res.end();
  }
  if (req.url === "/api/signup/agent") {
    // Stub: real verification is in the generic HTTP guide.
    res.writeHead(401, { "content-type": "application/json" });
    return res.end(JSON.stringify({
      error: "invalid_assertion",
      error_description: "Signature or JOSE header verification failed",
      retryable: false,
    }));
  }
  res.writeHead(404, { "content-type": "application/json" });
  res.end(JSON.stringify({ error: "not_found" }));
});

server.listen(3001, "127.0.0.1", () => console.log("listening on http://127.0.0.1:3001"));
node serve.mjs

Expected output:

listening on http://127.0.0.1:3001

In another terminal, check the transport:

curl -sS -D - -o /dev/null http://127.0.0.1:3001/.well-known/agent-signup.json

Expected, in part:

HTTP/1.1 200 OK
content-type: application/json
cache-control: public, max-age=60
access-control-allow-origin: *

4. Validate the document

Two paths are supported. Use the one that fits your project.

Path A, from a clone of the spec repository:

git clone https://github.com/agentregistrar/spec
cd spec
pnpm install
pnpm --silent validate:discovery http://127.0.0.1:3001/.well-known/agent-signup.json --json --probe-jwks

Path B, with the published package in any project. The package has no bin entry, so run its TypeScript source with tsx:

npm install @agentregistrar/discovery@1.2.0 tsx@4.23.15
npx tsx node_modules/@agentregistrar/discovery/src/validate-discovery.ts \
  http://127.0.0.1:3001/.well-known/agent-signup.json --json --probe-jwks

Both paths use --probe-jwks, so both report one probe. Expected from either path, shortened here:

{
  "validator": "agent-identity-v1",
  "validatorVersion": "1.2.0",
  "mode": "url",
  "valid": true,
  "exitCode": 0,
  "protocol": "agent-identity-v1",
  "audience": "example-rp",
  "providerName": "Example RP",
  "errors": [],
  "warnings": []
}

The v1.2 result also carries separate conformance and attestation objects; a document with no rp_attestation reports attestation.status: "unverified" and still exits 0. The exact field reference is in the conformance validator.

Exit code 0 means valid. 1 means the document was read and rejected; 2 means it could not be fetched. --probe-jwks additionally checks that every issuer publishes a usable JWKS and does not touch your signup endpoint.

If the validator fails on terms.sha256, hash the exact file bytes again; see Compute the ToS hash.

5. Return one stable error

Send a request with a malformed assertion:

curl -sS -X POST http://127.0.0.1:3001/api/signup/agent \
  -H 'Authorization: Bearer test' \
  -w '\nHTTP %{http_code}\n'

Expected output:

{"error":"invalid_assertion","error_description":"Signature or JOSE header verification failed","retryable":false}
HTTP 401

The envelope and the fixed code table are in Stable error codes. error_description is non-normative; agents branch on error.

6. Verify a real assertion

Replace the stub with a verifier. Start with Verify assertions, which includes a runnable local fixture and the mapping from each failed check to a stable code.

7. Register

Once the document passes live HTTPS validation, submit it with the evidence. The criteria and pull-request shape are in Validate and register.

Optional: become a verified provider

The base flow above is complete without an account. To add the optional broker-issued provider verification credential and the authenticated paths:

  1. Create a provider account with your RP broker and verify the organization contact. A new account starts pending and free.
  2. Prove the origin and audience. Create an origin registration, then satisfy either the HTTPS well-known challenge or the DNS TXT challenge. The first verified origin promotes the account to active.
  3. Register a key. Generate an Ed25519 keypair locally, request a proof-of-possession challenge, and register the public key with the required receipt/abuse purposes. The broker never sees the private key.
  4. Issue and renew the attestation. With a verified plan entitlement, an active key, and a verified origin, issue the attestation and publish it as rp_attestation in discovery. Renew on a 12-hour target against a 24-hour expiry.
  5. Sign receipts and verify their history. Sign Contract E receipts with the RP key named in the attestation, keep the exact attestation that contained that key, and send {signup_token, rp_attestation} to Contract F.
  6. Report abuse with standing. Sign the {rp_attestation, report} JWS and post it to the broker's advertised abuse_endpoint. A receipt-linked report can apply a protective suspension pending review; an unlinked report opens a case without suspending.

This release ships plan and entitlement hooks only. There is no checkout, pricing page, invoice, DPA, or provider terms text here. A free provider is conformant and cannot issue an attestation; a verified plan requires a live subscription set by the operator.

See Trust and limits for why verification is identity, not safety, and Validate and register for the registry's five-field verification snapshot.

Optional: receipt and context

A signup response may include an RP-signed signup_token and a hash-pinned context pointer to your guide. Both are optional. The v1.2 receipt has no exp; a legacy exp is ignored. See Issue a signup receipt and Publish a context guide.

Verify your work

  • terms.sha256 equals the SHA-256 of the exact terms.text bytes.
  • The discovery route returns 200, application/json, no redirect and no authentication.
  • The validator exits 0 with zero errors.
  • One failing signup request returns a fixed v1 error code.
  • No forbidden vendor field appears at any depth, including under extensions.

Next

sections

type to search.

up/down navigateenter selectesc close