{
  "openapi": "3.0.0",
  "paths": {
    "/v1/org/{org_id}/server": {
      "get": {
        "description": "`server.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listServers",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListServerDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar servidores",
        "tags": [
          "🖥️ Infraestructura / Servidores"
        ]
      },
      "post": {
        "description": "`server.create` — `OWNER` `ADMIN`\n\nDevuelve el token de aprovisionamiento una sola vez.",
        "operationId": "createServer",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateServerDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetServerDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Crear servidor",
        "tags": [
          "🖥️ Infraestructura / Servidores"
        ]
      }
    },
    "/v1/org/{org_id}/server/{id}/regenerate-provisioning-token": {
      "post": {
        "description": "`server.edit` — `OWNER` `ADMIN`\n\nInvalida el anterior.",
        "operationId": "regenerateServerProvisioningToken",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetServerDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Regenerar token de aprovisionamiento",
        "tags": [
          "🖥️ Infraestructura / Servidores"
        ]
      }
    },
    "/v1/org/{org_id}/approval": {
      "get": {
        "description": "`approval.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nTodas las aprobaciones de la organización, las más recientes primero.",
        "operationId": "listApprovals",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "only_pending",
            "required": true,
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListApprovalDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar aprobaciones",
        "tags": [
          "💬 Comunicación / Aprobaciones"
        ]
      }
    },
    "/v1/org/{org_id}/approval/{id}/resolve": {
      "patch": {
        "description": "`approval.resolve` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca la aprobación como RESOLVED. El problema de fondo (p. ej. el token OAuth) debe arreglarse antes — esto solo cambia el estado.",
        "operationId": "resolveApproval",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetApprovalDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Resolver aprobación",
        "tags": [
          "💬 Comunicación / Aprobaciones"
        ]
      }
    },
    "/v1/org/{org_id}/approval/{id}/answer": {
      "patch": {
        "description": "`approval.resolve` — `OWNER` `ADMIN` `OPERATOR`\n\nResponde una pregunta (#308) eligiendo opciones y/o con texto libre. El agente recibe la respuesta como turno en el hilo desde el que preguntó.",
        "operationId": "answerQuestion",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnswerQuestionDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetApprovalDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Responder una pregunta del agente",
        "tags": [
          "💬 Comunicación / Aprobaciones"
        ]
      }
    },
    "/v1/org/{org_id}/approval/{id}/dismiss": {
      "patch": {
        "description": "`approval.resolve` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca la aprobación como DISMISSED sin actuar sobre ella. Úsalo cuando la petición ya no aplica (p. ej. agente borrado).",
        "operationId": "dismissApproval",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetApprovalDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Descartar aprobación",
        "tags": [
          "💬 Comunicación / Aprobaciones"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/conversation": {
      "get": {
        "description": "`agent.output.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listConversations",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetConversationDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar conversaciones del agente",
        "tags": [
          "💬 Comunicación / Conversaciones"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/conversation/{conversation_id}/message": {
      "get": {
        "description": "`agent.output.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nPaginación por cursor, hace scroll hacia arriba.",
        "operationId": "listMessages",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "conversation_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "before_seq",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after_seq",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListMessageDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Mensajes paginados de una conversación",
        "tags": [
          "💬 Comunicación / Conversaciones"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/conversation/{conversation_id}/message/search": {
      "get": {
        "description": "`agent.output.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nBúsqueda sobre el historial completo de una conversación — devuelve los seq de los mensajes que casan.",
        "operationId": "searchConversation",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "conversation_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Buscar en todo el historial",
        "tags": [
          "💬 Comunicación / Conversaciones"
        ]
      }
    },
    "/v1/org/{org_id}/conversation": {
      "get": {
        "description": "`conversation.view` — `OWNER` `ADMIN`\n\nTodas las conversaciones de la organización — vista unificada, filtrada y paginada.",
        "operationId": "listOrgConversations",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": false,
            "in": "query",
            "description": "Exact conversation id (deep-link).",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Filter by the owning agent id.",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "contact_id",
            "required": false,
            "in": "query",
            "description": "Filter by contact id.",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "channel_id",
            "required": false,
            "in": "query",
            "description": "Filter by a specific channel id.",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "transport",
            "required": false,
            "in": "query",
            "description": "Filter by channel transport (TELEGRAM, GMAIL, DISCORD…).",
            "schema": {
              "minLength": 1,
              "maxLength": 32,
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Filter by conversation status (OPEN, PENDING, RESOLVED, ARCHIVED).",
            "schema": {
              "type": "string",
              "enum": [
                "OPEN",
                "PENDING",
                "RESOLVED",
                "ARCHIVED"
              ]
            }
          },
          {
            "name": "model",
            "required": false,
            "in": "query",
            "description": "Filter by the model last observed.",
            "schema": {
              "minLength": 1,
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "name": "ref",
            "required": false,
            "in": "query",
            "description": "Opaque conversation ref (case-insensitive).",
            "schema": {
              "minLength": 1,
              "maxLength": 40,
              "type": "string"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Free-text search over the contact name + channel name.",
            "schema": {
              "minLength": 1,
              "maxLength": 200,
              "type": "string"
            }
          },
          {
            "name": "sort",
            "required": false,
            "in": "query",
            "description": "Sort column: activity (last message), created (opened), contact (name), agent (name), channel (transport), model, status. Default: activity.",
            "schema": {
              "type": "string",
              "enum": [
                "activity",
                "created",
                "contact",
                "agent",
                "channel",
                "model",
                "status"
              ]
            }
          },
          {
            "name": "order",
            "required": false,
            "in": "query",
            "description": "Sort direction. Default: desc.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Page size (1–200, default 50).",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Rows to skip (default 0).",
            "schema": {
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationListPageDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar todas las conversaciones",
        "tags": [
          "💬 Comunicación / Conversaciones"
        ]
      }
    },
    "/v1/org/{org_id}/agent": {
      "get": {
        "description": "`agent.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listAgents",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar agentes",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      },
      "post": {
        "description": "`agent.create` — `OWNER` `ADMIN` `OPERATOR`\n\nSe crea con estado STOPPED hasta lanzarlo por el gateway WS.",
        "operationId": "createAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAgentDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Crear agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{id}": {
      "get": {
        "description": "`agent.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "getAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de un agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      },
      "delete": {
        "description": "`agent.delete` — `OWNER` `ADMIN`\n\nSolo mientras está en STOPPED o ERROR.",
        "operationId": "deleteAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Borrar agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      },
      "patch": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "updateAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAgentDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Editar metadatos del agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{id}/start": {
      "post": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nLanza el contenedor del agente en su servidor (devuelve al instante como SPAWNING).",
        "operationId": "startAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Arrancar agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{id}/stop": {
      "post": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nPara el contenedor del agente (devuelve al instante como STOPPING).",
        "operationId": "stopAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Parar agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{id}/pause": {
      "post": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nDevuelve al instante como PAUSING.",
        "operationId": "pauseAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Pausar agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{id}/resume": {
      "post": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nReanuda el agente desde PAUSED (devuelve al instante como RESUMING). Requiere email confirmado.",
        "operationId": "resumeAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Reanudar agente pausado",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{id}/reset": {
      "post": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nDevuelve un agente en ERROR a STOPPED (no-op en agentes sanos).",
        "operationId": "resetAgent",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetAgentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Limpiar estado de error del agente",
        "tags": [
          "🤖 Agentes / Agentes"
        ]
      }
    },
    "/v1/agent-image": {
      "get": {
        "description": "Solo las disponibles, salvo que se pida incluir el resto.",
        "operationId": "listAgentImages",
        "parameters": [
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "role_key",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_unavailable",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListAgentImageDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar imágenes de agente",
        "tags": [
          "🖥️ Infraestructura / Imágenes de agente"
        ]
      }
    },
    "/v1/org": {
      "get": {
        "description": "",
        "operationId": "listOrganizations",
        "parameters": [
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListOrganizationDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar mis organizaciones",
        "tags": [
          "👤 Cuenta y org / Organizaciones"
        ]
      }
    },
    "/v1/org/{org_id}": {
      "get": {
        "description": "`organization.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nHay que ser miembro.",
        "operationId": "getOrganization",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetOrganizationDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de la organización",
        "tags": [
          "👤 Cuenta y org / Organizaciones"
        ]
      }
    },
    "/v1/org/{org_id}/memory": {
      "get": {
        "description": "`memory.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nOpcionalmente filtrado por scope / agente / huérfanas.",
        "operationId": "listMemories",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "scope",
            "required": false,
            "in": "query",
            "description": "Restrict to one scope",
            "schema": {
              "type": "string",
              "enum": [
                "SHORT_TERM",
                "LONG_TERM"
              ]
            }
          },
          {
            "name": "scopes",
            "required": false,
            "in": "query",
            "description": "Memory scopes to keep (SHORT_TERM/LONG_TERM). Repeat or CSV. Wins over the legacy single-scope `scope` if both are sent.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "SHORT_TERM",
                  "LONG_TERM"
                ]
              }
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Restrict to memories assigned to this agent (handy for the per-agent view).",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "unassigned",
            "required": false,
            "in": "query",
            "description": "When true, only return memories with zero assignments (orphans). Default false.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "archived",
            "required": false,
            "in": "query",
            "description": "When true, return ONLY archived (soft-deleted) memories. Default false → live memories only. The two sets never mix; the trash view in the operator UI uses this flag.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Free-text search across name + description (case-insensitive substring).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort_by",
            "required": false,
            "in": "query",
            "description": "Sort column.",
            "schema": {
              "type": "string",
              "enum": [
                "updated_at",
                "name",
                "created_at"
              ]
            }
          },
          {
            "name": "sort_dir",
            "required": false,
            "in": "query",
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Page size (1-200). Default 200 (folder UI loads all).",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Offset (>= 0). Default 0.",
            "schema": {
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListMemoryDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar memorias de la organización",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      },
      "post": {
        "description": "`memory.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "createMemory",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMemoryDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMemoryWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Crear memoria",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      }
    },
    "/v1/org/{org_id}/memory/{id}": {
      "get": {
        "description": "`memory.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nCon contenido y asignaciones.",
        "operationId": "getMemory",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMemoryWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de una memoria",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      },
      "delete": {
        "description": "`memory.delete` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca los agentes asignados para sincronizar.",
        "operationId": "deleteMemory",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Borrar memoria",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      },
      "patch": {
        "description": "`memory.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca los agentes asignados para sincronizar.",
        "operationId": "updateMemory",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMemoryDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMemoryWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Editar memoria",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      }
    },
    "/v1/org/{org_id}/memory/{id}/agents": {
      "put": {
        "description": "`memory.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nFija la lista completa (diff de añadir+quitar). Devuelve la memoria con las asignaciones refrescadas.",
        "operationId": "setMemoryAgents",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetMemoryAgentsDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMemoryWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Fijar agentes de la memoria",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      }
    },
    "/v1/org/{org_id}/memory/{id}/archive": {
      "post": {
        "description": "`memory.delete` — `OWNER` `ADMIN` `OPERATOR`\n\nSoft-delete: desaparece de los listados y de los payloads de spawn, pero las filas de asignación sobreviven para que unarchive restaure la pertenencia limpiamente.",
        "operationId": "archiveMemory",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMemoryWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Archivar memoria",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      }
    },
    "/v1/org/{org_id}/memory/{id}/unarchive": {
      "post": {
        "description": "`memory.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nLos agentes asignados recuperan el acceso al instante.",
        "operationId": "unarchiveMemory",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMemoryWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Restaurar memoria",
        "tags": [
          "🧩 Recursos / Memorias"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/channel": {
      "get": {
        "description": "`agent.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listChannels",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetChannelDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar canales externos del agente",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      }
    },
    "/v1/org/{org_id}/channel": {
      "get": {
        "description": "`agent.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nTodos los canales externos de la organización; opcionalmente filtrado por agente.",
        "operationId": "listOrgChannels",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Restrict to channels of this agent (handy for the per-agent view).",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetChannelDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar canales externos",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      },
      "post": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nConecta un canal externo (p. ej. un bot de Telegram) a un agente. Requiere email confirmado.",
        "operationId": "createOrgChannel",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChannelGlobalDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChannelDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Conectar canal externo a un agente",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      }
    },
    "/v1/org/{org_id}/channel/{id}": {
      "get": {
        "description": "`agent.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "getOrgChannel",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChannelDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de un canal",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      },
      "delete": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "deleteOrgChannel",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Desconectar canal",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      },
      "patch": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nNombre, scope, token. Requiere email confirmado.",
        "operationId": "updateOrgChannel",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateChannelDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChannelDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Editar canal",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      }
    },
    "/v1/org/{org_id}/channel/{id}/whitelist": {
      "post": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nAñade un id de origen a la whitelist de entrada del canal.",
        "operationId": "addOrgChannelWhitelist",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddToWhitelistDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChannelDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Añadir origen a la whitelist",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      }
    },
    "/v1/org/{org_id}/channel/{id}/whitelist/{origin_id}": {
      "delete": {
        "description": "`agent.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nQuita un id de origen de la whitelist de entrada del canal.",
        "operationId": "removeOrgChannelWhitelist",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "origin_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChannelDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Quitar origen de la whitelist",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      }
    },
    "/v1/transport": {
      "get": {
        "description": "",
        "operationId": "listSupportedTransports",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Dirección de tráfico de cada transporte",
        "tags": [
          "💬 Comunicación / Canales"
        ]
      }
    },
    "/v1/org/{org_id}/cronjob": {
      "get": {
        "description": "`cronjob.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listCronjobs",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "include_internal",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetCronjobDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar cronjobs de la organización",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      },
      "post": {
        "description": "`cronjob.create` — `OWNER` `ADMIN` `OPERATOR`\n\nRequiere email confirmado: un cron gasta según su propio horario.",
        "operationId": "createCronjob",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCronjobDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCronjobDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Crear cronjob",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/cronjob": {
      "get": {
        "description": "`cronjob.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listAgentCronjobs",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "include_internal",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetCronjobDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar cronjobs de un agente",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      }
    },
    "/v1/org/{org_id}/cronjob/{id}": {
      "get": {
        "description": "`cronjob.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "getCronjob",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCronjobDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de un cronjob",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      },
      "delete": {
        "description": "`cronjob.delete` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "deleteCronjob",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Borrar cronjob",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      },
      "patch": {
        "description": "`cronjob.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "updateCronjob",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCronjobDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCronjobDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Editar cronjob",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      }
    },
    "/v1/org/{org_id}/cronjob/{id}/runs": {
      "get": {
        "description": "`cronjob.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listCronjobRuns",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CronjobRunDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Registro de ejecuciones de un cronjob",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      }
    },
    "/v1/org/{org_id}/cronjob/{id}/execute": {
      "post": {
        "description": "`cronjob.execute` — `OWNER` `ADMIN` `OPERATOR`\n\nRequiere email confirmado: dispara un turno que gasta.",
        "operationId": "executeCronjob",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "202": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Ejecutar cronjob ahora",
        "tags": [
          "⏰ Automatización / Tareas programadas"
        ]
      }
    },
    "/v1/org/{org_id}/mcp": {
      "get": {
        "description": "`mcp.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nOpcionalmente filtrado por agente / huérfanos / archivados.",
        "operationId": "listMcpServers",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Restrict to servers assigned to this agent (handy for the per-agent view).",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "unassigned",
            "required": false,
            "in": "query",
            "description": "When true, only return servers with zero assignments. Default false.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "archived",
            "required": false,
            "in": "query",
            "description": "When true, return ONLY archived (soft-deleted) servers. Default false → live only.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Free-text search across name + description (case-insensitive substring).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "transport",
            "required": false,
            "in": "query",
            "description": "Transport kind(s) to keep (STDIO / SSE / HTTP). Repeat the param or pass a CSV.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "STDIO",
                  "SSE",
                  "HTTP"
                ]
              }
            }
          },
          {
            "name": "sort_by",
            "required": false,
            "in": "query",
            "description": "Sort column.",
            "schema": {
              "type": "string",
              "enum": [
                "updated_at",
                "name",
                "created_at"
              ]
            }
          },
          {
            "name": "sort_dir",
            "required": false,
            "in": "query",
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Page size (1-200). Default 50.",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Offset (>= 0). Default 0.",
            "schema": {
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListMcpDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar servidores MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      },
      "post": {
        "description": "`mcp.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "createMcpServer",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMcpDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMcpWithAssignmentsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Registrar servidor MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      }
    },
    "/v1/org/{org_id}/mcp/{id}": {
      "get": {
        "description": "`mcp.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nCon sus asignaciones.",
        "operationId": "getMcpServer",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMcpWithAssignmentsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de un servidor MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      },
      "delete": {
        "description": "`mcp.delete` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca los agentes asignados para sincronizar.",
        "operationId": "deleteMcpServer",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Borrar servidor MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      },
      "patch": {
        "description": "`mcp.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca los agentes asignados para sincronizar.",
        "operationId": "updateMcpServer",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMcpDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMcpWithAssignmentsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Editar servidor MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      }
    },
    "/v1/org/{org_id}/mcp/{id}/agents": {
      "put": {
        "description": "`mcp.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nFija la lista completa (diff de añadir+quitar). Devuelve el servidor con las asignaciones refrescadas.",
        "operationId": "setMcpServerAgents",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetMcpAgentsDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMcpWithAssignmentsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Fijar agentes del servidor MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      }
    },
    "/v1/org/{org_id}/mcp/{id}/archive": {
      "post": {
        "description": "`mcp.delete` — `OWNER` `ADMIN` `OPERATOR`\n\nSoft-delete: desaparece de los listados y de los payloads de spawn, pero las filas de asignación sobreviven para que unarchive restaure la pertenencia limpiamente.",
        "operationId": "archiveMcpServer",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMcpWithAssignmentsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Archivar servidor MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      }
    },
    "/v1/org/{org_id}/mcp/{id}/unarchive": {
      "post": {
        "description": "`mcp.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nLos agentes asignados recuperan el acceso al instante.",
        "operationId": "unarchiveMcpServer",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMcpWithAssignmentsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Restaurar servidor MCP",
        "tags": [
          "🧩 Recursos / Servidores MCP"
        ]
      }
    },
    "/v1/org/{org_id}/credential": {
      "get": {
        "description": "`credential.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nOpcionalmente filtrado por agente / huérfanas / archivadas. Los VALORES de los campos no se devuelven nunca.",
        "operationId": "listCredentials",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Restrict to credentials assigned to this agent (per-agent view).",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "unassigned",
            "required": false,
            "in": "query",
            "description": "When true, only return credentials with zero assignments. Default false.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "archived",
            "required": false,
            "in": "query",
            "description": "When true, return ONLY archived (soft-deleted) credentials. Default false → live only.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Free-text search across name + description (case-insensitive substring).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "required": false,
            "in": "query",
            "description": "Free-form kind tag(s) to keep (e.g. \"login\", \"api-key\", \"ssh-key\"). Repeat the param or pass a CSV. Omit for any.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "sort_by",
            "required": false,
            "in": "query",
            "description": "Sort column.",
            "schema": {
              "type": "string",
              "enum": [
                "updated_at",
                "name",
                "created_at"
              ]
            }
          },
          {
            "name": "sort_dir",
            "required": false,
            "in": "query",
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Page size (1-200). Default 50.",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Offset (>= 0). Default 0.",
            "schema": {
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListCredentialDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar credenciales",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      },
      "post": {
        "description": "`credential.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "createCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCredentialDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCredentialWithFieldsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Crear credencial",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      }
    },
    "/v1/org/{org_id}/credential/{id}": {
      "get": {
        "description": "`credential.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nMetadatos de los campos (etiqueta + tipo, SIN valores) y asignaciones.",
        "operationId": "getCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCredentialWithFieldsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de una credencial",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      },
      "delete": {
        "description": "`credential.delete` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "deleteCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Borrar credencial",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      },
      "patch": {
        "description": "`credential.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nNombre / campos / tipo.",
        "operationId": "updateCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCredentialDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCredentialWithFieldsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Editar credencial",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      }
    },
    "/v1/org/{org_id}/credential/{id}/field/{field_id}/reveal": {
      "get": {
        "description": "`credential.reveal` — `OWNER` `ADMIN` `OPERATOR`\n\nDescifra y devuelve el valor de UN campo (para el ver/copiar del operador). Gateado en credential.reveal (separado de edit) — y, si está fijada, la ACL de operadores de la credencial.",
        "operationId": "revealCredentialField",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "field_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevealCredentialFieldDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Revelar valor de un campo",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      }
    },
    "/v1/org/{org_id}/credential/{id}/agents": {
      "put": {
        "description": "`credential.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nFija la lista completa (diff de añadir+quitar).",
        "operationId": "setCredentialAgents",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetCredentialAgentsDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCredentialWithFieldsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Fijar agentes de la credencial",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      }
    },
    "/v1/org/{org_id}/credential/{id}/archive": {
      "post": {
        "description": "`credential.delete` — `OWNER` `ADMIN` `OPERATOR`\n\nSoft-delete: desaparece de los listados por defecto; las filas de asignación sobreviven para que unarchive restaure la pertenencia.",
        "operationId": "archiveCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCredentialWithFieldsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Archivar credencial",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      }
    },
    "/v1/org/{org_id}/credential/{id}/unarchive": {
      "post": {
        "description": "`credential.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "unarchiveCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCredentialWithFieldsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Restaurar credencial",
        "tags": [
          "🧩 Recursos / Credenciales"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/credential/{credential_id}": {
      "post": {
        "description": "`credential.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nAsigna UNA credencial a este agente sin reemplazar sus otras asignaciones (incremental, idempotente). Seguro para cablear tokens de servicio — sin read-modify-write del conjunto completo.",
        "operationId": "addAgentCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "credential_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Asignar credencial al agente",
        "tags": [
          "🧩 Recursos / Credenciales (atajo por agente)"
        ]
      },
      "delete": {
        "description": "`credential.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nDesasigna UNA credencial de este agente dejando el resto intacto (incremental, idempotente).",
        "operationId": "removeAgentCredential",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "credential_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Desasignar credencial del agente",
        "tags": [
          "🧩 Recursos / Credenciales (atajo por agente)"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/connector/{connector_id}": {
      "post": {
        "description": "`connector.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nAsigna UN conector a este agente sin reemplazar los demás (incremental, idempotente).",
        "operationId": "addAgentConnector",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "connector_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Asignar conector al agente",
        "tags": [
          "🔌 Recursos / Conectores (atajo por agente)"
        ]
      },
      "delete": {
        "description": "`connector.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nDesasigna UN conector de este agente dejando el resto intacto (incremental, idempotente).",
        "operationId": "removeAgentConnector",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "connector_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Desasignar conector del agente",
        "tags": [
          "🔌 Recursos / Conectores (atajo por agente)"
        ]
      }
    },
    "/v1/org/{org_id}/skill": {
      "get": {
        "description": "`skill.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nOpcionalmente filtrado por agente / huérfanas / archivadas.",
        "operationId": "listSkills",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Restrict to skills assigned to this agent (handy for the per-agent view).",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "unassigned",
            "required": false,
            "in": "query",
            "description": "When true, only return skills with zero assignments. Default false.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "archived",
            "required": false,
            "in": "query",
            "description": "When true, return ONLY archived (soft-deleted) skills. Default false → live skills only.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "description": "Free-text search across name + description (case-insensitive substring).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "effort",
            "required": false,
            "in": "query",
            "description": "Per-skill effort overrides to keep (LOW/MEDIUM/HIGH/XHIGH/MAX). Repeat or CSV.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "LOW",
                  "MEDIUM",
                  "HIGH",
                  "XHIGH",
                  "MAX"
                ]
              }
            }
          },
          {
            "name": "sort_by",
            "required": false,
            "in": "query",
            "description": "Sort column.",
            "schema": {
              "type": "string",
              "enum": [
                "updated_at",
                "name",
                "created_at"
              ]
            }
          },
          {
            "name": "sort_dir",
            "required": false,
            "in": "query",
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Page size (1-200). Default 50.",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Offset (>= 0). Default 0.",
            "schema": {
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListSkillDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar skills de la organización",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      },
      "post": {
        "description": "`skill.edit` — `OWNER` `ADMIN` `OPERATOR`",
        "operationId": "createSkill",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSkillDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetSkillWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Crear skill",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      }
    },
    "/v1/org/{org_id}/skill/{id}": {
      "get": {
        "description": "`skill.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nCon contenido y asignaciones.",
        "operationId": "getSkill",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetSkillWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de una skill",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      },
      "delete": {
        "description": "`skill.delete` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca los agentes asignados para sincronizar.",
        "operationId": "deleteSkill",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Borrar skill",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      },
      "patch": {
        "description": "`skill.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nMarca los agentes asignados para sincronizar.",
        "operationId": "updateSkill",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSkillDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetSkillWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Editar skill",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      }
    },
    "/v1/org/{org_id}/skill/{id}/agents": {
      "put": {
        "description": "`skill.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nFija la lista completa (diff de añadir+quitar). Devuelve la skill con las asignaciones refrescadas.",
        "operationId": "setSkillAgents",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetSkillAgentsDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetSkillWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Fijar agentes de la skill",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      }
    },
    "/v1/org/{org_id}/skill/{id}/archive": {
      "post": {
        "description": "`skill.delete` — `OWNER` `ADMIN` `OPERATOR`\n\nSoft-delete: desaparece de los listados y de los payloads de spawn, pero las filas de asignación sobreviven para que unarchive restaure la pertenencia limpiamente.",
        "operationId": "archiveSkill",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetSkillWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Archivar skill",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      }
    },
    "/v1/org/{org_id}/skill/{id}/unarchive": {
      "post": {
        "description": "`skill.edit` — `OWNER` `ADMIN` `OPERATOR`\n\nLos agentes asignados recuperan el acceso al instante.",
        "operationId": "unarchiveSkill",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetSkillWithContentDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Restaurar skill",
        "tags": [
          "🧩 Recursos / Skills"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/backup": {
      "get": {
        "description": "`backup.view` — `OWNER` `ADMIN`",
        "operationId": "listAgentBackups",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetBackupDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar copias del volumen del agente",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      },
      "post": {
        "description": "`backup.manage` — `OWNER` `ADMIN`\n\nCopia el volumen /home del agente a R2 (asíncrono — sondea la lista hasta COMPLETED).",
        "operationId": "createAgentBackup",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetBackupDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Lanzar copia del volumen del agente",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/backup/{id}/restore": {
      "post": {
        "description": "`backup.manage` — `OWNER` `ADMIN`\n\nRestaura una copia sobre el volumen del agente (DESTRUCTIVO — sobrescribe el /home actual). Asíncrono.",
        "operationId": "restoreAgentBackup",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetBackupDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Restaurar copia en el volumen",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/backup/{id}": {
      "delete": {
        "description": "`backup.manage` — `OWNER` `ADMIN`\n\nBorra el objeto de R2 y la fila.",
        "operationId": "deleteAgentBackup",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Borrar copia de seguridad",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      }
    },
    "/v1/org/{org_id}/backup": {
      "get": {
        "description": "`backup.view` — `OWNER` `ADMIN`\n\nTodas las copias de seguridad de la organización.",
        "operationId": "listOrgBackups",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetBackupDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar todas las copias",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      }
    },
    "/v1/org/{org_id}/backup/unknown-objects": {
      "get": {
        "description": "`backup.manage` — `OWNER` `ADMIN`\n\nObjetos que están en el bucket de la organización y que ninguna fila de copia reclama. Solo lectura: no se borra ni se adopta nada — el bucket es de otro y nosotros solo miramos.",
        "operationId": "listUnknownBackupObjects",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Objetos sin dueño en el bucket de copias",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      }
    },
    "/v1/org/{org_id}/backup-destination": {
      "get": {
        "description": "`backup.view` — `OWNER` `ADMIN`\n\nEl bucket al que esta organización escribe sus copias, y si las copias están ocurriendo siquiera. Nunca devuelve la clave secreta.",
        "operationId": "getBackupDestination",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Destino de copias de la organización",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      },
      "put": {
        "description": "`backup.manage` — `OWNER` `ADMIN`\n\nApunta la organización a su propio bucket S3-compatible. El destino se PRUEBA antes de guardarse (se escribe y se borra un objeto de test); un bucket en el que no se puede escribir, o cuya credencial no puede borrar, se rechaza con 400 en vez de fallar la noche en que corre la copia.",
        "operationId": "setBackupDestination",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetBackupDestinationDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Configurar bucket de copias propio",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      },
      "delete": {
        "description": "`backup.manage` — `OWNER` `ADMIN`\n\nNo se borra nada del bucket del cliente — simplemente dejan de hacerse copias nuevas, y las existentes dejan de poder restaurarse a través de nosotros.",
        "operationId": "removeBackupDestination",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Olvidar destino de copias",
        "tags": [
          "🧩 Recursos / Copias de seguridad"
        ]
      }
    },
    "/v1/org/{org_id}/audit": {
      "get": {
        "description": "`audit.view` — `OWNER` `ADMIN`\n\nRegistro append-only de acciones sensibles, lo más reciente primero. Filtros: actor_kind, action, resource_type/id, rango de fechas. Paginado.",
        "operationId": "listAudit",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "actor_kind",
            "required": false,
            "in": "query",
            "description": "Filter by actor kind.",
            "schema": {
              "type": "string",
              "enum": [
                "HUMAN",
                "AGENT",
                "SERVICE",
                "SYSTEM"
              ]
            }
          },
          {
            "name": "action",
            "required": false,
            "in": "query",
            "description": "Filter by action (substring match, e.g. \"credential\").",
            "schema": {
              "minLength": 1,
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "name": "resource_type",
            "required": false,
            "in": "query",
            "description": "Filter by resource type (e.g. \"credential\").",
            "schema": {
              "minLength": 1,
              "maxLength": 32,
              "type": "string"
            }
          },
          {
            "name": "resource_id",
            "required": false,
            "in": "query",
            "description": "Filter by resource id.",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Everything about one agent: actions ON it OR done BY it.",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Lower bound (ISO date) on created_at.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Upper bound (ISO date) on created_at.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Page size (1–200, default 50).",
            "schema": {
              "minimum": 1,
              "maximum": 200,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Offset for pagination (default 0).",
            "schema": {
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListAuditDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar audit log de la organización",
        "tags": [
          "📊 Observabilidad / Auditoría"
        ]
      }
    },
    "/v1/org/{org_id}/agent/{agent_id}/logs": {
      "get": {
        "description": "`agent.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`",
        "operationId": "listAgentLogs",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "agent_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListAuditDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Feed de auditoría de un agente",
        "tags": [
          "📊 Observabilidad / Auditoría"
        ]
      }
    },
    "/v1/org/{org_id}/billing": {
      "get": {
        "description": "`billing.view` — `OWNER` `ADMIN`\n\nTotales de la organización (cost_micro + tokens) agregados de todos los agentes, con desglose por agente.",
        "operationId": "getBilling",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingSummaryDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Resumen de facturación en vivo",
        "tags": [
          "📊 Observabilidad / Facturación"
        ]
      }
    },
    "/v1/org/{org_id}/billing/agents": {
      "get": {
        "description": "`billing.view` — `OWNER` `ADMIN`\n\nCuántos agentes se pagan, cuántos hay, a cuánto se puede bajar y qué cambio hay programado para la renovación.",
        "operationId": "getContractedAgents",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContractedAgentsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Agentes contratados y margen",
        "tags": [
          "📊 Observabilidad / Facturación"
        ]
      }
    },
    "/v1/org/{org_id}/cost-analytics": {
      "get": {
        "description": "`billing.view` — `OWNER` `ADMIN`\n\nInforme de coste calculado del uso por mensaje: totales, serie temporal y desglose por agente/canal/transporte/scope/conversación/contacto/modelo, con separación por tipo de token y ventanas de fechas + filtros arbitrarios.",
        "operationId": "getCostAnalytics",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "Lower bound (ISO date) on the message timestamp. Default: 30 days ago.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "Upper bound (ISO date), exclusive. Default: now.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bucket",
            "required": false,
            "in": "query",
            "description": "Time-bucket granularity of the series.",
            "schema": {
              "type": "string",
              "enum": [
                "hour",
                "day",
                "week",
                "month"
              ]
            }
          },
          {
            "name": "group_by",
            "required": false,
            "in": "query",
            "description": "Dimension the breakdown is grouped by.",
            "schema": {
              "type": "string",
              "enum": [
                "agent",
                "channel",
                "transport",
                "scope",
                "conversation",
                "contact",
                "model",
                "cron"
              ]
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Filter to a single agent.",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "channel_id",
            "required": false,
            "in": "query",
            "description": "Filter to a single channel.",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "transport",
            "required": false,
            "in": "query",
            "description": "Filter to a single transport.",
            "schema": {
              "type": "string",
              "enum": [
                "INTERNAL",
                "WHATSAPP",
                "GMAIL",
                "DISCORD",
                "TELEGRAM",
                "SLACK",
                "WEBHOOK",
                "EMAIL",
                "GITHUB",
                "DRIVE",
                "CALENDAR"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CostAnalyticsDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Informe desglosado de costes",
        "tags": [
          "📊 Observabilidad / Facturación"
        ]
      }
    },
    "/v1/capability": {
      "get": {
        "description": "",
        "operationId": "listCapabilities",
        "parameters": [
          {
            "name": "page",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": true,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListCapabilityDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar catálogo global de capacidades",
        "tags": [
          "🤖 Agentes / Capacidades"
        ]
      }
    },
    "/v1/model": {
      "get": {
        "description": "",
        "operationId": "listModels",
        "parameters": [
          {
            "name": "all",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetModelDto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar catálogo global de modelos",
        "tags": [
          "🧬 Modelos"
        ]
      }
    },
    "/v1/org/{org_id}/inbox/contact": {
      "get": {
        "description": "`agent.output.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nContactos del inbox agrupados con sus conversaciones.",
        "operationId": "listInbox",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "scope",
            "required": false,
            "in": "query",
            "description": "mine = contacts where the authenticated user is the operator identity; all = every contact accessible in the org",
            "schema": {
              "default": "all",
              "type": "string",
              "enum": [
                "mine",
                "all"
              ]
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "description": "Filter conversations by status. ALL returns OPEN + CLOSED.",
            "schema": {
              "default": "open",
              "type": "string",
              "enum": [
                "open",
                "pending",
                "resolved",
                "all"
              ]
            }
          },
          {
            "name": "agent_id",
            "required": false,
            "in": "query",
            "description": "Restrict to a single agent",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "name": "channel_id",
            "required": false,
            "in": "query",
            "description": "Restrict to a single channel",
            "schema": {
              "minimum": 1,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListInboxDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar contactos del Agent Desk",
        "tags": [
          "💬 Comunicación / Inbox"
        ]
      }
    },
    "/v1/org/{org_id}/inbox/contact/{contact_id}": {
      "get": {
        "description": "`agent.output.view` — `OWNER` `ADMIN` `OPERATOR` `VIEWER`\n\nEl contacto y todas sus conversaciones.",
        "operationId": "getInboxContact",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "contact_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetInboxContactDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Detalle de un contacto del Agent Desk",
        "tags": [
          "💬 Comunicación / Inbox"
        ]
      }
    },
    "/v1/org/{org_id}/inbox/assignees": {
      "get": {
        "description": "`agent.input.send` — `OWNER` `ADMIN` `OPERATOR`\n\nCandidatos para el selector del Agent Desk (miembros + agentes disponibles para el Agent Desk).",
        "operationId": "listInboxAssignees",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListInboxAssigneesDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Listar asignables del Agent Desk",
        "tags": [
          "💬 Comunicación / Inbox"
        ]
      }
    },
    "/v1/org/{org_id}/inbox/conversation/{conversation_id}/status": {
      "patch": {
        "description": "`agent.input.send` — `OWNER` `ADMIN` `OPERATOR`\n\nConversación del Agent Desk: open / pending / resolved.",
        "operationId": "setInboxConversationStatus",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "conversation_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetConversationStatusDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Cambiar estado de la conversación",
        "tags": [
          "💬 Comunicación / Inbox"
        ]
      }
    },
    "/v1/org/{org_id}/inbox/contact/{contact_id}/assignee": {
      "patch": {
        "description": "`agent.input.send` — `OWNER` `ADMIN` `OPERATOR`\n\nAsigna o limpia el asignado del Agent Desk de un contacto externo.",
        "operationId": "setInboxAssignee",
        "parameters": [
          {
            "name": "org_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "contact_id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetAssigneeDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetInboxContactDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "JWT": []
          }
        ],
        "summary": "🔒 Asignar o liberar contacto del Agent Desk",
        "tags": [
          "💬 Comunicación / Inbox"
        ]
      }
    }
  },
  "info": {
    "title": "Kujira API",
    "description": "API del plano de control: organizaciones, agentes, recursos (skills, memorias, servidores MCP, credenciales), comunicación (canales, inbox, aprobaciones), tareas programadas, auditoría y facturación.\n\n## Autenticación\n\nLas integraciones se autentican con un **token de servicio** en cada petición:\n\n```\nAuthorization: Bearer kjtk_live_…\n```\n\nEl token se crea desde el panel (**Ajustes → Tokens de servicio**), pertenece a **una** organización y lleva sus propios permisos (scopes). El secreto completo solo se muestra una vez, al crearlo — guárdalo en un gestor de secretos. Se puede revocar en cualquier momento desde el mismo panel.\n\n## Rutas y organizaciones\n\nTodas las rutas van bajo el prefijo `/v1`. Los recursos de una organización viven bajo `/v1/org/{org_id}/…`: el token solo ve su organización — pedir la de otro responde **404**, sin revelar si existe.\n\nCada endpoint indica el permiso que exige (`recurso.accion`, p. ej. `agent.view`). Un token sin ese scope recibe **403**.\n\n## Errores\n\nLas respuestas de error comparten una misma forma:\n\n```json\n{ \"status\": 403, \"code\": \"PERMISSION_DENIED\", \"message\": \"…\" }\n```\n\n`code` es estable y es sobre lo que conviene ramificar en código; `message` es un texto de apoyo. Los errores de validación añaden una lista `errors` con el detalle por campo.\n\n## Límites de peticiones\n\nVentanas de 60 segundos por IP: **600 peticiones** sobre cualquier ruta como techo general, con cupos estrictos en superficies sensibles (autenticación **10/min**, webhooks públicos **120/min**, soporte **20/min**). Superado un límite, la API responde **429** con el header `Retry-After`.\n\n## Más ayuda\n\nGuías y conceptos de la plataforma: [docs.kujira.so](https://docs.kujira.so).",
    "version": "0.1.0",
    "contact": {}
  },
  "tags": [],
  "servers": [
    {
      "url": "https://api.kujira.so",
      "description": "Production"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "name": "Token de servicio",
        "description": "Token de servicio (`kjtk_live_…`), creado desde el panel en Ajustes → Tokens de servicio. Pertenece a una organización y lleva sus propios permisos.",
        "type": "http",
        "scheme": "bearer"
      },
      "JWT": {
        "name": "Token JWT",
        "description": "Sesión de una persona en el panel. No sirve para integrar.",
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "GetServerDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Server id",
            "example": 1
          },
          "organization_id": {
            "type": "number",
            "description": "Organization id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Display name",
            "example": "hetzner-fra-01"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Free-form description"
          },
          "ip": {
            "type": "string",
            "nullable": true,
            "description": "IPv4 or IPv6 address (auto-detected from the supervisor handshake)",
            "example": "49.12.44.201"
          },
          "os": {
            "type": "string",
            "nullable": true,
            "description": "Operating system label"
          },
          "cpu": {
            "type": "number",
            "nullable": true,
            "description": "CPU cores"
          },
          "ram_mb": {
            "type": "number",
            "nullable": true,
            "description": "RAM in MB"
          },
          "default_agent_memory_mb": {
            "type": "number",
            "nullable": true,
            "description": "Default container RAM cap (MB) inherited by this server’s agents. Null = no limit.",
            "example": 4096
          },
          "default_agent_cpu": {
            "type": "number",
            "nullable": true,
            "description": "Default container CPU cap (cores) inherited by this server’s agents. Null = no limit.",
            "example": 2
          },
          "status": {
            "enum": [
              "UNPROVISIONED",
              "ONLINE",
              "WARNING",
              "OFFLINE"
            ],
            "type": "string",
            "description": "Lifecycle status",
            "example": "UNPROVISIONED"
          },
          "load": {
            "type": "number",
            "nullable": true,
            "description": "Load average (0..1) reported by the agent"
          },
          "cpu_percent": {
            "type": "number",
            "nullable": true,
            "description": "Instantaneous CPU usage (0..100) reported by the agent"
          },
          "ram_percent": {
            "type": "number",
            "nullable": true,
            "description": "RAM usage (0..100) reported by the agent"
          },
          "alerts_enabled": {
            "type": "boolean",
            "description": "Whether the resource-alert watcher is active for this server (KUJI-136)",
            "example": true
          },
          "alert_ram_pct": {
            "type": "number",
            "nullable": true,
            "description": "RAM alert threshold (%). Overrides the org default; null = inherit (org default → 85).",
            "example": 85
          },
          "alert_cpu_pct": {
            "type": "number",
            "nullable": true,
            "description": "CPU alert threshold (%). Overrides the org default; null = inherit (org default → 90).",
            "example": 90
          },
          "alert_load_factor": {
            "type": "number",
            "nullable": true,
            "description": "Load alert factor (load > factor × cores). Overrides the org default; null = inherit (org default → 1.5).",
            "example": 1.5
          },
          "uptime_seconds": {
            "type": "number",
            "nullable": true,
            "description": "Uptime in seconds reported by the agent"
          },
          "last_seen_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Last heartbeat timestamp"
          },
          "provisioning_expires_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Provisioning expires at"
          },
          "supervisor_outdated": {
            "type": "boolean",
            "description": "Whether the supervisor runs a version BEHIND the platform target (same comparison the upgrade endpoint gates on). False when current or when the comparison is unknown.",
            "example": false
          },
          "agents_count": {
            "type": "number",
            "description": "Number of agents bound to this server",
            "example": 3
          },
          "has_ssh_key": {
            "type": "boolean",
            "description": "Whether an SSH key has been generated for this server",
            "example": true
          },
          "provisioning_token": {
            "type": "string",
            "description": "Provisioning token (only at create/regenerate time)"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          }
        },
        "required": [
          "id",
          "organization_id",
          "name",
          "status",
          "alerts_enabled",
          "supervisor_outdated",
          "agents_count",
          "has_ssh_key",
          "created_at",
          "updated_at"
        ]
      },
      "ListServerDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Items in the current page",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetServerDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total number of items matching the query",
            "example": 42
          },
          "page": {
            "type": "number",
            "description": "Current page (1-indexed)",
            "example": 1
          },
          "limit": {
            "type": "number",
            "description": "Page size",
            "example": 20
          }
        },
        "required": [
          "data",
          "total",
          "page",
          "limit"
        ]
      },
      "CreateServerDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Display name (unique within the org)",
            "example": "hetzner-fra-01"
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Free-form description",
            "example": "Main VPS in Frankfurt"
          },
          "ip": {
            "type": "string",
            "description": "IPv4 or IPv6 address. Optional — auto-detected from the supervisor handshake on connect (and refreshed on every reconnect, so dynamic IPs stay current).",
            "example": "49.12.44.201"
          },
          "os": {
            "type": "string",
            "maxLength": 80,
            "description": "Operating system label",
            "example": "Debian 12"
          },
          "cpu": {
            "type": "number",
            "minimum": 1,
            "description": "CPU cores",
            "example": 8
          },
          "ram_mb": {
            "type": "number",
            "minimum": 1,
            "description": "RAM in MB",
            "example": 32768
          },
          "default_agent_memory_mb": {
            "type": "number",
            "nullable": true,
            "minimum": 64,
            "maximum": 1048576,
            "description": "Default container RAM cap (MB) for this server’s agents. Null clears it (no limit).",
            "example": 4096
          },
          "default_agent_cpu": {
            "type": "number",
            "nullable": true,
            "minimum": 0.1,
            "maximum": 256,
            "description": "Default container CPU cap (cores) for this server’s agents. Null clears it (no limit).",
            "example": 2
          },
          "alerts_enabled": {
            "type": "boolean",
            "description": "Enable/disable the resource-alert watcher for this server.",
            "example": true
          },
          "alert_ram_pct": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "maximum": 100,
            "description": "RAM alert threshold (%). Null clears the override (inherit org default).",
            "example": 85
          },
          "alert_cpu_pct": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "maximum": 100,
            "description": "CPU alert threshold (%). Null clears the override (inherit org default).",
            "example": 90
          },
          "alert_load_factor": {
            "type": "number",
            "nullable": true,
            "minimum": 0.1,
            "maximum": 100,
            "description": "Load alert factor (load > factor × cores). Null clears the override (inherit org default).",
            "example": 1.5
          }
        },
        "required": [
          "name"
        ]
      },
      "GetApprovalDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Approval id",
            "example": 1
          },
          "organization_id": {
            "type": "number",
            "description": "Organization id",
            "example": 1
          },
          "agent_id": {
            "type": "number",
            "description": "Agent id this approval belongs to",
            "example": 1
          },
          "kind": {
            "enum": [
              "OAUTH_RENEWAL",
              "ACTION_REQUEST",
              "QUESTION"
            ],
            "type": "string",
            "description": "What kind of approval this is",
            "example": "OAUTH_RENEWAL"
          },
          "status": {
            "enum": [
              "PENDING",
              "RESOLVED",
              "DISMISSED",
              "EXPIRED"
            ],
            "type": "string",
            "description": "Lifecycle state of the approval",
            "example": "PENDING"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Kind-specific data (free-form JSON). OAUTH_RENEWAL leaves this null today; other kinds will pack amount/target/etc here."
          },
          "conversation_id": {
            "type": "number",
            "nullable": true,
            "description": "The operator conversation the agent asked from (null = no thread context). Lets the panel render the approval as an inline card in that chat."
          },
          "expires_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When this PENDING approval auto-expires (null = never)"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "resolved_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the approval was resolved (null while PENDING)"
          },
          "resolved_by": {
            "type": "number",
            "nullable": true,
            "description": "User id that resolved or dismissed it",
            "example": 1
          }
        },
        "required": [
          "id",
          "organization_id",
          "agent_id",
          "kind",
          "status",
          "created_at"
        ]
      },
      "ListApprovalDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Items in the current page",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetApprovalDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total number of items matching the query",
            "example": 3
          },
          "page": {
            "type": "number",
            "description": "Current page (1-indexed)",
            "example": 1
          },
          "limit": {
            "type": "number",
            "description": "Page size",
            "example": 20
          }
        },
        "required": [
          "data",
          "total",
          "page",
          "limit"
        ]
      },
      "AnswerQuestionDto": {
        "type": "object",
        "properties": {
          "selected": {
            "maxItems": 8,
            "description": "Chosen options as 0-based indices into metadata.options. A single-choice question keeps the first one. May be empty for a free-text answer.",
            "example": [
              0
            ],
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0
            }
          },
          "free_text": {
            "type": "string",
            "maxLength": 2000,
            "description": "Free-text nuance or full answer (\"bórrala pero deja los volúmenes\").",
            "example": "bórrala pero deja los volúmenes"
          }
        }
      },
      "ConversationContactDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Contact id",
            "example": 1
          },
          "kind": {
            "type": "string",
            "description": "Contact kind",
            "example": "INTERNAL_USER"
          },
          "name": {
            "type": "string",
            "description": "Contact display name",
            "example": "Alice"
          },
          "user_id": {
            "type": "number",
            "nullable": true,
            "description": "User id (internal_user only)"
          }
        },
        "required": [
          "id",
          "kind",
          "name"
        ]
      },
      "ConversationChannelDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Channel id",
            "example": 1
          },
          "transport": {
            "type": "string",
            "description": "Channel transport",
            "example": "TELEGRAM"
          },
          "name": {
            "type": "string",
            "description": "Channel display name",
            "example": "Chat interno"
          }
        },
        "required": [
          "id",
          "transport",
          "name"
        ]
      },
      "GetConversationDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Conversation id",
            "example": 1
          },
          "ref": {
            "type": "string",
            "description": "Opaque public reference — paste it to jump to this conversation.",
            "example": "MZROL9UC"
          },
          "contact_id": {
            "type": "number",
            "description": "Contact id",
            "example": 1
          },
          "channel_id": {
            "type": "number",
            "description": "Channel this thread lives on",
            "example": 1
          },
          "session_id": {
            "type": "string",
            "description": "Claude session UUID for --resume",
            "example": "a1b2c3d4-..."
          },
          "status": {
            "type": "string",
            "description": "Status",
            "example": "OPEN"
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Last Claude model seen in this conversation (slug). Null until first reply.",
            "example": "claude-haiku-4-5"
          },
          "model_override": {
            "type": "string",
            "nullable": true,
            "description": "Operator/agent-set model override for this session (KJ-150). Null = inherit the agent default. Distinct from `model` (what last ran).",
            "example": "claude-haiku-4-5"
          },
          "effort_override": {
            "type": "string",
            "nullable": true,
            "description": "Reasoning effort set for this conversation (#277): LOW/MEDIUM/HIGH/XHIGH/MAX. Null = inherit the agent default.",
            "example": "HIGH"
          },
          "is_operator_session": {
            "type": "boolean",
            "description": "Whether this is an operator multi-session chat thread (KUJI-52).",
            "example": true
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Session title for an operator session (\"Bug del login\"). Null = unnamed."
          },
          "icon": {
            "type": "string",
            "nullable": true,
            "description": "Lucide icon name tagging the session (curated set). Null = default."
          },
          "color": {
            "type": "string",
            "nullable": true,
            "description": "Theme palette color key tagging the session. Null = default."
          },
          "pinned": {
            "type": "boolean",
            "description": "Pinned to the top of the operator session list."
          },
          "last_action": {
            "type": "string",
            "nullable": true,
            "description": "Per-session last activity snippet (\"Ejecutando: …\"). Null until any."
          },
          "last_action_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When last_action was recorded."
          },
          "contact": {
            "description": "Contact summary",
            "allOf": [
              {
                "$ref": "#/components/schemas/ConversationContactDto"
              }
            ]
          },
          "channel": {
            "description": "Channel summary (separates per-channel views)",
            "allOf": [
              {
                "$ref": "#/components/schemas/ConversationChannelDto"
              }
            ]
          },
          "context_tokens": {
            "type": "number",
            "nullable": true,
            "description": "KUJI-87: live context size (input+cache tokens of the last turn). Null until the first result.",
            "example": 65000
          },
          "context_window": {
            "type": "number",
            "description": "The model's context window (denominator of the saturation bar).",
            "example": 1000000
          },
          "reset_threshold": {
            "type": "number",
            "nullable": true,
            "description": "Effective context cap (eje A) — marker for \"session compacts here\". Per-conversation override first, else the agent/org resolution. Null = no cap.",
            "example": 300000
          },
          "context_cap_tokens": {
            "type": "number",
            "nullable": true,
            "description": "Per-conversation context-cap override. Null = inherit the agent/org cap; 0 = no cap for this conversation; >0 = compact at this many tokens.",
            "example": 500000
          },
          "opened_at": {
            "format": "date-time",
            "type": "string",
            "description": "Opened at"
          },
          "last_activity_at": {
            "format": "date-time",
            "type": "string",
            "description": "Last activity"
          },
          "debug_visible_until": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Debug opt-in expiry. Future = exposed to admins/service for troubleshooting; null/past = private."
          }
        },
        "required": [
          "id",
          "ref",
          "contact_id",
          "channel_id",
          "session_id",
          "status",
          "model",
          "model_override",
          "effort_override",
          "is_operator_session",
          "title",
          "icon",
          "color",
          "pinned",
          "last_action",
          "last_action_at",
          "contact",
          "channel",
          "context_window",
          "opened_at",
          "last_activity_at",
          "debug_visible_until"
        ]
      },
      "MessageDeliveryDto": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "description": "PENDING | SENDING | SENT | FAILED | DRAFTED",
            "example": "SENT"
          },
          "attempts": {
            "type": "number",
            "description": "Delivery attempts so far",
            "example": 1
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Last transport error, when it failed"
          },
          "sent_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the transport accepted it"
          }
        },
        "required": [
          "status",
          "attempts"
        ]
      },
      "MessageAttachmentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Attachment id",
            "example": 42
          },
          "filename": {
            "type": "string",
            "description": "Original filename",
            "example": "report.csv"
          },
          "mime_type": {
            "type": "string",
            "description": "MIME type",
            "example": "image/png"
          },
          "size_bytes": {
            "type": "number",
            "description": "Size in bytes",
            "example": 12345
          },
          "url": {
            "type": "string",
            "description": "Backend proxy URL to stream the bytes (auth required).",
            "example": "/org/2/attachment/42"
          },
          "uploaded_by_agent_id": {
            "type": "number",
            "nullable": true,
            "description": "Agent that attached it (attachment_send), else null."
          }
        },
        "required": [
          "id",
          "filename",
          "mime_type",
          "size_bytes",
          "url"
        ]
      },
      "GetMessageDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Message id",
            "example": 1
          },
          "conversation_id": {
            "type": "number",
            "description": "Conversation id",
            "example": 1
          },
          "seq": {
            "type": "number",
            "description": "Monotonic sequence within conversation",
            "example": 1
          },
          "role": {
            "type": "string",
            "description": "Role",
            "example": "ASSISTANT"
          },
          "kind": {
            "type": "string",
            "description": "Kind",
            "example": "TEXT"
          },
          "via_channel_id": {
            "type": "number",
            "nullable": true,
            "description": "Channel the message arrived/left through"
          },
          "via_identity_id": {
            "type": "number",
            "nullable": true,
            "description": "Identity that sent the message"
          },
          "author_user_id": {
            "type": "number",
            "nullable": true,
            "description": "Human author (operator) user id. NULL for agent output and external contacts."
          },
          "author_name": {
            "type": "string",
            "nullable": true,
            "description": "Author display name when author_user_id is set."
          },
          "status": {
            "type": "string",
            "description": "DELIVERED or PENDING (auth failure / offline)",
            "example": "DELIVERED"
          },
          "delivery": {
            "nullable": true,
            "description": "Delivery to the customer channel (Telegram, mail…) for messages that go out through one. NULL for everything else — internal threads, inbound messages. Distinct from status, which is about whether it reached the agent.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/MessageDeliveryDto"
              }
            ]
          },
          "content": {
            "type": "object",
            "description": "Raw event content (stream-json shape)"
          },
          "attachments": {
            "description": "Files attached to this message (operator upload or the agent attachment_send).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MessageAttachmentDto"
            }
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          }
        },
        "required": [
          "id",
          "conversation_id",
          "seq",
          "role",
          "kind",
          "status",
          "content",
          "created_at"
        ]
      },
      "ListMessageDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Messages",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetMessageDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total messages in conversation"
          }
        },
        "required": [
          "data",
          "total"
        ]
      },
      "ConversationAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "soki"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar proxy URL (null = use generative)"
          },
          "provider": {
            "type": "string",
            "description": "Provider the agent's container actually booted against, so the picker can offer only models it can run. An agent with no model pinned anywhere boots against Anthropic, so it is never absent.",
            "example": "anthropic"
          }
        },
        "required": [
          "id",
          "name",
          "avatar_url",
          "provider"
        ]
      },
      "ConversationListItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Conversation id",
            "example": 1
          },
          "ref": {
            "type": "string",
            "description": "Opaque public reference — paste it to jump to this conversation.",
            "example": "MZROL9UC"
          },
          "status": {
            "type": "string",
            "description": "Status",
            "example": "OPEN"
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Last Claude model seen (slug). Null until first reply.",
            "example": "claude-haiku-4-5"
          },
          "model_override": {
            "type": "string",
            "nullable": true,
            "description": "Operator/agent-set model override for this conversation (KJ-22). Null = inherit the agent default. Distinct from `model` (what last ran).",
            "example": "claude-haiku-4-5"
          },
          "effort_override": {
            "type": "string",
            "nullable": true,
            "description": "Reasoning effort set for this conversation (#277): LOW/MEDIUM/HIGH/XHIGH/MAX. Null = inherit the agent default.",
            "example": "HIGH"
          },
          "effective_model": {
            "type": "string",
            "nullable": true,
            "description": "The model this conversation will actually run its NEXT turn with, resolved server-side by the shared precedence chain (conversation → agent → image). Lets a row say what it runs on without waiting for a first reply.",
            "example": "claude-opus-5"
          },
          "context_tokens": {
            "type": "number",
            "nullable": true,
            "description": "Tokens of context this conversation is currently carrying. Null until its first turn.",
            "example": 96000
          },
          "context_window": {
            "type": "number",
            "description": "The effective model's context window, so the row can turn `context_tokens` into a percentage without knowing the model catalogue.",
            "example": 1000000
          },
          "agent": {
            "nullable": true,
            "description": "Owning agent (null if unassigned)",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/ConversationAgentDto"
              }
            ]
          },
          "contact": {
            "description": "Contact summary",
            "allOf": [
              {
                "$ref": "#/components/schemas/ConversationContactDto"
              }
            ]
          },
          "channel": {
            "description": "Channel summary",
            "allOf": [
              {
                "$ref": "#/components/schemas/ConversationChannelDto"
              }
            ]
          },
          "last_activity_at": {
            "format": "date-time",
            "type": "string",
            "description": "Last activity"
          },
          "debug_visible_until": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Debug opt-in expiry (KUJI-41 Fase 4). When in the future, the owner has exposed this personal conversation to admins/service for troubleshooting. Null = private."
          }
        },
        "required": [
          "id",
          "ref",
          "status",
          "model",
          "model_override",
          "effort_override",
          "effective_model",
          "context_tokens",
          "context_window",
          "agent",
          "contact",
          "channel",
          "last_activity_at"
        ]
      },
      "ConversationListPageDto": {
        "type": "object",
        "properties": {
          "rows": {
            "description": "Conversations in this page",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversationListItemDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total matching the filters",
            "example": 1234
          },
          "limit": {
            "type": "number",
            "description": "Page size",
            "example": 50
          },
          "offset": {
            "type": "number",
            "description": "Offset",
            "example": 0
          }
        },
        "required": [
          "rows",
          "total",
          "limit",
          "offset"
        ]
      },
      "GetAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 1
          },
          "organization_id": {
            "type": "number",
            "description": "Organization id",
            "example": 1
          },
          "server_id": {
            "type": "number",
            "description": "Server id this agent runs on",
            "example": 1
          },
          "image_id": {
            "type": "number",
            "description": "AgentImage id pinned to this agent",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Display name",
            "example": "sales-bot-eu"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Free-form description"
          },
          "role": {
            "type": "string",
            "nullable": true,
            "description": "What the agent is FOR. Null while nobody has said: the AI Doctor proposes one from the identity and a person confirms it.",
            "example": "comercial"
          },
          "status": {
            "enum": [
              "SPAWNING",
              "RUNNING",
              "IDLE",
              "INPUT",
              "PAUSING",
              "PAUSED",
              "RESUMING",
              "STOPPING",
              "STOPPED",
              "ERROR",
              "DELETING"
            ],
            "type": "string",
            "description": "Lifecycle status — driven by the supervisor once running",
            "example": "STOPPED"
          },
          "has_model_credential": {
            "type": "boolean",
            "description": "Whether this agent has its OWN model credential configured, for whichever provider it currently runs on (Claude or any other — #541). The secret itself is encrypted at rest and never returned, only its presence. See `model_credential_provider` for which provider it applies to.",
            "example": false
          },
          "viewer_access": {
            "nullable": true,
            "enum": [
              "MANAGE",
              "CHAT"
            ],
            "type": "string",
            "description": "The requesting member's access level on this agent (KUJI-41 F2): 'MANAGE' (admin/service or a MANAGE grant), 'CHAT' (view+converse only), or null. Lets the UI hide admin actions / filter assignment pickers. Undefined for internal/no-session reads.",
            "example": "MANAGE"
          },
          "model_credential_type": {
            "type": "string",
            "description": "Type of this agent's OWN model credential: OAUTH (subscription token — Anthropic only) or API_KEY (a plain key, any provider).",
            "example": "OAUTH"
          },
          "model_credential_provider": {
            "type": "string",
            "nullable": true,
            "description": "Which provider this agent's own credential (`has_model_credential`) was saved for, e.g. \"anthropic\" or \"minimax\" — null when none is configured. Only applies while the agent's model still resolves to this same provider: it stops taking effect (silently — the org connector takes over) the moment `model_override` points somewhere else, until pasted again.",
            "example": "anthropic"
          },
          "model_credential_base_url": {
            "type": "string",
            "nullable": true,
            "description": "Endpoint override that travels with this agent's own credential. Null = the provider's default endpoint (Anthropic direct).",
            "example": null
          },
          "credential_choice": {
            "enum": [
              "API_KEY",
              "INHERIT",
              "SUBSCRIPTION"
            ],
            "type": "string",
            "description": "Which of its provider's credentials the agent runs on: INHERIT (the one the organization marked as default), SUBSCRIPTION or API_KEY. A credential pasted into this agent's own model_credential still wins over any of them.",
            "example": "INHERIT"
          },
          "model_override": {
            "type": "string",
            "nullable": true,
            "description": "Claude model slug override. Null → use whatever the image defaults to.",
            "example": "claude-sonnet-4-6"
          },
          "resolved_model": {
            "type": "string",
            "nullable": true,
            "description": "Effective model the agent would spawn with now (override ?? image default). Null = claude binary default.",
            "example": "claude-opus-4-8"
          },
          "context_floor_tokens": {
            "type": "number",
            "description": "Tokens the agent loads on EVERY step before anyone speaks (memories, skills, base prompt). Lets the list compare the fleet without opening each context map. Approximate — the exact breakdown is the context manifest.",
            "example": 67000
          },
          "context_floor_memory_tokens": {
            "type": "number",
            "description": "How much of that floor is short-term memories, the part usually worth moving.",
            "example": 46000
          },
          "context_floor_recommended_tokens": {
            "type": "number",
            "description": "What we advise not to exceed at boot, measured against whatever really limits this agent (its session-reset cap, or the model window when uncapped).",
            "example": 60000
          },
          "context_floor_cap_tokens": {
            "type": "number",
            "nullable": true,
            "description": "The session-reset cap this agent compacts at, or null when uncapped.",
            "example": 300000
          },
          "running_model": {
            "type": "string",
            "nullable": true,
            "description": "Model the agent was last actually spawned with. Differs from resolved_model → a restart is pending to apply the change.",
            "example": "claude-opus-4-7"
          },
          "analysis_score": {
            "type": "number",
            "nullable": true,
            "description": "Latest config-analysis score 0-100 (KUJI-90). null if never analyzed.",
            "example": 72
          },
          "effort_override": {
            "nullable": true,
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "XHIGH",
              "MAX"
            ],
            "type": "string",
            "description": "Reasoning-effort override → CLI --effort (LOW/MEDIUM/HIGH/XHIGH/MAX). The modern unified depth control; supersedes thinking on adaptive models. Null = not set (claude defaults to high).",
            "example": "HIGH"
          },
          "thinking_enabled": {
            "type": "boolean",
            "nullable": true,
            "description": "Force --thinking on or off for this agent. Null → use the image default.",
            "example": true
          },
          "language": {
            "type": "string",
            "nullable": true,
            "description": "BCP-47 / ISO language code appended to the system prompt by the image start script.",
            "example": "es"
          },
          "mcp_http_bridge": {
            "type": "boolean",
            "nullable": true,
            "description": "Wrap remote HTTP/SSE MCP servers in a local stdio bridge to stop per-turn reconnect churn (KUJI-34). Null/false → off.",
            "example": true
          },
          "response_style": {
            "enum": [
              "DEFAULT",
              "EXTENDED",
              "SUMMARIZED",
              "MINIMAL"
            ],
            "type": "string",
            "description": "Reply DETAIL (KUJI-53/62): DEFAULT (platform default), EXTENDED (full), SUMMARIZED (concise), or MINIMAL (just the essential).",
            "example": "DEFAULT"
          },
          "response_format": {
            "enum": [
              "PROSE",
              "SCANNABLE"
            ],
            "type": "string",
            "description": "Reply FORMAT (KUJI-62): PROSE (normal sentences) or SCANNABLE (headline + bullets, actionable first).",
            "example": "PROSE"
          },
          "single_message": {
            "type": "boolean",
            "description": "Reply MESSAGES (KUJI-62): false = may narrate steps as separate messages; true = work silently, send ONE message per turn.",
            "example": false
          },
          "no_emoji": {
            "type": "boolean",
            "description": "Reply EMOJI (KUJI-62): false = may use emojis where they aid scanning; true = NEVER use emojis (customer-facing agents).",
            "example": false
          },
          "approval_ask_threshold": {
            "type": "number",
            "description": "Approval \"ask\" threshold (0-100): the agent acts alone below it and asks the operator (approval_request with a self-assessed level) at or above it.",
            "example": 70
          },
          "approval_block_threshold": {
            "type": "number",
            "description": "Approval \"block\" threshold (0-100, >= ask): at or above it the agent waits for the human no matter what; in the [ask, block) band it asks but auto-proceeds after approval_timeout_minutes.",
            "example": 90
          },
          "approval_timeout_minutes": {
            "type": "number",
            "description": "Minutes the agent waits for a decision in the auto-proceed band before continuing on its own.",
            "example": 60
          },
          "approval_categories": {
            "type": "array",
            "items": {
              "required": true,
              "description": "Always-ask overrides (on top of the thresholds): free-text actions that ALWAYS require approval regardless of the agent's level estimate. Empty = thresholds only.",
              "example": [
                "Gastar dinero: compras, pagos o suscripciones",
                "Dar de baja a un cliente"
              ],
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "disabled_tool_groups": {
            "description": "Tool families this agent may NOT use. Empty = it can use everything.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "memory_mb_override": {
            "type": "number",
            "nullable": true,
            "description": "RAM ceiling for the agent container in MB. Null = inherit the server default (or unlimited if the server sets none). Per-agent override that wins over the server default.",
            "example": 2048
          },
          "cpu_override": {
            "type": "number",
            "nullable": true,
            "description": "CPU ceiling for the agent container in cores. Null = inherit the server default (or unlimited if the server sets none). Per-agent override that wins over the server default.",
            "example": 2
          },
          "hub_available": {
            "type": "boolean",
            "description": "Whether this agent is listed as an assignee candidate in the external-conversations Agent Desk. Defaults to false so internal-only agents stay invisible to customers.",
            "example": true
          },
          "voice_enabled": {
            "type": "boolean",
            "description": "Whether the voice channel (TTS replies) is enabled for this agent.",
            "example": false
          },
          "network_privileged": {
            "type": "boolean",
            "description": "Privileged networking (KJ-156): container gets /dev/net/tun + CAP_NET_ADMIN so the agent can run a VPN (Tailscale/WireGuard/…). Applied on the next spawn.",
            "example": false
          },
          "voice_id": {
            "type": "string",
            "nullable": true,
            "description": "ElevenLabs voice id for this agent's TTS, or null.",
            "example": "dNjJKg63Fr5AXwIdkATa"
          },
          "backup_schedule": {
            "enum": [
              "INHERIT",
              "OFF",
              "CUSTOM"
            ],
            "type": "string",
            "description": "Scheduled-backup mode: INHERIT (org default), OFF (disabled), or CUSTOM (own schedule).",
            "example": "INHERIT"
          },
          "timezone": {
            "type": "string",
            "nullable": true,
            "description": "Custom backup cron (UTC) when backup_schedule=CUSTOM. Null otherwise.",
            "example": "0 */6 * * *"
          },
          "backup_retention": {
            "type": "number",
            "nullable": true,
            "description": "Custom retention (SCHEDULED backups to keep) when CUSTOM. Null otherwise.",
            "example": 10
          },
          "cost_guard_mode": {
            "enum": [
              "INHERIT",
              "OFF",
              "CUSTOM"
            ],
            "type": "string",
            "description": "Cost-guard mode (KUJI-51): INHERIT (org default), OFF (disabled), or CUSTOM (own threshold).",
            "example": "INHERIT"
          },
          "cost_guard_tokens_per_hour": {
            "type": "number",
            "nullable": true,
            "description": "Custom hourly token threshold when cost_guard_mode=CUSTOM. Null otherwise.",
            "example": 500000
          },
          "cost_guard_action": {
            "nullable": true,
            "enum": [
              "NOTIFY",
              "PAUSE"
            ],
            "type": "string",
            "description": "Custom guard action (NOTIFY/PAUSE) when CUSTOM. Null = inherit."
          },
          "session_reset_mode": {
            "enum": [
              "INHERIT",
              "OFF",
              "CUSTOM"
            ],
            "type": "string",
            "description": "Session auto-reset mode (KUJI-51): INHERIT (org default), OFF (never), or CUSTOM (own token cap).",
            "example": "INHERIT"
          },
          "session_reset_tokens": {
            "type": "number",
            "nullable": true,
            "description": "Custom max context tokens before auto-reset when CUSTOM. Null otherwise.",
            "example": 300000
          },
          "last_backup_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Timestamp of the agent's most recent COMPLETED backup. Null if never backed up."
          },
          "container_id": {
            "type": "string",
            "nullable": true,
            "description": "Container id assigned by Docker (null when not running)"
          },
          "last_action": {
            "type": "string",
            "nullable": true,
            "description": "Last action reported by the agent",
            "example": "Analyzing diff in src/auth/session.ts"
          },
          "last_action_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Timestamp of the last reported action"
          },
          "auth_failed_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the provider rejected this agent's token, or null if it works. Cleared as soon as the agent completes a turn."
          },
          "uptime_seconds": {
            "type": "number",
            "nullable": true,
            "description": "Container uptime in seconds (null when not running)"
          },
          "tokens_used": {
            "type": "string",
            "description": "Total tokens consumed by the agent (string to preserve BigInt precision)",
            "example": "142000"
          },
          "cost_micro": {
            "type": "string",
            "description": "Accumulated cost in 1e-6 currency units (string to preserve BigInt precision). 2.84 in currency = \"2840000\".",
            "example": "2840000"
          },
          "usage_status": {
            "type": "string",
            "nullable": true,
            "description": "Latest Anthropic rate-limit status for this agent (ALLOWED / ALLOWED_WARNING / EXCEEDED). Null when no snapshot has been received yet.",
            "example": "ALLOWED"
          },
          "usage_resets_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Wall-clock moment Anthropic will reset the current rate-limit window. Null when no snapshot has been received yet."
          },
          "usage_rate_limit_type": {
            "type": "string",
            "nullable": true,
            "description": "Which rate-limit bucket triggered the event: FIVE_HOUR (rolling 5h window) or WEEKLY (Max-plan weekly cap).",
            "example": "FIVE_HOUR"
          },
          "usage_overage_status": {
            "type": "string",
            "nullable": true,
            "description": "Anthropic \"overage\" status (pay-as-you-go burst credit). REJECTED with overageDisabledReason \"org_level_disabled\" is the textbook \"the OAuth token belongs to a different Anthropic org than the operator expects\" diagnostic.",
            "example": "ALLOWED"
          },
          "usage_overage_resets_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Wall-clock moment the overage window resets."
          },
          "usage_is_overage": {
            "type": "boolean",
            "nullable": true,
            "description": "True when the agent is currently consuming the overage allowance.",
            "example": false
          },
          "usage_updated_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Server timestamp when the usage snapshot was last refreshed."
          },
          "sync_pending": {
            "type": "boolean",
            "description": "True when skills/memories changed and the supervisor has not yet picked them up",
            "example": false
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar URL — points at the backend proxy that streams from R2. Includes a v=<timestamp> cache-buster so a replacement is picked up on the next render. Null when no avatar has been uploaded.",
            "example": "/org/1/agent/3/avatar?v=1779650000000"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "backup_cron": {
            "type": "string",
            "nullable": true
          },
          "backup_freeze": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "organization_id",
          "server_id",
          "image_id",
          "name",
          "status",
          "has_model_credential",
          "model_credential_type",
          "credential_choice",
          "response_style",
          "response_format",
          "single_message",
          "no_emoji",
          "approval_ask_threshold",
          "approval_block_threshold",
          "approval_timeout_minutes",
          "approval_categories",
          "hub_available",
          "voice_enabled",
          "network_privileged",
          "voice_id",
          "backup_schedule",
          "cost_guard_mode",
          "session_reset_mode",
          "tokens_used",
          "cost_micro",
          "sync_pending",
          "created_at",
          "updated_at",
          "backup_cron",
          "backup_freeze"
        ]
      },
      "ListAgentDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Items in the current page",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetAgentDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total number of items matching the query",
            "example": 42
          },
          "page": {
            "type": "number",
            "description": "Current page (1-indexed)",
            "example": 1
          },
          "limit": {
            "type": "number",
            "description": "Page size",
            "example": 20
          }
        },
        "required": [
          "data",
          "total",
          "page",
          "limit"
        ]
      },
      "CreateAgentDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Display name (unique within the org)",
            "example": "sales-bot-eu"
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Free-form description",
            "example": "Handles EU leads in the sales pipeline"
          },
          "role": {
            "type": "string",
            "maxLength": 80,
            "description": "What the agent is FOR: «comercial», «atención al cliente», «recepción de clínica». Free text, not a closed list. Left empty, the AI Doctor proposes one from the agent identity and a person confirms it — it is never rewritten on its own.",
            "example": "comercial"
          },
          "server_id": {
            "type": "number",
            "minimum": 1,
            "description": "Server where the agent will run. Must belong to the same org.",
            "example": 1
          },
          "image_id": {
            "type": "number",
            "minimum": 1,
            "description": "AgentImage version pinned to this agent. Must be available.",
            "example": 1
          },
          "model_override": {
            "type": "string",
            "maxLength": 80,
            "pattern": "^$|^[A-Za-z0-9._[\\]-]+$",
            "description": "Model this agent runs, from the catalogue (any provider). Omit to inherit the image default. The image is the environment, not the provider — a model of any provider is valid here as long as the org has a connector for it.",
            "example": "claude-sonnet-5"
          }
        },
        "required": [
          "name",
          "server_id",
          "image_id"
        ]
      },
      "UpdateAgentDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Display name (unique within the org)",
            "example": "sales-bot-eu"
          },
          "image_id": {
            "type": "number",
            "minimum": 1,
            "description": "Switch the agent to a different image from the catalogue (e.g. base → flex). The image must be available. Changing it only updates the row; call POST /:id/update-image to pull the new tag and recreate the container — the volume, session_id and conversations are preserved. The server cannot be changed this way.",
            "example": 3
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Free-form description"
          },
          "role": {
            "type": "string",
            "maxLength": 80,
            "description": "What the agent is FOR: «comercial», «atención al cliente», «recepción de clínica». Free text, not a closed list. Left empty, the AI Doctor proposes one from the agent identity and a person confirms it — it is never rewritten on its own.",
            "example": "comercial"
          },
          "timezone": {
            "type": "string",
            "nullable": true,
            "description": "IANA time zone the schedules are read in (\"Europe/Madrid\"). Overrides the organization zone for the crons of this agent; empty inherits. Rejected if the runtime does not know the zone.",
            "example": "Europe/Madrid"
          },
          "model_credential": {
            "type": "string",
            "maxLength": 4000,
            "description": "This agent's OWN credential for whichever provider it currently runs on (its `model_override`, resolved at save time) — a Claude OAuth token from `claude setup-token`, an Anthropic Console API key, or a plain API key from any other provider's console (#541). Stored encrypted; never returned by the API. Pass an empty string to clear it (the agent falls back to the organization's connector for that provider, if any)."
          },
          "model_credential_type": {
            "enum": [
              "OAUTH",
              "API_KEY"
            ],
            "type": "string",
            "description": "Type of this credential: OAUTH (subscription token — Anthropic only) or API_KEY (a plain key, any provider). Auto-detected from the token prefix if omitted on an Anthropic model; defaults to API_KEY on any other provider.",
            "example": "OAUTH"
          },
          "model_credential_base_url": {
            "type": "string",
            "maxLength": 500,
            "format": "uri",
            "description": "Endpoint override that travels with this agent's own credential above — most non-Anthropic providers need one to be reachable at all. Empty string clears it back to the provider's default endpoint. Requires model_provider.edit (OWNER/ADMIN) — it decides where this agent's traffic (and every secret it handles) actually goes, same boundary as an org connector's endpoint."
          },
          "credential_choice": {
            "enum": [
              "API_KEY",
              "INHERIT",
              "SUBSCRIPTION"
            ],
            "type": "string",
            "description": "Which of its provider's credentials this agent runs on: INHERIT (default — whichever the organization marked as such), SUBSCRIPTION or API_KEY. Takes effect at the next start: the credential is injected into the container when it boots.",
            "example": "INHERIT"
          },
          "model_override": {
            "type": "string",
            "maxLength": 80,
            "pattern": "^$|^[A-Za-z0-9._[\\]-]+$",
            "description": "Model slug override (e.g. claude-sonnet-4-6, MiniMax-M3). Must be a slug from the catalogue. Empty string clears the override and returns the agent to the image default.",
            "example": "claude-sonnet-4-6"
          },
          "thinking_enabled": {
            "type": "boolean",
            "nullable": true,
            "description": "Force --thinking on or off for this agent. Pass null to clear the override and fall back to the image default.",
            "example": true
          },
          "effort_override": {
            "nullable": true,
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "XHIGH",
              "MAX"
            ],
            "type": "string",
            "description": "Reasoning-effort override → CLI --effort (LOW/MEDIUM/HIGH/XHIGH/MAX). Pass null to clear and fall back to the claude default (high). Supersedes --thinking on adaptive models.",
            "example": "HIGH"
          },
          "language": {
            "type": "string",
            "maxLength": 10,
            "pattern": "^$|^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})?$",
            "description": "BCP-47 / ISO language code. Empty string clears the override.",
            "example": "es"
          },
          "approval_ask_threshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Approval \"ask\" threshold (0-100): the agent acts alone below it, asks at/above it. Takes effect on the next spawn/restart.",
            "example": 70
          },
          "approval_block_threshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Approval \"block\" threshold (0-100, kept >= ask server-side): at/above it the agent waits no matter what; between ask and block it auto-proceeds after the timeout.",
            "example": 90
          },
          "approval_timeout_minutes": {
            "type": "number",
            "minimum": 1,
            "description": "Minutes to wait for a decision in the auto-proceed band before continuing.",
            "example": 60
          },
          "approval_categories": {
            "type": "array",
            "items": {
              "required": false,
              "maxItems": 50,
              "description": "Always-ask overrides (on top of the thresholds): free-text actions that ALWAYS require approval. Empty array = thresholds only. Blank items are dropped server-side.",
              "example": [
                "Gastar dinero: compras, pagos o suscripciones",
                "Dar de baja a un cliente"
              ],
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 300
              }
            }
          },
          "disabled_tool_groups": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "credentials",
                "scheduling",
                "mcp_servers",
                "third_party_access",
                "self_management",
                "outbound",
                "teammates",
                "knowledge"
              ]
            },
            "description": "Tool families this agent may NOT use (#302). Empty = it can use everything, which is how every agent behaved before this existed. Enforced server-side on every MCP call, not just by not announcing them.",
            "example": [
              "credentials",
              "mcp_servers"
            ]
          },
          "mcp_http_bridge": {
            "type": "boolean",
            "nullable": true,
            "description": "Wrap remote HTTP/SSE MCP servers in a local stdio bridge to stop per-turn reconnect churn (KUJI-34). Pass null/false to disable. Takes effect on the next spawn/restart.",
            "example": true
          },
          "response_style": {
            "enum": [
              "DEFAULT",
              "EXTENDED",
              "SUMMARIZED",
              "MINIMAL"
            ],
            "type": "string",
            "description": "Reply DETAIL (KUJI-53/62). DEFAULT follows the platform default; EXTENDED = full; SUMMARIZED = concise; MINIMAL = just the essential.",
            "example": "DEFAULT"
          },
          "response_format": {
            "enum": [
              "PROSE",
              "SCANNABLE"
            ],
            "type": "string",
            "description": "Reply FORMAT (KUJI-62). PROSE = normal sentences; SCANNABLE = headline + bullets, actionable first.",
            "example": "PROSE"
          },
          "single_message": {
            "type": "boolean",
            "description": "Reply MESSAGES (KUJI-62). false = may narrate steps; true = work silently, send ONE message per turn.",
            "example": false
          },
          "no_emoji": {
            "type": "boolean",
            "description": "Reply EMOJI (KUJI-62). false = may use emojis where they help; true = NEVER use emojis (customer-facing).",
            "example": false
          },
          "memory_mb_override": {
            "type": "number",
            "nullable": true,
            "minimum": 64,
            "maximum": 131072,
            "description": "RAM ceiling override for this agent in MB. Null clears the override (falls back to the server default, or unlimited if the server sets none).",
            "example": 2048
          },
          "cpu_override": {
            "type": "number",
            "nullable": true,
            "minimum": 0.1,
            "maximum": 128,
            "description": "CPU ceiling override for this agent in cores. Null clears the override (falls back to the server default, or unlimited if the server sets none).",
            "example": 2
          },
          "hub_available": {
            "type": "boolean",
            "description": "Whether this agent appears in the Agent Desk assignee picker. False (default) hides it from external-conversations workflows.",
            "example": true
          },
          "voice_enabled": {
            "type": "boolean",
            "description": "Enable the voice channel (TTS replies) for this agent (KUJI — voice).",
            "example": true
          },
          "network_privileged": {
            "type": "boolean",
            "description": "Privileged networking (KJ-156): spawn the container with /dev/net/tun + CAP_NET_ADMIN so the agent can run a VPN. Powerful — opt-in. Applies on the next spawn (needs a recreate).",
            "example": false
          },
          "voice_id": {
            "type": "string",
            "description": "ElevenLabs voice id for this agent's TTS. Empty string clears it.",
            "example": "dNjJKg63Fr5AXwIdkATa"
          },
          "backup_schedule": {
            "enum": [
              "INHERIT",
              "OFF",
              "CUSTOM"
            ],
            "type": "string",
            "description": "Scheduled-backup mode (KUJI-43). INHERIT (default) uses the org schedule; OFF disables backups for this agent; CUSTOM uses backup_cron/backup_retention below.",
            "example": "INHERIT"
          },
          "backup_cron": {
            "type": "string",
            "nullable": true,
            "description": "Custom backup schedule (cron, UTC) when backup_schedule=CUSTOM. Empty/null clears it.",
            "example": "0 */6 * * *"
          },
          "backup_retention": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "maximum": 365,
            "description": "How many SCHEDULED backups to keep for this agent (when CUSTOM).",
            "example": 10
          },
          "backup_freeze": {
            "type": "boolean",
            "description": "Freeze this agent while its disk is read, so the copy cannot catch a file half-written. It is unreachable for the duration of the tar (minutes on a big volume) and its open connections may time out, so it is off by default and the call is the operator's.",
            "example": false
          },
          "cost_guard_mode": {
            "enum": [
              "INHERIT",
              "OFF",
              "CUSTOM"
            ],
            "type": "string",
            "description": "Cost-guard mode (KUJI-51). INHERIT (default) uses the org guard; CUSTOM uses the threshold/action below; OFF drops both. On a standard plan OFF does NOT leave the agent unguarded: it falls back to the plan's hourly ceiling, which cannot be switched off from here. Only ENTERPRISE, which negotiates its own, can run with no guard at all.",
            "example": "INHERIT"
          },
          "cost_guard_tokens_per_hour": {
            "type": "number",
            "nullable": true,
            "minimum": 0,
            "description": "Custom hourly token threshold when cost_guard_mode=CUSTOM. Null/0 clears your number; on a standard plan the plan's ceiling then applies. A value BELOW the plan's is honoured — tightening the guard is always allowed, switching it off is not.",
            "example": 500000
          },
          "cost_guard_action": {
            "nullable": true,
            "enum": [
              "NOTIFY",
              "PAUSE"
            ],
            "type": "string",
            "description": "Custom guard action (NOTIFY/PAUSE) when CUSTOM."
          },
          "session_reset_mode": {
            "enum": [
              "INHERIT",
              "OFF",
              "CUSTOM"
            ],
            "type": "string",
            "description": "Session auto-reset mode (KUJI-51). INHERIT (default) uses the org cap; OFF never auto-resets; CUSTOM uses session_reset_tokens below.",
            "example": "INHERIT"
          },
          "session_reset_tokens": {
            "type": "number",
            "nullable": true,
            "minimum": 50000,
            "maximum": 900000,
            "description": "Custom max context tokens before auto-reset when CUSTOM (50k–900k).",
            "example": 300000
          }
        },
        "required": [
          "approval_categories"
        ]
      },
      "AgentImageCapabilityDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Capability id",
            "example": 1
          },
          "key": {
            "type": "string",
            "description": "Stable slug — the agent-base/supervisor look at this.",
            "example": "playwright"
          },
          "label": {
            "type": "string",
            "description": "Display name",
            "example": "Playwright + Chromium"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Long description"
          },
          "icon": {
            "type": "string",
            "nullable": true,
            "description": "Lucide icon name hint for the frontend",
            "example": "theater"
          }
        },
        "required": [
          "id",
          "key",
          "label"
        ]
      },
      "GetAgentImageDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "AgentImage id",
            "example": 1
          },
          "role_key": {
            "type": "string",
            "description": "Role key (stable identifier across versions)",
            "example": "sales"
          },
          "version": {
            "type": "string",
            "description": "Image version",
            "example": "0.1.0"
          },
          "image_tag": {
            "type": "string",
            "description": "Fully qualified image tag in the registry",
            "example": "ghcr.io/calltek/kj-agent-sales:0.1.0"
          },
          "name": {
            "type": "string",
            "description": "Display name",
            "example": "Sales Agent"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Free-form description"
          },
          "icon": {
            "type": "string",
            "nullable": true,
            "description": "Icon hint for the UI"
          },
          "color": {
            "type": "string",
            "nullable": true,
            "description": "Color hint for the UI (hex)"
          },
          "default_memory_mb": {
            "type": "number",
            "description": "Default container memory limit in MB",
            "example": 512
          },
          "default_cpu": {
            "type": "number",
            "description": "Default container CPU share",
            "example": 0.5
          },
          "default_model": {
            "type": "string",
            "nullable": true,
            "description": "Default claude model baked into the image start.sh. Surfaced so the UI can show the effective model when the agent has no model_override.",
            "example": "claude-sonnet-4-6"
          },
          "is_available": {
            "type": "boolean",
            "description": "False to hide from selectors without removing the row"
          },
          "capabilities": {
            "description": "Capabilities pre-installed in the image (internet, playwright, git, …). Read-only from the panel; managed by the Kujira team.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentImageCapabilityDto"
            }
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar URL (cache-busted) or null; the UI falls back to the icon.",
            "example": "/agent-image/8/avatar?v=1717000000000"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          }
        },
        "required": [
          "id",
          "role_key",
          "version",
          "image_tag",
          "name",
          "default_memory_mb",
          "default_cpu",
          "is_available",
          "capabilities",
          "created_at",
          "updated_at"
        ]
      },
      "ListAgentImageDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Items in the current page",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetAgentImageDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total number of items matching the query",
            "example": 3
          },
          "page": {
            "type": "number",
            "description": "Current page (1-indexed)",
            "example": 1
          },
          "limit": {
            "type": "number",
            "description": "Page size",
            "example": 20
          }
        },
        "required": [
          "data",
          "total",
          "page",
          "limit"
        ]
      },
      "GetOrganizationDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Organization id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Organization name",
            "example": "Acme Labs"
          },
          "plan": {
            "enum": [
              "FREE",
              "BASIC",
              "PRO",
              "ENTERPRISE"
            ],
            "type": "string",
            "description": "Subscription plan",
            "example": "FREE"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar URL — points at the backend proxy that streams from R2. Includes a v=<timestamp> cache-buster so a replacement is picked up on the next render. Null when no avatar has been uploaded.",
            "example": "/org/1/avatar?v=1779650000000"
          },
          "viewer_role": {
            "nullable": true,
            "enum": [
              "OWNER",
              "ADMIN",
              "OPERATOR",
              "VIEWER",
              "SERVICE"
            ],
            "type": "string",
            "description": "The requesting user's role in this organization. Lets the frontend gate edit/delete UI. Null when the role can't be resolved.",
            "example": "OWNER"
          },
          "viewer_permissions": {
            "description": "The viewer's effective permission patterns — what the backend will actually evaluate for them. Includes wildcards (`*`) and negations. Empty when the viewer is not a member. Use this to gate the UI; re-deriving it from the role is wrong as soon as a member's permissions are customised.",
            "example": [
              "agent.view",
              "agent.edit"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "has_elevenlabs_key": {
            "type": "boolean",
            "description": "Whether an ElevenLabs API key is configured for the voice channel (KUJI — voice). The key itself is never returned.",
            "example": false
          },
          "has_groq_key": {
            "type": "boolean",
            "description": "Whether a Groq API key is configured for speech-to-text. The key itself is never returned. When false, transcription falls back to the server-wide GROQ_API_KEY if there is one.",
            "example": false
          },
          "has_google_oauth_app": {
            "type": "boolean",
            "description": "Si la organización usa su propia app de Google para los conectores (#310). Sin ella no se pueden conectar cuentas de Google. El secreto no se devuelve nunca.",
            "example": false
          },
          "google_oauth_client_id": {
            "type": "string",
            "nullable": true,
            "description": "El client_id de su app de Google, para poder enseñarlo y comprobar que es el que toca. No es secreto: viaja en cada URL de autorización.",
            "example": "1234-abc.apps.googleusercontent.com"
          },
          "timezone": {
            "type": "string",
            "description": "Org-wide default backup schedule (cron, UTC). Null = no default; agents inherit it unless they override.",
            "nullable": true,
            "example": "0 3 * * *"
          },
          "trial_ends_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Cuándo termina la prueba de PRO (#395). Null = no está de prueba: nunca lo estuvo, o ya terminó."
          },
          "grace_until": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Hasta cuándo se tolera lo que ya no cabe en el plan, tras cualquier bajada (prueba vencida, impago o mano de staff). Pasada esa fecha se PARA lo que sobra — nunca se borra nada. Null = no hay bajada pendiente. Es la cuenta atrás que el panel enseña."
          },
          "grace_from_plan": {
            "nullable": true,
            "enum": [
              "FREE",
              "BASIC",
              "PRO",
              "ENTERPRISE"
            ],
            "type": "string",
            "description": "De qué plan venía esa bajada, para poder contarlo (\"de PRO a FREE\")."
          },
          "limits": {
            "type": "object",
            "properties": {
              "max_agents": {
                "type": "number",
                "nullable": true
              },
              "max_servers": {
                "type": "number",
                "nullable": true
              },
              "channels_per_agent": {
                "type": "number",
                "nullable": true
              },
              "min_cron_interval_minutes": {
                "type": "number",
                "nullable": true
              },
              "min_mail_poll_minutes": {
                "type": "number",
                "nullable": true
              },
              "doctor_runs_per_month": {
                "type": "number",
                "nullable": true
              },
              "cron_llm_runs_per_month": {
                "type": "number",
                "nullable": true
              },
              "file_versions": {
                "type": "number",
                "nullable": true
              }
            },
            "required": [
              "max_agents",
              "max_servers",
              "channels_per_agent",
              "min_cron_interval_minutes",
              "min_mail_poll_minutes",
              "doctor_runs_per_month",
              "cron_llm_runs_per_month",
              "file_versions"
            ]
          },
          "support_access_until": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Hasta cuándo el soporte de la plataforma puede entrar en esta organización. Null o pasado = no puede. Lo concede el propietario y caduca solo."
          },
          "support_access_granted_by": {
            "type": "number",
            "nullable": true,
            "description": "Quién concedió ese acceso y cuándo, para que el cliente pueda mirarlo."
          },
          "support_access_granted_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true
          },
          "customer_type": {
            "nullable": true,
            "enum": [
              "BUSINESS",
              "INDIVIDUAL"
            ],
            "type": "string",
            "description": "Empresa o particular (#417). Decide qué datos se le piden: a una empresa se le exigen todos, a un particular por debajo de 400 € sólo el país, porque su factura simplificada no lleva los demás. `null` = sin declarar.",
            "example": "BUSINESS"
          },
          "tax_name": {
            "type": "string",
            "nullable": true,
            "description": "Nombre o razón social que aparece en la factura (#417).",
            "example": "Acme Labs SL"
          },
          "tax_id": {
            "type": "string",
            "nullable": true,
            "description": "NIF/CIF/VAT. Opcional: un particular no tiene.",
            "example": "B12345678"
          },
          "tax_country": {
            "type": "string",
            "nullable": true,
            "description": "País en ISO 3166-1 alfa-2. Es lo que decide el IVA de la factura, incluso sin NIF: a un consumidor se le aplica el de SU país.",
            "example": "ES"
          },
          "tax_address": {
            "type": "string",
            "nullable": true,
            "description": "Dirección fiscal."
          },
          "tax_postal_code": {
            "type": "string",
            "nullable": true,
            "description": "Código postal fiscal."
          },
          "tax_city": {
            "type": "string",
            "nullable": true,
            "description": "Ciudad fiscal."
          },
          "vat_validated": {
            "type": "boolean",
            "nullable": true,
            "description": "Si el NIF se comprobó contra VIES y resultó válido. Es la prueba que justifica un 0% intracomunitario."
          },
          "vat_validated_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Cuándo se hizo esa comprobación."
          },
          "backup_retention": {
            "type": "number",
            "nullable": true,
            "description": "How many SCHEDULED backups to keep per agent (org default).",
            "example": 7
          },
          "cost_guard_tokens_per_hour": {
            "type": "number",
            "nullable": true,
            "description": "Org-wide cost guardrail: max spend per agent per hour (1e-6 units). Null/0 = off.",
            "example": 500000
          },
          "cost_guard_action": {
            "enum": [
              "NOTIFY",
              "PAUSE"
            ],
            "type": "string",
            "description": "What the cost guard does on breach: NOTIFY or PAUSE.",
            "example": "NOTIFY"
          },
          "session_reset_tokens": {
            "type": "number",
            "description": "Org-wide default: recycle a live conversation once its context passes this many tokens (KUJI-51). Agents inherit unless they override.",
            "example": 300000
          },
          "transcript_retention_days": {
            "type": "number",
            "nullable": true,
            "description": "Transcript retention override in days (KUJI-113). null = the retention is derived from the plan default. Only ENTERPRISE orgs can set it.",
            "example": 365
          },
          "contracted_agents": {
            "type": "number",
            "nullable": true,
            "description": "Agents the org pays for (#394, per-agent pricing). Governs how many agents and servers it may create. null = not contracted yet: paid plans fall back to the hard cap (50), FREE is always 1. Staff-set until billing exists.",
            "example": 3
          },
          "max_channels_per_agent": {
            "type": "number",
            "nullable": true,
            "description": "Per-org override of the external-channel allowance per contracted agent (#394). null = plan default (BASIC 3, PRO/ENTERPRISE 5). Staff-set.",
            "example": 10
          },
          "doctor_runs_per_month": {
            "type": "number",
            "nullable": true,
            "description": "Per-org override of AI Doctor passes per month (#394). null = plan default. Staff-set, and enforced: a pass beyond the allowance is refused before the model is called.",
            "example": 8
          },
          "cron_llm_runs_per_month": {
            "type": "number",
            "nullable": true,
            "description": "Per-org override of monthly cron runs that wake the agent (#394). null = plan default. Staff-set. Not enforced yet.",
            "example": 20000
          },
          "file_versions": {
            "type": "number",
            "nullable": true,
            "description": "Per-org override of how many versions are kept per memory and per skill (#405). null = plan default. Staff-set; only ENTERPRISE reads it.",
            "example": 50
          },
          "version_retention": {
            "enum": [
              "SPREAD",
              "RECENT"
            ],
            "type": "string",
            "description": "Which versions of a memory or skill survive once its history is full (#405). SPREAD keeps them spaced across time — reaches months back with less grain in the last days; RECENT keeps only the newest. Chosen by the organization in Settings.",
            "example": "SPREAD"
          },
          "server_alert_ram_pct": {
            "type": "number",
            "nullable": true,
            "description": "Org default RAM alert threshold (%) for servers (KUJI-136). null = built-in default (85).",
            "example": 85
          },
          "server_alert_cpu_pct": {
            "type": "number",
            "nullable": true,
            "description": "Org default CPU alert threshold (%) for servers. null = built-in default (90).",
            "example": 90
          },
          "server_alert_load_factor": {
            "type": "number",
            "nullable": true,
            "description": "Org default load alert factor (load > factor × cores). null = built-in default (1.5).",
            "example": 1.5
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "backup_cron": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "id",
          "name",
          "plan",
          "viewer_permissions",
          "has_elevenlabs_key",
          "has_groq_key",
          "has_google_oauth_app",
          "limits",
          "cost_guard_action",
          "session_reset_tokens",
          "transcript_retention_days",
          "contracted_agents",
          "max_channels_per_agent",
          "doctor_runs_per_month",
          "cron_llm_runs_per_month",
          "file_versions",
          "version_retention",
          "server_alert_ram_pct",
          "server_alert_cpu_pct",
          "server_alert_load_factor",
          "created_at",
          "updated_at",
          "backup_cron"
        ]
      },
      "ListOrganizationDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Items in the current page",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetOrganizationDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total number of items matching the query",
            "example": 42
          },
          "page": {
            "type": "number",
            "description": "Current page (1-indexed)",
            "example": 1
          },
          "limit": {
            "type": "number",
            "description": "Page size",
            "example": 20
          }
        },
        "required": [
          "data",
          "total",
          "page",
          "limit"
        ]
      },
      "GetMemoryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Memory id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Filename in /home/agent/.claude/memories/",
            "example": "guia-marca.md"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "One-line summary of what the memory is and when to use it (the skills \"name + description\" pattern). Shown in the UI without opening the file and given to the agent on recall. Null when unset (legacy/free memories).",
            "example": "Brand voice and tone guidelines for customer replies."
          },
          "scope": {
            "enum": [
              "SHORT_TERM",
              "LONG_TERM"
            ],
            "type": "string",
            "description": "SHORT_TERM = injected at spawn time (working memory). LONG_TERM = referenced on demand via MCP tool (not yet runtime).",
            "example": "SHORT_TERM"
          },
          "content_hash": {
            "type": "string",
            "description": "SHA-256 of `content`, lets the supervisor skip a no-op rewrite"
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this memory is assigned to. Zero = unassigned (orphan).",
            "example": 3
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the memory was archived (soft-deleted). Null on live memories. Archived memories are excluded from default listings and never written to a container volume."
          },
          "canonical_kind": {
            "nullable": true,
            "enum": [
              "IDENTITY",
              "SOUL"
            ],
            "type": "string",
            "description": "Set when this is a canonical per-agent memory (auto-seeded on agent creation). IDENTITY = identity.md, SOUL = soul.md. Free memories return null. Canonical memories cannot be archived, deleted, or unassigned from their owning agent.",
            "example": "IDENTITY"
          }
        },
        "required": [
          "id",
          "name",
          "scope",
          "content_hash",
          "assignment_count",
          "created_at",
          "updated_at"
        ]
      },
      "ListMemoryDto": {
        "type": "object",
        "properties": {
          "rows": {
            "description": "Page rows",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetMemoryDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total matching rows across all pages",
            "example": 31
          },
          "limit": {
            "type": "number",
            "description": "Echo of the requested page size",
            "example": 200
          },
          "offset": {
            "type": "number",
            "description": "Echo of the requested offset",
            "example": 0
          }
        },
        "required": [
          "rows",
          "total",
          "limit",
          "offset"
        ]
      },
      "MemoryAssignedAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          },
          "assigned_at": {
            "format": "date-time",
            "type": "string",
            "description": "When the memory was assigned to this agent"
          }
        },
        "required": [
          "id",
          "name",
          "assigned_at"
        ]
      },
      "GetMemoryWithContentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Memory id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Filename in /home/agent/.claude/memories/",
            "example": "guia-marca.md"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "One-line summary of what the memory is and when to use it (the skills \"name + description\" pattern). Shown in the UI without opening the file and given to the agent on recall. Null when unset (legacy/free memories).",
            "example": "Brand voice and tone guidelines for customer replies."
          },
          "scope": {
            "enum": [
              "SHORT_TERM",
              "LONG_TERM"
            ],
            "type": "string",
            "description": "SHORT_TERM = injected at spawn time (working memory). LONG_TERM = referenced on demand via MCP tool (not yet runtime).",
            "example": "SHORT_TERM"
          },
          "content_hash": {
            "type": "string",
            "description": "SHA-256 of `content`, lets the supervisor skip a no-op rewrite"
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this memory is assigned to. Zero = unassigned (orphan).",
            "example": 3
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the memory was archived (soft-deleted). Null on live memories. Archived memories are excluded from default listings and never written to a container volume."
          },
          "canonical_kind": {
            "nullable": true,
            "enum": [
              "IDENTITY",
              "SOUL"
            ],
            "type": "string",
            "description": "Set when this is a canonical per-agent memory (auto-seeded on agent creation). IDENTITY = identity.md, SOUL = soul.md. Free memories return null. Canonical memories cannot be archived, deleted, or unassigned from their owning agent.",
            "example": "IDENTITY"
          },
          "content": {
            "type": "string",
            "description": "Markdown content of the memory"
          },
          "assignments": {
            "description": "Agents this memory is currently assigned to",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MemoryAssignedAgentDto"
            }
          }
        },
        "required": [
          "id",
          "name",
          "scope",
          "content_hash",
          "assignment_count",
          "created_at",
          "updated_at",
          "content",
          "assignments"
        ]
      },
      "CreateMemoryDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 4,
            "maxLength": 512,
            "description": "S3-style key written to /home/agent/.claude/memories/<name>. May contain `/` to nest under folders. Must end in `.md`. The parent folder is created automatically if missing.",
            "example": "comercial/oferta-q2.md"
          },
          "content": {
            "type": "string",
            "minLength": 0,
            "maxLength": 200000,
            "description": "Markdown content",
            "example": "# Guía de marca\n\n- Tono cordial…"
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 1024,
            "description": "One-line summary of what the memory is and when to use it (skills \"name + description\" pattern). Shown in the UI and given to the agent on recall. Optional — keep the body clean markdown, the summary lives here.",
            "example": "Brand voice and tone guidelines for customer replies."
          },
          "scope": {
            "enum": [
              "SHORT_TERM",
              "LONG_TERM"
            ],
            "type": "string",
            "description": "SHORT_TERM = injected at /home/agent/.claude/memories/ on spawn. LONG_TERM = MCP-fetched on demand (runtime TBD).",
            "example": "SHORT_TERM"
          },
          "force": {
            "type": "boolean",
            "description": "Override the credential-in-content check (#309). When the detector flags a likely API key / PEM / connection string, the save is rejected with a 400 — pass `force: true` only if you know the match is an example, not a real secret. The detector still audits the attempt either way.",
            "example": false
          }
        },
        "required": [
          "name",
          "content",
          "scope"
        ]
      },
      "UpdateMemoryDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 4,
            "maxLength": 512,
            "description": "Filename (full path including folders)",
            "example": "ventas/oferta-q2.md"
          },
          "content": {
            "type": "string",
            "minLength": 0,
            "maxLength": 200000,
            "description": "Markdown content"
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 1024,
            "description": "One-line summary (skills \"name + description\" pattern). Pass an empty string to clear it.",
            "example": "Brand voice and tone guidelines for customer replies."
          },
          "scope": {
            "enum": [
              "SHORT_TERM",
              "LONG_TERM"
            ],
            "type": "string"
          },
          "force": {
            "type": "boolean",
            "description": "Override the credential-in-content check (#309). When the detector flags a likely API key / PEM / connection string, the save is rejected with a 400 — pass `force: true` only if you know the match is an example, not a real secret. The detector still audits the attempt either way.",
            "example": false
          }
        }
      },
      "SetMemoryAgentsDto": {
        "type": "object",
        "properties": {
          "agent_ids": {
            "uniqueItems": true,
            "description": "Full list of agents this memory should be assigned to. The service syncs: new ids are added, missing ids are removed. Pass [] to detach the memory from every agent.",
            "example": [
              3,
              5
            ],
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 1
            }
          }
        },
        "required": [
          "agent_ids"
        ]
      },
      "GetWebhookConfigDto": {
        "type": "object",
        "properties": {
          "outbound_url": {
            "type": "string",
            "nullable": true,
            "description": "Outbound POST URL"
          },
          "has_outbound_secret": {
            "type": "boolean",
            "description": "Whether an outbound HMAC secret is set",
            "example": false
          },
          "has_inbound_secret": {
            "type": "boolean",
            "description": "Whether an inbound HMAC secret is set",
            "example": true
          },
          "signature_header": {
            "type": "string",
            "nullable": true,
            "description": "Header the inbound signature (plain HMAC-SHA256 hex) travels in. Null = default (x-kujira-signature). Plane: x-plane-signature.",
            "example": "x-plane-signature"
          },
          "echo_filter": {
            "type": "object",
            "properties": {
              "actor_paths": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "self_ids": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "actor_paths",
              "self_ids"
            ]
          }
        },
        "required": [
          "has_outbound_secret",
          "has_inbound_secret",
          "echo_filter"
        ]
      },
      "GetGithubOAuthDto": {
        "type": "object",
        "properties": {
          "authorized": {
            "type": "boolean",
            "description": "Whether the channel is authorized to act on GitHub",
            "example": true
          },
          "user_login": {
            "type": "string",
            "nullable": true,
            "description": "The GitHub account the channel posts as (login). Null when not authorized.",
            "example": "n0v4-SYS"
          },
          "expires_at": {
            "type": "string",
            "nullable": true,
            "description": "When the current access token expires (ISO). Null = non-expiring / unauthorized."
          }
        },
        "required": [
          "authorized"
        ]
      },
      "GetGithubConfigDto": {
        "type": "object",
        "properties": {
          "installation_id": {
            "type": "number",
            "nullable": true,
            "description": "The GitHub App installation id this channel rides.",
            "example": 82345678
          },
          "repos": {
            "description": "Repo full_names listened to. ['*'] = all repos.",
            "example": [
              "calltek/kj-backend"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "events": {
            "description": "Event types listened to. ['*'] = all subscribed events.",
            "example": [
              "pull_request",
              "pull_request_review"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "listen_scope": {
            "type": "string",
            "description": "Listening scope: \"all\" = every event of the configured repos/events wakes the agent; \"involved\" = only events that concern it (review requested / assigned / @-mentioned / its own PRs), GitHub-notifications style.",
            "enum": [
              "all",
              "involved"
            ],
            "example": "all"
          },
          "raw_payload": {
            "type": "boolean",
            "description": "Whether each event carries the full raw webhook payload. False by default — it is ~3.000 tokens per event that the agent re-reads on every step, for ~61 that mean something (#272).",
            "example": false
          },
          "oauth": {
            "description": "Per-channel OAuth identity: whether the channel is authorized to post on GitHub AS a bot account, and as whom (Fase 3).",
            "allOf": [
              {
                "$ref": "#/components/schemas/GetGithubOAuthDto"
              }
            ]
          }
        },
        "required": [
          "repos",
          "events",
          "listen_scope",
          "raw_payload",
          "oauth"
        ]
      },
      "GetGoogleConnectionDto": {
        "type": "object",
        "properties": {
          "connected": {
            "type": "boolean",
            "description": "Whether the agent has a Google account connected",
            "example": true
          },
          "google_email": {
            "type": "string",
            "nullable": true,
            "description": "The connected Gmail address. Null when not connected.",
            "example": "soporte@gmail.com"
          }
        },
        "required": [
          "connected"
        ]
      },
      "GetDriveConfigDto": {
        "type": "object",
        "properties": {
          "folder_id": {
            "type": "string",
            "description": "Google Drive folder id being watched",
            "example": "1AbCdEfGhIjKlMnOpQrStUvWxYz"
          },
          "folder_name": {
            "type": "string",
            "description": "Folder display name",
            "example": "Facturas"
          }
        },
        "required": [
          "folder_id",
          "folder_name"
        ]
      },
      "GetCalendarEntryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Google Calendar id",
            "example": "primary"
          },
          "name": {
            "type": "string",
            "description": "Calendar display name",
            "example": "Agenda Calltek"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "GetCalendarConfigDto": {
        "type": "object",
        "properties": {
          "calendar_whitelist": {
            "description": "Calendar allowlist. EMPTY = every calendar of the connected account is processed; entries restrict to those calendars.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetCalendarEntryDto"
            }
          },
          "reminder_lead_minutes": {
            "type": "number",
            "nullable": true,
            "description": "Reminder lead in minutes (a heads-up before each event starts). Null = changes only.",
            "example": 15
          }
        },
        "required": [
          "calendar_whitelist"
        ]
      },
      "GetNotifyConversationDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Conversation id",
            "example": 42
          },
          "ref": {
            "type": "string",
            "description": "Public conversation ref",
            "example": "MZROL9UC"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Session title (operator sessions). Null = unnamed/default.",
            "example": "Avisos infra"
          },
          "contact_name": {
            "type": "string",
            "description": "Contact on the other side",
            "example": "Jonatan"
          },
          "transport": {
            "enum": [
              "INTERNAL",
              "WHATSAPP",
              "GMAIL",
              "DISCORD",
              "TELEGRAM",
              "SLACK",
              "WEBHOOK",
              "EMAIL",
              "GITHUB",
              "DRIVE",
              "CALENDAR"
            ],
            "type": "string",
            "description": "Transport of the conversation channel",
            "example": "TELEGRAM"
          }
        },
        "required": [
          "id",
          "ref",
          "contact_name",
          "transport"
        ]
      },
      "GetChannelDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Channel id",
            "example": 1
          },
          "agent_id": {
            "type": "number",
            "description": "Owning agent id",
            "example": 3
          },
          "transport": {
            "enum": [
              "INTERNAL",
              "WHATSAPP",
              "GMAIL",
              "DISCORD",
              "TELEGRAM",
              "SLACK",
              "WEBHOOK",
              "EMAIL",
              "GITHUB",
              "DRIVE",
              "CALENDAR"
            ],
            "type": "string",
            "description": "Transport",
            "example": "TELEGRAM"
          },
          "scope": {
            "enum": [
              "EXTERNAL",
              "INTERNAL"
            ],
            "type": "string",
            "description": "Scope: EXTERNAL (Agent Desk-facing) or INTERNAL (operators/rooms)",
            "example": "EXTERNAL"
          },
          "direction": {
            "enum": [
              "INBOUND",
              "OUTBOUND",
              "BIDIRECTIONAL"
            ],
            "type": "string",
            "description": "Traffic direction: INBOUND (receive only), OUTBOUND (send only), BIDIRECTIONAL (both).",
            "example": "BIDIRECTIONAL"
          },
          "name": {
            "type": "string",
            "description": "Display name",
            "example": "Soporte Telegram"
          },
          "target": {
            "type": "string",
            "description": "Transport-side target (bot @username for Telegram)",
            "example": "@kujira_support_bot"
          },
          "bot_username": {
            "type": "string",
            "nullable": true,
            "description": "Bot username (Telegram)"
          },
          "has_token": {
            "type": "boolean",
            "description": "Whether a bot token is configured (the token itself is never returned)",
            "example": true
          },
          "whitelist": {
            "description": "Inbound whitelist: origin chat/channel ids this channel accepts messages from (Telegram chat id / Discord channel id). Empty means the channel accepts nothing — ids are added explicitly.",
            "example": [
              "1512401528445730899"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "label_whitelist": {
            "description": "GMAIL only: label allowlist. Empty = every inbound email is processed; non-empty = only mail carrying at least one of these Gmail labels gets through (the rest is marked read and skipped). Always empty for other transports.",
            "example": [
              "Kujira"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "mention_off": {
            "description": "Per-chat-id mention exceptions. Every group chat_id requires an @bot mention by DEFAULT before forwarding; chat_ids in this list opt OUT of that — the agent answers every message in those groups. DMs are never gated. Empty list means \"default everywhere\": all groups require a mention.",
            "example": [
              "-100500"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "webhook_registered": {
            "type": "boolean",
            "description": "Whether the inbound webhook has been registered with the provider",
            "example": true
          },
          "inbound_path": {
            "type": "string",
            "nullable": true,
            "description": "For WEBHOOK channels: the relative inbound path external systems POST to (the secret IS the URL — not a provider auth token). Null for token-based transports, whose webhook_secret stays private.",
            "example": "/webhooks/webhook/ab12cd34"
          },
          "invite_url": {
            "type": "string",
            "nullable": true,
            "description": "For DISCORD channels: the bot invite URL (built from the public Application ID) with minimal messaging permissions. Null for other transports or when the client_id is unknown.",
            "example": "https://discord.com/oauth2/authorize?client_id=123&scope=bot&permissions=68608"
          },
          "webhook": {
            "nullable": true,
            "description": "For WEBHOOK channels: editable config (outbound URL, whether secrets are set, signature header + prefix). Secrets are never returned. Null for non-WEBHOOK transports.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/GetWebhookConfigDto"
              }
            ]
          },
          "github": {
            "nullable": true,
            "description": "For GITHUB channels: the installation + repo/event filters. Null for other transports.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/GetGithubConfigDto"
              }
            ]
          },
          "google": {
            "nullable": true,
            "description": "For GMAIL/DRIVE/CALENDAR channels: the agent's Google connection status. Null for other transports.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/GetGoogleConnectionDto"
              }
            ]
          },
          "drive": {
            "nullable": true,
            "description": "For DRIVE channels: the watched folder. Null for other transports.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/GetDriveConfigDto"
              }
            ]
          },
          "calendar": {
            "nullable": true,
            "description": "For CALENDAR channels: the watched calendar. Null for other transports.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/GetCalendarConfigDto"
              }
            ]
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "Operating instructions for an EVENT channel (WEBHOOK/GITHUB/DRIVE/CALENDAR) or a mail channel (EMAIL/GMAIL): what the agent does with the events/emails from this channel, injected as a <system-reminder> per message. Null when unset or for chat transports.",
            "example": "Review PRs where you are requested; approve if clean, else comment."
          },
          "notify_conversation_id": {
            "type": "number",
            "nullable": true,
            "description": "Escalation destination (#567): the INTERNAL conversation of this same agent where notices/approval prompts born on this channel land (channel_send without an explicit target resolves here). Null = not configured.",
            "example": 42
          },
          "notify_conversation": {
            "nullable": true,
            "description": "Summary of the escalation destination, to name it in the panel. Null when unset.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/GetNotifyConversationDto"
              }
            ]
          },
          "is_active": {
            "type": "boolean",
            "description": "Active flag",
            "example": true
          },
          "inactive_reason": {
            "type": "string",
            "nullable": true,
            "description": "Why the channel was deactivated — set by the DRIVE/CALENDAR poller when it cannot get a usable token for the linked connector (missing link or needs reconnecting). Always null while is_active."
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "last_activity_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Last activity at"
          },
          "alert_recipient_user_ids": {
            "description": "Who receives the alerts raised in this channel THREADS THAT BELONG TO NOBODY — a group room, a scheduled task, an unassigned Agent Desk contact. Threads that do belong to someone (an employee chatting with the agent) notify that person instead and never read this. EMPTY MEANS NOBODY, not everybody: what was never configured does not interrupt anyone.",
            "example": [
              4,
              9
            ],
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        },
        "required": [
          "id",
          "agent_id",
          "transport",
          "scope",
          "direction",
          "name",
          "target",
          "has_token",
          "whitelist",
          "label_whitelist",
          "mention_off",
          "webhook_registered",
          "is_active",
          "created_at",
          "alert_recipient_user_ids"
        ]
      },
      "EchoFilterDto": {
        "type": "object",
        "properties": {
          "actor_paths": {
            "description": "Dot-paths into the payload where the event's actor lives (provider-specific, arrays are traversed). E.g. [\"sender.login\"] for GitHub, [\"history_items.user.id\"] for ClickUp.",
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 256
            }
          },
          "self_ids": {
            "description": "The agent's OWN identities in that system. An inbound event whose actor matches one of these is dropped. E.g. [\"n0v4-SYS\"].",
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 256
            }
          }
        }
      },
      "WebhookConfigDto": {
        "type": "object",
        "properties": {
          "outbound_url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri",
            "description": "URL the agent POSTs to (outbound)."
          },
          "outbound_secret": {
            "type": "string",
            "nullable": true,
            "maxLength": 255,
            "description": "Shared secret to HMAC-sign outbound posts. On PATCH: a string sets it, null removes it, omitting/\"\" keeps the current one."
          },
          "inbound_secret": {
            "type": "string",
            "nullable": true,
            "maxLength": 255,
            "description": "HMAC secret for INBOUND verification. When set, every incoming POST must carry a valid HMAC-SHA256 of the raw body in the configured header. On PATCH: a string sets it, null removes it (disabling verification), omitting/\"\" keeps the current one. Stored encrypted."
          },
          "signature_header": {
            "type": "string",
            "maxLength": 128,
            "description": "Header carrying the inbound signature (plain HMAC-SHA256 hex). Defaults to x-kujira-signature. Plane uses x-plane-signature, GitHub x-hub-signature-256."
          },
          "echo_filter": {
            "description": "Anti-echo: where the actor lives in the payload + the agent's own ids. Events whose actor is the agent are dropped. On PATCH the whole block is replaced; an empty block clears the filter.",
            "allOf": [
              {
                "$ref": "#/components/schemas/EchoFilterDto"
              }
            ]
          }
        }
      },
      "ImapConfigDto": {
        "type": "object",
        "properties": {
          "host": {
            "type": "string",
            "maxLength": 255
          },
          "port": {
            "type": "number",
            "minimum": 1,
            "maximum": 65535
          },
          "user": {
            "type": "string",
            "maxLength": 255
          },
          "pass": {
            "type": "string",
            "maxLength": 1024
          },
          "tls": {
            "type": "boolean"
          }
        },
        "required": [
          "host",
          "port",
          "user",
          "pass"
        ]
      },
      "SmtpConfigDto": {
        "type": "object",
        "properties": {
          "host": {
            "type": "string",
            "maxLength": 255
          },
          "port": {
            "type": "number",
            "minimum": 1,
            "maximum": 65535
          },
          "user": {
            "type": "string",
            "maxLength": 255
          },
          "pass": {
            "type": "string",
            "maxLength": 1024
          },
          "from": {
            "type": "string",
            "maxLength": 320,
            "format": "email"
          }
        },
        "required": [
          "host",
          "port",
          "user",
          "pass",
          "from"
        ]
      },
      "EmailConfigDto": {
        "type": "object",
        "properties": {
          "imap": {
            "description": "IMAP receive config",
            "allOf": [
              {
                "$ref": "#/components/schemas/ImapConfigDto"
              }
            ]
          },
          "smtp": {
            "description": "SMTP send config",
            "allOf": [
              {
                "$ref": "#/components/schemas/SmtpConfigDto"
              }
            ]
          }
        },
        "required": [
          "imap",
          "smtp"
        ]
      },
      "GithubChannelConfigDto": {
        "type": "object",
        "properties": {
          "installation_id": {
            "type": "number",
            "description": "The org's GitHub App installation id this channel rides.",
            "example": 82345678
          },
          "repos": {
            "description": "Repo full_names to listen to. Empty/omit = all repos of the installation.",
            "example": [
              "calltek/kj-backend"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 140
            }
          },
          "events": {
            "description": "Event types to listen to (pull_request, issues, push…). Empty/omit = all subscribed events.",
            "example": [
              "pull_request",
              "pull_request_review"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 60
            }
          },
          "listen_scope": {
            "enum": [
              "all",
              "involved"
            ],
            "type": "string",
            "description": "Listening scope, GitHub-notifications style. \"all\" (default): every event of the configured repos/events wakes the agent. \"involved\": only events that concern the agent — review requested of it, assigned to it, @-mentioned, or activity on a PR/issue it authored. Needs the agent's GitHub connector (its identity) to resolve; with no connector it falls back to \"all\" (fail-open, never a missed review).",
            "example": "involved"
          },
          "raw_payload": {
            "type": "boolean",
            "description": "Attach the full raw webhook payload to each event. Off by default: measured on production it is ~3.000 tokens per event of API template URLs and repeated user objects, for ~61 tokens of meaning — and the agent re-reads it on every step of that turn. Turn it on only for a channel whose skill genuinely parses an exotic field.",
            "example": false
          }
        },
        "required": [
          "installation_id"
        ]
      },
      "DriveChannelConfigDto": {
        "type": "object",
        "properties": {
          "folder_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Google Drive folder id to watch (from the folder picker). New or modified files in it become events for the agent.",
            "example": "1AbCdEfGhIjKlMnOpQrStUvWxYz"
          },
          "folder_name": {
            "type": "string",
            "maxLength": 255,
            "description": "Folder display name (labels the Agent Desk thread and the event text).",
            "example": "Facturas"
          }
        },
        "required": [
          "folder_id"
        ]
      },
      "CalendarChannelConfigDto": {
        "type": "object",
        "properties": {
          "reminder_lead_minutes": {
            "type": "number",
            "minimum": 1,
            "maximum": 1440,
            "description": "Optional reminder lead: the agent also gets a heads-up this many minutes before each event starts (one per occurrence). Omit/0 = changes only.",
            "example": 15
          }
        }
      },
      "CreateChannelGlobalDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Display name for the channel",
            "example": "Soporte Telegram"
          },
          "scope": {
            "enum": [
              "EXTERNAL",
              "INTERNAL"
            ],
            "type": "string",
            "description": "Scope. EXTERNAL (default): reaches customers, surfaces in the Agent Desk. INTERNAL: operators/rooms — never creates external contacts, never in the Agent Desk.",
            "example": "EXTERNAL"
          },
          "direction": {
            "enum": [
              "INBOUND",
              "OUTBOUND",
              "BIDIRECTIONAL"
            ],
            "type": "string",
            "description": "Traffic direction. BIDIRECTIONAL (default): receives and replies. INBOUND: receives only (the agent acts via tools/API, never replies down the channel). OUTBOUND: sends only, never ingests inbound (pure notifications; a WEBHOOK OUTBOUND channel requires outbound_url). Event channels (GITHUB/DRIVE/CALENDAR) are always INBOUND.",
            "example": "BIDIRECTIONAL"
          },
          "transport": {
            "enum": [
              "INTERNAL",
              "WHATSAPP",
              "GMAIL",
              "DISCORD",
              "TELEGRAM",
              "SLACK",
              "WEBHOOK",
              "EMAIL",
              "GITHUB",
              "DRIVE",
              "CALENDAR"
            ],
            "type": "string",
            "description": "Transport. TELEGRAM/DISCORD use a bot token; WEBHOOK uses webhook config; EMAIL uses imap/smtp config.",
            "example": "TELEGRAM"
          },
          "bot_token": {
            "type": "string",
            "minLength": 10,
            "description": "Bot token for TELEGRAM/DISCORD (validated against the provider, stored encrypted). Not used by WEBHOOK / EMAIL.",
            "example": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
          },
          "destination": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Destination the agent sends to. Required for DISCORD (target text-channel / DM id). Ignored for Telegram.",
            "example": "123456789012345678"
          },
          "webhook": {
            "description": "WEBHOOK transport config (outbound).",
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookConfigDto"
              }
            ]
          },
          "email": {
            "description": "EMAIL transport config (imap + smtp).",
            "allOf": [
              {
                "$ref": "#/components/schemas/EmailConfigDto"
              }
            ]
          },
          "github": {
            "description": "GITHUB transport config (installation + repo/event filters).",
            "allOf": [
              {
                "$ref": "#/components/schemas/GithubChannelConfigDto"
              }
            ]
          },
          "drive": {
            "description": "DRIVE transport config (the folder to watch).",
            "allOf": [
              {
                "$ref": "#/components/schemas/DriveChannelConfigDto"
              }
            ]
          },
          "calendar": {
            "description": "CALENDAR transport config (the calendar to watch + reminder lead).",
            "allOf": [
              {
                "$ref": "#/components/schemas/CalendarChannelConfigDto"
              }
            ]
          },
          "instructions": {
            "type": "string",
            "maxLength": 8000,
            "description": "Operating instructions for an EVENT channel (WEBHOOK/GITHUB/DRIVE/CALENDAR) or a mail channel (EMAIL/GMAIL): what the agent should do with the events/emails from this channel. Injected as a <system-reminder> with every forwarded message, so it never bloats the agent's permanent context. Markdown, optional. Ignored for chat transports (Telegram/Discord act on memory).",
            "example": "Review PRs where you are requested; approve if clean, else comment."
          },
          "agent_id": {
            "type": "number",
            "minimum": 1,
            "description": "Agent this channel belongs to",
            "example": 1
          }
        },
        "required": [
          "name",
          "transport",
          "agent_id"
        ]
      },
      "UpdateGithubConfigDto": {
        "type": "object",
        "properties": {
          "repos": {
            "description": "Repo full_names to listen to. Empty = all repos of the installation.",
            "example": [
              "calltek/kj-backend"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 140
            }
          },
          "events": {
            "description": "Event types to listen to. Empty = all subscribed events.",
            "example": [
              "pull_request",
              "issue_comment"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 60
            }
          },
          "listen_scope": {
            "enum": [
              "all",
              "involved"
            ],
            "type": "string",
            "description": "Listening scope: \"all\" = every event; \"involved\" = only events that concern the agent (review requested / assigned / @-mentioned / its own PRs), GitHub-notifications style.",
            "example": "involved"
          },
          "raw_payload": {
            "type": "boolean",
            "description": "Attach the full raw webhook payload to each event. Off by default: measured on production it is ~3.000 tokens per event of API template URLs and repeated user objects, for ~61 tokens of meaning — and the agent re-reads it on every step of that turn. Turn it on only for a channel whose skill genuinely parses an exotic field.",
            "example": false
          }
        }
      },
      "UpdateChannelDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Display name",
            "example": "Soporte Telegram"
          },
          "scope": {
            "enum": [
              "EXTERNAL",
              "INTERNAL"
            ],
            "type": "string",
            "description": "Scope: EXTERNAL (Agent Desk-facing) or INTERNAL (operators/rooms)"
          },
          "direction": {
            "enum": [
              "INBOUND",
              "OUTBOUND",
              "BIDIRECTIONAL"
            ],
            "type": "string",
            "description": "Traffic direction: INBOUND (receive only), OUTBOUND (send only), BIDIRECTIONAL (both). Switching a WEBHOOK to OUTBOUND requires an outbound_url. Ignored for event channels (GITHUB/DRIVE/CALENDAR), which stay INBOUND."
          },
          "bot_token": {
            "type": "string",
            "minLength": 10,
            "description": "New bot token to rotate to. Validated + stored encrypted; never returned."
          },
          "mention_off": {
            "uniqueItems": true,
            "description": "Per-chat-id mention exceptions. Group chat_ids in this list opt OUT of the require-mention default — the agent answers every message in them. Send the FULL intended list (the server replaces, not merges). No effect on DMs.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "webhook": {
            "description": "WEBHOOK transport config. Editable in place: outbound URL/secret, inbound HMAC secret, signature header + prefix. Secrets are only rewritten when present (empty/omitted = keep the current one).",
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookConfigDto"
              }
            ]
          },
          "github": {
            "description": "GITHUB transport config, editable in place: the listening scope (all/involved) and the repo/event filters. Only present fields change.",
            "allOf": [
              {
                "$ref": "#/components/schemas/UpdateGithubConfigDto"
              }
            ]
          },
          "instructions": {
            "type": "string",
            "maxLength": 8000,
            "description": "Operating instructions for an EVENT channel (WEBHOOK/GITHUB/DRIVE/CALENDAR) or a mail channel (EMAIL/GMAIL), injected as a <system-reminder> with each forwarded message. A string sets it, an empty string clears it, omitting keeps the current one."
          },
          "notify_conversation_id": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "description": "Escalation destination (#567): id of an INTERNAL conversation of this same agent (operator thread / internal room) where notices and approval prompts born on this channel land. Must live on a channel that can send (not inbound-only) and never on a customer thread. null clears it, omitting keeps the current one.",
            "example": 42
          },
          "is_active": {
            "type": "boolean",
            "description": "Active flag. A DRIVE/CALENDAR channel the poller could not run (see inactive_reason on the channel) is set to false automatically — flip it back to true after fixing the underlying connector to resume polling; that also clears inactive_reason. Setting this to false stops the channel from ingesting or sending — it is a containment switch, not a credential revocation: it does NOT rotate webhook_secret or invalidate a Discord/Telegram bot token. If the channel was compromised (leaked secret, stolen token), delete it (or rotate the bot token) instead of just disabling it. When rotating a Discord bot token, write the new one HERE FIRST and only revoke the old one in Discord afterwards: the replica that owns the socket closes it on its next lease check because the stored token no longer matches, so rotating here is what actually kills the old connection. Revoking in Discord first still works, but the ugly way: the socket reconnects against a dead credential until Discord rejects it (close code 4004), and the channel then DEACTIVATES ITSELF with an inactive_reason — so you end up having to flip this back to true by hand after storing the new token. DISCORD takes effect within ~15s when turning off (the bot's gateway session is closed on its next lease check) and up to ~30s when turning back on (picked up by the periodic connect sweep) — other transports take effect immediately. That ~15s is only the socket teardown: containment itself is immediate on every replica — ingestion, outbound sends and HITL approval buttons all read the flag per message, so nothing gets through in the meantime. GITHUB: turning this off does not defer webhook deliveries for replay later — GitHub does not retry a webhook that got a 2xx, so events that arrive while the channel is off are dropped for good."
          }
        }
      },
      "AddToWhitelistDto": {
        "type": "object",
        "properties": {
          "origin_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Origin chat/channel id to accept inbound from (Telegram chat id / Discord channel id). Added to the channel whitelist.",
            "example": "1512401528445730899"
          }
        },
        "required": [
          "origin_id"
        ]
      },
      "CronjobAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "flex-test"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "CronjobCredentialDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Credential id",
            "example": 7
          },
          "name": {
            "type": "string",
            "description": "Credential name",
            "example": "stripe"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "GetCronjobDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Cronjob id",
            "example": 1
          },
          "organization_id": {
            "type": "number",
            "description": "Organization id",
            "example": 2
          },
          "agent_id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "agent": {
            "description": "Agent summary",
            "allOf": [
              {
                "$ref": "#/components/schemas/CronjobAgentDto"
              }
            ]
          },
          "name": {
            "type": "string",
            "description": "Name",
            "example": "daily-report"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description"
          },
          "schedule": {
            "type": "string",
            "nullable": true,
            "description": "Cron expression (recurring). Null on a one-shot — see run_at.",
            "example": "0 9 * * 1"
          },
          "run_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "One-shot (KJ-190): the instant it fires. Null on a recurring cron."
          },
          "expires_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Stop running after this instant (KJ-190). Null = no deadline."
          },
          "max_runs": {
            "type": "number",
            "nullable": true,
            "description": "Stop after this many runs (KJ-190). Null = unlimited.",
            "example": 3
          },
          "run_count": {
            "type": "number",
            "description": "Scheduled runs so far (KJ-190)",
            "example": 0
          },
          "kind": {
            "enum": [
              "INPUT",
              "SCRIPT"
            ],
            "type": "string",
            "description": "Kind",
            "example": "INPUT"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "PENDING_APPROVAL",
              "DISABLED",
              "EXPIRED"
            ],
            "type": "string",
            "description": "Status",
            "example": "ACTIVE"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether the schedule is active",
            "example": true
          },
          "internal": {
            "type": "boolean",
            "description": "The agent's own bookkeeping (monitor/probe): hidden from the cron list unless include_internal=true, and always carries a forced expires_at.",
            "example": false
          },
          "message": {
            "type": "string",
            "nullable": true,
            "description": "INPUT: injected turn text"
          },
          "command": {
            "type": "string",
            "nullable": true,
            "description": "SCRIPT: the command"
          },
          "timeout_ms": {
            "type": "number",
            "description": "SCRIPT: timeout (ms)",
            "example": 60000
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "INPUT: Claude model slug to run this scheduled turn on (KJ-22). Null = inherit the agent/conversation default.",
            "example": "claude-haiku-4-5"
          },
          "report_when": {
            "enum": [
              "ALWAYS",
              "ON_CHANGE",
              "ON_MATCH",
              "NEVER"
            ],
            "type": "string",
            "description": "SCRIPT: when output is reported to the agent",
            "example": "ALWAYS"
          },
          "report_pattern": {
            "type": "string",
            "nullable": true,
            "description": "SCRIPT + ON_MATCH: regex deciding when to notify"
          },
          "notify_conversation_id": {
            "type": "number",
            "nullable": true,
            "description": "Conversation to report to (contact + channel); null = log only"
          },
          "credentials": {
            "description": "Credentials injected as env vars into a SCRIPT cron (KJ-38). Names only, never values.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CronjobCredentialDto"
            }
          },
          "last_run_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Last run timestamp"
          },
          "last_status": {
            "type": "string",
            "nullable": true,
            "description": "Last run status"
          },
          "last_result": {
            "type": "object",
            "description": "Last run result (exit_code/output for SCRIPT)",
            "nullable": true
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id (null for MCP/service)"
          },
          "is_mine": {
            "type": "boolean",
            "description": "Whether the requesting user owns this cron (private to them)",
            "example": true
          },
          "shared_user_ids": {
            "description": "User ids this cron is shared with (besides the owner)",
            "example": [
              3,
              7
            ],
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "timezone": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "id",
          "organization_id",
          "agent_id",
          "agent",
          "name",
          "run_count",
          "kind",
          "status",
          "enabled",
          "internal",
          "timeout_ms",
          "report_when",
          "credentials",
          "is_mine",
          "shared_user_ids",
          "created_at",
          "updated_at",
          "timezone"
        ]
      },
      "CronjobRunDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Run id",
            "example": 1
          },
          "status": {
            "type": "string",
            "description": "Status",
            "example": "SUCCESS"
          },
          "exit_code": {
            "type": "number",
            "nullable": true,
            "description": "Exit code (SCRIPT)"
          },
          "output": {
            "type": "string",
            "nullable": true,
            "description": "Truncated stdout/stderr"
          },
          "truncated": {
            "type": "boolean",
            "description": "Whether the output was truncated",
            "example": false
          },
          "notified": {
            "type": "boolean",
            "description": "Whether this run was sent to the notify channel",
            "example": false
          },
          "duration_ms": {
            "type": "number",
            "nullable": true,
            "description": "Wall-clock duration of the run in ms (KUJI-131). Null for legacy rows. Lets a silent script still show it ran + how long.",
            "example": 1240
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "When it ran"
          }
        },
        "required": [
          "id",
          "status",
          "truncated",
          "notified",
          "created_at"
        ]
      },
      "CreateCronjobDto": {
        "type": "object",
        "properties": {
          "agent_id": {
            "type": "number",
            "minimum": 1,
            "description": "Agent this cronjob runs against. Must belong to the same org.",
            "example": 5
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Display name (unique within the org).",
            "example": "daily-report"
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 1024,
            "description": "Free-form description.",
            "example": "Resumen de pedidos cada mañana"
          },
          "schedule": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Recurring: raw cron expression (5 fields), validated on save. Mutually exclusive with run_at — send exactly one.",
            "example": "0 9 * * 1"
          },
          "run_at": {
            "type": "string",
            "description": "One-shot (KJ-190): ISO date-time this cron fires ONCE, then expires. Mutually exclusive with schedule. Must be in the future.",
            "example": "2026-08-12T09:00:00.000Z"
          },
          "timezone": {
            "type": "string",
            "nullable": true,
            "description": "IANA zone the expression is read in (\"Europe/Madrid\"), so \"0 7 * * *\" means seven in the morning THERE, daylight saving included. Omitted/empty inherits the agent zone, then the organization one, then UTC.",
            "example": "Europe/Madrid"
          },
          "expires_at": {
            "type": "string",
            "description": "Stop running after this instant (KJ-190) — the cron flips to EXPIRED, keeping its history. Must be in the future.",
            "example": "2026-09-01T00:00:00.000Z"
          },
          "max_runs": {
            "type": "number",
            "minimum": 1,
            "description": "Stop after this many runs (KJ-190). Omit for unlimited.",
            "example": 3
          },
          "kind": {
            "enum": [
              "INPUT",
              "SCRIPT"
            ],
            "type": "string",
            "description": "INPUT injects a turn into the agent; SCRIPT runs a command in its container.",
            "example": "INPUT"
          },
          "internal": {
            "type": "boolean",
            "description": "Agent bookkeeping (a monitor, a re-check, a probe): kept out of the cron list unless include_internal=true. An internal cron is ALWAYS given a deadline — the one sent, capped at 7 days, or 1 day if none — so a monitor can't outlive whatever it was watching.",
            "example": false
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8192,
            "description": "For INPUT: the text injected as a scheduled turn.",
            "example": "Genera el reporte de ayer y avísame."
          },
          "command": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8192,
            "description": "For SCRIPT: the shell command run inside the container.",
            "example": "python3 /home/agent/scripts/report.py"
          },
          "timeout_ms": {
            "type": "number",
            "minimum": 1000,
            "maximum": 1500000,
            "description": "For SCRIPT: hard timeout in ms (1s–25min).",
            "example": 60000
          },
          "report_when": {
            "enum": [
              "ALWAYS",
              "ON_CHANGE",
              "ON_MATCH",
              "NEVER"
            ],
            "type": "string",
            "description": "SCRIPT report gate (only applies with notify_conversation_id): ALWAYS / ON_CHANGE (output differs) / ON_MATCH (matches report_pattern) / NEVER. Failures always notify except NEVER. Ignored for INPUT.",
            "example": "ON_CHANGE"
          },
          "report_pattern": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "SCRIPT + ON_MATCH: regex; the output is reported only when it matches.",
            "example": "\"reales61\":\\s*[1-9]"
          },
          "notify_conversation_id": {
            "type": "number",
            "minimum": 1,
            "description": "Conversation (contact + channel) to report to. SCRIPT delivers its stdout there (non-LLM); INPUT asks the agent to report there. Must be a conversation of this agent. Omit to only log.",
            "example": 7
          },
          "credential_ids": {
            "uniqueItems": true,
            "description": "SCRIPT only (KJ-38): ids of credentials to inject as env vars into the command. Each MUST be assigned to this agent. A field becomes `<CRED>_<FIELD>` (uppercased), plus a bare `<CRED>` when the credential has one field; OTP fields yield the live code. Never written to disk.",
            "example": [
              7,
              9
            ],
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "model": {
            "type": "string",
            "minLength": 0,
            "maxLength": 80,
            "description": "INPUT only (KJ-22): Claude model for this scheduled turn — \"haiku\"/\"sonnet\"/\"opus\" or a full claude-… slug. Routine crons run cheaper. Empty/omitted = inherit the default.",
            "example": "haiku"
          }
        },
        "required": [
          "agent_id",
          "name",
          "kind"
        ]
      },
      "UpdateCronjobDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Display name.",
            "example": "daily-report"
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 1024,
            "description": "Free-form description."
          },
          "schedule": {
            "type": "string",
            "minLength": 0,
            "maxLength": 128,
            "description": "Raw cron expression. Send \"\" (empty) together with run_at to turn a recurring cron into a one-shot.",
            "example": "0 9 * * 1"
          },
          "run_at": {
            "type": "string",
            "nullable": true,
            "description": "One-shot instant (KJ-190), ISO date-time. Null clears it. Setting it re-arms an already-fired one-shot.",
            "example": "2026-08-12T09:00:00.000Z"
          },
          "timezone": {
            "type": "string",
            "nullable": true,
            "description": "IANA zone the expression is read in (\"Europe/Madrid\"), so \"0 7 * * *\" means seven in the morning THERE, daylight saving included. Empty inherits the agent zone, then the organization one, then UTC.",
            "example": "Europe/Madrid"
          },
          "expires_at": {
            "type": "string",
            "nullable": true,
            "description": "Stop running after this instant (KJ-190). Null clears the deadline."
          },
          "max_runs": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "description": "Stop after this many runs (KJ-190). Null = unlimited.",
            "example": 3
          },
          "enabled": {
            "type": "boolean",
            "description": "Enable/disable the schedule.",
            "example": true
          },
          "internal": {
            "type": "boolean",
            "description": "Mark/unmark as agent bookkeeping (hidden from the list by default). While internal, the cron's deadline is re-capped on every save — you cannot park an internal cron forever by clearing expires_at.",
            "example": false
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8192,
            "description": "For INPUT: the injected turn text."
          },
          "command": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8192,
            "description": "For SCRIPT: the command."
          },
          "timeout_ms": {
            "type": "number",
            "minimum": 1000,
            "maximum": 1500000,
            "description": "For SCRIPT: timeout in ms (1s–25min)."
          },
          "report_when": {
            "enum": [
              "ALWAYS",
              "ON_CHANGE",
              "ON_MATCH",
              "NEVER"
            ],
            "type": "string",
            "description": "SCRIPT report gate (with notify_conversation_id): ALWAYS | ON_CHANGE | ON_MATCH | NEVER."
          },
          "report_pattern": {
            "type": "string",
            "minLength": 0,
            "maxLength": 512,
            "description": "SCRIPT + ON_MATCH: regex; output is reported only when it matches."
          },
          "notify_conversation_id": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "description": "Conversation to report to (contact + channel). null clears it."
          },
          "credential_ids": {
            "uniqueItems": true,
            "description": "SCRIPT only (KJ-38): replace the set of credentials injected as env vars. Each MUST be assigned to this agent. [] clears them.",
            "example": [
              7,
              9
            ],
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "model": {
            "type": "string",
            "minLength": 0,
            "maxLength": 80,
            "description": "INPUT only (KJ-22): model for this scheduled turn (\"haiku\"/\"sonnet\"/\"opus\" or a claude-… slug). Empty string clears it (inherit).",
            "example": "haiku"
          }
        }
      },
      "McpAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "GetMcpDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "MCP server id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Slug name — the key in the .mcp.json mcpServers object.",
            "example": "github"
          },
          "description": {
            "type": "string",
            "description": "One-line summary.",
            "example": "Lee repos, crea issues"
          },
          "transport": {
            "enum": [
              "STDIO",
              "SSE",
              "HTTP"
            ],
            "type": "string",
            "description": "Transport",
            "example": "STDIO"
          },
          "command": {
            "type": "string",
            "nullable": true,
            "description": "STDIO command"
          },
          "args": {
            "description": "STDIO argv",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "nullable": true,
            "description": "SSE/HTTP endpoint"
          },
          "auth": {
            "description": "Names of the env vars configured on this server (the \"auth\" the agent gets). Values are NEVER returned — they live encrypted at rest.",
            "example": [
              "GITHUB_TOKEN"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this server is assigned to. Zero = unassigned.",
            "example": 3
          },
          "agents": {
            "description": "Agents this server is assigned to (id, name, avatar). Enough to render them on a list row — the full assignment records come with the detail endpoint.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/McpAgentDto"
            }
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the server was archived (soft-deleted). Null on live servers. Archived servers are excluded from default listings and never shipped in a spawn payload."
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "transport",
          "args",
          "auth",
          "assignment_count",
          "agents",
          "created_at",
          "updated_at"
        ]
      },
      "ListMcpDto": {
        "type": "object",
        "properties": {
          "rows": {
            "description": "Page rows",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetMcpDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total matching rows across all pages",
            "example": 23
          },
          "limit": {
            "type": "number",
            "description": "Echo of the requested page size",
            "example": 50
          },
          "offset": {
            "type": "number",
            "description": "Echo of the requested offset",
            "example": 0
          }
        },
        "required": [
          "rows",
          "total",
          "limit",
          "offset"
        ]
      },
      "McpAssignedAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          },
          "assigned_at": {
            "format": "date-time",
            "type": "string",
            "description": "When the server was assigned to this agent"
          }
        },
        "required": [
          "id",
          "name",
          "assigned_at"
        ]
      },
      "GetMcpWithAssignmentsDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "MCP server id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Slug name — the key in the .mcp.json mcpServers object.",
            "example": "github"
          },
          "description": {
            "type": "string",
            "description": "One-line summary.",
            "example": "Lee repos, crea issues"
          },
          "transport": {
            "enum": [
              "STDIO",
              "SSE",
              "HTTP"
            ],
            "type": "string",
            "description": "Transport",
            "example": "STDIO"
          },
          "command": {
            "type": "string",
            "nullable": true,
            "description": "STDIO command"
          },
          "args": {
            "description": "STDIO argv",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "nullable": true,
            "description": "SSE/HTTP endpoint"
          },
          "auth": {
            "description": "Names of the env vars configured on this server (the \"auth\" the agent gets). Values are NEVER returned — they live encrypted at rest.",
            "example": [
              "GITHUB_TOKEN"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this server is assigned to. Zero = unassigned.",
            "example": 3
          },
          "agents": {
            "description": "Agents this server is assigned to (id, name, avatar). Enough to render them on a list row — the full assignment records come with the detail endpoint.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/McpAgentDto"
            }
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the server was archived (soft-deleted). Null on live servers. Archived servers are excluded from default listings and never shipped in a spawn payload."
          },
          "assignments": {
            "description": "Agents this server is currently assigned to",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/McpAssignedAgentDto"
            }
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "transport",
          "args",
          "auth",
          "assignment_count",
          "agents",
          "created_at",
          "updated_at",
          "assignments"
        ]
      },
      "CreateMcpDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 255,
            "description": "Slug name. Lowercase letters, digits, hyphens, underscores. Becomes the key in the .mcp.json mcpServers object.",
            "example": "github"
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "One-line summary shown in the operator panel.",
            "example": "Lee repos, crea issues, comenta PRs"
          },
          "transport": {
            "enum": [
              "STDIO",
              "SSE",
              "HTTP"
            ],
            "type": "string",
            "description": "Transport the server speaks. STDIO = local process (command + args). SSE/HTTP = remote endpoint (url).",
            "example": "STDIO"
          },
          "command": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "STDIO only: the executable to run.",
            "example": "npx"
          },
          "args": {
            "description": "STDIO only: argv passed to the command.",
            "example": [
              "-y",
              "@modelcontextprotocol/server-github"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "maxLength": 1024,
            "description": "SSE/HTTP only: the server endpoint.",
            "example": "https://mcp.notion.com/v1"
          },
          "env": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Env vars to inject into the server process, as a name→value map. Values are encrypted at rest and never returned. The GET DTO exposes only the names (auth).",
            "example": {
              "GITHUB_TOKEN": "ghp_xxx"
            }
          }
        },
        "required": [
          "name",
          "description",
          "transport"
        ]
      },
      "UpdateMcpDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 255,
            "description": "New slug name. Optional.",
            "example": "github"
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "New one-line summary. Optional."
          },
          "transport": {
            "enum": [
              "STDIO",
              "SSE",
              "HTTP"
            ],
            "type": "string",
            "description": "New transport. Optional."
          },
          "command": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "STDIO command. Optional."
          },
          "args": {
            "description": "STDIO argv. Optional.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1024,
            "description": "SSE/HTTP endpoint. Optional."
          },
          "env": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Replace the env var set entirely. name→value map; values encrypted at rest. Omit to leave the current env untouched; pass {} to clear it.",
            "example": {
              "GITHUB_TOKEN": "ghp_xxx"
            }
          }
        }
      },
      "SetMcpAgentsDto": {
        "type": "object",
        "properties": {
          "agent_ids": {
            "uniqueItems": true,
            "description": "Full list of agents this server should be assigned to. The service syncs: new ids are added, missing ids removed. Pass [] to detach from every agent.",
            "example": [
              3,
              5
            ],
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 1
            }
          }
        },
        "required": [
          "agent_ids"
        ]
      },
      "CredentialAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "GetCredentialDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Credential id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Unique name per org",
            "example": "STRIPE_SECRET"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description"
          },
          "kind": {
            "type": "string",
            "nullable": true,
            "description": "Free-form kind for the UI"
          },
          "destinations": {
            "description": "Where this credential is meant to be used. Empty = not set.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this credential is assigned to. Zero = unassigned.",
            "example": 3
          },
          "agents": {
            "description": "Agents this credential is assigned to (id, name, avatar). Enough to render them on a list row — the full assignment records, with who granted them and when, come with the detail endpoint.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialAgentDto"
            }
          },
          "field_count": {
            "type": "number",
            "description": "Number of fields",
            "example": 2
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When archived (soft-deleted). Null on live credentials."
          }
        },
        "required": [
          "id",
          "name",
          "destinations",
          "assignment_count",
          "agents",
          "field_count",
          "created_at",
          "updated_at"
        ]
      },
      "ListCredentialDto": {
        "type": "object",
        "properties": {
          "rows": {
            "description": "Page rows",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetCredentialDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total matching rows across all pages",
            "example": 47
          },
          "limit": {
            "type": "number",
            "description": "Echo of the requested page size",
            "example": 50
          },
          "offset": {
            "type": "number",
            "description": "Echo of the requested offset",
            "example": 0
          }
        },
        "required": [
          "rows",
          "total",
          "limit",
          "offset"
        ]
      },
      "CredentialFieldDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Field id",
            "example": 10
          },
          "label": {
            "type": "string",
            "description": "Field label",
            "example": "api_key"
          },
          "type": {
            "enum": [
              "TEXT",
              "USERNAME",
              "PASSWORD",
              "EMAIL",
              "URL",
              "API_KEY",
              "OTP",
              "NOTE",
              "SSH_PRIVATE_KEY",
              "SSH_PUBLIC_KEY"
            ],
            "type": "string",
            "description": "Field type"
          },
          "position": {
            "type": "number",
            "description": "Display order",
            "example": 0
          },
          "has_value": {
            "type": "boolean",
            "description": "Whether a value is set. The value itself is never returned here.",
            "example": true
          }
        },
        "required": [
          "id",
          "label",
          "type",
          "position",
          "has_value"
        ]
      },
      "CredentialAssignedAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          },
          "assigned_at": {
            "format": "date-time",
            "type": "string",
            "description": "When the credential was assigned to this agent"
          }
        },
        "required": [
          "id",
          "name",
          "assigned_at"
        ]
      },
      "CredentialMemberDto": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "number",
            "description": "User id",
            "example": 7
          },
          "name": {
            "type": "string",
            "description": "User name",
            "example": "Ana"
          },
          "email": {
            "type": "string",
            "description": "User email",
            "example": "ana@acme.com"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          },
          "granted_at": {
            "format": "date-time",
            "type": "string",
            "description": "When access was granted"
          }
        },
        "required": [
          "user_id",
          "name",
          "email",
          "granted_at"
        ]
      },
      "GetCredentialWithFieldsDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Credential id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Unique name per org",
            "example": "STRIPE_SECRET"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description"
          },
          "kind": {
            "type": "string",
            "nullable": true,
            "description": "Free-form kind for the UI"
          },
          "destinations": {
            "description": "Where this credential is meant to be used. Empty = not set.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this credential is assigned to. Zero = unassigned.",
            "example": 3
          },
          "agents": {
            "description": "Agents this credential is assigned to (id, name, avatar). Enough to render them on a list row — the full assignment records, with who granted them and when, come with the detail endpoint.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialAgentDto"
            }
          },
          "field_count": {
            "type": "number",
            "description": "Number of fields",
            "example": 2
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When archived (soft-deleted). Null on live credentials."
          },
          "fields": {
            "description": "Fields (label + type only, no values)",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialFieldDto"
            }
          },
          "assignments": {
            "description": "Agents this credential is assigned to",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialAssignedAgentDto"
            }
          },
          "members": {
            "description": "Operators allowed to reveal this credential. Empty = open (any member with credential.reveal); non-empty = only these (plus OWNER/ADMIN).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialMemberDto"
            }
          }
        },
        "required": [
          "id",
          "name",
          "destinations",
          "assignment_count",
          "agents",
          "field_count",
          "created_at",
          "updated_at",
          "fields",
          "assignments",
          "members"
        ]
      },
      "RevealCredentialFieldDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Field id",
            "example": 10
          },
          "label": {
            "type": "string",
            "description": "Field label",
            "example": "api_key"
          },
          "type": {
            "enum": [
              "TEXT",
              "USERNAME",
              "PASSWORD",
              "EMAIL",
              "URL",
              "API_KEY",
              "OTP",
              "NOTE",
              "SSH_PRIVATE_KEY",
              "SSH_PUBLIC_KEY"
            ],
            "type": "string",
            "description": "Field type"
          },
          "value": {
            "type": "string",
            "description": "Decrypted plaintext value"
          }
        },
        "required": [
          "id",
          "label",
          "type",
          "value"
        ]
      },
      "CredentialFieldInput": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Field label, e.g. \"username\", \"api_key\", \"host\"."
          },
          "type": {
            "enum": [
              "TEXT",
              "USERNAME",
              "PASSWORD",
              "EMAIL",
              "URL",
              "API_KEY",
              "OTP",
              "NOTE",
              "SSH_PRIVATE_KEY",
              "SSH_PUBLIC_KEY"
            ],
            "type": "string",
            "description": "Field type"
          },
          "value": {
            "type": "string",
            "minLength": 0,
            "maxLength": 20000,
            "description": "Plaintext value. Encrypted server-side, never returned."
          },
          "position": {
            "type": "number",
            "minimum": 0,
            "description": "Display order (0-based)."
          }
        },
        "required": [
          "label",
          "type",
          "value"
        ]
      },
      "CreateCredentialDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Unique name per org. Letters, digits, . _ -",
            "example": "STRIPE_SECRET"
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 512,
            "description": "One-line description."
          },
          "kind": {
            "type": "string",
            "minLength": 0,
            "maxLength": 64,
            "description": "Free-form kind for the UI icon/summary (login / api-key / database …).",
            "example": "api-key"
          },
          "destinations": {
            "description": "Where this credential is meant to be used: URLs, IPs or host names. The agent receives them and knows where the key belongs. Empty = not set, which blocks nothing.",
            "example": [
              "https://api.stripe.com",
              "dashboard.stripe.com"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "fields": {
            "description": "The credential fields.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialFieldInput"
            }
          }
        },
        "required": [
          "name",
          "fields"
        ]
      },
      "UpdateCredentialDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "New name. Optional."
          },
          "description": {
            "type": "string",
            "minLength": 0,
            "maxLength": 512,
            "description": "New description. Optional."
          },
          "kind": {
            "type": "string",
            "minLength": 0,
            "maxLength": 64,
            "description": "New kind. Optional."
          },
          "destinations": {
            "description": "Replace the full list of destinations. Omit to leave it untouched; pass [] to clear it.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "fields": {
            "description": "Replace the full set of fields. Omit to leave fields untouched; pass [] to clear them. Each entry is the plaintext field; values are re-encrypted.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialFieldInput"
            }
          }
        }
      },
      "SetCredentialAgentsDto": {
        "type": "object",
        "properties": {
          "agent_ids": {
            "uniqueItems": true,
            "description": "Full list of agents this credential should be assigned to. The service syncs: new ids are added, missing ids removed. Pass [] to detach from every agent.",
            "example": [
              3,
              5
            ],
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 1
            }
          }
        },
        "required": [
          "agent_ids"
        ]
      },
      "GetSkillDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Skill id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Slug name — becomes the directory under /home/agent/.claude/skills/<name>/",
            "example": "code-review"
          },
          "description": {
            "type": "string",
            "description": "One-line summary, mirrored into the SKILL.md frontmatter description.",
            "example": "Review a diff for correctness and style."
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Per-skill model override (SKILL.md frontmatter `model:`). Null = inherit the agent spawn model.",
            "example": "claude-haiku-4-5"
          },
          "effort": {
            "nullable": true,
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "XHIGH",
              "MAX"
            ],
            "type": "string",
            "description": "Per-skill effort override (SKILL.md frontmatter `effort:`). Null = inherit the session effort. One of LOW|MEDIUM|HIGH|XHIGH|MAX.",
            "example": "LOW"
          },
          "content_hash": {
            "type": "string",
            "description": "SHA-256 of `content`, lets the supervisor skip a no-op rewrite"
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this skill is assigned to. Zero = unassigned.",
            "example": 3
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the skill was archived (soft-deleted). Null on live skills. Archived skills are excluded from default listings and never seeded to a container volume."
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "content_hash",
          "assignment_count",
          "created_at",
          "updated_at"
        ]
      },
      "ListSkillDto": {
        "type": "object",
        "properties": {
          "rows": {
            "description": "Page rows",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetSkillDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total matching rows across all pages",
            "example": 31
          },
          "limit": {
            "type": "number",
            "description": "Echo of the requested page size",
            "example": 50
          },
          "offset": {
            "type": "number",
            "description": "Echo of the requested offset",
            "example": 0
          }
        },
        "required": [
          "rows",
          "total",
          "limit",
          "offset"
        ]
      },
      "SkillAssignedAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          },
          "assigned_at": {
            "format": "date-time",
            "type": "string",
            "description": "When the skill was assigned to this agent"
          }
        },
        "required": [
          "id",
          "name",
          "assigned_at"
        ]
      },
      "GetSkillWithContentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Skill id",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Slug name — becomes the directory under /home/agent/.claude/skills/<name>/",
            "example": "code-review"
          },
          "description": {
            "type": "string",
            "description": "One-line summary, mirrored into the SKILL.md frontmatter description.",
            "example": "Review a diff for correctness and style."
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "Per-skill model override (SKILL.md frontmatter `model:`). Null = inherit the agent spawn model.",
            "example": "claude-haiku-4-5"
          },
          "effort": {
            "nullable": true,
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "XHIGH",
              "MAX"
            ],
            "type": "string",
            "description": "Per-skill effort override (SKILL.md frontmatter `effort:`). Null = inherit the session effort. One of LOW|MEDIUM|HIGH|XHIGH|MAX.",
            "example": "LOW"
          },
          "content_hash": {
            "type": "string",
            "description": "SHA-256 of `content`, lets the supervisor skip a no-op rewrite"
          },
          "assignment_count": {
            "type": "number",
            "description": "Number of agents this skill is assigned to. Zero = unassigned.",
            "example": 3
          },
          "created_by": {
            "type": "number",
            "nullable": true,
            "description": "Creator user id"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          },
          "archived_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "When the skill was archived (soft-deleted). Null on live skills. Archived skills are excluded from default listings and never seeded to a container volume."
          },
          "content": {
            "type": "string",
            "description": "Markdown body of the SKILL.md (without frontmatter)"
          },
          "assignments": {
            "description": "Agents this skill is currently assigned to",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SkillAssignedAgentDto"
            }
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "content_hash",
          "assignment_count",
          "created_at",
          "updated_at",
          "content",
          "assignments"
        ]
      },
      "CreateSkillDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 255,
            "description": "Slug name. Lowercase letters, digits, hyphens. Becomes the directory under /home/agent/.claude/skills/<name>/.",
            "example": "code-review"
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "One-line summary (mirrored into SKILL.md frontmatter description).",
            "example": "Review a diff for correctness and style."
          },
          "content": {
            "type": "string",
            "minLength": 0,
            "maxLength": 200000,
            "description": "Markdown body of the SKILL.md (without frontmatter).",
            "example": "# Code review\n\nWhen asked to review…"
          },
          "model": {
            "type": "string",
            "minLength": 0,
            "maxLength": 64,
            "description": "Optional per-skill model override → SKILL.md frontmatter `model:`. When the skill is active the agent switches model for the rest of the turn. Null/omit = inherit the agent spawn model.",
            "example": "claude-haiku-4-5"
          },
          "effort": {
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "XHIGH",
              "MAX"
            ],
            "type": "string",
            "description": "Optional per-skill effort override → SKILL.md frontmatter `effort:`. Same \"rest of the turn\" semantics as model. Omit = inherit the session effort.",
            "example": "LOW"
          },
          "force": {
            "type": "boolean",
            "description": "Override the credential-in-content check (#309). When the detector flags a likely API key / PEM / connection string, the save is rejected with a 400 — pass `force: true` only if you know the match is an example, not a real secret. The detector still audits the attempt either way.",
            "example": false
          }
        },
        "required": [
          "name",
          "description",
          "content"
        ]
      },
      "UpdateSkillDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 255,
            "description": "New slug name (renames the skill directory). Optional.",
            "example": "code-review"
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "New one-line summary. Optional."
          },
          "content": {
            "type": "string",
            "minLength": 0,
            "maxLength": 200000,
            "description": "New markdown body. Optional."
          },
          "model": {
            "type": "string",
            "minLength": 0,
            "maxLength": 64,
            "description": "Per-skill model override → SKILL.md frontmatter `model:`. Empty string clears it (inherit spawn model). Optional.",
            "example": "claude-haiku-4-5"
          },
          "effort": {
            "type": "string",
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "XHIGH",
              "MAX"
            ],
            "description": "Per-skill effort override → SKILL.md frontmatter `effort:`. Pass an empty string to clear it. Optional."
          },
          "force": {
            "type": "boolean",
            "description": "Override the credential-in-content check (#309). When the detector flags a likely API key / PEM / connection string, the save is rejected with a 400 — pass `force: true` only if you know the match is an example, not a real secret. The detector still audits the attempt either way.",
            "example": false
          }
        }
      },
      "SetSkillAgentsDto": {
        "type": "object",
        "properties": {
          "agent_ids": {
            "uniqueItems": true,
            "description": "Full list of agents this skill should be assigned to. The service syncs: new ids are added, missing ids are removed. Pass [] to detach the skill from every agent.",
            "example": [
              3,
              5
            ],
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 1
            }
          }
        },
        "required": [
          "agent_ids"
        ]
      },
      "GetBackupDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "agent_id": {
            "type": "number"
          },
          "agent_name": {
            "type": "string"
          },
          "status": {
            "enum": [
              "PENDING",
              "FAILED",
              "UPLOADING",
              "COMPLETED"
            ],
            "type": "string"
          },
          "kind": {
            "enum": [
              "MANUAL",
              "SCHEDULED",
              "PRE_MIGRATION"
            ],
            "type": "string"
          },
          "size_bytes": {
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "completed_at": {
            "format": "date-time",
            "type": "string",
            "nullable": true
          },
          "object": {
            "enum": [
              "PRESENT",
              "MISSING",
              "UNKNOWN"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "agent_id",
          "status",
          "kind",
          "size_bytes",
          "error",
          "created_at",
          "completed_at",
          "object"
        ]
      },
      "SetBackupDestinationDto": {
        "type": "object",
        "properties": {
          "endpoint": {
            "type": "string",
            "maxLength": 512,
            "format": "uri",
            "description": "S3-compatible endpoint of the bucket"
          },
          "bucket": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Bucket name"
          },
          "region": {
            "type": "string",
            "maxLength": 64,
            "description": "Region. Empty means `auto`, which is what R2 requires."
          },
          "access_key_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Access key id"
          },
          "secret_access_key": {
            "type": "string",
            "minLength": 1,
            "description": "Secret access key. Stored encrypted, never returned."
          },
          "prefix": {
            "type": "string",
            "maxLength": 255,
            "description": "Optional key prefix inside the bucket"
          }
        },
        "required": [
          "endpoint",
          "bucket",
          "access_key_id",
          "secret_access_key"
        ]
      },
      "GetAuditDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Entry id",
            "example": 1
          },
          "actor_kind": {
            "enum": [
              "HUMAN",
              "AGENT",
              "SERVICE",
              "SYSTEM"
            ],
            "type": "string",
            "description": "Actor kind"
          },
          "actor_id": {
            "type": "number",
            "nullable": true,
            "description": "Actor id (user/agent), null for SYSTEM"
          },
          "actor_label": {
            "type": "string",
            "nullable": true,
            "description": "Denormalized actor label (email / agent name)"
          },
          "action": {
            "type": "string",
            "description": "Action, \"resource.verb\"",
            "example": "credential.reveal"
          },
          "resource_type": {
            "type": "string",
            "description": "Resource type",
            "example": "credential"
          },
          "resource_id": {
            "type": "number",
            "nullable": true,
            "description": "Resource id"
          },
          "result": {
            "enum": [
              "SUCCESS",
              "FAILURE"
            ],
            "type": "string",
            "description": "Result"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Non-sensitive context (NEVER secrets)"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "When the action happened"
          }
        },
        "required": [
          "id",
          "actor_kind",
          "action",
          "resource_type",
          "result",
          "created_at"
        ]
      },
      "ListAuditDto": {
        "type": "object",
        "properties": {
          "rows": {
            "description": "The entries (most recent first)",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetAuditDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total matching the filters (ignoring pagination)"
          },
          "limit": {
            "type": "number",
            "description": "Applied page size"
          },
          "offset": {
            "type": "number",
            "description": "Applied offset"
          }
        },
        "required": [
          "rows",
          "total",
          "limit",
          "offset"
        ]
      },
      "BillingSubscriptionDto": {
        "type": "object",
        "properties": {
          "status": {
            "nullable": true,
            "enum": [
              "TRIALING",
              "ACTIVE",
              "PAST_DUE",
              "CANCELED",
              "ENDED"
            ],
            "type": "string",
            "description": "Subscription state, or null when there has never been one."
          },
          "contracted_agents": {
            "type": "number",
            "nullable": true,
            "description": "How many agents are contracted. Set by the team, so it holds even with no card payment.",
            "example": 3
          },
          "current_period_end": {
            "type": "string",
            "nullable": true,
            "description": "When it renews (or when what was paid for runs out)."
          },
          "cancel_at_period_end": {
            "type": "boolean",
            "description": "Whether cancellation is already scheduled for the end of the period.",
            "example": false
          },
          "has_customer": {
            "type": "boolean",
            "description": "Whether this organization has a customer record at the gateway. It is what decides if the customer portal can be opened: no record, nothing to manage.",
            "example": true
          },
          "trial_ends_at": {
            "type": "string",
            "nullable": true,
            "description": "When the trial ends, if there is one. It lives here and not on the organization so the screen has everything in one place."
          },
          "granted": {
            "type": "boolean",
            "description": "The plan was agreed by hand, not bought by card (ENTERPRISE, or granted by the team). Whoever has one is not shown tariffs: their price was agreed apart.",
            "example": false
          }
        },
        "required": [
          "cancel_at_period_end",
          "has_customer",
          "granted"
        ]
      },
      "BillingSummaryDto": {
        "type": "object",
        "properties": {
          "plan": {
            "enum": [
              "FREE",
              "BASIC",
              "PRO",
              "ENTERPRISE"
            ],
            "type": "string",
            "description": "Organization plan"
          },
          "agent_count": {
            "type": "number",
            "description": "Number of agents in the org",
            "example": 3
          },
          "subscription": {
            "description": "Payment state: what is contracted and how the gateway stands.",
            "allOf": [
              {
                "$ref": "#/components/schemas/BillingSubscriptionDto"
              }
            ]
          }
        },
        "required": [
          "plan",
          "agent_count",
          "subscription"
        ]
      },
      "ContractedAgentsDto": {
        "type": "object",
        "properties": {
          "contracted": {
            "type": "number",
            "nullable": true,
            "description": "Agentes que la organización puede tener ahora mismo. Es el número que decide si se puede crear otro.",
            "example": 3
          },
          "in_use": {
            "type": "number",
            "description": "Agentes que ya existen, encendidos o no.",
            "example": 3
          },
          "running": {
            "type": "number",
            "description": "Agentes levantados, que son los que ocupan plaza.",
            "example": 2
          },
          "min": {
            "type": "number",
            "description": "Cantidad mínima a la que se puede bajar: los agentes que están levantados, y nunca menos de uno.",
            "example": 2
          },
          "max": {
            "type": "number",
            "description": "Cantidad máxima que admite el plan.",
            "example": 50
          },
          "paid_for": {
            "type": "number",
            "nullable": true,
            "description": "Agentes cobrados en la pasarela. Puede ir por delante del contratado durante los segundos que tarda en llegar el aviso del cobro.",
            "example": 4
          },
          "period_end": {
            "format": "date-time",
            "type": "string",
            "nullable": true,
            "description": "Cuándo renueva la suscripción, que es también cuándo entra en vigor una reducción programada.",
            "example": "2026-09-28T10:00:00.000Z"
          },
          "scheduled": {
            "type": "number",
            "nullable": true,
            "description": "Reducción ya programada para la renovación, si la hay.",
            "example": 2
          },
          "settling": {
            "type": "boolean",
            "description": "El cobro ya está hecho y el número contratado todavía no lo refleja. Vuelve a consultar en unos segundos.",
            "example": false
          },
          "can_change": {
            "type": "boolean",
            "description": "Si se puede cambiar la cantidad desde el panel.",
            "example": true
          },
          "blocked_reason": {
            "nullable": true,
            "enum": [
              "gateway_not_configured",
              "not_billed",
              "plan_not_sold_by_card",
              "no_subscription",
              "subscription_not_current"
            ],
            "type": "string",
            "description": "Por qué no se puede cambiar, cuando no se puede.",
            "example": "no_subscription"
          }
        },
        "required": [
          "contracted",
          "in_use",
          "running",
          "min",
          "max",
          "paid_for",
          "period_end",
          "scheduled",
          "settling",
          "can_change",
          "blocked_reason"
        ]
      },
      "CostFiguresDto": {
        "type": "object",
        "properties": {
          "cost_micro": {
            "type": "string",
            "description": "Cost in 1e-6 currency units.",
            "example": "5000000"
          },
          "input_tokens": {
            "type": "string",
            "description": "Input (uncached) tokens.",
            "example": "120000"
          },
          "output_tokens": {
            "type": "string",
            "description": "Output tokens.",
            "example": "34000"
          },
          "cache_read_tokens": {
            "type": "string",
            "description": "Cache-read tokens.",
            "example": "900000"
          },
          "cache_write_tokens": {
            "type": "string",
            "description": "Cache-write (creation) tokens.",
            "example": "45000"
          },
          "total_tokens": {
            "type": "string",
            "description": "Total tokens (sum of the four above).",
            "example": "1099000"
          }
        },
        "required": [
          "cost_micro",
          "input_tokens",
          "output_tokens",
          "cache_read_tokens",
          "cache_write_tokens",
          "total_tokens"
        ]
      },
      "CostSeriesPointDto": {
        "type": "object",
        "properties": {
          "cost_micro": {
            "type": "string",
            "description": "Cost in 1e-6 currency units.",
            "example": "5000000"
          },
          "input_tokens": {
            "type": "string",
            "description": "Input (uncached) tokens.",
            "example": "120000"
          },
          "output_tokens": {
            "type": "string",
            "description": "Output tokens.",
            "example": "34000"
          },
          "cache_read_tokens": {
            "type": "string",
            "description": "Cache-read tokens.",
            "example": "900000"
          },
          "cache_write_tokens": {
            "type": "string",
            "description": "Cache-write (creation) tokens.",
            "example": "45000"
          },
          "total_tokens": {
            "type": "string",
            "description": "Total tokens (sum of the four above).",
            "example": "1099000"
          },
          "bucket": {
            "type": "string",
            "description": "Bucket start (ISO date, UTC).",
            "example": "2026-06-18T00:00:00.000Z"
          }
        },
        "required": [
          "cost_micro",
          "input_tokens",
          "output_tokens",
          "cache_read_tokens",
          "cache_write_tokens",
          "total_tokens",
          "bucket"
        ]
      },
      "CostBreakdownRowDto": {
        "type": "object",
        "properties": {
          "cost_micro": {
            "type": "string",
            "description": "Cost in 1e-6 currency units.",
            "example": "5000000"
          },
          "input_tokens": {
            "type": "string",
            "description": "Input (uncached) tokens.",
            "example": "120000"
          },
          "output_tokens": {
            "type": "string",
            "description": "Output tokens.",
            "example": "34000"
          },
          "cache_read_tokens": {
            "type": "string",
            "description": "Cache-read tokens.",
            "example": "900000"
          },
          "cache_write_tokens": {
            "type": "string",
            "description": "Cache-write (creation) tokens.",
            "example": "45000"
          },
          "total_tokens": {
            "type": "string",
            "description": "Total tokens (sum of the four above).",
            "example": "1099000"
          },
          "key": {
            "type": "string",
            "description": "Stable key for the dimension value.",
            "example": "10"
          },
          "label": {
            "type": "string",
            "description": "Human label for the dimension value.",
            "example": "Ailfred"
          },
          "agent_id": {
            "type": "number",
            "nullable": true,
            "description": "Owning agent id (channel breakdown only; null otherwise). Lets the UI show the agent's avatar.",
            "example": 7
          },
          "transport": {
            "type": "string",
            "nullable": true,
            "description": "Channel transport (channel/conversation breakdown; null otherwise). Lets the UI show the brand logo.",
            "example": "TELEGRAM"
          },
          "contact_name": {
            "type": "string",
            "nullable": true,
            "description": "Contact name (conversation breakdown only; null otherwise). Seeds the contact's avatar.",
            "example": "Soki"
          },
          "person_kind": {
            "type": "string",
            "nullable": true,
            "description": "Contact kind (conversation breakdown): EXTERNAL | INTERNAL_USER | INTERNAL_ROOM.",
            "example": "INTERNAL_USER"
          },
          "person_avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Real avatar URL of the linked operator (conversation breakdown, INTERNAL_USER with a photo). Null → UI uses the generative avatar.",
            "example": "/user/3/avatar?v=1718900000000"
          },
          "message_count": {
            "type": "string",
            "description": "Number of assistant messages.",
            "example": "991"
          }
        },
        "required": [
          "cost_micro",
          "input_tokens",
          "output_tokens",
          "cache_read_tokens",
          "cache_write_tokens",
          "total_tokens",
          "key",
          "label",
          "message_count"
        ]
      },
      "CostAnalyticsDto": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "description": "Effective lower bound (ISO).",
            "example": "2026-05-20T00:00:00.000Z"
          },
          "to": {
            "type": "string",
            "description": "Effective upper bound (ISO).",
            "example": "2026-06-19T00:00:00.000Z"
          },
          "bucket": {
            "enum": [
              "hour",
              "day",
              "month",
              "week"
            ],
            "type": "string",
            "description": "Bucket granularity used.",
            "example": "day"
          },
          "group_by": {
            "enum": [
              "agent",
              "channel",
              "scope",
              "transport",
              "model",
              "contact",
              "conversation",
              "cron"
            ],
            "type": "string",
            "description": "Dimension grouped by.",
            "example": "agent"
          },
          "totals": {
            "description": "Totals over the whole window.",
            "allOf": [
              {
                "$ref": "#/components/schemas/CostFiguresDto"
              }
            ]
          },
          "series": {
            "description": "Cost per time bucket (ascending).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CostSeriesPointDto"
            }
          },
          "breakdown": {
            "description": "Cost per dimension value (highest cost first).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CostBreakdownRowDto"
            }
          }
        },
        "required": [
          "from",
          "to",
          "bucket",
          "group_by",
          "totals",
          "series",
          "breakdown"
        ]
      },
      "GetCapabilityDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Capability id",
            "example": 1
          },
          "key": {
            "type": "string",
            "description": "Stable slug — the agent-base/supervisor look at this.",
            "example": "playwright"
          },
          "label": {
            "type": "string",
            "description": "Display name",
            "example": "Playwright + Chromium"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Long description"
          },
          "icon": {
            "type": "string",
            "nullable": true,
            "description": "Lucide icon name hint for the frontend",
            "example": "theater"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          }
        },
        "required": [
          "id",
          "key",
          "label",
          "created_at",
          "updated_at"
        ]
      },
      "ListCapabilityDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Items in the current page",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GetCapabilityDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total number of items matching the query",
            "example": 5
          },
          "page": {
            "type": "number",
            "description": "Current page (1-indexed)",
            "example": 1
          },
          "limit": {
            "type": "number",
            "description": "Page size",
            "example": 20
          }
        },
        "required": [
          "data",
          "total",
          "page",
          "limit"
        ]
      },
      "GetModelDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Model id",
            "example": 1
          },
          "slug": {
            "type": "string",
            "description": "Exact model id passed to `claude --model`.",
            "example": "claude-opus-4-8"
          },
          "label": {
            "type": "string",
            "description": "Pretty name shown in the picker.",
            "example": "Opus 4.8 (1M context)"
          },
          "sort_order": {
            "type": "number",
            "description": "Sort order (lower first).",
            "example": 10
          },
          "active": {
            "type": "boolean",
            "description": "Whether the model is offered in the picker.",
            "example": true
          },
          "provider": {
            "type": "string",
            "description": "Provider key — which LLM provider serves this model (KUJI-67).",
            "example": "anthropic"
          },
          "family": {
            "type": "string",
            "nullable": true,
            "description": "Pricing-family key (null = derived from the slug).",
            "example": "opus"
          },
          "supports_manual_thinking": {
            "type": "boolean",
            "description": "Whether this model still uses the manual --thinking on/off toggle. False = reasons adaptively and is driven by --effort (the whole current catalogue); true only for legacy models (Opus 4.5 and earlier). Drives whether the UI shows the thinking control.",
            "example": false
          },
          "price_input": {
            "type": "number",
            "nullable": true,
            "description": "Price in USD per MILLION input tokens. Null = fall back to the built-in family table. These are COST prices, with no markup.",
            "example": 5
          },
          "price_output": {
            "type": "number",
            "nullable": true,
            "description": "Price in USD per million output tokens. Null = family table.",
            "example": 25
          },
          "price_cache_read": {
            "type": "number",
            "nullable": true,
            "description": "Price in USD per million tokens READ from cache. Null = family table.",
            "example": 0.5
          },
          "price_cache_write_5m": {
            "type": "number",
            "nullable": true,
            "description": "Price per million tokens WRITTEN to the 5-minute cache. Null = family table.",
            "example": 6.25
          },
          "price_cache_write_1h": {
            "type": "number",
            "nullable": true,
            "description": "Price per million tokens WRITTEN to the 1-hour cache. Null = family table.",
            "example": 10
          },
          "context_window": {
            "type": "number",
            "nullable": true,
            "description": "Context window in tokens. Null = derived from the slug by the built-in heuristic.",
            "example": 1000000
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          }
        },
        "required": [
          "id",
          "slug",
          "label",
          "sort_order",
          "active",
          "provider",
          "supports_manual_thinking",
          "price_input",
          "price_output",
          "price_cache_read",
          "price_cache_write_5m",
          "price_cache_write_1h",
          "context_window",
          "created_at",
          "updated_at"
        ]
      },
      "InboxAgentBadgeDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 3
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "InboxAssigneeDto": {
        "type": "object",
        "properties": {
          "kind": {
            "enum": [
              "AGENT",
              "USER"
            ],
            "type": "string",
            "description": "Discriminator: USER (human operator) or AGENT (Claude agent)",
            "example": "AGENT"
          },
          "id": {
            "type": "number",
            "description": "Assignee id (user.id or agent.id depending on kind)",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Display name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url for AGENT kind (proxy endpoint), null for USER"
          }
        },
        "required": [
          "kind",
          "id",
          "name"
        ]
      },
      "InboxChannelBadgeDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Channel id",
            "example": 2
          },
          "transport": {
            "type": "string",
            "description": "Channel transport",
            "example": "INTERNAL"
          },
          "name": {
            "type": "string",
            "description": "Channel name",
            "example": "Internal operators"
          }
        },
        "required": [
          "id",
          "transport",
          "name"
        ]
      },
      "InboxLastMessageDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Message id",
            "example": 42
          },
          "role": {
            "type": "string",
            "description": "Role",
            "example": "ASSISTANT"
          },
          "kind": {
            "type": "string",
            "description": "Message kind",
            "example": "TEXT"
          },
          "preview": {
            "type": "string",
            "nullable": true,
            "description": "Short text preview (best-effort from content)",
            "example": "Sí, te llamas Maria Test"
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          }
        },
        "required": [
          "id",
          "role",
          "kind",
          "created_at"
        ]
      },
      "InboxConversationDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Conversation id",
            "example": 7
          },
          "status": {
            "type": "string",
            "description": "Status",
            "example": "OPEN"
          },
          "session_id": {
            "type": "string",
            "description": "Claude session UUID",
            "example": "a1b2…"
          },
          "opened_at": {
            "format": "date-time",
            "type": "string",
            "description": "Opened at"
          },
          "last_activity_at": {
            "format": "date-time",
            "type": "string",
            "description": "Last activity at"
          },
          "last_message": {
            "nullable": true,
            "description": "Last message preview",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/InboxLastMessageDto"
              }
            ]
          }
        },
        "required": [
          "id",
          "status",
          "session_id",
          "opened_at",
          "last_activity_at"
        ]
      },
      "InboxContactDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Contact id",
            "example": 12
          },
          "kind": {
            "type": "string",
            "description": "Contact kind",
            "example": "EXTERNAL"
          },
          "name": {
            "type": "string",
            "description": "Contact display name",
            "example": "Maria"
          },
          "agent": {
            "nullable": true,
            "description": "Legacy \"owner\" agent of this contact (kept for backwards compat — prefer `assignee`).",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/InboxAgentBadgeDto"
              }
            ]
          },
          "assignee": {
            "nullable": true,
            "description": "Current Agent Desk assignee (human operator or agent). Null when nobody is currently handling the contact (fresh inbound).",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/InboxAssigneeDto"
              }
            ]
          },
          "channels": {
            "description": "Channels the contact is reachable through",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxChannelBadgeDto"
            }
          },
          "conversations": {
            "description": "Conversations sorted by last_activity_at desc",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxConversationDto"
            }
          },
          "last_activity_at": {
            "format": "date-time",
            "type": "string",
            "description": "Most recent activity across the contact"
          }
        },
        "required": [
          "id",
          "kind",
          "name",
          "channels",
          "conversations",
          "last_activity_at"
        ]
      },
      "ListInboxDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "Contacts grouped, each carrying conversation list",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxContactDto"
            }
          },
          "total": {
            "type": "number",
            "description": "Total contacts matched (not conversations)"
          }
        },
        "required": [
          "data",
          "total"
        ]
      },
      "InboxIdentityDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Identity id",
            "example": 9
          },
          "channel": {
            "description": "Channel this identity belongs to",
            "allOf": [
              {
                "$ref": "#/components/schemas/InboxChannelBadgeDto"
              }
            ]
          },
          "identifier": {
            "type": "string",
            "description": "Stable identifier (email, phone, user_id, …)",
            "example": "user:42"
          },
          "display_name": {
            "type": "string",
            "nullable": true,
            "description": "Display name (optional)"
          },
          "first_seen_at": {
            "format": "date-time",
            "type": "string",
            "description": "First seen at"
          },
          "last_seen_at": {
            "format": "date-time",
            "type": "string",
            "description": "Last seen at"
          }
        },
        "required": [
          "id",
          "channel",
          "identifier",
          "first_seen_at",
          "last_seen_at"
        ]
      },
      "GetInboxContactDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Contact id",
            "example": 12
          },
          "kind": {
            "type": "string",
            "description": "Contact kind",
            "example": "EXTERNAL"
          },
          "name": {
            "type": "string",
            "description": "Contact display name",
            "example": "Maria"
          },
          "user_id": {
            "type": "number",
            "nullable": true,
            "description": "Linked user id (operator contacts only)"
          },
          "agent": {
            "nullable": true,
            "description": "Legacy \"owner\" agent (kept for backwards compat — prefer `assignee`).",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/InboxAgentBadgeDto"
              }
            ]
          },
          "assignee": {
            "nullable": true,
            "description": "Current Agent Desk assignee. Null if unassigned.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/InboxAssigneeDto"
              }
            ]
          },
          "identities": {
            "description": "All identities this contact has (one per channel)",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxIdentityDto"
            }
          },
          "conversations": {
            "description": "All conversations (open + closed) ordered by last_activity_at desc",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxConversationDto"
            }
          },
          "created_at": {
            "format": "date-time",
            "type": "string",
            "description": "Created at"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string",
            "description": "Updated at"
          }
        },
        "required": [
          "id",
          "kind",
          "name",
          "identities",
          "conversations",
          "created_at",
          "updated_at"
        ]
      },
      "InboxAssigneeUserDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "User id",
            "example": 2
          },
          "name": {
            "type": "string",
            "description": "Display name",
            "example": "Alice"
          },
          "email": {
            "type": "string",
            "description": "Email",
            "example": "alice@example.com"
          },
          "role": {
            "type": "string",
            "description": "Membership role",
            "example": "OPERATOR"
          }
        },
        "required": [
          "id",
          "name",
          "email",
          "role"
        ]
      },
      "InboxAssigneeAgentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Agent id",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Agent name",
            "example": "Captain"
          },
          "avatar_url": {
            "type": "string",
            "nullable": true,
            "description": "Avatar url (proxy endpoint)"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "ListInboxAssigneesDto": {
        "type": "object",
        "properties": {
          "users": {
            "description": "Members of the organization eligible to be assigned",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxAssigneeUserDto"
            }
          },
          "agents": {
            "description": "Agents in the organization eligible to be assigned",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxAssigneeAgentDto"
            }
          }
        },
        "required": [
          "users",
          "agents"
        ]
      },
      "SetConversationStatusDto": {
        "type": "object",
        "properties": {
          "status": {
            "enum": [
              "open",
              "pending",
              "resolved"
            ],
            "type": "string",
            "description": "Target status for the conversation. open = reactive / reopen, pending = waiting for action, resolved = closed."
          }
        },
        "required": [
          "status"
        ]
      },
      "SetAssigneeDto": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "description": "Human operator to assign. Null to clear."
          },
          "agent_id": {
            "type": "number",
            "nullable": true,
            "minimum": 1,
            "description": "Agent to assign. Null to clear."
          }
        }
      }
    }
  }
}
