Mandatos y conexiones
Un mandato es una autorización que tu backend de confianza declara una sola vez: qué cliente, qué acciones, qué límites y hasta cuándo. Guard evalúa cada propuesta según el mandato. Una conexión es la cuenta del proveedor sobre la que se ejecutan las acciones.
Registra un mandato
POST /v1/mandates: clave de backend o sesión (editor+). Lo habitual es un mandato por ticket o por conversación.
{
"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
}Respuesta 201: el mandato con su mandate_id y su version. Los mandatos son inmutables; para cambiar la autorización, registra uno nuevo. Un mandato caducado o agotado hace que toda propuesta posterior sea DENY con el motivo mandate_inactive.
Cómo se traducen los límites en decisiones
| Condición | Decisión |
|---|---|
acción que no está en actions, sujeto incorrecto, moneda incorrecta, por encima de max_amount_minor, por encima de max_operations, una pausa más larga que max_pause_days (o sin fecha de fin cuando pause_open_ended es never), un plan que no está en allowed_price_ids, un cupón que no está en allowed_coupons, un cambio que cobra más de max_proration_minor | DENY |
importe por encima de auto_allow_up_to_minor pero dentro del máximo; modo de cancelación en cancel_modes_review; una pausa sin fecha de fin cuando pause_open_ended es review; un cambio de plan que hoy cobra más de auto_allow_proration_up_to_minor | REVIEW (aprobación humana) |
estado del proveedor ilegible, límite por cliente final superado con HOLD | HOLD (se puede volver a proponer más tarde) |
| todo dentro de los límites, guardrails superados | ALLOW → ejecutada una vez → verificada |
Conexiones
Una conexión es una cuenta de proveedor vinculada a un proyecto. Hoy existen estos tipos:
- Sandbox (Stripe simulado): POST
/v1/projects/{id}/connections/sandboxcrea un mundo aislado a partir de una fixture de plantilla, opcionalmente con fallos (errores enrefund.create/subscription.cancel/read, retrasos). Las ejecuciones de prueba crean sus propios mundos por intento; este endpoint sirve para probar Guard sin cuenta de Stripe. - Stripe: se conecta desde la página del proyecto (o con
POST /v1/projects/{id}/connections/stripeconsecret_key+webhook_secret) con una clave restringida; las claves live deben ser restringidas (rk_live_) y los permisos se comprueban en modo solo lectura antes de guardar. Rótala con…/connections/{connection_id}/rotate; elimínala conDELETE(se rechaza mientras la usen mandatos activos) (escritura en reembolsos y suscripciones, lectura para la verificación) y un secreto de firma de webhooks. La clave se cifra por organización y solo la usa el executor. - Paddle Billing: se conecta desde la página del proyecto (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_…del destino de notificaciones). La clave necesita Transactions en lectura, Subscriptions en lectura y escritura, Adjustments en lectura y escritura y Customers en lectura; una clave que además pueda escribir productos se rechaza. En Paddle, COLVO puede reembolsar (un adjustment de reembolso, que espera la revisión de Paddle antes de contar como verificado), cancelar, pausar, reanudar y deshacer una cancelación programada; todavía no puede cambiar de plan ni aplicar descuentos. Paddle no tiene claves de idempotencia: COLVO marca el motivo de cada reembolso con[colvo:<operation id>]y lo busca antes de cualquier reintento. - Chargebee: se conecta desde la página del proyecto (Add a connection → Chargebee) o con
POST /v1/projects/{id}/connections/chargebeeconsite(p. ej.acme-test) +api_key. Las claves de Chargebee no se pueden limitar a ciertas acciones: usa una clave de acceso completo del tipo Write key creada solo para COLVO (una clave de solo lectura se rechaza; una clave que puede borrar recibe un aviso). La respuesta incluye la URL, el usuario y la contraseña del webhook (se muestran una sola vez). En Chargebee, un reembolso indica una factura (invoice_id, opayment_id= el id de la factura) y se convierte en una nota de crédito reembolsable; cancelar, pausar, reanudar, deshacer una cancelación, cambios de plan (item prices) y cupones funcionan igual que en Stripe. Las escrituras llevanchargebee-idempotency-key= el id de la operación; Chargebee guarda las claves durante 30 minutos, así que pasado ese tiempo COLVO nunca reenvía una escritura: abre un incidente para que lo revise una persona.
Cada proyecto tiene una conexión Guard por defecto; un mandato puede indicar otra.
Límites por cliente final
GET / PUT /v1/projects/{id}/end-user-caps: hasta seis límites por proyecto, asociados al customer_id del sujeto del mandato:
{ "caps": [
{ "window": "day", "max_actions": 3, "action_on_exceed": "HOLD" },
{ "window": "month", "max_amount_minor": 20000, "action_on_exceed": "DENY" }
] }