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.

Rédigé à partir du code · mis à jour le 26 sept. 2026 · Une erreur ou un oubli ? Écrivez-nous

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

ConditionDé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_minorDENY
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_minorREVIEW (approbation humaine)
état du fournisseur illisible, plafond par client final dépassé avec HOLDHOLD (peut être reproposée plus tard)
tout est dans les limites, guardrails franchis sans alerteALLOW → 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/sandbox crée un monde isolé à partir d’une fixture de modèle, éventuellement avec des pannes (échecs de refund.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/stripe avec secret_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 avec DELETE (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/paddle avec api_key (pdl_sdbx_apikey_… ou pdl_live_apikey_…) + webhook_secret (la clé secrète pdl_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/chargebee avec site (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, ou payment_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 portent chargebee-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" }
] }
Mandats et connexions · Docs · COLVO