{
    "variable": [
        {
            "id": "baseUrl",
            "key": "baseUrl",
            "type": "string",
            "name": "string",
            "value": "https:\/\/icsmanager.docloud.es"
        }
    ],
    "info": {
        "name": "ICSManager Agent API",
        "_postman_id": "51601089-31d5-498e-93da-e1d1e37f1936",
        "description": "API para agentes externos (Marta, etc.) que leen el estado de los servicios contratados en ICSManager y se auto-configuran.",
        "schema": "https:\/\/schema.getpostman.com\/json\/collection\/v2.1.0\/collection.json"
    },
    "item": [
        {
            "name": "Agent API",
            "description": "\nEndpoints consumidos por agentes externos (Marta y similares) para leer\nservicios contratados y auto-configurarse.",
            "item": [
                {
                    "name": "Listar servicios por tipo",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/agent\/services",
                            "query": [
                                {
                                    "key": "type",
                                    "value": "dashboard-pbs",
                                    "description": "Tipo de servicio. Valores: `dashboard-pbs`, `dashboard-pve`, `pve-node`, `pbs-node`.",
                                    "disabled": false
                                },
                                {
                                    "key": "include_unpublished",
                                    "value": "",
                                    "description": "Incluye servicios sin SLUG (vista admin). Por defecto solo se devuelven los publicables (con SLUG).",
                                    "disabled": true
                                },
                                {
                                    "key": "include",
                                    "value": "pve_nodes%2Cdatastores",
                                    "description": "Lista separada por comas de embeds: `pve_nodes`, `datastores`. Cada servicio incluir\u00e1 los recursos del propietario, evitando llamadas N+1.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/agent\/services?type=dashboard-pbs&include_unpublished=&include=pve_nodes%2Cdatastores"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Devuelve la lista de servicios contratados del tipo indicado, con su slug,\npropietario y emails autorizados. El propietario **siempre aparece** tambi\u00e9n\ndentro de `autorizados`. Los servicios sin `SLUG` definido no se incluyen\n(salvo que se pase `include_unpublished=1`). Los servicios con `activo:false`\nse siguen devolviendo hasta que el agente confirme la desprovisi\u00f3n v\u00eda\n`DELETE \/api\/agent\/services\/{id}`.\n\n## Derivaci\u00f3n de `activo` para tipos de nodo\n\nPara `type=pve-node` y `type=pbs-node`, el campo `activo` no es literalmente\n`servicios.activo` de BD \u2014 se cruza con la existencia de un dashboard activo\nque lo referencie, garantizando coherencia con `?type=dashboard-*`:\n\n- **pve-node**: `activo:true` \u21d4 `servicios.activo=true` Y el propietario\n  de al menos un `dashboard-pve` activo tiene autorizaci\u00f3n (en cualquier\n  scope: perfil, contrato o servicio) sobre este nodo.\n- **pbs-node**: misma regla sim\u00e9trica con `dashboard-pbs`. El cruce es\n  por **autorizaciones** (no por `perfil_fiscal`), porque el perfil del\n  nodo refleja facturaci\u00f3n (a menudo el proveedor de infraestructura,\n  ej. OVH) y no necesariamente coincide con el perfil del dashboard.\n\nCuando se aplica el override, el payload a\u00f1ade\n`\"activo_origen\":\"no_referenciado_en_dashboard\"` para que el agente pueda\ndistinguir \"desactivado en BD\" de \"derivado desde dashboards\". En ese caso\n`deactivated_at` se rellena con `updated_at` del servicio como **proxy**\n(no es el momento exacto en que perdi\u00f3 la referencia del dashboard, pero\nmantiene el contrato `activo:false \u21d2 deactivated_at siempre poblado`).\n\nLos reconcilers que filtran por `activo:true` (Marta\/pulso, generaci\u00f3n\nde scrape configs) se autocorrigen sin tocar BD."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "[\n  {\n    \"id\": 2382,\n    \"slug\": \"JRM04-ns31275378\",\n    \"activo\": true,\n    \"deactivated_at\": null,\n    \"last_modified\": \"2026-05-09T12:21:00+02:00\",\n    \"propietario\": {\n      \"email\": \"juanjo@reyesinformatica.com\",\n      \"nombre\": \"Juanjo Reyes\"\n    },\n    \"autorizados\": [\n      {\"email\": \"juanjo@reyesinformatica.com\"}\n    ],\n    \"fqdn\": \"ns31275378.core.com.es\"\n  }\n]",
                            "name": "OK pve-node"
                        },
                        {
                            "header": [],
                            "code": 200,
                            "body": "[\n  {\n    \"id\": 2382,\n    \"slug\": \"JRM04-ns31275378\",\n    \"activo\": false,\n    \"activo_origen\": \"no_referenciado_en_dashboard\",\n    \"deactivated_at\": null,\n    \"last_modified\": \"2026-05-09T12:21:00+02:00\",\n    \"propietario\": {\"email\": \"juanjo@reyesinformatica.com\", \"nombre\": \"Juanjo Reyes\"},\n    \"autorizados\": [{\"email\": \"juanjo@reyesinformatica.com\"}],\n    \"fqdn\": \"ns31275378.core.com.es\"\n  }\n]",
                            "name": "activo derivado a false"
                        },
                        {
                            "header": [],
                            "code": 400,
                            "body": "{\"error\":\"bad_request\",\"message\":\"Query param \\\"type\\\" is required and must be one of: dashboard-pbs, dashboard-pve, pve-node, pbs-node\"}",
                            "name": "type inv\u00e1lido"
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"unauthorized\",\"message\":\"Missing Authorization: Bearer <token> header\"}",
                            "name": "sin token"
                        }
                    ]
                },
                {
                    "name": "Confirmar desprovisi\u00f3n de un servicio",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/agent\/services\/:id",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/agent\/services\/:id",
                            "variable": [
                                {
                                    "id": "id",
                                    "key": "id",
                                    "value": "2821",
                                    "description": "ID num\u00e9rico del servicio."
                                }
                            ]
                        },
                        "method": "DELETE",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"reason\":\"\\\"Carpeta Grafana eliminada tras baja de contrato\\\"\"}"
                        },
                        "description": "El agente llama este endpoint tras haber desprovisionado el recurso asociado\n(ej: borrar la carpeta Grafana + grupo Authentik del dashboard). Marca el\nservicio con `desprovisionado_at = now()` para que no vuelva a aparecer en\nel endpoint de listado. Idempotente.\n\nReglas:\n- S\u00f3lo se permite sobre servicios con `activo=false` \u2014 un servicio activo\n  devuelve `409 Conflict`.\n- No borra el servicio en ICSManager."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 204,
                            "body": "",
                            "name": "OK"
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"unauthorized\",\"message\":\"Missing Authorization: Bearer <token> header\"}",
                            "name": "sin token"
                        },
                        {
                            "header": [],
                            "code": 404,
                            "body": "{\"error\":\"not_found\",\"message\":\"Service 99999999 not found\"}",
                            "name": "servicio inexistente"
                        },
                        {
                            "header": [],
                            "code": 409,
                            "body": "{\"error\":\"conflict\",\"message\":\"Cannot deprovision an active service \u2014 set activo=false first\"}",
                            "name": "servicio activo"
                        }
                    ]
                },
                {
                    "name": "Datastores accesibles para un usuario",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/agent\/users\/:email\/datastores",
                            "query": [
                                {
                                    "key": "type",
                                    "value": "pbs",
                                    "description": "Tipo de recurso. Actualmente s\u00f3lo `pbs`.",
                                    "disabled": false
                                }
                            ],
                            "raw": "{{baseUrl}}\/api\/agent\/users\/:email\/datastores?type=pbs",
                            "variable": [
                                {
                                    "id": "email",
                                    "key": "email",
                                    "value": "soft4ebusiness%40gmail.com",
                                    "description": "Email del usuario (URL-encoded)."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Devuelve los datastores a los que el email tiene acceso \u2014 sea como propietario\no como autorizado (en scope perfil, contrato o servicio espec\u00edfico).\nUn mismo `nombre` puede aparecer varias veces si hay acceso desde distintos\ncontratos; el cliente debe deduplicar por `nombre` si lo necesita."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\n  \"user_email\": \"soft4ebusiness@gmail.com\",\n  \"datastores\": [\n    {\"nombre\": \"mooring\", \"contratado_gb\": 1250, \"contrato\": \"CTR-1318\"},\n    {\"nombre\": \"track\", \"contratado_gb\": 1000, \"contrato\": \"CTR-1294\"}\n  ]\n}",
                            "name": "OK"
                        },
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"user_email\":\"foo@bar.example\",\"datastores\":[]}",
                            "name": "sin datastores"
                        },
                        {
                            "header": [],
                            "code": 400,
                            "body": "{\"error\":\"bad_request\",\"message\":\"Query param \\\"type\\\" is required and must be one of: pbs\"}",
                            "name": "type inv\u00e1lido"
                        }
                    ]
                },
                {
                    "name": "Nodos PVE accesibles para un usuario",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/agent\/users\/:email\/pve-nodes",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/agent\/users\/:email\/pve-nodes",
                            "variable": [
                                {
                                    "id": "email",
                                    "key": "email",
                                    "value": "juanjo%40reyesinformatica.com",
                                    "description": "Email del usuario (URL-encoded)."
                                }
                            ]
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Devuelve los nodos PVE (servicios `plugin=pve` activos con contrato activo)\na los que el email tiene acceso \u2014 sea como propietario o como autorizado en\nscope perfil, contrato o servicio espec\u00edfico.\n\nUn mismo nodo puede aparecer varias veces si hay acceso desde distintos\ncontratos; el cliente debe deduplicar por `fqdn` si lo necesita.\n\nEl campo `pve_vms` est\u00e1 reservado para el futuro caso de VMs individuales\nen PVEs compartidos \u2014 hoy devuelve array vac\u00edo."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\n  \"user_email\": \"juanjo@reyesinformatica.com\",\n  \"pve_nodes\": [\n    {\"fqdn\": \"ns3189882.ip-152-228-223.eu\", \"slug\": \"reyes-pve-01\"},\n    {\"fqdn\": \"ns31275378.ip-51-210-117.eu\", \"slug\": \"\"}\n  ],\n  \"pve_vms\": []\n}",
                            "name": "OK"
                        },
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"user_email\":\"foo@bar.example\",\"pve_nodes\":[],\"pve_vms\":[]}",
                            "name": "sin nodos"
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"unauthorized\",\"message\":\"Missing Authorization: Bearer <token> header\"}",
                            "name": "sin token"
                        }
                    ]
                }
            ]
        },
        {
            "name": "DNS Slave API",
            "description": "\nEndpoint consumido por los servidores DNS secundarios para auto-configurarse:\ndevuelven la lista de zonas primarias de las que deben hacer de secundarios,\ncon su FQDN, IPs, clave TSIG y usuarios autorizados (para el panel Technium).",
            "item": [
                {
                    "name": "Listar zonas dns-slave",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/agent\/dns-slaves",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/agent\/dns-slaves"
                        },
                        "method": "GET",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": null,
                        "description": "Devuelve las zonas DNS del plugin `dns-slave` que un servidor secundario\ndebe gestionar. Incluye las activas y tambi\u00e9n las dadas de BAJA (servicio\ninactivo o contrato no activo) marcadas con `activo:false` y su\n`deactivated_at`, para que el secundario pueda retirarlas sin dejar zonas\nhu\u00e9rfanas. Solo se omiten las desprovisionadas definitivamente. Cada zona\nincluye FQDN del primario, IPs (IPv4\/IPv6), puerto, clave TSIG (AXFR) y los\nusuarios autorizados (propietario + autorizaciones lectura\/escritura\/t\u00e9cnico).\nTodos los secundarios autenticados reciben la misma lista."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\n  \"generated_at\": \"2026-07-25T12:00:00+02:00\",\n  \"count\": 2,\n  \"zonas\": [\n    {\n      \"servicio_id\": 2868,\n      \"fqdn\": \"jrm1.plesk.do\",\n      \"ipv4\": \"51.254.54.217\",\n      \"ipv6\": \"2001:41d0:303:f2dc::23\",\n      \"port\": 53,\n      \"tsig\": \"hmac-sha256:clave...\",\n      \"activo\": true,\n      \"deactivated_at\": null,\n      \"autorizados\": [\n        {\"email\": \"juanjo@reyesinformatica.com\", \"nombre\": \"Juanjo Reyes\", \"propietario\": true},\n        {\"email\": \"soporte@reyesinformatica.com\", \"nombre\": \"Soporte\"}\n      ]\n    },\n    {\n      \"servicio_id\": 2900,\n      \"fqdn\": \"antigua.example.com\",\n      \"ipv4\": \"203.0.113.10\",\n      \"ipv6\": null,\n      \"port\": 53,\n      \"tsig\": null,\n      \"activo\": false,\n      \"deactivated_at\": \"2026-06-01T09:00:00+02:00\",\n      \"autorizados\": []\n    }\n  ]\n}",
                            "name": "ok"
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"unauthorized\",\"message\":\"Missing Authorization: Bearer <token> header\"}",
                            "name": "sin token"
                        }
                    ]
                },
                {
                    "name": "Recibir informe de estado de un secundario",
                    "request": {
                        "url": {
                            "host": "{{baseUrl}}",
                            "path": "api\/agent\/dns-slaves\/informe",
                            "query": [],
                            "raw": "{{baseUrl}}\/api\/agent\/dns-slaves\/informe"
                        },
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application\/json"
                            },
                            {
                                "key": "Accept",
                                "value": "application\/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\"servicio_id\":2868,\"secundario\":\"ns1.tecnium.example\",\"correcto\":false,\"reported_at\":\"2026-07-25T12:00:00+02:00\",\"bloques\":[{\"codigo\":\"transferencia\",\"titulo\":\"Transferencia fallida\",\"severidad\":\"error\",\"accion\":\"Revisar AXFR\",\"dominios\":[{\"dominio\":\"example.com\",\"diagnostico\":\"syncFailed\",\"info\":{\"serial\":123}}]}]}"
                        },
                        "description": "Un servidor DNS secundario env\u00eda (fire-and-forget, idempotente por hash\nlocal) el estado de un servicio cuando cambia \u2014 incluido el estado verde\n(`correcto: true`). Es **advisory**: solo se retiene el \u00faltimo informe por\n(servicio, secundario) y se muestra en el panel; no factura ni modela\nzonas. El cuerpo lleva la clave en s\u00ed mismo (`servicio_id`, `secundario`).\n\n`bloques` es una lista de avisos tipados y autodescriptivos; el panel los\npinta en gen\u00e9rico, as\u00ed que se pueden a\u00f1adir tipos nuevos sin cambiar el\ncontrato (arranque: dns_externo, sin_delegacion, transferencia, ruido)."
                    },
                    "response": [
                        {
                            "header": [],
                            "code": 200,
                            "body": "{\"ok\": true, \"servicio_id\": 2868, \"secundario\": \"ns1.tecnium.example\", \"correcto\": false}",
                            "name": "ok"
                        },
                        {
                            "header": [],
                            "code": 401,
                            "body": "{\"error\":\"unauthorized\",\"message\":\"Missing Authorization: Bearer <token> header\"}",
                            "name": "sin token"
                        },
                        {
                            "header": [],
                            "code": 404,
                            "body": "{\"error\":\"not_found\",\"message\":\"Servicio no encontrado\"}",
                            "name": "servicio inexistente"
                        },
                        {
                            "header": [],
                            "code": 422,
                            "body": "{\"error\":\"unprocessable\",\"message\":\"The servicio_id field is required.\"}",
                            "name": "cuerpo inv\u00e1lido"
                        }
                    ]
                }
            ]
        }
    ],
    "auth": {
        "type": "bearer",
        "bearer": [
            {
                "key": "Authorization",
                "type": "string"
            }
        ]
    }
}