PocketDocsPowered by x402

Requiring a verified human

Let an agent spend only when a person verified with Self stands behind it.

In short

  1. On the agent's page, Register with Self and scan the QR code with the Self app.
  2. Connect the agent with its Self key, so every request it sends is signed.
  3. Turn on the gates you want in Policy. All are off by default.

An API key proves your organization issued a credential, not that a person stands behind the agent. Self Agent ID answers that: a person proves who they are once with their passport, registers the agent to themselves, and the agent signs every request. Pocket checks the signature and the proof before money moves.

An agent's page with the Self verification card showing Not verified and a Register with Self button.
The Self verification card, on the agent's page

1. Register the agent

An agent with no registered human shows Not registered with Self and a Register action. It shows a QR code; the person scans it with the Self app and approves. Only a signed-in person can do this, never an API key.

An API key cannot link an agent to Self, so a leaked key cannot register agents that policy would read as human-backed.

Through the API, two calls, as a signed-in person:

  1. POST /v1/agents/{id}/self/registration starts it. Optional body names what to ask the person to disclose: { "disclosures": { "minimumAge": 18, "ofac": true } }. The answer carries a sessionId, a scanUrl (a QR code image), a deepLink and an expiry.
  2. POST /v1/agents/{id}/self with { "sessionId": "srs_..." } finishes it: 202 "pending" until the person scans, then 200 "linked".
CodeMeans
SELF_SESSION_ALREADY_USEDThe session was already finished
SELF_SESSION_EXPIREDThe session lapsed. Start again
SELF_PROOF_VERIFICATION_FAILEDThe scan left no verified human
SELF_AGENT_ALREADY_LINKEDThe agent has a link. Deregister it first
SELF_AGENT_NOT_REGISTEREDThe registry does not hold that id at that address

Calls to Self's hosted service time out after 10 seconds with UPSTREAM_UNAVAILABLE; the session is kept, so retry. They are limited to 10 a minute per credential.

Already registered with Self elsewhere? Pass it when creating the agent:

POST /v1/agents
Authorization: Bearer <session token>

{
  "name": "Research agent",
  "self": { "agentId": "42", "address": "0x..." }
}

2. Sign every request

A registered agent adds two headers to each request:

HeaderWhat it carries
x-self-agent-signatureThe signature over this exact method, path and body
x-self-agent-timestampWhen it was signed, in Unix milliseconds

Simplest: connect to the MCP server with the agent's Self key beside your API key, and it signs for you:

claude mcp add --transport http pocket https://mcp.pocket-app.xyz/mcp \
  --header "Authorization: Bearer pocket_sk_..." \
  --header "X-Self-Agent-Key: 0x..."

The key is used for that request only: never stored, logged, or shown to the model. Registering through the dashboard shows the key once, the moment it links. Lost it? Remove link to Self and register again.

RuleOtherwise
Sign it just now (within 5 minutes)AGENT_SIGNATURE_EXPIRED
Sign it onceAGENT_SIGNATURE_REPLAYED
Send both headers or neitherUNAUTHORIZED

The gates judge the agent that signed, and only when it is the Self agent linked to the agent paying.

Sellers that serve verified agents only

When a seller refuses with AGENT_SIGNATURE_REQUIRED, an MCP server holding the agent's key buys again with the agent's signature over the seller's request (sellerSignature). Pocket forwards it only when it comes from the agent that signed the purchase. A seller that still refuses comes back failed with SELLER_REFUSED, and nothing is charged.

What an agent cannot change

An agent can never loosen its own limits: changing a policy, budget or status, deleting or moving funds, and approving payments all answer FORBIDDEN to a request a Self agent signed. Only a signed-in person can make those changes.

3. Turn on the gates

In Policy. Each is off until set, and each refuses with its own code.

In the dashboardAPI fieldWhat it does
Require a verified humanrequireHumanVerifiedOnly pay when this agent is registered to a person verified with Self.
Only for payments over $[ ]requireHumanVerifiedAboveUsdPayments we can't price count as over the limit.
Minimum owner age [ ]minimumAgeThe verified owner must be at least this old.
Block sanctioned ownersrequireOfacCleanRefuse if the owner is on the OFAC sanctions list.
Allowed nationalitiesallowedNationalitiesLeave empty to allow any nationality.
Max agents per person [ ]maxAgentsPerHumanCounts every agent this person has registered with Self, including agents for other apps.
  • Only for payments over $[ ]: small purchases run unattended; larger ones need a verified human.
  • Allowed nationalities takes codes like GBR or IND. Empty, null or left out allows anyone. Never send []: the API refuses it.
PUT /v1/agents/agent_1a2b3c/policy
Authorization: Bearer <session token>

{
  "allowedAssets": ["USDC"],
  "allowedChains": ["celo-sepolia"],
  "allowedCategories": ["data", "research"],
  "maxTransactionAmount": "5.00",
  "unknownRecipientBehaviour": "require_approval",
  "requireHumanVerified": false,
  "requireHumanVerifiedAboveUsd": "1.00",
  "minimumAge": 18,
  "requireOfacClean": true,
  "allowedNationalities": null,
  "maxAgentsPerHuman": 3
}

Saving replaces the whole document: a gate you leave out is turned off.

When a gate refuses

The refusal names the rule and the limit, never the person's nationality.

CodeWhat stopped the payment
AGENT_HUMAN_NOT_VERIFIEDNo verified human stands behind the agent, or the request was not signed
AGENT_PROOF_STALEThe person's proof has expired
AGENT_MINIMUM_AGE_NOT_METThe person has not proved the minimum age
AGENT_OFAC_NOT_CLEANThe person has not proved a clean sanctions screening
AGENT_NATIONALITY_NOT_ALLOWEDThe person has not proved an allowed nationality
AGENT_PER_HUMAN_LIMIT_EXCEEDEDThe person already has more agents registered than the limit

The dashboard reads the per-person limit back with your number:

Refused: the person behind this agent already has the maximum of {limit} agents.

An agent with no registered human shows only AGENT_HUMAN_NOT_VERIFIED, beside Not registered with Self. Through the API, the refusal's violations list every gate that failed, so an agent can fix them all at once.

A purchase you start from the dashboard carries no agent signature, so when the policy needs a verified human, it says:

This agent requires a verified human, so purchases have to come from the agent itself.

Keeping the proof fresh

  • POST /v1/agents/{id}/self/refresh re-reads the registry. A lapsed proof returns a handoff: a QR link and deep link to renew in the Self app.
  • POST /v1/agents/{id}/self/deregister starts removing the agent from Self. Pocket stops treating it as human-backed straight away.
  • Either, on an agent never linked: SELF_AGENT_NOT_LINKED.

What Pocket sees

The agent's Self id, signing address, whether a human stands behind it, when the proof expires, and what the person chose to disclose. It shows the age threshold and sanctions result. It never shows nationality: a policy can check it, nobody can read it.

On this page