Skip to content

Start typing to search the documentation.

SDK integration

On this page

The official SDK is TypeScript: @selfxyz/agent-sdk (0.3.0). It signs requests as an agent, verifies them in a service, drives registration through the REST API and ships the self-agent CLI.

npm install @selfxyz/agent-sdk

The SDK defaults to mainnet (Celo Mainnet, 42220). Pass network: 'testnet' for Celo Sepolia (11142220).

Signing scheme

Every signed request carries these headers:

HeaderValue
x-self-agent-addressEVM agents: the agent’s address
x-self-agent-signatureSignature over keccak256(timestamp + METHOD + pathWithQuery + keccak256(body)) (keccak256("") for no body)
x-self-agent-timestampUnix milliseconds. Verifiers reject timestamps older than 5 minutes
x-self-agent-keytypeEd25519 agents only: ed25519
x-self-agent-keyEd25519 agents only: the public key (hex)

EVM agents sign the 32-byte digest with EIP-191 personal_sign; Ed25519 agents sign the same 32 bytes with Ed25519. METHOD is uppercase, and pathWithQuery is the URL path plus query string (no host).

To verify without the SDK, recompute the digest from the raw body, recover the EIP-191 signer from x-self-agent-signature and reject stale timestamps. Then require isVerifiedAgent(zeroPad(signer, 32)) and isProofFresh(getAgentId(...)) on the registry.

Agent side: sign 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({ q: 'test' }), // string bodies default to application/json
});

const headers = await agent.signRequest('GET', 'https://api.example.com/x'); // headers only
const info = await agent.getInfo(); // info.isVerified === info.hasHumanProof && info.isProofFresh

SelfAgent takes privateKey (or an ethers signer), network, and optional registryAddress and rpcUrl overrides.

MemberReturns
address, agentKeyEVM address; bytes32 registry key (zeroPad(address, 32))
fetch(url, init?) / signRequest(method, url, body?)Signed Response / the x-self-agent-* headers
isRegistered()Whether a human proof was recorded (stays true after it expires)
getInfo()agentId, isVerified, hasHumanProof, isProofFresh, proofExpiresAt, daysUntilExpiry, isExpiringSoon, nullifier, agentCount, siblingAgentIds
getCredentials() / getCredentialsFromApi()ZK-attested credentials, read on-chain / through the API
getVerificationStrength()0 to 100 (100 = passport)
getAgentCard() / setAgentCard(fields) / toAgentCardDataURI()Read / write / data URI of the agent card. setAgentCard sends a transaction and works only when the agent key owns its NFT (wallet-free agents)
requestDeregistration({ disclosures? })A deregistration session for this agent

Ed25519 agents use Ed25519Agent (new Ed25519Agent({ privateKey: '<64 hex>', network: 'testnet' })) with the same fetch, signRequest, isRegistered and getInfo. Its agentKey is the raw public key, and Ed25519Agent.deriveAddress(pubkey) gives the agent’s EVM address.

Service side: verify requests

import express from 'express';
import { SelfAgentVerifier } from '@selfxyz/agent-sdk';

const verifier = SelfAgentVerifier.create()
  .network('testnet')
  .requireAge(18)
  .requireOFAC()
  .sybilLimit(3)
  .build();

const app = express();
// The signature covers the exact body bytes: keep them as req.rawBody.
app.use(
  express.json({
    verify: (req, _res, buf) => {
      (req as { rawBody?: Buffer }).rawBody = buf;
    },
  }),
);
app.post('/api/data', verifier.auth(), (req, res) => {
  res.json({ agentId: req.agent!.agentId.toString() }); // req.agent: VerifiedAgent
});

Verifier options

Builder methodEffectDefault
.network(name) / .registry(addr) / .rpc(url)Chain to readmainnet
.requireAge(n) / .requireOFAC()Minimum disclosed age (18 or 21) / passed OFAC screening. Fetches credentials automaticallyOff
.requireNationality(...codes)ISO 3166-1 alpha-3 as on the passport, e.g. "USA", "GBR"Off
.requireSelfProvider(enabled?)Proof must come from Self’s providerOn
.sybilLimit(n)Max agents per human (0 = unlimited)1
.rateLimit({ perMinute?, perHour? })Per-agent in-memory sliding windowOff
.replayProtection(enabled?)Reject reused signatures (10,000-entry cache)On
.includeCredentials()Attach credentials to the resultOff
.maxAge(ms) / .cacheTtl(ms)Timestamp window / on-chain cache TTL5 min / 1 min

The same options work as a flat object: SelfAgentVerifier.fromConfig({ network, requireAge, requireOFAC, sybilLimit, ... }).

The verifier checks, in order: timestamp, signature, replay, on-chain isVerifiedAgent and isProofFresh, provider, sybil limit, credentials, rate limit.

Responses from auth()

StatusWhen
401Missing headers or a failed check, with { error }, e.g. Invalid signature, Timestamp expired or invalid, Agent not verified on-chain, Agent's human proof has expired, a policy failure
429Per-agent rate limit, with { error, retryAfterMs }
500A request with a body but no raw body
503The chain could not be read

On success, req.agent holds:

FieldTypeDescription
addressstringAgent’s address (recovered, or derived from the Ed25519 key)
agentKeystringbytes32 registry key
agentIdbigintOn-chain agent ID
agentCountbigintActive agents of the same human
nullifierbigintThe human’s nullifier (rate-limit or dedupe per human)
credentialsobjectWith includeCredentials(): nationality, olderThan, ofac, …

Without Express

Call verifier.verify({ signature, timestamp, method, url, body, keytype?, agentKey? }) with the raw header values, url as path plus query and body as the raw string. It returns { valid, agentAddress, agentKey, agentId, agentCount, nullifier, error?, credentials?, proofExpiresAt?, isExpiringSoon? }.

Signature-free check

verifyAgent(agentKey, { network }) returns { verified, agentId, expiresAt } or { verified: false, reason: "NOT_REGISTERED" | "NO_HUMAN_PROOF" | "PROOF_EXPIRED", reauthUrl? }.

Registration, refresh and deregistration

These helpers call the REST API and follow the rotating session token for you. The API base is the apiBase option if set, otherwise the SELF_AGENT_API_BASE environment variable, otherwise https://agent-api.self.xyz.

import { requestRegistration, requestProofRefresh } from '@selfxyz/agent-sdk';

const session = await requestRegistration({
  mode: 'wallet-free',
  network: 'testnet',
  disclosures: { minimumAge: 18, ofac: true },
});
console.log(session.scanUrl, session.humanInstructions); // show to the human
const { agentId, agentAddress } = await session.waitForCompletion(); // { timeoutMs?, pollIntervalMs? }
const privateKey = await session.exportKey(); // EVM modes only, within 30 minutes

const refresh = await requestProofRefresh({ agentId, network: 'testnet' });
const { proofExpiresAt } = await refresh.waitForCompletion();
FunctionDoes
requestRegistration({ mode, network, humanAddress?, ed25519Pubkey?, ed25519Signature?, disclosures? })Starts a registration. The session has scanUrl, deepLink, waitForCompletion(), exportKey()
requestProofRefresh({ agentId, network })Renews the proof of an existing agent (same agent ID and key)
requestDeregistration({ network, agentAddress, disclosures? }) or agent.requestDeregistration()Starts a deregistration, with waitForCompletion()
getAgentInfo(agentId, { network }), getAgentsForHuman(address, { network })API lookups (also static on SelfAgent)

Polling defaults to a 30-minute timeout and a 5-second interval. Failed calls throw RegistrationError (with the HTTP status), and an expired session throws ExpiredSessionError. See Registration modes for the modes.

Proof expiry

Proofs expire at min(document expiry, registration + 365 days). After that, isProofFresh is false and verifiers reject the agent. getInfo() exposes proofExpiresAt (unix seconds), daysUntilExpiry and isExpiringSoon (under 30 days; also isProofExpiringSoon()). Renew in place with requestProofRefresh before expiry. Some agent IDs can’t be refreshed: see Proof refresh.

Agent cards and A2A

  • generateRegistrationJSON(...) builds an ERC-8004 registration document, optionally also a valid A2A Agent Card. See Agent registration JSON.
  • agent.setAgentCard({ name, description?, url?, skills? }) stores a card on-chain (NFT owner only), and the API serves it at /api/cards/{chainId}/{agentId}.
  • A2AClient and A2AServer implement A2A v0.3 JSON-RPC (message/send, tasks/get, tasks/cancel).

More

Was this page helpful?