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.
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.amountis an integer in the minor unit, and onlypurchaseandpaymentaccept one — otherwiseAMOUNT_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" }
}'{
"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.
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.