AP2 MANDATES.
An AP2 mandate is a signed authorization that lets a specific AI agent spend USDC on your behalf — but only up to your defined budget, only in approved categories, and only until the expiry date you set.
What is a Mandate?
Instead of giving an agent your private key — which would let it spend any amount, any time — you sign a mandate. A mandate is a policy document stored in P402 that the router enforces on every request the agent makes.
The mandate defines:
- Who is authorized: A specific agent DID — no other agent can use this mandate.
- Budget cap: A maximum USD amount. The router blocks any request that would exceed it.
- Allowed categories: Optional list of service categories (e.g. "inference", "search"). Unlisted categories are blocked.
- Expiry: An ISO 8601 timestamp. Expired mandates are automatically rejected.
Intent Mandate
"Allow agent X to spend up to $50 on inference and search."
Use when an agent has broad autonomy within a task category. Good for research agents, coding agents, and data pipeline automation.
Payment Mandate
"Settle this specific service call for $0.05."
Use when an agent is executing a known, bounded task with a pre-agreed price. Auto-provisioned when you create a CDP session with a budget.
DID Format
Mandates use Decentralized Identifiers (DIDs) to identify both the user (grantor) and the agent (grantee). A DID is a string that uniquely identifies an entity without a central authority.
| Format | Example | When to use |
|---|---|---|
| did:p402:tenant:{id} | did:p402:tenant:t_abc123 | Your P402 tenant account (user_did) |
| did:p402:agent:{name} | did:p402:agent:my-research-agent | An agent registered in P402 (agent_did) |
| did:key:z... | did:key:zQ3shm... | Cryptographic key-based identity for external agents |
| did:ethr:0x... | did:ethr:0xAbc... | Ethereum wallet address as identity |
Find your tenant DID:
Your tenant DID is did:p402:tenant:{your_tenant_id}. Find your tenant ID at Dashboard → Settings → Account, or in the tenant_id field of any API response.
Auto-Provisioned Mandates (CDP Sessions)
The easiest way to use mandates. When you create a session with wallet_source: "cdp" and an agent_id, P402 automatically creates a payment mandate and links it to the session. Every auto-pay call through this session is enforced against the mandate — no separate API call needed.
bash — create a CDP session with automatic mandate
curl -X POST https://p402.io/api/v2/sessions \
-H "Authorization: Bearer $P402_API_KEY" \
-H "Content-Type: application/json" \
-d '{\n "wallet_source": "cdp",\n "agent_id": "my-research-agent",\n "budget_usd": 10.00,\n "expires_in_hours": 24\n }'
json — response includes the auto-created mandate ID
{\n "session_id": "ses_...",\n "budget_usd": 10.00,\n "expires_at": "2026-04-17T12:00:00.000Z",\n "policies": {\n "ap2_mandate_id": "mnd_abc123" // Mandate auto-created and linked\n },\n "wallet": {\n "address": "0x...",\n "source": "cdp"\n }\n}
Manual Mandate Creation
For non-CDP sessions or when you need custom constraints — like restricting to specific service categories or using a cryptographic key identity — create a mandate directly.
bash — POST /api/a2a/mandates
curl -X POST https://p402.io/api/a2a/mandates \
-H "Authorization: Bearer $P402_API_KEY" \
-H "Content-Type: application/json" \
-d '{\n "mandate": {\n "type": "intent",\n "user_did": "did:p402:tenant:t_abc123",\n "agent_did": "did:p402:agent:my-research-agent",\n "constraints": {\n "max_amount_usd": 50.00,\n "allowed_categories": ["inference", "search", "data"],\n "valid_until": "2026-12-31T23:59:59Z"\n },\n "signature": "0x..."\n }\n }'
json — response
{\n "mandate_id": "mnd_xyz789",\n "status": "active",\n "type": "intent",\n "user_did": "did:p402:tenant:t_abc123",\n "agent_did": "did:p402:agent:my-research-agent",\n "constraints": {\n "max_amount_usd": 50.00,\n "allowed_categories": ["inference", "search", "data"],\n "valid_until": "2026-12-31T23:59:59Z"\n },\n "amount_spent_usd": 0.00,\n "created_at": "2026-04-16T12:00:00.000Z"\n}
Allowed Categories
The allowed_categories constraint is an allowlist. If you omit it, the agent can spend in any category. If you include it, only the listed categories are permitted — any request categorized differently returns MANDATE_CATEGORY_DENIED.
| Category | Covers |
|---|---|
| inference | LLM chat completions, text generation, summarization |
| search | Web search, semantic search, vector retrieval |
| data | Database queries, data API calls, structured data retrieval |
| compute | Code execution, sandboxed computation, function calls |
| media | Image generation, audio synthesis, video processing |
| storage | File upload, object storage, IPFS pinning |
| agents | Calls to other A2A agents in the Bazaar marketplace |
Mandate Lifecycle
A mandate moves through these states. Status transitions are triggered automatically by the router — you don't need to update them manually.
| Status | Meaning | Agent can spend? |
|---|---|---|
| active | Mandate is valid and within budget. | Yes, up to remaining budget |
| exhausted | amount_spent_usd has reached max_amount_usd. | No — create a new mandate |
| expired | Current time is past valid_until. | No — create a new mandate |
| revoked | Manually revoked via DELETE /api/a2a/mandates/:id. | No — permanent |
Check mandate status
bash — GET /api/a2a/mandates/:id
curl https://p402.io/api/a2a/mandates/mnd_xyz789 \
-H "Authorization: Bearer $P402_API_KEY"
json — response
{\n "mandate_id": "mnd_xyz789",\n "status": "active",\n "type": "intent",\n "constraints": {\n "max_amount_usd": 50.00,\n "allowed_categories": ["inference", "search", "data"],\n "valid_until": "2026-12-31T23:59:59Z"\n },\n "amount_spent_usd": 12.34, // How much the agent has spent so far\n "remaining_usd": 37.66, // Budget remaining\n "created_at": "2026-04-16T12:00:00.000Z"\n}
How Agents Request Spending
An agent doesn't call the mandates API directly. Instead, it includes its agent_did and the mandate_id in the session or request. P402 verifies the mandate before processing the request and deducts from the budget after settlement.
The verification order is:
- Mandate exists
- Status is active
- valid_until is in the future
- amount_spent_usd + request_amount ≤ max_amount_usd
- Request category is in allowed_categories (if set)
- Signature valid against public_key (if set)
Backwards compatible:
Sessions without a mandate linked in their policies skip mandate verification entirely. Existing integrations that don't use mandates continue to work without any changes.
Revoke a Mandate
Revocation is immediate and permanent. Any in-flight request using this mandate will be rejected. Use this if you lose trust in an agent or need to stop spending immediately.
bash — DELETE /api/a2a/mandates/:id
curl -X DELETE https://p402.io/api/a2a/mandates/mnd_xyz789 \
-H "Authorization: Bearer $P402_API_KEY"
# Response: 200 OK
{ "mandate_id": "mnd_xyz789", "status": "revoked" }
Error Codes
| Code | HTTP | Action |
|---|---|---|
| MANDATE_NOT_FOUND | 404 | The mandate_id does not exist. Check the ID. |
| MANDATE_INACTIVE | 403 | Mandate is exhausted, expired, or revoked. Create a new one. |
| MANDATE_EXPIRED | 403 | valid_until has passed. Create a new mandate with a future expiry. |
| MANDATE_BUDGET_EXCEEDED | 403 | Request would exceed max_amount_usd. Increase budget or create a new mandate. |
| MANDATE_CATEGORY_DENIED | 403 | Request category is not in allowed_categories. Expand the allowlist or change the request. |
| MANDATE_SIGNATURE_INVALID | 401 | EIP-712 signature does not verify against public_key. Re-sign. |