API de Guard
El agente propone; COLVO decide, ejecuta una sola vez y verifica. Bastan tres endpoints: proponer, leer y decidir.
Proponer una operación
POST /v1/operations: clave de agente o de backend, o sesión (editor+). Responde de inmediato con 202 y la decisión; la ejecución y la verificación continúan de forma asíncrona.
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": ["…"] } }Acciones y parámetros
| action | parámetros |
|---|---|
| refund.create | uno de payment_id | invoice_id | charge_id (pagos antiguos, anteriores a los PaymentIntents), amount_minor (entero > 0), currency (3 letras), reason? |
| subscription.cancel | subscription_id, mode: period_end (por defecto) | immediate |
| subscription.pause | subscription_id, resumes_at? (fecha o fecha y hora; si falta = sin fecha de fin), behavior: void (por defecto) | keep_as_draft. La facturación se detiene, el acceso se mantiene. |
| subscription.resume | subscription_id: quita la pausa; no se envía nada si la suscripción no está pausada. |
| subscription.change_plan | subscription_id, price_id, at: now (por defecto, con prorrateo) | period_end (programado; el plan actual se mantiene hasta la fecha de fin). |
| coupon.apply | subscription_id, coupon_id. Nunca encima de un descuento existente: en ese caso la operación falla antes de enviar nada. |
| order.cancel | Solo Shopify. order_id (#1042, id numérico o gid), refund (por defecto false: si es true, lo que queda en el pedido se reembolsa en el mismo paso), restock (por defecto true), reason: customer (por defecto) | fraud | inventory | declined | other. COLVO lee primero el pedido: un pedido ya cancelado → DENY, un pedido que no puede leer → HOLD, y con un reembolso los límites se aplican al importe reembolsable. |
| subscription.cancel_undo | subscription_id: elimina una cancelación programada. Una suscripción ya terminada no se puede recuperar: la operación falla antes de enviar nada. |
Límites del mandato para las pausas: max_pause_days (por defecto 30; más → DENY) y pause_open_ended: never | review (por defecto) | allow para una pausa sin fecha de fin. La verificación vuelve a leer la suscripción: pause_collection definido (o eliminado), cancel_at_period_end en false después de deshacer una cancelación, y la suscripción sigue activa y con acceso.
Cambios de plan y cupones: allowed_price_ids, change_plan_at (now / period_end), auto_allow_proration_up_to_minor (un cambio que hoy cobra más → REVIEW; los créditos nunca necesitan revisión), max_proration_minor (más → DENY) y allowed_coupons. Para un cambio “now”, COLVO pide al proveedor una vista previa del cargo antes de decidir; si no puede leerla, la decisión es HOLD. La verificación comprueba el nuevo precio (o el cambio programado) y el cupón en la suscripción.
En Shopify, el pedido hace el papel del pago: refund.create recibe el pedido en payment_id y reembolsa dinero sobre él (no se repone stock y el pedido sigue abierto). Las acciones de suscripción se rechazan en una conexión de Shopify.
Idempotencia
- Envía
Idempotency-Keyen cada propuesta. La misma clave con el mismo cuerpo devuelve la operación original (200); la misma clave con un cuerpo distinto se rechaza con409 idempotency_key_reused. - Con independencia del header, COLVO deriva una clave de negocio a partir de mandato + acción + objetivo; una segunda propuesta con el mismo efecto se rechaza con
409 business_key_conflict. Un reintento nunca duplica un reembolso.
Respuesta
{
"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": "…"
}Decisiones
ALLOW: se pone en cola para ejecutarse ya.REVIEW: espera a una persona;approvalincluye el digest que debe firmar.HOLD: ahora no se puede hacer de forma segura (estado ilegible, límite superado, proyecto en pausa). Vuelve a proponerla más tarde.DENY: infracción de la política; no va a pasar nada. Los motivos son explícitos.
Ciclo de vida
exec_state: NOT_SENT → QUEUED → SENDING → ACKNOWLEDGED | PROVIDER_REVIEW | FAILED | UNKNOWN | CANCELED_BEFORE_SEND. verify_state: NOT_DUE → PENDING → VERIFIED | MISMATCH | UNVERIFIABLE. MISMATCH y UNVERIFIABLE abren un incidente y disparan las alertas.
Leer una operación
GET /v1/operations/{id}: cualquier clave o sesión. Misma forma que arriba, actualizada. Consúltala después de un REVIEW para conocer el resultado, o suscríbete a las alertas.
Aprobar o rechazar
POST /v1/operations/{id}/decision: solo sesión (editor+). Quien revisa firma el digest exacto; un digest desactualizado se rechaza (409 digest_mismatch), igual que una aprobación caducada (409 approval_expired).
{ "decision": "approve", "approval_digest": "sha256:…", "rationale": "customer confirmed duplicate charge" }Notificaciones de aprobación
Un REVIEW avisa a quienes pueden decidir: un email a cada owner y editor, y una alerta a cada canal de alertas del proyecto suscrito a approval.requested / approval.reminder / approval.expired (Slack, email, webhook firmado). La ventana de aprobación es la más corta entre 24 horas y la caducidad del mandato. Se envía un recordatorio al 75 % de la ventana; al final, una aprobación sin decidir pasa a EXPIRED, la operación a CANCELED_BEFORE_SEND, y se avisa a quienes aprueban. Los emails enlazan a la aprobación; la decisión siempre se toma con la sesión iniciada, sobre el digest exacto.
Kill-switch
POST /v1/projects/{id}/pause con { "reason": "…" } detiene las nuevas escrituras: las nuevas propuestas reciben HOLD y las ejecuciones en cola esperan. POST /v1/projects/{id}/unpause vuelve a poner en cola las operaciones retenidas. Ambos son solo sesión (editor+) y quedan registrados.
Permiso del plan
Proponer fuera de un intento de prueba requiere la funcionalidad Guard. Sin ella, la API responde 403 entitlement_required con una sugerencia de upgrade; la consola muestra “Desbloquear Guard”. Consulta los precios.