Skip to content
Skip the search — install the Self skill Let your AI agent integrate Self for you.

Start typing to search the documentation.

Verifying Agents (Service Operator)

This guide shows how to add Self Agent ID verification to your API. After setup, only registered, human-backed agents can access your protected endpoints.

1. Install the SDK

npm install @selfxyz/agent-sdk    # TypeScript
pip install selfxyz-agent-sdk      # Python
cargo add self-agent-sdk           # Rust

2. Create a Verifier

Use the builder pattern to configure verification policy:

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

const verifier = SelfAgentVerifier.create()
  .network("mainnet")
  .requireAge(18)
  .requireOFAC()
  .requireSelfProvider()   // Ensure Self Protocol proofs (default: true)
  .sybilLimit(3)           // Max 3 agents per human
  .rateLimit({ perMinute: 10 })
  .build();

Builder Options

MethodDescriptionDefault
.network(name)"mainnet" or "testnet""testnet"
.requireAge(n)Minimum age (18 or 21)None
.requireOFAC()OFAC sanctions screeningOff
.requireNationality(...codes)Allowed ISO country codesAny
.requireSelfProvider()Require Self Protocol proofstrue
.sybilLimit(n)Max agents per human (0 = unlimited)1
.rateLimit(config)Per-agent rate limitingNone
.replayProtection(enabled?)Signature replay detectiontrue
.includeCredentials()Attach credentials to requestfalse
.maxAge(ms)Max signature age300000 (5 min)
.cacheTtl(ms)On-chain query cache60000 (1 min)

3. Add Middleware

import express from "express";

const app = express();
app.use(express.json());

// Protect all /api routes
app.use("/api", verifier.auth());

app.post("/api/data", (req, res) => {
  // req.agent is populated after verification
  console.log("Agent:", req.agent.address);
  console.log("Agent ID:", req.agent.agentId);
  console.log("Credentials:", req.agent.credentials);
  res.json({ ok: true });
});

4. Request Shape After Verification

After successful verification, req.agent (or equivalent) contains:

FieldTypeDescription
addressstringAgent’s Ethereum address
agentIdnumberOn-chain NFT token ID
agentKeystringDerived bytes32 key
isVerifiedbooleanOn-chain verification status
proofProviderstringProvider contract address
credentialsobjectZK-attested credentials (if includeCredentials())

5. Credential-Based Access Control

Use credentials to gate by age, OFAC status, or nationality:

const verifier = SelfAgentVerifier.create()
  .requireAge(21)                          // Must be 21+
  .requireOFAC()                           // Must pass OFAC screening
  .requireNationality("US", "GB", "DE")    // Only these countries
  .includeCredentials()                    // Attach credentials to request
  .build();

app.post("/api/restricted", verifier.auth(), (req, res) => {
  const { nationality, olderThan, ofac } = req.agent.credentials;
  // nationality: "US", olderThan: 21, ofac: [true, true, true]
});

6. Sybil Resistance

Control how many agents one human can use against your API:

SettingBehavior
.sybilLimit(1)Strict — one agent per human (default)
.sybilLimit(5)Moderate — up to 5 agents per human
.sybilLimit(0)Detection only — unlimited, but sameHuman() available

The verifier checks getAgentCountForHuman(nullifier) on-chain.

7. Provider Verification

The verifier checks that getProofProvider(agentId) matches Self Protocol’s provider address.

8. Replay Protection + Rate Limiting

Replay protection (enabled by default):

  • Caches {signature + timestamp} hashes (10,000 entries)
  • Same signature cannot be used twice

Rate limiting (optional):

.rateLimit({ perMinute: 10, perHour: 100 })

Per-agent sliding-window rate limits.

9. Request Body Fidelity (raw bytes)

Signature verification is byte-sensitive: the signature covers a hash of the exact request body. Verify against the raw bytes your HTTP server received, not a re-serialized object.

  • Prefer raw-body capture (for example, a JSON parser verify hook that stashes req.rawBody).
  • Do not mutate, normalize, or re-stringify parsed JSON before verifying.
  • If you cannot access raw bytes, enforce a single canonical serialization end to end.
import express from "express";

const app = express();
app.use(
  express.json({
    verify: (req: any, _res, buf) => {
      req.rawBody = typeof buf === "string" ? buf : buf.toString("utf8");
    },
  }),
);
app.use("/api", verifier.auth());

10. Verification Drills

Run these three drills against a registered agent to confirm an integration is wired correctly:

DrillActionExpected result
TamperSign body A, then send body B with the same signed headersInvalid-signature rejection (403)
ExpiredSend a timestamp older than the configured maxAge (default 5 min)Timestamp-freshness rejection (403)
ReplaySubmit an identical signed request twiceFirst accepted, second rejected (replay protection on)

A quick pre-deploy smoke check: one registered-agent success request plus at least two of the failure drills above, with the verifier health endpoint confirmed reachable.

11. Error Handling

The middleware returns standard HTTP errors:

StatusMeaning
401Missing or invalid signature headers
403Agent not verified, failed policy check, or rate limited
500On-chain query failed

Handle these in your client:

const res = await agent.fetch("https://api.example.com/data", { method: "POST" });
if (res.status === 403) {
  const error = await res.json();
  // { error: "Agent not verified on-chain" }
  // { error: "Age requirement not met: requires 18, has 0" }
  // { error: "Rate limit exceeded" }
}

Next Steps

Was this page helpful?