Mandati e connessioni
Un mandato è un’autorizzazione, dichiarata una volta dal tuo backend fidato: quale cliente, quali azioni, quali limiti, fino a quando. Guard valuta ogni proposta in base al mandato. Una connessione è l’account del provider su cui vengono eseguite le azioni.
Registra un mandato
POST /v1/mandates — chiave backend o sessione (editor+). Di solito si crea un mandato per ticket o per conversazione.
{
"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"] },
// Shopify: "order_ids": ["#1042"] — the orders order.cancel may touch
"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
"max_pause_days": 30, // subscription.pause: longest pause; longer → DENY
"pause_open_ended": "review", // a pause with no end date: never | review | allow
"allowed_price_ids": ["price_basic"], // subscription.change_plan: plans it may move to
"change_plan_at": ["now", "period_end"],
"auto_allow_proration_up_to_minor": 1500, // a switch that charges more today → REVIEW
"allowed_coupons": ["WINBACK20"] // coupon.apply
},
"expires_at": "2026-10-01T12:00:00Z",
"approver": "[email protected]" // optional label shown to reviewers
}Risposta 201: il mandato con il suo mandate_id e la version. I mandati sono immutabili; per cambiare l’autorizzazione registrane uno nuovo. Un mandato scaduto o esaurito rende DENY ogni proposta successiva, con motivo mandate_inactive.
Come i limiti diventano decisioni
| Condizione | Decisione |
|---|---|
azione non presente in actions, soggetto sbagliato, valuta sbagliata, oltre max_amount_minor, oltre max_operations, una pausa più lunga di max_pause_days (o senza data di fine quando pause_open_ended è never), un piano non presente in allowed_price_ids, un coupon non presente in allowed_coupons, un cambio che addebita più di max_proration_minor | DENY |
importo sopra auto_allow_up_to_minor ma entro il massimo; modalità di disdetta in cancel_modes_review; una pausa senza data di fine quando pause_open_ended è review; un cambio di piano che oggi addebita più di auto_allow_proration_up_to_minor | REVIEW (approvazione umana) |
stato del provider non leggibile, limite per cliente finale superato con HOLD | HOLD (si può riproporre più tardi) |
| tutto entro i limiti, guardrail superati | ALLOW → eseguita una volta → verificata |
Connessioni
Una connessione è un account del provider collegato a un progetto. Oggi esistono questi tipi:
- Sandbox (Stripe simulato) — POST
/v1/projects/{id}/connections/sandboxcrea un mondo isolato da una fixture di modello, eventualmente con guasti (errori surefund.create/subscription.cancel/read, ritardi). Le esecuzioni di test creano i propri mondi per ogni tentativo; questo endpoint serve per provare Guard senza un account Stripe. - Stripe — collegato dalla pagina del progetto (o con
POST /v1/projects/{id}/connections/stripeconsecret_key+webhook_secret) con una chiave limitata; le chiavi live devono essere limitate (rk_live_) e i permessi vengono verificati in sola lettura prima del salvataggio. Ruota la chiave con…/connections/{connection_id}/rotate; rimuovila conDELETE(rifiutato finché la usano mandati attivi) (scrittura su rimborsi e abbonamenti, lettura per la verifica) e un secret di firma dei webhook. La chiave è cifrata per organizzazione e la usa solo l’executor. - Paddle Billing — collegato dalla pagina del progetto (Add a connection → Paddle Billing) o con
POST /v1/projects/{id}/connections/paddleconapi_key(pdl_sdbx_apikey_…opdl_live_apikey_…) +webhook_secret(la secret keypdl_ntfset_…della destinazione delle notifiche). La chiave deve avere Transactions in lettura, Subscriptions in lettura e scrittura, Adjustments in lettura e scrittura, Customers in lettura; una chiave che può anche scrivere i prodotti viene rifiutata. Su Paddle COLVO può rimborsare (un adjustment di rimborso, che attende la revisione di Paddle prima di contare come verificato), disdire, mettere in pausa, riprendere e annullare una disdetta programmata — non ancora cambiare piano o applicare sconti. Paddle non ha chiavi di idempotenza: COLVO marca il motivo di ogni rimborso con[colvo:<operation id>]e lo cerca prima di ogni nuovo tentativo. - Chargebee — collegato dalla pagina del progetto (Add a connection → Chargebee) o con
POST /v1/projects/{id}/connections/chargebeeconsite(ad es.acme-test) +api_key. Le chiavi Chargebee non si possono limitare ad alcune azioni: usa una chiave ad accesso completo di tipo Write key creata solo per COLVO (una chiave di sola lettura viene rifiutata; una chiave che può cancellare riceve un avviso). La risposta contiene URL, username e password del webhook (mostrati una sola volta). Su Chargebee un rimborso indica una fattura (invoice_id, oppurepayment_id= l’id della fattura) e diventa una nota di credito rimborsabile; disdetta, pausa, ripresa, annullamento della disdetta, cambi di piano (item price) e coupon funzionano come su Stripe. Le scritture portanochargebee-idempotency-key= l’id dell’operazione; Chargebee conserva le chiavi per 30 minuti, quindi dopo quel tempo COLVO non reinvia mai una scrittura — apre un incidente perché una persona controlli.
Ogni progetto ha una connessione Guard predefinita; un mandato può indicarne un’altra.
Limiti per cliente finale
GET / PUT /v1/projects/{id}/end-user-caps — fino a sei limiti per progetto, legati al customer_id del soggetto del mandato:
{ "caps": [
{ "window": "day", "max_actions": 3, "action_on_exceed": "HOLD" },
{ "window": "month", "max_amount_minor": 20000, "action_on_exceed": "DENY" }
] }