Clear ExchangeDocs Live demo

Agent authentication

Agents can identify themselves to the exchange in three ways. All three work side by side, so the public demo stays easy to use.

ModeHow the agent identifies itselfBudget and rate limitEntitlement
DemoA self-declared agent_id in the request bodyNoneA plain bearer token, as before
API keyX-Agent-Key: <key>Per key, enforced by the exchangeBound to the agent
Signed (Web Bot Auth)A signed request, as above or on its ownPer keyBound to the agent, with the verified key recorded

The demo path

By default (AGENT_AUTH=optional) nothing changes for anonymous agents: send agent_id in the body, with no credentials, and everything in the API reference works. The harness, the e2e script and the web console use this path unless told otherwise. One rule protects authenticated agents: an agent_id that belongs to a registered agent cannot be claimed without its credentials (401 agent_auth_required).

Set AGENT_AUTH=required to refuse every agent call that has neither a key nor a signature. Use it on a deployment that has no anonymous demo traffic.

Issuing keys (operator)

Keys are issued with the admin API, using the ADMIN_TOKEN, or in the web UI under Operator, Agent keys.

curl -s -X POST $BASE/v1/admin/agents \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \
  -d '{"label":"acme research assistant","budget":{"amount":"25.00","currency":"USD"},"rate_limit_per_minute":120}'

The response holds the agent (its agent_id, budget, spend and settings) and the api_key. The key is shown once; the exchange stores only a SHA-256 hash and a short key_hint. Fields:

FieldMeaning
labelWho the key is for (required, up to 80 characters)
budget{ "amount": "25.00", "currency": "USD" }, or null for unlimited. Defaults to AGENT_DEFAULT_BUDGET
rate_limit_per_minuteRequests per minute, or null for none. Defaults to AGENT_DEFAULT_RATE_LIMIT (60)
signing_keysUp to five Ed25519 public JWKs the agent signs requests with (see below)
require_signatureIf true, the key alone is refused: requests must be signed too

Other endpoints: GET /v1/admin/agents (all agents, with spend, reserved and remaining budget), GET /v1/admin/agents/{id}, PATCH /v1/admin/agents/{id} (change budget, rate limit or signing keys), POST /v1/admin/agents/{id}/rotate-key (a new key; the old one stops working) and DELETE /v1/admin/agents/{id} (revoke).

Using a key

Send X-Agent-Key on every agent call (query, accept, retrieve, telemetry, acquire). It is separate from the entitlement, which still travels as Authorization: Bearer. The agent id is the key's, so agent_id can be left out of the body; if sent it must match (403 agent_id_mismatch).

Budget and rate limit

  • Budget. Accepting an offer reserves its price against the key's budget. Spend is paid retrievals plus open entitlements (accepted, not yet retrieved, not expired). An offer that does not fit is refused with 402 agent_budget_exceeded; details gives the budget, spent, reserved, remaining and the offer's price. A budget in one currency refuses offers in another (402 agent_budget_currency).
  • Rate limit. Each authenticated call counts toward a fixed one-minute window. Responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. Over the limit is 429 rate_limit_exceeded with Retry-After.

Signed requests (Web Bot Auth)

An agent that signs its requests with HTTP Message Signatures (RFC 9421), following the IETF Web Bot Auth draft, is verified cryptographically, without the shared secret leaving the agent. Register the agent's Ed25519 public key (a JWK) in signing_keys when issuing the key; the keyid is the RFC 7638 JWK thumbprint, which the admin response returns.

A signature must:

  • use Ed25519 with tag="web-bot-auth", a keyid, a nonce, and created and expires (at most 10 minutes apart, created not more than 60 seconds ahead);
  • cover @method, @authority and @path, plus @query when there is a query string, plus content-digest (RFC 9530, sha-256) when there is a body.

Each nonce is accepted once per key, so a captured request cannot be replayed. A signed request may carry X-Agent-Key as well; if it does not, the key is found from the keyid. A verified signature raises the assurance on the agent from api_key to web_bot_auth. Anything outside this profile is rejected, with 401 agent_signature_invalid and a reason. The repository's harness/signing.ts is a small reference signer.

Entitlements are bound to the agent

An entitlement issued to an authenticated agent carries cx.agent:

"agent": { "agent_id": "agt_…", "label": "acme research assistant", "assurance": "web_bot_auth", "key_thumbprint": "…" }

Retrieval and telemetry with that token require the same agent's credentials as well. A leaked token alone gets 401 agent_auth_required, and another agent's credentials get 403 entitlement_agent_mismatch. The accept response, the entitlement the publisher can inspect, and each ledger entry (agent_assurance) say how the buyer was identified, so publishers can tell a verified agent from a declared one. Entitlements for demo agents stay plain bearer grants.

Errors

StatusCodeWhen
401agent_auth_requiredNo credentials in required mode, a registered agent_id without its credentials, or a bound entitlement used without the agent's credentials
401agent_key_invalidX-Agent-Key unknown, rotated or revoked
401agent_signature_requiredThe key requires signed requests
401agent_signature_invalid, agent_signature_replayedSignature rejected; nonce reused
402agent_budget_exceeded, agent_budget_currencyThe offer does not fit the key's budget
403agent_id_mismatch, entitlement_agent_mismatchThe body or the entitlement names a different agent
429rate_limit_exceededOver the per-key rate limit

Configuration

VariableDefaultMeaning
AGENT_AUTHoptionaloptional or required
AGENT_DEFAULT_BUDGET5.00Budget for a new key that names none (USD), or unlimited
AGENT_DEFAULT_RATE_LIMIT60Requests per minute for a new key that names none; 0 for none

Not built yet

The exchange verifies signatures against keys the operator registers. It does not yet fetch an agent operator's published key directory (Signature-Agent, /.well-known/http-message-signatures-directory), so a signing agent must be registered first. Rate limits are per process and in memory.