Referencia de la API
Endpoints REST de stacks IaC - autenticación, scopes, cuerpos de petición y respuesta, y códigos de error.
Todo lo que hace el portal de stacks está disponible por REST bajo /v1/iac. Esta página es
la referencia completa — pensada también para agentes de IA que crean y despliegan stacks
por ti (esta misma página existe como Markdown limpio agregando .md a la URL).
export CORTEX_API_URL="https://cortex.opensolvex.co" # http://localhost:3006 en desarrolloAutenticación
Dos mecanismos, el mismo contrato:
- API key (machine-to-machine, CI, agentes): header
x-api-key: osx_sk_xxx. Si la key pertenece a un tenant, todas las operaciones ocurren en ese tenant y el campotenantIddel body/query se ignora. Las keys de plataforma deben enviartenantIdexplícito: en el body de losPOST/DELETEy como query param (?tenantId=org_x) en losGET; sin él, la operación responde400. - Token de portal (humanos): header
Authorization: Bearer <token>emitido por la consola de OpenSolvex; el tenant se toma de tu organización.
Toda operación queda auditada con su actor (api-key:<id> o user:<id>). Cuando una key
de plataforma opera en nombre de un tenant, el actor queda decorado como
api-key:<id> on-behalf-of:<tenantId> y los eventos de dominio iac.stack.* llevan
onBehalfOf: true — así se puede auditar qué hizo cada key de plataforma y sobre qué
tenants.
Scopes
| Scope | Permite |
|---|---|
iac:read | Listar y leer stacks, changesets y eventos |
iac:manage | Validar templates, crear stacks, proponer changesets, detectar drift, exportar |
iac:deploy | Ejecutar changesets y eliminar stacks |
Un scope insuficiente responde 403. iac:deploy equivale a administrar el tenant
completo — ver Seguridad.
Endpoints
| Método | Ruta | Scope |
|---|---|---|
POST | /v1/iac/templates/validate | iac:manage |
GET | /v1/iac/stacks | iac:read |
POST | /v1/iac/stacks | iac:manage |
GET | /v1/iac/stacks/:id | iac:read |
GET | /v1/iac/stacks/:id/events | iac:read |
GET | /v1/iac/stacks/:id/change-sets | iac:read |
POST | /v1/iac/stacks/:id/change-sets | iac:manage |
POST | /v1/iac/stacks/:id/change-sets/:csId/execute | iac:deploy |
POST | /v1/iac/stacks/:id/detect-drift | iac:manage |
DELETE | /v1/iac/stacks/:id | iac:deploy |
GET | /v1/iac/export | iac:manage |
Validar un template
POST /v1/iac/templates/validate — dry-run puro, no muta nada.
{ "template": "<YAML como string>" }200 si es válido (con los recursos detectados y el orden de despliegue); 422 si no:
{
"message": "Template inválido",
"errors": [
{ "path": "resources.Modelo.properties.accountId", "message": "referencia a un nombre inexistente" }
]
}Crear un stack
POST /v1/iac/stacks — crea el stack en REVIEW_IN_PROGRESS con su changeset inicial.
No materializa recursos: eso ocurre al ejecutar el changeset.
{
"name": "mi-primer-stack",
"template": "<YAML como string>",
"parameters": { "OpenaiApiKey": "sk-..." }
}name: kebab-case (^[a-z0-9][a-z0-9-]*$), único por tenant (409si ya existe).parameters: valores para los parámetros del template. LosnoEchonunca vuelven en ninguna respuesta.
Responde { "stack": { ... }, "changeSet": { ... } } con los ids de ambos.
Leer stacks
GET /v1/iac/stacks— lista con nombre, estado y metadatos.GET /v1/iac/stacks/:id— detalle:status,statusReason,templateSource(el YAML vigente),parameters(redactados),outputsyresources[](nombre lógico → tipo, id físico, estado, drift).GET /v1/iac/stacks/:id/events— traza de despliegue, más recientes primero: recurso, estado, motivo, actor, timestamp.
Proponer un changeset
POST /v1/iac/stacks/:id/change-sets
{
"template": "<YAML como string>",
"parameters": { "OpenaiApiKey": "sk-..." }
}Valida el template, calcula el diff contra lo aplicado y responde el changeset en pending.
El pendiente anterior (si lo había) queda superseded. 409 si el stack está en progreso.
{
"id": "chs_01H...",
"status": "pending",
"diff": {
"entries": [
{ "logicalId": "AgenteVentas", "type": "Cortex::Agent", "action": "modify", "changedFields": ["systemPrompt"] },
{ "logicalId": "CrmTools", "type": "Cortex::ToolSource", "action": "replace", "replacement": "delete-first", "reason": "kind es inmutable" }
],
"applyOrder": ["..."],
"removeOrder": ["..."]
}
}GET /v1/iac/stacks/:id/change-sets lista el historial completo.
Ejecutar un changeset
POST /v1/iac/stacks/:id/change-sets/:csId/execute — sin body. Responde 202 y despliega
en segundo plano. El changeset debe estar pending; 409 si el stack ya está en progreso.
Para saber el resultado, consulta el stack hasta que salga de *_IN_PROGRESS
(CREATE_COMPLETE/UPDATE_COMPLETE = éxito; ROLLBACK_COMPLETE/UPDATE_ROLLBACK_COMPLETE
= falló y se revirtió; los detalles quedan en /events).
Detectar drift
POST /v1/iac/stacks/:id/detect-drift — compara lo aplicado con lo que existe y responde el
estado por recurso (IN_SYNC, DRIFTED, DELETED) con el diff por propiedad.
Eliminar un stack
DELETE /v1/iac/stacks/:id
{ "retain": ["SkillCalificar"] }Responde 202 con { "status": "DELETE_IN_PROGRESS" }. Elimina los recursos en orden
inverso; los nombres en retain sobreviven desasociados (400 si un nombre no existe en el
stack). El historial del stack se conserva.
Exportar un template
GET /v1/iac/export — genera un template YAML a partir de los recursos existentes del
tenant (?resources= para seleccionar). Útil para adoptar como código lo creado a mano; el
resultado usa la forma JSON de las intrínsecas, equivalente a los tags.
Códigos de error
| Código | Cuándo |
|---|---|
400 | Body inválido (nombre mal formado, retain con nombres desconocidos) |
401 | Sin credenciales o credenciales inválidas |
403 | Scopes insuficientes |
404 | Stack o changeset inexistente (o de otro tenant) |
409 | Nombre de stack duplicado; stack en progreso (mutex); changeset no ejecutable |
422 | Template inválido (con errors[] detallado) |
Receta para agentes
El flujo mínimo que un agente debe seguir para dejar un stack funcionando:
# 1. Validar
curl -sX POST "$CORTEX_API_URL/v1/iac/templates/validate" \
-H "x-api-key: $CORTEX_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --rawfile t template.yaml '{template: $t}')"
# 2. Crear stack (guarda stack.id y changeSet.id de la respuesta)
curl -sX POST "$CORTEX_API_URL/v1/iac/stacks" \
-H "x-api-key: $CORTEX_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --rawfile t template.yaml \
'{name: "mi-stack", template: $t, parameters: {OpenaiApiKey: env.OPENAI_API_KEY}}')"
# 3. Ejecutar el changeset
curl -sX POST "$CORTEX_API_URL/v1/iac/stacks/$STACK_ID/change-sets/$CHANGESET_ID/execute" \
-H "x-api-key: $CORTEX_API_KEY"
# 4. Esperar hasta CREATE_COMPLETE (o ROLLBACK_COMPLETE → revisar /events)
until [ "$(curl -s "$CORTEX_API_URL/v1/iac/stacks/$STACK_ID" \
-H "x-api-key: $CORTEX_API_KEY" | jq -r .status)" != "CREATE_IN_PROGRESS" ]; do sleep 3; doneReglas que el agente debe respetar:
- Los valores sensibles van solo en
parameters(contra parámetrosnoEcho), nunca dentro del YAML. - Nunca asumas éxito tras el
202: consulta el estado final y, si hubo rollback, lee/eventspara diagnosticar antes de re-proponer. - Ante
409por stack en progreso, espera y reintenta — no crees un stack paralelo. - Para modificar un stack existente propón un changeset; no elimines y recrees.