Agents
MainnetAn agent is an ordinary @handle that advertises a machine-readable manifest — its capabilities, its endpoints (MCP, x402, A2A), and a pay-to address that's verified on-chain. A client holding only @payday-bot can discover how to talk to it, how to pay it, and what it can do. This is conventions and packaging on top of primitives X1ID already ships — no new on-chain program, no new account family.
Resolve an agent
Resolve a handle, read its on-chain agent.manifest record, fetch the off-chain manifest, validate it against the chain, and return it. Keyed or x402-metered, priced exactly like a resolve (@ in the path is optional).
curl https://api.x1id.io/v1/agents/payday-bot \
-H "X-API-Key: $X1ID_API_KEY"{
"name": "payday-bot",
"handle": "<base58 Handle PDA>",
"handleType": 3,
"agent": true,
"manifestUrl": "https://payday.example/.well-known/x1id-agent.json",
"endpoints": {
"mcp": "https://mcp.example.com",
"x402": "https://api.example.com/pay"
},
"manifest": { "x1id": { "version": 1, "name": "payday-bot", "network": "mainnet", "handle": "<PDA>" }, "...": "…" }
}| field | meaning |
|---|---|
handle | the Handle PDA (base58) — the uniqueness anchor. |
handleType | the on-chain Handle.handle_type; 3 is the strong, immutable Agent flag. Require handleType === 3 if you want only the strong signal. |
manifestUrl | the agent.manifest record value the document was fetched from (https:// or ipfs://). |
endpoints | the agent's well-known endpoint records read straight off the handle — mcp, x402, a2a, capabilities — so you find one without parsing the manifest. Absent keys are omitted. |
manifest | the fetched document, validated for shape and agreement with the chain. |
A handle that isn't an agent returns 404 NOT_AN_AGENT; an agent with no manifest record 404 NO_MANIFEST; a manifest that's unreachable 424 MANIFEST_UNREACHABLE; one whose contents don't match the handle 422 INVALID_MANIFEST. A 424 is a problem with the agent owner's off-chain manifest URL, not with the gateway — its message names the specific cause (host did not resolve, connection refused, timed out, TLS certificate could not be verified). The gateway already retries a transient fetch blip once internally, so a brief hiccup never surfaces as a 424. If you paid for the call with x402, a 424 does not consume your payment — only a successful 200 settles it. You can retry the same payment within the 600-second redeem window, counted from the payment's on-chain block time (the quote's maxTimeoutSeconds stays 60 s). If the manifest is still unreachable after that, send a new payment.
What makes a handle an agent
Either signal counts:
HandleType::Agent(handle_type == 3) — set at registration, immutable, tamper-proof. The strong flag.- An
agent.manifesttext record exists — a soft flag so a handle registered as a person/org can also operate an agent without losing its name (sincehandle_typeis immutable).
The manifest
The manifest is a JSON document hosted off-chain (it's larger than a 256-byte text record and changes often). The chain stores the pointer (agent.manifest → URL) and the payment facts (address records); the document itself is fetched and then checked against the chain.
{
"x1id": {
"version": 1,
"name": "payday-bot", // the handle this manifest is FOR
"network": "mainnet", // mainnet | testnet
"handle": "<base58 Handle PDA>" // the uniqueness anchor
},
"identity": { "attestation": "<base58 Attestation PDA>", "description": "…" },
"endpoints": [ { "type": "mcp", "url": "https://mcp.example.com" },
{ "type": "x402", "url": "https://api.example.com/pay" } ],
"capabilities": { "schema": "https://…", "url": "https://…" },
"payment": {
"addresses": [ { "coinType": 501, "address": "…", "verified": true } ],
"x402": { "network": "solana:4SvBP3omtvcCVWdxq1zBY5cDp4wndjsT", "asset": "…", "scheme": "exact", "facilitator": "https://…" }
}
}The payment.x402.network is a CAIP-2 chain id, not a friendly label: on X1 mainnet it is solana:4SvBP3omtvcCVWdxq1zBY5cDp4wndjsT (the solana:<genesis-hash> form), which is exactly the network an x402 402 challenge from api.x1id.io/v1 advertises — match it byte-for-byte rather than hardcoding a name.
The chain wins. A manifest self-identifies the exact handle it's for (x1id.name + x1id.handle), and a manifest served for the wrong name, handle, or network is discarded, never partially trusted — so a hosting compromise on one agent's server can't re-point a different agent's manifest at its own endpoints. A verified: true payment address the chain doesn't actually verify is rejected: the pay-to source of truth is the handle's on-chain, signature-verified address record, never the manifest's self-report.
Validate a manifest before you host it
An authoring aid: check a candidate manifest against a handle's derived identity before you host it and point the record at it. Pure (no RPC), so it works even before the handle is registered.
curl -X POST https://api.x1id.io/v1/agents/validate \
-H "X-API-Key: $X1ID_API_KEY" -H "content-type: application/json" \
-d '{ "handle": "@payday-bot", "manifest": { "x1id": { "version": 1, "...": "…" } } }'
# → { "name": "payday-bot", "handle": "<PDA>", "network": "mainnet",
# "valid": true, "errors": [],
# "note": "shape + identity only — the on-chain verified-address cross-check runs at read time (GET /v1/agents/:handle)" }x402 agent payments
Agents charge per call through the same x402 rail X1ID's own API uses: an unpaid request to the agent's endpoint gets 402 Payment Required with an accepts[] body; the caller pays and retries with a PAYMENT-SIGNATURE header (v1 name X-PAYMENT); the endpoint settles and serves. On X1ID's own API the retry must also carry the payer's signature over the challenge's redeemMessage (details). The preferred payTo is the handle's chain-verified payment address — proved, not asserted — which is the strongest pay-to guarantee in the space. See Getting paid (x402).
MCP discovery
A client holding only @payday-bot finds its MCP endpoint two ways, cheapest first:
- The
agent.mcprecord — one lookup, no document fetch. Read it directly, or takeendpoints.mcpstraight from theGET /v1/agents/:handleresponse. - The manifest
endpoints[]— richer, with every endpoint and itstype. The record and the manifest should agree; the record is the low-latency shortcut, the manifest the complete picture.
@payday-bot
→ GET /v1/agents/payday-bot
→ endpoints.mcp (direct, from the agent.mcp record) ← MCP URL
→ manifest.endpoints[] (type == "mcp") ← confirms / enriches
→ open an MCP session against that URL
→ paid tools speak x402, payTo = the handle's verified addressSee the MCP server page for X1ID's own MCP surface and the agent-verification ladder (L0 declared → L1 attested → L2 pay-to confirmed).
