# Issue a mandate

`POST /functions/v1/mandate-issue`

A mandate is born from a live capture no older than five minutes, carries the scope, ceiling, constraints and expiry, and is immutable once written.

Server key. Requires `x-sidaxis-timestamp` and `x-sidaxis-nonce`. A mandate can only be issued from proof that a live human was present.

## Proof

`proof.capture_id` must be a capture no older than **five minutes**, belonging to the principal, with liveness passed and no coercion signal.

`assurance_level` is read from the level frozen on that capture and is never sent by the client. A capture that included a verified document yields `face_voice_doc` — which is what makes a high mandate band possible, so a ten-thousand mandate cannot rest on the same proof as a hundred one. An old capture never infers a document. See [assurance](/assurance).

## Body

| Field | Type | Notes |
| --- | --- | --- |
| granted_by | uuid, required | The human |
| granted_to | string, required | An opaque machine identifier — never the label a person reads |
| granted_to_kind | string, required | What kind of holder it is |
| agent_display_name | string ≤64 | The readable name the screens show |
| agent_descriptor | string ≤140 | One line on what the agent does |
| scopes | array, required | Exact match, no wildcard |
| constraints | object | merchant_allowlist, category_allowlist, country_allowlist |
| ceiling | object | kind, amount, currency, count, period |
| expires_at | timestamp, required | No mandate is open-ended |
| step_up_of | uuid | The mandate this one steps up from |
| proof.capture_id | string, required | ≤5 minutes old |

## Agent identity

`agent_display_name` is the readable name the authorisation, revocation and receipt screens show — “your travel assistant”. `agent_descriptor` is one line saying what it does. Both are frozen with the mandate.

Blank or over-length is refused by name: `AGENT_DISPLAY_NAME_EMPTY`, `AGENT_DISPLAY_NAME_TOO_LONG`, `AGENT_DESCRIPTOR_EMPTY`, `AGENT_DESCRIPTOR_TOO_LONG`. Absent stays absent — the screen falls back to the identifier rather than inventing a name.

## Constraints

Mandatory for `task`, refused with `CONSTRAINTS_REQUIRED_FOR_SCOPE` when missing or empty. An unconstrained mandate is a shared password with a biometric proof attached, and the authorisation screen builds the limit it shows out of the constraints themselves.

## Ceiling

- `kind`: `amount` | `count` | `both` | `none`.
- `amount` is an integer in the minor unit, and only `purchase` and `payment` accept one — otherwise `AMOUNT_CEILING_NOT_APPLICABLE`.
- `period`: `lifetime` | `daily` | `weekly` | `monthly`. The bucket is computed server-side; a client-sent period anchor is ignored.

## Example

```
curl -X POST https://api.sidaxis.com/functions/v1/mandate-issue \
  -H "x-sidaxis-api-key: sk_live_..." \
  -H "x-sidaxis-timestamp: 1789564821" \
  -H "x-sidaxis-nonce: 41c8de92aa" \
  -H "content-type: application/json" \
  -d '{
    "granted_by": "0f9c2a51-7c33-4a1e-9b02-16c2b7f0d5aa",
    "granted_to": "agt_3f77c1de",
    "granted_to_kind": "agent",
    "agent_display_name": "Your travel assistant",
    "agent_descriptor": "Books flights and hotels inside the limit you set",
    "scopes": ["payment"],
    "constraints": { "merchant_allowlist": ["mrc_9f2c"], "country_allowlist": ["US"] },
    "ceiling": { "kind": "amount", "amount": 50000, "currency": "USD", "period": "daily" },
    "expires_at": "2026-12-31T23:59:59Z",
    "proof": { "capture_id": "cap_7f31c9a2" }
  }'
```

201:

```
{
  "mandate_id": "mdt_a91f3c",
  "status": "active",
  "granted_by": "0f9c2a51-7c33-4a1e-9b02-16c2b7f0d5aa",
  "granted_to": "agt_3f77c1de",
  "agent_display_name": "Your travel assistant",
  "agent_descriptor": "Books flights and hotels inside the limit you set",
  "scopes": ["payment"],
  "scope_labels": [{ "scope": "payment", "label": "Pay someone" }],
  "ceiling": { "kind": "amount", "amount": 50000, "currency": "USD", "period": "daily" },
  "assurance_level": "face_voice_doc",
  "expires_at": "2026-12-31T23:59:59Z",
  "created_at": "2026-09-16T12:00:21Z"
}
```

## Immutability

A mandate is immutable. There is no update endpoint, and only `status`, `revoked_at` and `updated_at` ever change. Need more room? Issue a new mandate with `step_up_of` pointing at the old one — so an old receipt always describes an object that never changed.

> The standing mandate's balance is **not** consumed, reset or altered by a step-up. It keeps what it had, and the operation that overflowed is authorised by the new, separate mandate.

A step-up needs face only, not face and voice, because the document is already on record and the identity is already proven.

## A money scope requires a document

Issuing `purchase` or `payment` without a valid document record on the identity answers `403 DOCUMENT_REQUIRED`. See [the document rule](/document).

- The eight scopes: https://docs.sidaxis.com/mandates/scopes
- Consume a mandate: https://docs.sidaxis.com/mandates/consume
- Errors and refusals: https://docs.sidaxis.com/errors
