Building an agent
On this page
This guide registers an AI agent with Self Agent ID and makes authenticated requests with it. By the end, your agent has an on-chain identity backed by a human’s passport scan.
1. Install the SDK
npm install @selfxyz/agent-sdk
@selfxyz/agent-sdk is the official SDK (TypeScript). From another language, register through the REST API and sign requests with the signing scheme.
2. Choose a mode
| Mode | Best for | Human wallet |
|---|---|---|
wallet-free | Quick start, embedded agents, CLI-only setups | No |
linked | Autonomous agents owned by a human’s wallet | Yes |
privy / smartwallet | Humans with a Privy or passkey smart wallet | Yes |
ed25519 | Agents that already sign with Ed25519 (OpenClaw, ElizaOS) | No |
ed25519-linked | Ed25519 agents with a guardian wallet | Yes |
Details: Registration modes.
3. Register
Use Celo Sepolia (testnet) while developing, where the human scans with a mock passport. Celo Mainnet (mainnet) needs a real passport or ID card.
With the SDK
import { requestRegistration } from '@selfxyz/agent-sdk';
const session = await requestRegistration({
mode: 'linked',
network: 'testnet',
humanAddress: '0xYourWallet',
disclosures: { minimumAge: 18, ofac: true },
});
console.log(session.scanUrl); // a QR image URL: show or send it to the human
const { agentId } = await session.waitForCompletion();
const privateKey = await session.exportKey(); // within 30 minutes; store it as a secret
With the CLI
npx -p @selfxyz/agent-sdk self-agent register init --mode linked --human-address 0xYourWallet --network testnet --minimum-age 18 --ofac --out .self/session.json
npx -p @selfxyz/agent-sdk self-agent register open --session .self/session.json # prints the QR URL
npx -p @selfxyz/agent-sdk self-agent register wait --session .self/session.json
npx -p @selfxyz/agent-sdk self-agent register export --session .self/session.json --unsafe --out-key .self/agent.key
The CLI generates the key locally and defaults to testnet. See CLI.
With plain HTTP
curl -s -X POST https://agent-api.self.xyz/api/agent/register \
-H "Content-Type: application/json" \
-d '{"mode":"wallet-free","network":"testnet"}'
Show the human the scanUrl, then poll GET /api/agent/register/status with Authorization: Bearer <sessionToken> until stage is completed, sending the rotated token from each response. Then call POST /api/agent/register/export with the latest token. Full walkthrough: REST API quick start.
From another agent (A2A)
Agents can register themselves through the A2A endpoint with a register intent, then poll with the status intent:
curl -s -X POST https://agent-api.self.xyz/api/a2a \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"data","data":{"intent":"register","mode":"wallet-free","network":"testnet"}}]}}}'
The task’s data part carries the session token and QR links. Send { "intent": "help" } for the mode decision guide. See A2A.
4. Sign outbound requests
import { SelfAgent } from '@selfxyz/agent-sdk';
const agent = new SelfAgent({ privateKey: process.env.AGENT_PRIVATE_KEY!, network: 'testnet' });
const res = await agent.fetch('https://api.example.com/data', {
method: 'POST',
body: JSON.stringify({ query: 'test' }),
});
fetch adds x-self-agent-address, x-self-agent-signature and x-self-agent-timestamp (unix ms). Ed25519 agents use Ed25519Agent the same way.
5. Check status and credentials
const info = await agent.getInfo();
// { agentId, isVerified, hasHumanProof, isProofFresh, proofExpiresAt, daysUntilExpiry, isExpiringSoon, ... }
const creds = await agent.getCredentials();
// { nationality, olderThan, ofac, dateOfBirth, gender, issuingState, ... } (disclosed fields only)
6. Keep the proof fresh
Proofs expire at min(document expiry, registration + 365 days). When info.isExpiringSoon is true (under 30 days), renew in place. The agent ID and key stay the same:
import { requestProofRefresh } from '@selfxyz/agent-sdk';
const refresh = await requestProofRefresh({ agentId: Number(info.agentId), network: 'testnet' });
console.log(refresh.scanUrl); // the same human scans it
await refresh.waitForCompletion();
7. Publish an agent card (optional)
await agent.setAgentCard({
name: 'My Agent',
description: 'Analyzes market data',
url: 'https://myagent.example.com/a2a',
skills: [{ id: 'market-analysis', name: 'Market analysis' }],
});
setAgentCard writes the card on-chain and works when the agent key owns its NFT (wallet-free). For linked agents, the human’s wallet writes it. The API then serves the card at /api/cards/{chainId}/{agentId}. For a hosted ERC-8004 document, see Agent registration JSON.
Common pitfalls
- Match the document to the network. Celo Sepolia accepts mock passports only and Celo Mainnet real documents only. The wrong one fails with
InvalidIdentityCommitmentRoot. - Use the latest session token. Each status poll rotates it, and exporting with the start token returns
409. - Protect the exported key. It is the agent’s identity. Keep it in a secrets manager, never in source control.
Next steps
Thanks. What went wrong?
Thanks for your feedback!