API/Issue a mandate
v1.0.0-beta.mdGet API keys
Mandates

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

FieldTypeNotes
granted_byuuid, requiredThe human
granted_tostring, requiredAn opaque machine identifier — never the label a person reads
granted_to_kindstring, requiredWhat kind of holder it is
agent_display_namestring ≤64The readable name the screens show
agent_descriptorstring ≤140One line on what the agent does
scopesarray, requiredExact match, no wildcard
constraintsobjectmerchant_allowlist, category_allowlist, country_allowlist
ceilingobjectkind, amount, currency, count, period
expires_attimestamp, requiredNo mandate is open-ended
step_up_ofuuidThe mandate this one steps up from
proof.capture_idstring, 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.

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.

Sidaxis is the human authorisation layer. It never moves money, holds funds or settles. The machine-readable contract is openapi.yaml.
Sidaxis API documentationQuickstartAuthenticationHosted or headlessSandbox and productionIdempotencyRate limitsErrors and refusalsField namesWebhooksAssuranceThe document ruleAlias availabilityMerchant themeMCP serverCompatibilityStatusRoadmapChangelogIdentityEnroll an identityRecognise a personDisclose an attributeMandatesThe eight scopesIssue a mandateCheck an actionConsume a mandateRead a mandateRevoke a mandateReceiptsRead a merchant's receiptsVerify a receiptHosted sessionsCreate a hosted sessionConsume a hosted sessionOne Face for agents