# Agentic Payments — MPC Delegated Signing

> Coming soon, upon request — this feature is turned on for your tenant by the Aurea team. [Contact us](https://www.aureahub.com/contact)

Let an agent pay from a user's MPC wallet without the user present for each payment. The user signs once, with the wallet's ordinary MPC signature: a token permit and an `AgentMandate` for the agent allowance router — the cap over every payment, the recipients, the expiry. The agent's payments under that mandate then go through the router, which checks each one against what the wallet signed and moves the token from the wallet itself.

## Overview

- **One delegation per mandate.** A delegation binds one active [mandate](https://docs.aureahub.com/docs/agentic-api.md) of an agent to one MPC wallet, one token, a cap, up to 32 recipients and an expiry (`notAfter`).
- **The wallet signs it, nobody else.** Aurea never signs for the wallet alone: the two digests are signed through the wallet's own signing ceremony, with the user's device. The router records the mandate only with the wallet's signature over exactly these terms, and its nonce is used once.
- **The router enforces it on-chain.** Each payment must be in the mandate's token, to a listed recipient, before `notAfter`, with a payment reference never used before, and within the cap counted over every payment. Aurea cannot raise a cap, add a recipient, extend a mandate or create one the wallet did not sign.
- **The wallet's owner stays in control.** The user revokes through the API, or the wallet itself revokes in the router or sets the token's allowance for the router to `0`. Revoking the mandate revokes its delegation too.
- **Every payment still passes the policy.** An agent's payment from a delegated wallet goes through the same PolicyGuard, limits and approvals as every other agent payment (`POST /v1/agent-payments/executions`); the router is a second, on-chain bound.

## Before You Start

- **Your tenant has it.** Agent Payments and MPC delegated signing are both turned on for your tenant by the Aurea team. Until then every route on this page answers `404`: `AGENT_MPC_DELEGATION_NOT_ENABLED`, or no code while Agent Payments are off.
- **An active mandate** ([POST /v1/agent-payments/mandates](https://docs.aureahub.com/docs/agentic-api.md)) with:

  - `walletId` — an MPC wallet on an EVM chain of the user who will sign;
  - `asset` — the token's contract address. The token must support ERC-2612 `permit` (`409` `AGENT_DELEGATION_TOKEN_NO_PERMIT` otherwise);
  - `chain` — the router's chain (`eip155:<chainId>` or the decimal chain id), or none (`409` `AGENT_DELEGATION_CHAIN_MISMATCH` otherwise);
  - `expiresAt` — or send `notAfter` when you prepare.
- **The user, present, with their device**: the routes that prepare, confirm, read one and revoke a delegation take the wallet's user's access token. Another user's token gets `403` `AGENT_DELEGATION_NOT_WALLET_OWNER` on prepare, and `404` `AGENT_DELEGATION_NOT_FOUND` for a delegation that is not theirs.
- Your tenant's administrators list every delegation (`GET /v1/agent-payments/delegations`).

## The Flow

1. **Prepare** — `POST /v1/agent-payments/mandates/:id/delegation` with the recipients and the cap. The answer holds the delegation (`status` `prepared`) and, in `toSign`, two digests with the EIP-712 typed data each is the hash of.
2. **Sign** — the wallet signs both digests through its ordinary MPC signing ceremony: the permit first, then the mandate.
3. **Confirm** — `POST /v1/agent-payments/delegations/:id/confirm` with both signatures, within 30 minutes of the prepare. Aurea checks them and submits them to the router: the delegation becomes `active`.
4. **Pay** — the agent's payments under the mandate from that wallet go through the router.
5. **Read, revoke** — `GET /v1/agent-payments/delegations/:id` answers what the router says now (spent, remaining); `POST /v1/agent-payments/delegations/:id/revoke` ends it.

Preparing and confirming move no money. Only the agent's payments do, each one within the delegation.

## 1. Prepare

The wallet's user sends the terms: `recipients` (1 to 32 EVM addresses, each once, never the wallet itself, each allowed by the mandate's policy), `cap` (a whole number of the token's smallest unit: the most the agent may pay under the delegation, all payments together) and, optionally, `notAfter` (when nothing moves any more: by default the mandate's `expiresAt`; never after it, and within the next 366 days).

```javascript
const API = 'https://api.aureahub.com';

const prepared = await fetch(`${API}/v1/agent-payments/mandates/${mandateId}/delegation`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${userAccessToken}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    recipients: ['0x2222222222222222222222222222222222222222'],
    cap: '250000000',                       // 250 of a 6-decimal token
    notAfter: '2026-12-31T23:59:59Z',
  }),
}).then((r) => r.json());

const { delegation, toSign } = prepared;   // toSign[0]: permit, toSign[1]: mandate
```

The answer stores the terms exactly as the wallet will sign them: the recipients sorted and in lower case, the token's permit nonce, a fresh `AgentMandate` nonce, and a signature deadline 30 minutes away (`signatureDeadline`). Preparing again for the same mandate replaces a delegation still waiting for its signatures (it becomes `failed`, `error` `superseded`); a mandate with an active delegation answers `409` `AGENT_DELEGATION_ACTIVE`.

## 2. Sign the Two Digests

Each `toSign` entry has its `purpose` (`permit` or `mandate`), its `digest` (32 bytes, `0x`-prefixed) and the `typedData` it is the EIP-712 hash of (`domain`, `types`, `primaryType`, `message`). Show the user what they authorise — the token, the cap, the recipients, the expiry — and recompute the digest from the typed data before signing it:

```javascript
import { TypedDataEncoder } from 'ethers';

for (const { purpose, digest, typedData } of toSign) {
  const { domain, types, message } = typedData;
  if (TypedDataEncoder.hash(domain, types, message).toLowerCase() !== digest.toLowerCase()) {
    throw new Error(`the ${purpose} digest does not match its typed data`);
  }
}
```

Then sign each digest with the wallet's ordinary signing ceremony — [Start Signing Ceremony](https://docs.aureahub.com/docs/mpc-sign-start.md) with `committee` `["DEVICE","AUREA"]` and `msgHashHex` the digest without its `0x` prefix, then the device's ceremony loop and the result — the permit first, then the mandate. Sign the digest itself, with no message prefix.

Each signature is 65 bytes, `r` ‖ `s` ‖ `v`, as `0x` + 130 hex characters; `v` may be `27`/`28` or `0`/`1`, and either form of `s` is accepted.

## 3. Confirm

```javascript
const confirmed = await fetch(`${API}/v1/agent-payments/delegations/${delegation.id}/confirm`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${userAccessToken}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ permitSignature, mandateSignature }),
}).then((r) => r.json());

// confirmed.status: 'active' — or still 'prepared' with createTxHash: GET it again
```

- Aurea checks that both signatures are the wallet's for exactly the prepared terms (`400` `AGENT_DELEGATION_BAD_SIGNATURE`, `details.field` the signature at fault) and submits them to the router in one transaction (the permit, then the mandate).
- The answer is the delegation: `active` once the router recorded it (`createTxHash`, `activatedAt`, `onchain`), or still `prepared` with its `createTxHash` while the transaction's receipt is not read yet — read it again with `GET /v1/agent-payments/delegations/:id`.
- Confirming an active delegation again answers it as it is. One that is not waiting for its signatures answers `409` `AGENT_DELEGATION_NOT_PREPARED`.
- After `signatureDeadline`: `410` `AGENT_DELEGATION_SIGNATURE_EXPIRED`, and the delegation is `failed`. Prepare again.
- The router refused the transaction: `409` `AGENT_ROUTER_TX_FAILED` with `details.txHash`, and the delegation is `failed`. It could not be sent: `503` `AGENT_ROUTER_TX_FAILED`, and it stays `prepared` until its deadline.

## Payments Under a Delegation

Once your tenant has MPC delegated signing, an agent's payment from an MPC wallet goes through the router — `POST /v1/agent-payments/executions` as for any agent payment, through the PolicyGuard, its limits and approvals:

- `mandateId` — the delegated mandate, and `walletId` — its wallet;
- `asset` — the delegation's token, and `recipient` — one of its recipients;
- `amount` — a whole number of the token's smallest unit, like the cap.

A payment the router takes answers `rail` `onchain` with its `txHash`; the token moves from the wallet to the recipient, and the delegation's `spent` grows. A payment that is not within the delegation moves nothing: the execution is `failed`, its reason in `denialReason`:

| denialReason | Why |
| --- | --- |
| `mandate-required` | No `mandateId`: a payment from an MPC wallet is paid only under a delegated mandate. |
| `no-active-delegation` | The mandate has no active delegation. |
| `delegation-wallet-mismatch` | The payment's wallet is not the delegation's. |
| `delegation-token-mismatch` | The payment's `asset` is not the delegation's token. |
| `recipient-not-an-address` | The recipient is not an EVM address. |
| `delegation-recipient-not-listed` | The recipient is not one of the delegation's. |
| `amount must be the token's smallest unit` | The amount is not a positive whole number. |
| `delegation-expired` | Past the delegation's `notAfter`. |
| `router-reverted (0x…)` | The router refused the payment — the cap would be passed, the delegation was revoked in the router, or the wallet's allowance or balance is short — with the transaction's hash. |

## Read and Revoke

- **Read one** — `GET /v1/agent-payments/delegations/:id`, the wallet's user only. While it is active, `onchain` holds what the router says now: `spent`, `remaining` (`0` once revoked or expired) and `revoked`. A delegation sent but not yet read becomes `active` (or `failed`) here once its receipt is.
- **List** — `GET /v1/agent-payments/delegations`, your tenant's administrators: newest first, at most 200; add `?mandateId=` for one mandate's. The list does not read the router (`onchain` is `null`).
- **Revoke through the API** — `POST /v1/agent-payments/delegations/:id/revoke`, the wallet's user: an active delegation is revoked in the router (`revokeTxHash`), one still waiting for its signatures is dropped. Either way it is `revoked` and nothing moves under it afterwards. Revoking a revoked, expired or failed one answers it as it is. If the revocation cannot be sent to the router: `503` `AGENT_ROUTER_TX_FAILED`, the delegation stays `active`; try again.
- **Revoke the mandate** — your administrators revoke the mandate (`POST /v1/agent-payments/mandates/:id/revoke`): its delegation is revoked in the router within 15 minutes.
- **Revoke from the wallet** — the wallet itself can always call the router's `revoke(mandateId)` with the delegation's `onchainId`: the router refuses every later payment, and the delegation reads `revoked` within 15 minutes. Setting the token's allowance for the router to `0` also stops every payment (`router-reverted`), but the delegation keeps reading `active`: revoke it as well.
- **Expiry** — the router refuses every payment past `notAfter`, and the delegation reads `expired` within 15 minutes.

## Endpoints

### `POST /v1/agent-payments/mandates/:id/delegation`

Authentication: bearer token required.

Prepare an MPC wallet's delegation of a mandate: the wallet's user, present, gets the two digests the wallet signs (the token's ERC-2612 permit for the agent allowance router, then the router's AgentMandate). Confirm within 30 minutes. Moves no money.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The mandate's id — active, of an MPC wallet of the caller |

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `recipients` | string[] | yes | 1 to 32 EVM addresses the agent may pay, each once, not the wallet itself, each allowed by the mandate's policy (`400` `AGENT_DELEGATION_RECIPIENT_NOT_ALLOWED`, `details.recipients` the refused ones) |
| `cap` | string | yes | The most the agent may pay under the delegation, all payments together: a positive whole number of the token's smallest unit (up to 78 digits) |
| `notAfter` | date-time | no | ISO 8601 with its offset: when nothing moves any more. Default the mandate's `expiresAt` (required when it has none); never after it; more than a minute from now and within 366 days |

**Responses**

`201` Prepared

```json
{
  "delegation": {
    "id": "99999999-9999-9999-9999-999999999999",
    "mandateId": "55555555-5555-5555-5555-555555555555",
    "walletId": "66666666-6666-6666-6666-666666666666",
    "chainId": 84532,
    "router": "0x3333333333333333333333333333333333333333",
    "token": "0x4444444444444444444444444444444444444444",
    "ownerAddress": "0x1111111111111111111111111111111111111111",
    "onchainId": "0x8f3c…e21a",
    "cap": "250000000",
    "recipients": ["0x2222222222222222222222222222222222222222"],
    "notAfter": "2026-12-31T23:59:59.000Z",
    "signatureDeadline": "2026-10-29T10:30:00.000Z",
    "mandateDigest": "0x7d41…09bc",
    "permitDigest": "0x1ae0…5f73",
    "status": "prepared",
    "createTxHash": null,
    "revokeTxHash": null,
    "error": null,
    "createdAt": "2026-10-29T10:00:00.000Z",
    "activatedAt": null,
    "revokedAt": null,
    "onchain": null
  },
  "toSign": [
    {
      "purpose": "permit",
      "digest": "0x1ae0…5f73",
      "typedData": {
        "domain": { "name": "Example Token", "version": "1", "chainId": 84532, "verifyingContract": "0x4444444444444444444444444444444444444444" },
        "types": { "Permit": [ { "name": "owner", "type": "address" }, { "name": "spender", "type": "address" }, { "name": "value", "type": "uint256" }, { "name": "nonce", "type": "uint256" }, { "name": "deadline", "type": "uint256" } ] },
        "primaryType": "Permit",
        "message": { "owner": "0x1111111111111111111111111111111111111111", "spender": "0x3333333333333333333333333333333333333333", "value": "250000000", "nonce": "0", "deadline": "1761733800" }
      }
    },
    {
      "purpose": "mandate",
      "digest": "0x7d41…09bc",
      "typedData": {
        "domain": { "name": "AureaAgentAllowanceRouter", "version": "1", "chainId": 84532, "verifyingContract": "0x3333333333333333333333333333333333333333" },
        "types": { "AgentMandate": [ { "name": "mandateId", "type": "bytes32" }, { "name": "owner", "type": "address" }, { "name": "token", "type": "address" }, { "name": "cap", "type": "uint256" }, { "name": "notAfter", "type": "uint64" }, { "name": "recipients", "type": "address[]" }, { "name": "nonce", "type": "uint256" }, { "name": "deadline", "type": "uint256" } ] },
        "primaryType": "AgentMandate",
        "message": { "mandateId": "0x8f3c…e21a", "owner": "0x1111111111111111111111111111111111111111", "token": "0x4444444444444444444444444444444444444444", "cap": "250000000", "notAfter": "1798761599", "recipients": ["0x2222222222222222222222222222222222222222"], "nonce": "20416733197551302937418848196044271761", "deadline": "1761733800" }
      }
    }
  ]
}
```

`400` Invalid

```json
{
  "error": "BadRequestError",
  "message": "notAfter is past the mandate's expiry",
  "details": { "code": "AGENT_DELEGATION_INVALID", "field": "notAfter" }
}
```

`409` Delegated Already

```json
{
  "error": "ConflictError",
  "message": "The mandate is delegated already",
  "details": { "code": "AGENT_DELEGATION_ACTIVE" }
}
```

### `POST /v1/agent-payments/delegations/:id/confirm`

Authentication: bearer token required.

Confirm a delegation with the wallet's two signatures: checked to be the wallet's for exactly the prepared terms, then submitted to the agent allowance router. Answers the delegation — active, or still prepared with its transaction while the receipt is not read (GET it again).

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The delegation's id |

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `permitSignature` | string | yes | The wallet's signature of the `permit` digest: 65 bytes, `0x` + 130 hex |
| `mandateSignature` | string | yes | The wallet's signature of the `mandate` digest: 65 bytes, `0x` + 130 hex |

**Responses**

`200` Active

```json
{
  "id": "99999999-9999-9999-9999-999999999999",
  "mandateId": "55555555-5555-5555-5555-555555555555",
  "status": "active",
  "cap": "250000000",
  "createTxHash": "0x5b2e…c4d0",
  "activatedAt": "2026-10-29T10:05:12.000Z",
  "onchain": { "spent": "0", "remaining": "250000000", "revoked": false }
}
```

`400` Bad Signature

```json
{
  "error": "BadRequestError",
  "message": "A signature is not the wallet's for these terms",
  "details": { "code": "AGENT_DELEGATION_BAD_SIGNATURE", "field": "mandateSignature" }
}
```

`410` Window Over

```json
{
  "error": "GoneError",
  "message": "The delegation's signature window is over",
  "details": { "code": "AGENT_DELEGATION_SIGNATURE_EXPIRED" }
}
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/agent-payments/delegations/99999999-9999-9999-9999-999999999999/confirm \
  -H "Authorization: Bearer USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "permitSignature": "0xPERMIT_SIGNATURE_130_HEX", "mandateSignature": "0xMANDATE_SIGNATURE_130_HEX" }'
```

### `POST /v1/agent-payments/delegations/:id/revoke`

Authentication: bearer token required.

Revoke one's own delegation: an active one in the agent allowance router, one still waiting for its signatures dropped. Nothing moves under it afterwards.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The delegation's id |

**Responses**

`200` Revoked

```json
{
  "id": "99999999-9999-9999-9999-999999999999",
  "status": "revoked",
  "revokeTxHash": "0x0c9a…71fe",
  "revokedAt": "2026-11-02T08:00:00.000Z",
  "onchain": null
}
```

### `GET /v1/agent-payments/delegations`

Authentication: bearer token required.

Your tenant's delegations, newest first, at most 200 — your administrators. Add ?mandateId= with a mandate's id for that mandate's only. The list does not read the router (onchain is null).

**Responses**

`200` OK

```json
{
  "data": [
    {
      "id": "99999999-9999-9999-9999-999999999999",
      "mandateId": "55555555-5555-5555-5555-555555555555",
      "walletId": "66666666-6666-6666-6666-666666666666",
      "status": "active",
      "cap": "250000000",
      "notAfter": "2026-12-31T23:59:59.000Z",
      "onchain": null
    }
  ]
}
```

### `GET /v1/agent-payments/delegations/:id`

Authentication: bearer token required.

One's own delegation (the wallet's user), with what the agent allowance router says of it now while it is active: spent, remaining, revoked.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The delegation's id |

**Responses**

`200` OK

```json
{
  "id": "99999999-9999-9999-9999-999999999999",
  "status": "active",
  "cap": "250000000",
  "notAfter": "2026-12-31T23:59:59.000Z",
  "onchain": { "spent": "40000000", "remaining": "210000000", "revoked": false }
}
```

`404` Not Found

```json
{
  "error": "NotFoundError",
  "message": "Delegation not found",
  "details": { "code": "AGENT_DELEGATION_NOT_FOUND" }
}
```

## The Delegation

Every route answers the delegation with these fields (prepare answers it under `delegation`, the list under `data`). Numbers of the token are whole numbers of its smallest unit, as strings.

| Field | What it is |
| --- | --- |
| `id`, `mandateId`, `walletId` | The delegation, its mandate and its MPC wallet. |
| `chainId`, `router` | The chain and the agent allowance router's address. |
| `token` | The token's contract address (the mandate's `asset`), lower case. |
| `ownerAddress` | The wallet's address, which signs and pays. |
| `onchainId` | The delegation's `mandateId` in the router (`bytes32`): what the router's reads and `revoke` take. |
| `cap` | The most paid under it, all payments together. |
| `recipients` | The addresses the agent may pay, sorted, lower case. |
| `notAfter` | When nothing moves any more. |
| `signatureDeadline` | When the 30 minutes to sign and confirm end. |
| `mandateDigest`, `permitDigest` | The two digests the wallet signs. |
| `status` | `prepared` (waiting for its signatures, or sent and not read yet), `active`, `revoked`, `expired` or `failed`. |
| `createTxHash`, `revokeTxHash` | The router's transactions that recorded and revoked it, or `null`. |
| `error` | Why it failed: `superseded` (prepared again), `signature window over`, `transaction reverted`; otherwise `null`. |
| `createdAt`, `activatedAt`, `revokedAt` | When it was prepared, became active, was revoked. |
| `onchain` | One delegation read while active: `{ spent, remaining, revoked }` from the router; otherwise `null`. |

## What the Wallet Signs

Two EIP-712 messages, both in `toSign` with their digests. Every number is a decimal string in `message`.

### The token's permit (ERC-2612)

Domain: the token's own EIP-712 `name` and `version`, the `chainId`, `verifyingContract` the token. It lets the router move up to `cap` of the token from the wallet; `nonce` is the token's current permit nonce for the wallet, `deadline` the signature deadline (seconds).

```text
Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)
  owner    the wallet
  spender  the router
  value    cap
```

### The router's AgentMandate

Domain: `name` `AureaAgentAllowanceRouter`, `version` `1`, the `chainId`, `verifyingContract` the router.

```text
AgentMandate(bytes32 mandateId,address owner,address token,uint256 cap,uint64 notAfter,address[] recipients,uint256 nonce,uint256 deadline)
  mandateId   the delegation's onchainId
  owner       the wallet
  token       the mandate's token
  cap         the most paid, all payments together
  notAfter    seconds since the epoch
  recipients  strictly ascending (lower-case hex order), no duplicates
  nonce       128 random bits, used once per wallet
  deadline    the signature deadline, seconds
```

The router computes the same digest itself: `mandateDigest(terms, recipients)` on the contract returns it for given terms.

## The Router Contract

`AgentAllowanceRouter`, at the `router` address of each delegation (Solidity ^0.8.20, EIP-712 domain `AureaAgentAllowanceRouter` v1). Payments are made by Aurea's operator and checked by the contract; the reads and `revoke` are for anyone to verify a delegation and for the wallet to end it.

```solidity
struct Mandate {
    address owner;
    address token;
    uint256 cap;
    uint256 spent;
    uint64  notAfter;
    bool    revoked;
    bytes32 recipientsHash;
}

struct MandateTerms {
    bytes32 mandateId;
    address owner;
    address token;
    uint256 cap;
    uint64  notAfter;
    uint256 nonce;
    uint256 deadline;
}

struct Permit { uint256 value; uint256 deadline; uint8 v; bytes32 r; bytes32 s; }

// Recording a mandate the owner signed (what Confirm submits)
function permitAndCreateMandate(MandateTerms terms, address[] recipients, bytes ownerSignature, Permit permit) external;
function createMandate(MandateTerms terms, address[] recipients, bytes ownerSignature) external;
function createMandateAsOwner(MandateTerms terms, address[] recipients) external;   // msg.sender == terms.owner

// Paying within it: Aurea's operator only
function pay(bytes32 mandateId, address to, uint256 amount, bytes32 paymentRef) external;

// Ending it: the owner, or Aurea's operator
function revoke(bytes32 mandateId) external;

// Reads
function mandateOf(bytes32 mandateId) external view returns (Mandate memory);
function remaining(bytes32 mandateId) external view returns (uint256);        // 0 once revoked or expired
function isRecipient(bytes32 mandateId, address to) external view returns (bool);
function nonceUsed(address owner, uint256 nonce) external view returns (bool);
function paymentUsed(bytes32 paymentRef) external view returns (bool);
function mandateDigest(MandateTerms terms, address[] recipients) external view returns (bytes32);
function recipientsHashOf(address[] recipients) external pure returns (bytes32);
function domainSeparator() external view returns (bytes32);
function isOperator(address account) external view returns (bool);

uint256 public constant MAX_RECIPIENTS = 32;
uint64  public constant MAX_DURATION   = 366 days;

event MandateCreated(bytes32 indexed mandateId, address indexed owner, address indexed token, uint256 cap, uint64 notAfter, bytes32 recipientsHash);
event MandatePaid(bytes32 indexed mandateId, address indexed to, uint256 amount, bytes32 indexed paymentRef, uint256 spent);
event MandateRevoked(bytes32 indexed mandateId, address indexed by);
```

`pay` moves the token with `transferFrom` from the owner to `to`, and only when the mandate exists, is not revoked, is before `notAfter`, `to` is listed, `amount` is not `0`, `paymentRef` was never used, and `spent + amount` is at most `cap`. Payments can be paused as a whole; recording and revoking a mandate still work then. The contract's errors:

| Error | When |
| --- | --- |
| `InvalidTerms()` | The terms break a bound: no `mandateId`, owner or token, a cap of 0, no recipient or more than 32, the owner among the recipients, `notAfter` past or more than 366 days away. |
| `RecipientsNotSorted()` | The recipients are not strictly ascending (a duplicate, or the zero address). |
| `SignatureExpired()` | Past the signature's `deadline`. |
| `BadSignature()` | The signature is not the owner's for these terms (or `createMandateAsOwner` not sent by the owner). |
| `NonceUsed(owner, nonce)` | The owner's `AgentMandate` nonce was used before. |
| `MandateExists(mandateId)` | That `mandateId` is recorded already. |
| `UnknownMandate(mandateId)` | No such mandate. |
| `NotOperator(caller)` | `pay` from an address that is not an operator. |
| `NotAllowedToRevoke(caller)` | `revoke` from neither the owner nor an operator. |
| `MandateRevokedError(mandateId)` | The mandate is revoked. |
| `MandateExpired(mandateId)` | Past `notAfter`. |
| `NotRecipient(mandateId, to)` | `to` is not listed. |
| `InvalidAmount()` | An amount of 0. |
| `PaymentRefUsed(paymentRef)` | The payment reference was used before. |
| `CapExceeded(mandateId, spent, amount, cap)` | The payment would pass the cap. |

## Error Codes

The routes answer the usual [error body](https://docs.aureahub.com/docs/errors.md), the code in `details.code`:

| Status | details.code | When |
| --- | --- | --- |
| 400 | `AGENT_DELEGATION_INVALID` | A term is not valid (`details.field`: `recipients`, `cap`, `notAfter`), or the mandate names no wallet (`walletId`) or its `asset` is not a token address (`asset`). |
| 400 | `AGENT_DELEGATION_RECIPIENT_NOT_ALLOWED` | A recipient is not allowed by the mandate's policy (`details.recipients`). |
| 400 | `AGENT_DELEGATION_BAD_SIGNATURE` | A signature is not the wallet's for the prepared terms (`details.field`). |
| 403 | `AGENT_DELEGATION_NOT_WALLET_OWNER` | The caller is not the mandate wallet's user. |
| 404 | `AGENT_MPC_DELEGATION_NOT_ENABLED` | MPC delegated signing is not on for your tenant. |
| 404 | `AGENT_MANDATE_NOT_FOUND` | No such mandate in your tenant. |
| 404 | `AGENT_DELEGATION_NOT_FOUND` | No such delegation, or not the caller's. |
| 409 | `AGENT_DELEGATION_MANDATE_NOT_ACTIVE` | The mandate is not active (`details.status`). |
| 409 | `AGENT_DELEGATION_ACTIVE` | The mandate has an active delegation already. |
| 409 | `AGENT_DELEGATION_WALLET_NOT_MPC` | The mandate's wallet is not an MPC wallet on an EVM chain. |
| 409 | `AGENT_DELEGATION_CHAIN_MISMATCH` | The mandate's `chain` is not the router's. |
| 409 | `AGENT_DELEGATION_TOKEN_NO_PERMIT` | The token has no ERC-2612 permit the router can use. |
| 409 | `AGENT_DELEGATION_NEEDS_POLICY_DECISIONS` | A setting MPC delegated signing needs is not on for your tenant yet: ask the Aurea team. |
| 409 | `AGENT_DELEGATION_NOT_PREPARED` | Confirm on a delegation that is not waiting for its signatures (`details.status`). |
| 409 | `AGENT_ROUTER_TX_FAILED` | The router refused the transaction (`details.txHash`). |
| 410 | `AGENT_DELEGATION_SIGNATURE_EXPIRED` | Confirm after `signatureDeadline`: prepare again. |
| 423 | `TENANT_FROZEN`, `USER_FROZEN`, `ACCOUNT_FROZEN`, `WALLET_FROZEN` | Prepare for a frozen tenant, user or wallet: nothing is handed out to sign ([Freezes & Quarantines](https://docs.aureahub.com/docs/guide-freezes.md)). |
| 503 | `AGENT_ROUTER_NOT_CONFIGURED` | The router is not available for your tenant right now: try again later, or ask the Aurea team. |
| 503 | `AGENT_ROUTER_TX_FAILED` | The transaction could not be sent. Confirm: the delegation stays `prepared`, confirm again before `signatureDeadline`. Revoke: it stays `active`, revoke again. |

Without Agent Payments on for your tenant every route answers `404` with no code; a role that may not call a route gets `403`. Your tenant's [signing rules](https://docs.aureahub.com/docs/guide-signing-rules.md) apply to the permit the wallet is asked to sign, as to any other.

---

Web version: https://docs.aureahub.com/#agentic-mpc-delegation
