>_ DOCS / REFERENCE
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:
A specific agent DID — no other agent can use this mandate.
A maximum USD amount. The router blocks any request that would exceed it.
Optional list of service categories (e.g. "inference", "search"). Unlisted categories are blocked.
An ISO 8601 timestamp. Expired mandates are automatically rejected.
"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.
"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.
curl -X POST https://p402.io/api/v2/sessions \
-H "Authorization: Bearer $P402_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"wallet_source": "cdp",
"agent_id": "my-research-agent",
"budget_usd": 10.00,
"expires_in_hours": 24
}'{
"session_id": "ses_...",
"budget_usd": 10.00,
"expires_at": "2026-04-17T12:00:00.000Z",
"policies": {
"ap2_mandate_id": "mnd_abc123" // Mandate auto-created and linked
},
"wallet": {
"address": "0x...",
"source": "cdp"
}
}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.
curl -X POST https://p402.io/api/a2a/mandates \
-H "Authorization: Bearer $P402_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mandate": {
"type": "intent",
"user_did": "did:p402:tenant:t_abc123",
"agent_did": "did:p402:agent:my-research-agent",
"constraints": {
"max_amount_usd": 50.00,
"allowed_categories": ["inference", "search", "data"],
"valid_until": "2026-12-31T23:59:59Z"
},
"signature": "0x..."
}
}'{
"mandate_id": "mnd_xyz789",
"status": "active",
"type": "intent",
"user_did": "did:p402:tenant:t_abc123",
"agent_did": "did:p402:agent:my-research-agent",
"constraints": {
"max_amount_usd": 50.00,
"allowed_categories": ["inference", "search", "data"],
"valid_until": "2026-12-31T23:59:59Z"
},
"amount_spent_usd": 0.00,
"created_at": "2026-04-16T12:00:00.000Z"
}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
curl https://p402.io/api/a2a/mandates/mnd_xyz789 \
-H "Authorization: Bearer $P402_API_KEY"{
"mandate_id": "mnd_xyz789",
"status": "active",
"type": "intent",
"constraints": {
"max_amount_usd": 50.00,
"allowed_categories": ["inference", "search", "data"],
"valid_until": "2026-12-31T23:59:59Z"
},
"amount_spent_usd": 12.34, // How much the agent has spent so far
"remaining_usd": 37.66, // Budget remaining
"created_at": "2026-04-16T12:00:00.000Z"
}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:
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.
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. |