# 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:

1. Mandate exists
2. Status is active
3. valid_until is in the future
4. amount_spent_usd + request_amount ≤ max_amount_usd
5. Request category is in allowed_categories (if set)
6. 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.
