Guard API

L’agente propone; COLVO decide, esegue una sola volta e verifica. Bastano tre endpoint: proporre, leggere, decidere.

Scritto a partire dal codice · aggiornato il 26 set 2026 · Manca qualcosa o c’è un errore? Scrivici

Proporre un’operazione

POST /v1/operations — chiave agente o backend, o sessione (editor+). Risponde subito con 202 e la decisione; esecuzione e verifica proseguono in modo asincrono.

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": ["…"] } }

Azioni e parametri

actionparametri
refund.createuno tra payment_id | invoice_id | charge_id (pagamenti più vecchi, precedenti ai PaymentIntents), amount_minor (intero > 0), currency (3 lettere), reason?
subscription.cancelsubscription_id, mode: period_end (predefinito) | immediate
subscription.pausesubscription_id, resumes_at? (data o data e ora; se assente = nessuna data di fine), behavior: void (predefinito) | keep_as_draft. La fatturazione si ferma, l’accesso resta.
subscription.resumesubscription_id — toglie la pausa; non viene inviato nulla se l’abbonamento non è in pausa.
subscription.change_plansubscription_id, price_id, at: now (predefinito, con proration) | period_end (programmato; il piano attuale resta fino alla data di fine).
coupon.applysubscription_id, coupon_id. Mai sopra uno sconto già presente: in quel caso l’operazione fallisce prima di inviare qualcosa.
order.cancelSolo Shopify. order_id (#1042, id numerico o gid), refund (predefinito false: se true, quanto resta sull’ordine viene rimborsato nello stesso passaggio), restock (predefinito true), reason: customer (predefinito) | fraud | inventory | declined | other. COLVO legge prima l’ordine: un ordine già annullato → DENY, un ordine che non riesce a leggere → HOLD; con un rimborso, i limiti si applicano all’importo rimborsabile.
subscription.cancel_undosubscription_id — rimuove una disdetta programmata. Un abbonamento già terminato non si può ripristinare: l’operazione fallisce prima di inviare qualcosa.

Limiti del mandato per le pause: max_pause_days (predefinito 30; oltre → DENY) e pause_open_ended: never | review (predefinito) | allow per una pausa senza data di fine. La verifica rilegge l’abbonamento: pause_collection impostato (o rimosso), cancel_at_period_end false dopo un ripristino, e l’abbonamento ancora attivo con accesso.

Cambi di piano e coupon: allowed_price_ids, change_plan_at (now / period_end), auto_allow_proration_up_to_minor (un cambio che oggi addebita di più → REVIEW; i crediti non richiedono mai revisione), max_proration_minor (oltre → DENY) e allowed_coupons. Per un cambio “now” COLVO chiede al provider un’anteprima dell’addebito prima di decidere; se non riesce a leggerla, la decisione è HOLD. La verifica controlla il nuovo prezzo (o il cambio programmato) e il coupon sull’abbonamento.

Su Shopify l’ordine fa la parte del pagamento: refund.create riceve l’ordine in payment_id e rimborsa denaro su di esso (nulla torna a magazzino, l’ordine resta aperto). Le azioni sugli abbonamenti vengono rifiutate su una connessione Shopify.

Idempotenza

  • Invia Idempotency-Key con ogni proposta. La stessa chiave con lo stesso body restituisce l’operazione originale (200); la stessa chiave con un body diverso viene rifiutata con 409 idempotency_key_reused.
  • Indipendentemente dall’header, COLVO ricava una chiave di business da mandato + azione + destinatario; una seconda proposta con lo stesso effetto viene rifiutata con 409 business_key_conflict. Un nuovo tentativo non raddoppia mai un rimborso.

Risposta

{
  "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": "…"
}

Decisioni

  • ALLOW — messa subito in coda per l’esecuzione.
  • REVIEW — attende una persona; approval contiene il digest che deve firmare.
  • HOLD — ora non si può fare in sicurezza (stato non leggibile, limite superato, progetto in pausa). Riproponila più tardi.
  • DENY — violazione della policy; non succederà nulla. I motivi sono espliciti.

Ciclo di vita

exec_state: NOT_SENT → QUEUED → SENDING → ACKNOWLEDGED | PROVIDER_REVIEW | FAILED | UNKNOWN | CANCELED_BEFORE_SEND. verify_state: NOT_DUE → PENDING → VERIFIED | MISMATCH | UNVERIFIABLE. MISMATCH e UNVERIFIABLE aprono un incidente e inviano gli alert.

Leggere un’operazione

GET /v1/operations/{id} — qualsiasi chiave o sessione. Stessa forma di sopra, aggiornata. Interrogala dopo un REVIEW per conoscere l’esito, oppure iscriviti agli alert.

Approvare o rifiutare

POST /v1/operations/{id}/decision — solo sessione (editor+). Chi rivede firma il digest esatto; un digest non aggiornato viene rifiutato (409 digest_mismatch), così come un’approvazione scaduta (409 approval_expired).

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

Notifiche di approvazione

Un REVIEW avvisa chi può decidere: un’email a ogni owner ed editor, e un alert a ogni canale di alert del progetto iscritto a approval.requested / approval.reminder / approval.expired (Slack, email, webhook firmato). La finestra di approvazione è la più breve tra 24 ore e la scadenza del mandato. Un promemoria parte al 75 % della finestra; alla fine un’approvazione non decisa diventa EXPIRED, l’operazione CANCELED_BEFORE_SEND, e chi approva viene avvisato. Le email puntano all’approvazione — la decisione avviene sempre da utente autenticato, sul digest esatto.

Kill-switch

POST /v1/projects/{id}/pause con { "reason": "…" } ferma le nuove scritture: le nuove proposte ricevono HOLD, le esecuzioni in coda attendono. POST /v1/projects/{id}/unpause rimette in coda le operazioni trattenute. Entrambi sono solo sessione (editor+) e registrati.

Abilitazione

Proporre fuori da un tentativo di test richiede la funzionalità Guard. Senza di essa l’API risponde 403 entitlement_required con un suggerimento di upgrade; la console mostra “Sblocca Guard”. Vedi i prezzi.

API di Guard · Docs · COLVO