> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getsupervisor.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Pedir subir archivos a un destino

> Reserva un sitio para cada archivo y devuelve la URL con la que el cliente lo sube
directamente al almacén: la API nunca ve los bytes.

El **namespace va en la ruta** porque identifica el destino del archivo, y cada uno
tiene su propia política —qué extensiones acepta, cuánto pesa como mucho cada archivo,
cuántos admite una petición y qué scope exige—. Uno de los permisos lo decide ese
namespace: `agent-briefs` pide `agents:write` y `knowledge-sources` pide
`knowledge-bases:write`.

Y hace falta **además `uploads:write`**, que es el mismo que exigen `confirm` y `DELETE`.
Son dos preguntas distintas —«puedes escribir en este destino» y «puedes manejar archivos
subidos»— y firmar sin la segunda dejaría pasar una petición que no va a poder cerrar el
ciclo: el objeto llegaría a S3 sin forma de confirmarlo ni descartarlo.

Cada archivo deja una fila creada **antes** de firmar, y por eso la respuesta es `201`
aunque todavía no se haya subido un solo byte: esa fila es lo que hace localizable a un
archivo que nunca llegue a subirse. La operación **no es idempotente**: dos llamadas
dan dos `uploadId` y dos filas, y la que no se confirme caduca a las 24 h.

El carril está detrás de un feature flag por workspace. Cerrado, responde `404`.




## OpenAPI

````yaml /openapi/public-api.yaml post /v1/uploads/{namespace}
openapi: 3.1.0
info:
  title: Agents Studio API
  version: 1.0.0
  description: >
    API del backend de Agents Studio. Expone servicios multi-tenant para
    gestionar agentes

    (texto y voz), catálogo de herramientas, habilitación de workspaces y
    operaciones de

    telefonía. Todos los endpoints requieren autenticación Bearer y respetan los
    contratos

    documentados en `docs/modules`.
  license:
    name: Proprietary
    url: https://getsupervisor.ai/legal/terms
servers:
  - url: https://api-prod.studio.getsupervisor.ai
    description: Producción
  - url: https://sandbox.agents.studio.getsupervisor.ai
    description: Sandbox
security:
  - BearerAuth: []
tags:
  - name: Agents
    description: >
      Gestión general de agentes y sus metadatos.

      Scopes requeridos: `agents:read` para consultas y `agents:write` para
      creación, actualización y operaciones sobre teléfonos.
  - name: Agent Versions
    description: Versionado y mantenimiento de versiones publicadas de los agentes.
  - name: Agent Schedules
    description: >-
      Configuración de horarios regulares y excepciones puntuales para controlar
      la disponibilidad de los agentes.
  - name: Campaigns
    description: >
      Gestión de campañas masivas basadas en CSV para ejecutar agentes de forma
      batch.

      Scopes requeridos: `campaigns:read` para consultas y `campaigns:write`
      para creación.
  - name: Calls
    description: |
      Consultas de llamadas (Speech Analytics) disponibles para el workspace.
      Scopes requeridos: `calls:read`.
  - name: Agent Instructions
    description: >
      Operaciones sobre instrucciones (prompt) gestionadas por los usuarios
      finales para

      guiar y corregir el comportamiento del agente. Las instrucciones se
      aplican a la

      versión activa del agente, se mantienen como texto libre sin formato (1 a
      500

      caracteres) y su prioridad se controla mediante el campo `order`.

      Scopes requeridos: `agent-instructions:read` para consultas y
      `agent-instructions:write` para creación o edición.
  - name: Catalogs
    description: >
      Gestión centralizada de catálogos (idiomas, estilos de mensaje, tonos,
      etiquetas, voces y eventos de

      webhook) con alcance global o específico por workspace. Las voces del
      workspace se consultan aquí, con

      `GET /v1/catalogs/items?type=voice`. Scopes requeridos: `catalogs:read`
      para consultas y

      `catalogs:write` para creación y modificaciones.
  - name: Catalog Templates
    description: >
      Catálogo de plantillas de agente publicadas. Sirve para descubrir qué
      plantillas hay disponibles y con

      qué `agentVersionId` clonarlas mediante `POST /v1/agents/from-template`.

      Scopes requeridos: `catalogs:read`.
  - name: Agent Phones
    description: Conexión y desconexión de teléfonos asignados a agentes.
  - name: API Keys
    description: Gestión de credenciales de acceso programático y sus permisos.
  - name: Agent Blueprints
    description: >
      Gestión del blueprint (personalidad) asociado a cada versión de agente.

      Scopes requeridos: `agent-blueprints:read` para lectura y
      `agent-blueprints:write` para cambios en blueprint.
  - name: Blueprint Stages
    description: >
      Autoría de stages de un blueprint del agente, abarcando orden, prompts y
      validaciones previas a la

      publicación. Todas las rutas cuelgan del blueprint
      (`/v1/agents/{agentId}/blueprints/{blueprintId}/stages`),

      así que hay que resolver primero el `blueprintId` con `GET
      /v1/agents/{agentId}/blueprints`.

      Scopes requeridos: `blueprint-stages:read` para consultas y
      `blueprint-stages:write` para creación y edición.

      La validación del grafo se ejecuta automáticamente en cada mutación y la
      sincronización con el proveedor

      sucede mediante jobs internos tras los cambios.
  - name: Stage Triggers
    description: >
      Gestión detallada de triggers que conectan stages dentro del blueprint y
      definen las transiciones

      conversacionales. Scopes requeridos: `stage-triggers:read` y
      `stage-triggers:write`.
  - name: Workspaces
    description: Gestión multi-tenant y habilitación de credenciales por workspace.
  - name: Tools
    description: >
      Catálogo y ejecución de tools disponibles para los agentes.

      Scopes requeridos: `tools:read` para listar y `tools:execute` para
      ejecutar tools.
  - name: Webhooks
    description: >
      Gestión de webhooks por workspace para suscribirse a eventos del dominio y
      recibir notificaciones HTTP.

      Scopes requeridos: `webhooks:read` para consultas y `webhooks:write` para
      crear, actualizar o eliminar webhooks y suscripciones.
  - name: SIP Trunks
    description: >
      Provisionamiento y gestión de SIP trunks para conectar agentes de voz con
      carriers PSTN

      (Twilio, ccc2.uno, etc.) via Kamailio. Genera credenciales digest
      automaticamente.

      Scopes requeridos: `sip:read` para consultas y `sip:write` para creación,
      actualización y eliminación.
  - name: Documents
    description: >
      Gestión de documentos asociados al workspace. Permite obtener URLs
      presignadas

      para subir archivos que luego se referencian al crear o personalizar
      agentes.

      Scopes requeridos: `documents:write`.
  - name: Message Templates
    description: >
      Gestión de message templates de WhatsApp (Kapso). Permite crear, listar y
      eliminar templates,

      así como consultar el estado de la conexión con el número de teléfono
      configurado.

      Scopes requeridos: `agents:read` para consultas y `agents:write` para
      creación y eliminación.
  - name: Billing
    description: |
      Consulta del balance de consumo del workspace sobre el plan contratado.
      Scopes requeridos: `billing:read`.
  - name: Usage
    description: |
      Consultas de consumo de recursos agregado por agente.
      Scopes requeridos: `usage:read`.
  - name: Uploads
    description: >
      Subida de archivos por URL prefirmada: la API reserva el sitio y devuelve
      la URL, y

      el cliente sube contra el almacén sin que los bytes pasen por aquí.

      Toda escritura exige `uploads:write`, y la firma exige **además** el scope
      que decide el

      namespace de la ruta: `agent-briefs` y `avatars` exigen `agents:write`, y

      `knowledge-sources` exige `knowledge-bases:write`.


      Cada namespace declara además qué acepta. `avatars` sólo admite PNG, JPEG
      y WEBP —y

      **no** SVG: se sirven en línea, y un SVG es texto ejecutable—, con un
      archivo por

      lote y 1 MiB como máximo.
  - name: Health
    description: >
      Sonda de disponibilidad del servicio. No requiere autenticación ni
      `X-Workspace-Id`.
  - name: Knowledge Bases
    description: >
      Bases de conocimiento del workspace: el material que el agente consulta
      durante la

      conversación.


      El prefijo nombra el recurso y no la tool que lo implementa, así que
      renombrar la

      tool `knowledge` no mueve estas rutas.


      Las sirve un plugin instalable, y eso tiene una consecuencia visible: si
      la tool no

      está abierta para el workspace, **todas** responden `404` —no `403`—. Una
      feature

      que no está disponible para un cliente no existe para él.


      Scopes requeridos: `knowledge-bases:read` para consultas y
      `knowledge-bases:write`

      para el alta, la baja y el mantenimiento de sus documentos.
paths:
  /v1/uploads/{namespace}:
    post:
      tags:
        - Uploads
      summary: Pedir subir archivos a un destino
      description: >
        Reserva un sitio para cada archivo y devuelve la URL con la que el
        cliente lo sube

        directamente al almacén: la API nunca ve los bytes.


        El **namespace va en la ruta** porque identifica el destino del archivo,
        y cada uno

        tiene su propia política —qué extensiones acepta, cuánto pesa como mucho
        cada archivo,

        cuántos admite una petición y qué scope exige—. Uno de los permisos lo
        decide ese

        namespace: `agent-briefs` pide `agents:write` y `knowledge-sources` pide

        `knowledge-bases:write`.


        Y hace falta **además `uploads:write`**, que es el mismo que exigen
        `confirm` y `DELETE`.

        Son dos preguntas distintas —«puedes escribir en este destino» y «puedes
        manejar archivos

        subidos»— y firmar sin la segunda dejaría pasar una petición que no va a
        poder cerrar el

        ciclo: el objeto llegaría a S3 sin forma de confirmarlo ni descartarlo.


        Cada archivo deja una fila creada **antes** de firmar, y por eso la
        respuesta es `201`

        aunque todavía no se haya subido un solo byte: esa fila es lo que hace
        localizable a un

        archivo que nunca llegue a subirse. La operación **no es idempotente**:
        dos llamadas

        dan dos `uploadId` y dos filas, y la que no se confirme caduca a las 24
        h.


        El carril está detrás de un feature flag por workspace. Cerrado,
        responde `404`.
      operationId: presignUploads
      parameters:
        - $ref: '#/components/parameters/XWorkspaceId'
        - $ref: '#/components/parameters/UploadNamespace'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PresignUploadsRequest'
            examples:
              twoFiles:
                summary: Dos documentos para el brief de un agente
                value:
                  files:
                    - fileName: manual-cobranza.pdf
                      sizeBytes: 348172
                    - fileName: guion-llamadas.docx
                      sizeBytes: 91240
      responses:
        '201':
          description: Sitio reservado y URLs de subida emitidas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresignUploadsResponse'
              examples:
                created:
                  summary: Una URL por archivo
                  value:
                    uploads:
                      - uploadId: 7f3a1b2c-9d4e-4f6a-8b1c-2d3e4f5a6b7c
                        namespace: agent-briefs
                        key: >-
                          agent-briefs/a44bb95e-0000-4000-8000-000000000001/7f3a1b2c-9d4e-4f6a-8b1c-2d3e4f5a6b7c.pdf
                        uploadUrl: >-
                          https://bucket.s3.amazonaws.com/agent-briefs/...?X-Amz-...
                        method: PUT
                        headers:
                          Content-Type: application/pdf
                        urlExpiresAt: '2026-09-04T22:45:00.000Z'
        '400':
          description: >
            El namespace no existe, una extensión no está en su lista, o el lote
            trae más

            archivos de los que ese namespace admite.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >
            A la petición le falta alguno de los **dos** scopes que exige
            firmar: el que pide el

            namespace de destino (`agents:write` para `agent-briefs`,
            `knowledge-bases:write`

            para `knowledge-sources`) y `uploads:write`. El mensaje nombra
            **todos** los que

            faltan, no el primero: con uno solo haría falta una segunda vuelta
            para descubrir

            el otro.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: >
            El carril de subida por URL prefirmada está cerrado para este
            workspace. El cuerpo

            es genérico a propósito: nombrar la feature delataría lo que el
            `404` oculta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: >
            Un archivo pasa del `maxBytes` de su namespace, o la suma del lote
            pasa de su

            `maxBatchBytes`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  parameters:
    XWorkspaceId:
      name: x-workspace-id
      in: header
      required: true
      description: Identificador del workspace multi-tenant.
      schema:
        type: string
        format: uuid
    UploadNamespace:
      name: namespace
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/UploadNamespace'
  schemas:
    PresignUploadsRequest:
      type: object
      description: Los archivos que se quieren subir a este namespace.
      properties:
        files:
          type: array
          items:
            $ref: '#/components/schemas/PresignUploadItem'
          minItems: 1
          maxItems: 25
      required:
        - files
    PresignUploadsResponse:
      type: object
      properties:
        uploads:
          type: array
          items:
            $ref: '#/components/schemas/PresignedUpload'
      required:
        - uploads
    ErrorResponse:
      type: object
      properties:
        code:
          type: string
          description: Código principal del error (alto nivel).
        message:
          type: string
          description: Mensaje legible para humanos.
        details:
          type: object
          description: >
            Información adicional específica del error.

            Cuando aplique, `details.subcode` provee un identificador estable
            para que el frontend decida qué UI mostrar.
          properties:
            subcode:
              type: string
              description: >-
                Identificador específico del error (p.ej.
                `WORKSPACE_NOT_PROVISIONED`).
            workspaceId:
              type: string
              format: uuid
              description: Identificador de workspace relacionado cuando aplica.
          additionalProperties: true
      required:
        - code
        - message
    UploadNamespace:
      type: string
      enum:
        - agent-briefs
        - knowledge-sources
      description: >
        Destino del archivo. Es un conjunto cerrado y versionado en el código:
        cada namespace

        trae su propia política de formatos, tamaños y permisos.
    PresignUploadItem:
      type: object
      description: >
        Un archivo del lote. `fileName` es una etiqueta, no una ruta: de él sólo
        se lee la

        extensión, y sólo para validarla contra la lista cerrada del namespace.
        Nunca entra a

        la `key`, que es lo que hace irrepresentable el path traversal.
      properties:
        fileName:
          type: string
          maxLength: 255
          description: Nombre original del archivo, con su extensión.
        sizeBytes:
          type: integer
          minimum: 1
          description: >
            Lo que el cliente dice que pesa. Se valida contra la política del
            namespace y se

            firma como `Content-Length`, así que el almacén rechaza el PUT que
            no lo respete.
      required:
        - fileName
        - sizeBytes
    PresignedUpload:
      type: object
      properties:
        uploadId:
          type: string
          format: uuid
          description: >-
            La fila que ya existe para este archivo. Es con lo que se confirma
            luego.
        namespace:
          $ref: '#/components/schemas/UploadNamespace'
        key:
          type: string
          description: Dónde vivirá el objeto en el almacén.
        uploadUrl:
          type: string
          format: uri
          description: URL prefirmada contra la que el cliente sube el archivo.
        method:
          type: string
          enum:
            - PUT
        headers:
          type: object
          additionalProperties:
            type: string
          description: >
            Los headers exactos que hay que reenviar, ni uno más ni uno menos.

            `Content-Length` no aparece aunque vaya firmado: es un *forbidden
            header name* y el

            `fetch` del navegador lo pone él desde el Blob.
        urlExpiresAt:
          type: string
          format: date-time
          description: >
            Cuándo vence **esta URL** (15 min). No confundir con el plazo de
            staging de la

            fila, que es de 24 h.
      required:
        - uploadId
        - namespace
        - key
        - uploadUrl
        - method
        - headers
        - urlExpiresAt
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````