Agent Registration JSON & Agent Cards
Every agent registered with SelfAgentRegistry can publish a JSON document at its agentURI. This file is the agent’s identity document: it is how services, indexers, and 8004scan discover and verify the agent’s capabilities.
How it relates to A2A
The document at agentURI is an ERC-8004 registration file. When the A2A fields (version, url, provider, capabilities, securitySchemes) are included, the same document is simultaneously a valid A2A Agent Card. There is no second document and no separate A2A registration step. The two specs read non-overlapping fields and ignore what they do not recognize.
ERC-8004 reads: type, name, description, image, services[], active, registrations[], supportedTrust[]
A2A reads: name, description, url, version, provider, capabilities, securitySchemes,
skills[], defaultInputModes, defaultOutputModes
Minimal ERC-8004 format
Enough for the on-chain registry and 8004scan:
{
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "My Agent",
"description": "What this agent does",
"image": "https://example.com/avatar.png",
"services": [
{
"name": "A2A",
"endpoint": "https://my-agent.example.com/a2a",
"version": "1.0"
}
]
}
Combined ERC-8004 + A2A format
Adding the A2A fields makes this single document a valid A2A Agent Card too:
{
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "My Agent",
"description": "A human-backed AI agent verified via Self Protocol",
"image": "https://my-agent.example.com/avatar.png",
"version": "0.1.0",
"url": "https://my-agent.example.com/a2a",
"provider": { "name": "Acme Corp", "url": "https://acme.example.com" },
"capabilities": { "streaming": false, "pushNotifications": false },
"securitySchemes": [
{ "type": "bearer", "description": "API key in Authorization header" }
],
"active": true,
"services": [
{ "name": "A2A", "endpoint": "https://my-agent.example.com/a2a", "version": "1.0" },
{ "name": "MCP", "endpoint": "https://my-agent.example.com/mcp", "version": "1.0" }
],
"registrations": [
{
"agentId": 42,
"agentRegistry": "eip155:42220:0xaC3DF9ABf80d0F5c020C06B04Cced27763355944"
}
],
"supportedTrust": ["reputation"]
}
Field reference
| Field | Required | Protocol | Notes |
|---|---|---|---|
type | YES | ERC-8004 | Must be "https://eips.ethereum.org/EIPS/eip-8004#registration-v1" |
name | YES | ERC-8004 + A2A | Human-readable agent name |
description | YES | ERC-8004 + A2A | What the agent does |
image | YES | ERC-8004 | Avatar URL |
services | YES | ERC-8004 | At least one service endpoint |
services[].name | YES | ERC-8004 | One of: web, A2A, MCP, OASF, ENS, DID, email |
services[].endpoint | YES | ERC-8004 | Service URI |
services[].version | YES (for A2A/MCP) | ERC-8004 | Required by A2A protocol, e.g. "1.0" |
active | NO | ERC-8004 | Set false when the agent is inactive (e.g. proof expired) |
registrations | NO | ERC-8004 | Cross-chain registry refs using CAIP-10: eip155:<chainId>:<address> |
supportedTrust | NO | ERC-8004 | reputation, crypto-economic, tee-attestation |
version | NO* | A2A | Agent software version (e.g. "0.1.0"). *Required for an A2A Agent Card |
url | NO* | A2A | A2A primary endpoint. MUST equal services[name="A2A"].endpoint. *Required for an A2A Agent Card |
provider | NO* | A2A | Publisher identity { name, url?, email? }. *Required for an A2A Agent Card |
capabilities | NO* | A2A | { streaming: bool, pushNotifications: bool }. *Required for an A2A Agent Card |
securitySchemes | NO* | A2A | Auth methods. { type: "bearer" | "apiKey" | "oauth2" | "none" }[]. *Required for an A2A Agent Card |
defaultInputModes | NO | A2A | MIME types the agent accepts, e.g. ["text/plain"] |
defaultOutputModes | NO | A2A | MIME types the agent produces |
skills | NO | A2A | [{ name, description? }] — specific tasks the agent offers |
Constraints when combining the two
url(the A2A primary endpoint) andservices[name="A2A"].endpointMUST point to the same address. A2A clients readurl; ERC-8004 indexers readservices.version= agent software version (e.g."0.1.0");services[].version= protocol version (e.g."1.0"). These are distinct fields.- The path
/.well-known/agent-card.jsonmay serve this same document for A2A well-known discovery — no duplication required.
Agents minted via Self Protocol start with a blank agentURI
You must call setAgentURI() after registration:
registry.setAgentURI(agentId, "https://my-agent.example.com/.well-known/agent-card.json");
Until setAgentURI() is called:
- 8004scan indexes the agent with no identity document
- A2A clients cannot discover your service endpoints
- The
activeandservicesfields are invisible to the ecosystem
This does not apply to agents registered via the register(agentURI) or registerWithHumanProof(agentURI, ...) overloads, which accept a URI directly.
On-chain metadata
When registered via SelfAgentRegistry, agents can carry key-value metadata set with setMetadata(agentId, key, value):
| Key | Description |
|---|---|
agentWallet | Reserved. Set via setAgentWallet() — a payment address separate from the NFT owner, with an EIP-712 signature from the new wallet. |
Any custom key is allowed except the reserved agentWallet.
SDK helper
The TypeScript SDK builds valid documents with generateRegistrationJSON:
import { generateRegistrationJSON } from "@selfxyz/agent-sdk";
// Minimal ERC-8004 only
const minimalDoc = generateRegistrationJSON({
name: "My Agent",
description: "What it does",
image: "https://...",
services: [{ name: "A2A", endpoint: "https://...", version: "1.0" }],
});
// Combined ERC-8004 + A2A (single document, both protocols)
const fullDoc = generateRegistrationJSON({
name: "My Agent",
description: "What it does",
image: "https://...",
services: [
{ name: "A2A", endpoint: "https://my-agent.example.com/a2a", version: "1.0" },
],
a2a: {
version: "0.1.0",
url: "https://my-agent.example.com/a2a",
provider: { name: "Acme Corp" },
capabilities: { streaming: false, pushNotifications: false },
securitySchemes: [{ type: "bearer" }],
},
});
Hosting requirements
The JSON file must be:
- Accessible over HTTPS at the
agentURIprovided during registration - Served with
Content-Type: application/json - Updated whenever the agent’s services or status change (update the document, then call
setAgentURI()if the URL itself moved)
Validation
Use the 8004scan validator at 8004scan.xyz to confirm your registration JSON is correctly formatted before registering.
Where the API serves cards from
The API reads the on-chain agentMetadata and serves the stored card as HTTP JSON, so you do not have to host anything yourself if you set the card on-chain:
GET /api/cards/{chainId}/{agentId}— the Agent Card JSONGET /.well-known/a2a/{agentId}?chain={chainId}— redirects to the card resolver
See REST API and SDK Integration.
Thanks for your feedback!