Mandats et connexions
Un mandat est une autorisation, déclarée une fois par votre backend de confiance : quel client, quelles actions, quelles limites, jusqu’à quand. Guard évalue chaque proposition au regard de ce mandat. Une connexion est le compte fournisseur sur lequel les actions s’exécutent.
Enregistrer un mandat
POST /v1/mandates — clé backend ou session (éditeur+). Le grain habituel est un mandat par ticket ou par conversation.
{
"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
}Réponse 201 : le mandat avec son mandate_id et sa version. Les mandats sont immuables ; pour modifier une autorisation, enregistrez-en un nouveau. Un mandat expiré ou épuisé rend DENY toute proposition ultérieure, avec le motif mandate_inactive.
Comment les limites se traduisent en décisions
| Condition | Décision |
|---|---|
action absente de actions, mauvais sujet, mauvaise devise, au-delà de max_amount_minor, au-delà de max_operations, une pause plus longue que max_pause_days (ou sans date de fin lorsque pause_open_ended vaut never), une offre absente de allowed_price_ids, un coupon absent de allowed_coupons, un changement facturant plus de max_proration_minor | DENY |
montant supérieur à auto_allow_up_to_minor mais dans la limite du maximum ; mode de résiliation dans cancel_modes_review ; une pause sans date de fin lorsque pause_open_ended vaut review ; un changement d’offre facturant aujourd’hui plus de auto_allow_proration_up_to_minor | REVIEW (approbation humaine) |
état du fournisseur illisible, plafond par client final dépassé avec HOLD | HOLD (peut être reproposée plus tard) |
| tout est dans les limites, guardrails franchis sans alerte | ALLOW → exécutée une fois → vérifiée |
Connexions
Une connexion est un compte fournisseur rattaché à un projet. Les types suivants existent aujourd’hui :
- Sandbox (Stripe simulé) — POST
/v1/projects/{id}/connections/sandboxcrée un monde isolé à partir d’une fixture de modèle, éventuellement avec des pannes (échecs derefund.create/subscription.cancel/read, délais). Les exécutions de test créent leurs propres mondes pour chaque tentative ; cet endpoint sert à essayer Guard sans compte Stripe. - Stripe — connecté depuis la page du projet (ou via
POST /v1/projects/{id}/connections/stripeavecsecret_key+webhook_secret) avec une clé restreinte ; les clés live doivent être restreintes (rk_live_), et les permissions sont vérifiées en lecture seule avant l’enregistrement. Renouvelez la clé avec…/connections/{connection_id}/rotate; supprimez-la avecDELETE(refusé tant que des mandats actifs l’utilisent) (écriture sur les remboursements et les abonnements, lecture pour la vérification) et un secret de signature des webhooks. La clé est chiffrée par organisation et seul l’executor l’utilise. - Paddle Billing — connecté depuis la page du projet (Add a connection → Paddle Billing) ou via
POST /v1/projects/{id}/connections/paddleavecapi_key(pdl_sdbx_apikey_…oupdl_live_apikey_…) +webhook_secret(la clé secrètepdl_ntfset_…de la destination des notifications). La clé doit disposer de Transactions en lecture, Subscriptions en lecture et écriture, Adjustments en lecture et écriture, Customers en lecture ; une clé pouvant également écrire les produits est refusée. Sur Paddle, COLVO peut rembourser (un adjustment de remboursement, qui attend la revue de Paddle avant d’être considéré comme vérifié), résilier, mettre en pause, reprendre et annuler une résiliation programmée — mais pas encore changer d’offre ni appliquer de remises. Paddle ne dispose pas de clés d’idempotence : COLVO marque le motif de chaque remboursement avec[colvo:<operation id>]et le recherche avant toute nouvelle tentative. - Chargebee — connecté depuis la page du projet (Add a connection → Chargebee) ou via
POST /v1/projects/{id}/connections/chargebeeavecsite(par ex.acme-test) +api_key. Les clés Chargebee ne peuvent pas être limitées à certaines actions : utilisez une clé à accès complet de type Write key créée uniquement pour COLVO (une clé en lecture seule est refusée ; une clé pouvant supprimer reçoit un avertissement). La réponse contient l’URL, le nom d’utilisateur et le mot de passe du webhook (affichés une seule fois). Sur Chargebee, un remboursement désigne une facture (invoice_id, oupayment_id= l’identifiant de la facture) et devient un avoir remboursable ; résiliation, pause, reprise, annulation de résiliation, changements d’offre (item prices) et coupons fonctionnent comme sur Stripe. Les écritures portentchargebee-idempotency-key= l’identifiant de l’opération ; Chargebee conserve les clés pendant 30 minutes, COLVO ne renvoie donc jamais une écriture au-delà — il ouvre un incident pour qu’une personne vérifie.
Chaque projet possède une connexion Guard par défaut ; un mandat peut en désigner une autre.
Plafonds par client final
GET / PUT /v1/projects/{id}/end-user-caps — jusqu’à six plafonds par projet, rattachés au customer_id du sujet du mandat :
{ "caps": [
{ "window": "day", "max_actions": 3, "action_on_exceed": "HOLD" },
{ "window": "month", "max_amount_minor": 20000, "action_on_exceed": "DENY" }
] }