API de Guard

El agente propone; COLVO decide, ejecuta una sola vez y verifica. Bastan tres endpoints: proponer, leer y decidir.

Escrito a partir del código · actualizado el 26 sept 2026 · ¿Falta algo o hay un error? Escríbenos

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

actionparámetros
refund.createuno de payment_id | invoice_id | charge_id (pagos antiguos, anteriores a los PaymentIntents), amount_minor (entero > 0), currency (3 letras), reason?
subscription.cancelsubscription_id, mode: period_end (por defecto) | immediate
subscription.pausesubscription_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.resumesubscription_id: quita la pausa; no se envía nada si la suscripción no está pausada.
subscription.change_plansubscription_id, price_id, at: now (por defecto, con prorrateo) | period_end (programado; el plan actual se mantiene hasta la fecha de fin).
coupon.applysubscription_id, coupon_id. Nunca encima de un descuento existente: en ese caso la operación falla antes de enviar nada.
order.cancelSolo 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_undosubscription_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-Key en cada propuesta. La misma clave con el mismo cuerpo devuelve la operación original (200); la misma clave con un cuerpo distinto se rechaza con 409 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; approval incluye 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.

API de Guard · Docs · COLVO