Mandates & connections
A mandate is authority, declared once by your trusted backend: which customer, which actions, which limits, until when. Guard evaluates every proposal against it. A connection is the provider account the actions run on.
Register a mandate
POST /v1/mandates — backend key or session (editor+). One mandate per ticket or conversation is the usual grain.
{
"project_id": "…",
"connection_id": "…", // optional: defaults to the project's Guard connection
"source_request_id": "ticket_4711", // your id; ties every operation back to the request
"subject": { "customer_id": "cus_01", "payment_ids": ["pi_01"], "subscription_ids": ["sub_01"] },
"actions": ["refund.create", "subscription.cancel"],
"limits": {
"currency": "eur",
"max_amount_minor": 5000, // total refundable under this mandate; above → DENY
"auto_allow_up_to_minor": 2000, // per operation; above (but within max) → REVIEW
"max_operations": 2,
"cancel_modes_allowed": ["period_end"],
"cancel_modes_review": ["immediate"] // allowed only after a human approves
},
"expires_at": "2026-10-01T12:00:00Z",
"approver": "[email protected]" // optional label shown to reviewers
}Response 201: the mandate with its mandate_id and version. Mandates are immutable; register a new one to change authority. An expired or exhausted mandate makes every further proposal DENY with reason mandate_inactive.
How limits map to decisions
| Condition | Decision |
|---|---|
action not in actions, wrong subject, wrong currency, over max_amount_minor, over max_operations | DENY |
amount above auto_allow_up_to_minor but within the max; cancel mode in cancel_modes_review | REVIEW (human approval) |
provider state unreadable, per-end-user cap exceeded with HOLD | HOLD (re-proposable later) |
| everything within limits, guardrails clear | ALLOW → executed once → verified |
Connections
A connection is a provider account attached to a project. Two kinds exist today:
- Sandbox (simulated Stripe) — POST
/v1/projects/{id}/connections/sandboxcreates an isolated world from a template fixture, optionally with faults (refund.create/subscription.cancel/readfailures, delays). Test runs create their own worlds per attempt; this endpoint is for trying Guard without a Stripe account. - Stripe — connected from the project page (or
POST /v1/projects/{id}/connections/stripewithsecret_key+webhook_secret) with a restricted key; live keys must be restricted (rk_live_), and the permissions are probed read-only before saving. Rotate with…/connections/{connection_id}/rotate; remove withDELETE(refused while active mandates use it) (refunds and subscriptions write, read for verification) and a webhook signing secret. The key is encrypted per organisation and only the executor uses it.
Each project has one default Guard connection; a mandate may name another.
Per-end-user caps
GET / PUT /v1/projects/{id}/end-user-caps — up to six caps per project, keyed by the mandate subject’s customer_id:
{ "caps": [
{ "window": "day", "max_actions": 3, "action_on_exceed": "HOLD" },
{ "window": "month", "max_amount_minor": 20000, "action_on_exceed": "DENY" }
] }