API Guard

L’agent propose ; COLVO décide, exécute une seule fois et vérifie. Trois endpoints suffisent : proposer, lire, décider.

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

Proposer une opération

POST /v1/operations — clé agent ou backend, ou session (éditeur+). Répond immédiatement 202 avec la décision ; l’exécution et la vérification se poursuivent de manière asynchrone.

POST /v1/operations
Authorization: Bearer cak_…
Idempotency-Key: ticket_4711:refund
Content-Type: application/json

{ "mandate_id": "…", "source_request_id": "ticket_4711",
  "action": "refund.create",
  "parameters": { "payment_id": "pi_01", "amount_minor": 5000, "currency": "eur", "reason": "duplicate charge" },
  "context": { "user_message": "…", "agent_reply": "…", "retrieved": ["…"] } }

Actions et paramètres

actionparamètres
refund.createl’un de payment_id | invoice_id | charge_id (paiements plus anciens, antérieurs aux PaymentIntents), amount_minor (entier > 0), currency (3 lettres), reason?
subscription.cancelsubscription_id, mode : period_end (par défaut) | immediate
subscription.pausesubscription_id, resumes_at? (date ou date et heure ; absent = pas de date de fin), behavior : void (par défaut) | keep_as_draft. La facturation s’arrête, l’accès est conservé.
subscription.resumesubscription_id — lève la pause ; rien n’est envoyé si l’abonnement n’est pas en pause.
subscription.change_plansubscription_id, price_id, at : now (par défaut, au prorata) | period_end (programmé ; l’offre actuelle reste en place jusqu’à la date de fin).
coupon.applysubscription_id, coupon_id. Jamais en plus d’une remise existante : dans ce cas, l’opération échoue avant tout envoi.
order.cancelShopify uniquement. order_id (#1042, identifiant numérique ou gid), refund (par défaut false : si true, le solde restant de la commande est remboursé dans la même étape), restock (par défaut true), reason : customer (par défaut) | fraud | inventory | declined | other. COLVO lit d’abord la commande : une commande déjà annulée → DENY, une commande illisible → HOLD ; avec un remboursement, les limites s’appliquent au montant remboursable.
subscription.cancel_undosubscription_id — supprime une résiliation programmée. Un abonnement déjà terminé ne peut pas être rétabli : l’opération échoue avant tout envoi.

Limites du mandat pour les pauses : max_pause_days (30 par défaut ; au-delà → DENY) et pause_open_ended : never | review (par défaut) | allow pour une pause sans date de fin. La vérification relit l’abonnement : pause_collection défini (ou effacé), cancel_at_period_end à false après une annulation de résiliation, et l’abonnement toujours actif avec accès.

Changements d’offre et coupons : allowed_price_ids, change_plan_at (now / period_end), auto_allow_proration_up_to_minor (un changement qui facture davantage aujourd’hui → REVIEW ; les crédits ne nécessitent jamais de revue), max_proration_minor (au-delà → DENY) et allowed_coupons. Pour un changement « now », COLVO demande au fournisseur un aperçu de la facturation avant de décider ; s’il ne peut pas le lire, la décision est HOLD. La vérification contrôle le nouveau prix (ou le changement programmé) et le coupon sur l’abonnement.

Sur Shopify, la commande tient le rôle du paiement : refund.create reçoit la commande dans payment_id et rembourse de l’argent sur celle-ci (rien n’est remis en stock, la commande reste ouverte). Les actions sur les abonnements sont refusées sur une connexion Shopify.

Idempotence

  • Envoyez Idempotency-Key avec chaque proposition. La même clé avec le même corps renvoie l’opération d’origine (200) ; la même clé avec un corps différent est refusée avec 409 idempotency_key_reused.
  • Indépendamment de l’en-tête, COLVO dérive une clé métier à partir du mandat + de l’action + de la cible ; une seconde proposition ayant le même effet est refusée avec 409 business_key_conflict. Une nouvelle tentative ne double jamais un remboursement.

Réponse

{
  "operation_id": "…", "project_id": "…", "mandate_id": "…",
  "action": "refund.create", "parameters": { … }, "source_request_id": "ticket_4711", "business_key": "…",
  "decision": "REVIEW",
  "decision_reasons": ["amount 5000 above auto-allow 2000 (within max 5000)"],
  "guardrail_hits": [{ "rail": "pii_input", "stage": "input", "action": "REDACT", "advisory": false, "reason": "email redacted" }],
  "exec_state": "NOT_SENT", "verify_state": "NOT_DUE",
  "provider_id": null,
  "approval": { "approval_id": "…", "digest": "sha256:…", "expires_at": "…", "decision": "PENDING" },
  "policy_version": "policy-v1", "mandate_version": 1, "evidence_count": 3, "last_error": null,
  "created_at": "…", "updated_at": "…"
}

Décisions

  • ALLOW — mise immédiatement en file d’attente pour exécution.
  • REVIEW — attend une personne ; approval contient le digest qu’elle doit signer.
  • HOLD — ne peut pas être effectuée en toute sécurité pour le moment (état illisible, plafond dépassé, projet en pause). Reproposez-la plus tard.
  • DENY — violation de la politique ; rien ne se produira. Les motifs sont explicites.

Cycle de vie

exec_state : NOT_SENT → QUEUED → SENDING → ACKNOWLEDGED | PROVIDER_REVIEW | FAILED | UNKNOWN | CANCELED_BEFORE_SEND. verify_state : NOT_DUE → PENDING → VERIFIED | MISMATCH | UNVERIFIABLE. MISMATCH et UNVERIFIABLE ouvrent un incident et déclenchent les alertes.

Lire une opération

GET /v1/operations/{id} — toute clé ou session. Même forme que ci-dessus, actualisée. Interrogez-la après un REVIEW pour connaître l’issue, ou abonnez-vous aux alertes.

Approuver ou rejeter

POST /v1/operations/{id}/decision — session uniquement (éditeur+). La personne qui examine signe le digest exact ; un digest périmé est refusé (409 digest_mismatch), de même qu’une approbation expirée (409 approval_expired).

{ "decision": "approve", "approval_digest": "sha256:…", "rationale": "customer confirmed duplicate charge" }

Notifications d’approbation

Un REVIEW prévient les personnes habilitées à décider : un e-mail à chaque owner et editor, et une alerte vers chaque canal d’alerte du projet abonné à approval.requested / approval.reminder / approval.expired (Slack, e-mail, webhook signé). La fenêtre d’approbation est la plus courte entre 24 heures et l’expiration du mandat. Un rappel est envoyé à 75 % de la fenêtre ; à son terme, une approbation non tranchée passe à EXPIRED, l’opération à CANCELED_BEFORE_SEND, et les approbateurs en sont informés. Les e-mails renvoient vers l’approbation — la décision elle-même se prend toujours en étant connecté, sur le digest exact.

Kill-switch

POST /v1/projects/{id}/pause avec { "reason": "…" } stoppe les nouvelles écritures : les nouvelles propositions reçoivent HOLD, les exécutions en file d’attente patientent. POST /v1/projects/{id}/unpause remet en file les opérations retenues. Les deux sont réservés à la session (éditeur+) et journalisés.

Droit d’accès

Proposer en dehors d’une tentative de test nécessite la fonctionnalité Guard. Sans elle, l’API répond 403 entitlement_required avec une suggestion de mise à niveau ; la console affiche « Débloquer Guard ». Voir les tarifs.

API Guard · Docs · COLVO