# Archivos

Sube archivos, consulta la cuota y obtén direcciones de descarga firmadas desde tu aplicación con /external/v1/files y los permisos fm:files:*.

Con `/external/v1/files` tu aplicación sube archivos (por ejemplo, para
adjuntarlos a un ticket), consulta cuánto espacio le queda y obtiene
direcciones firmadas para descargarlos. Los archivos de cada aplicación
se guardan aparte, dentro de tu organización.

**Adjuntar un archivo a un ticket**

Participantes: Tu aplicación y UTILIA OS.

1. Tu aplicación → UTILIA OS: `POST /api/external/v1/files/upload`. El archivo en multipart/form-data, campo file.
2. UTILIA OS → Tu aplicación (respuesta): El archivo, con su id. Dentro del tamaño máximo y la cuota de la aplicación.
3. Tu aplicación → UTILIA OS: `POST /api/external/v1/tickets`. Con attachmentIds: el id del archivo.
4. Tu aplicación → UTILIA OS: `GET /api/external/v1/files/:id/url`. Cuando alguien necesita descargarlo.
5. UTILIA OS → Tu aplicación (respuesta): Dirección firmada, válida una hora

## Autenticación y plan

Todas las peticiones llevan la clave de API de la aplicación:

```
X-Api-Key: <api_key_de_la_app>
```

El plan de tu organización tiene que incluir el aplicativo **Gestor de
archivos**; si no, la respuesta es `403 MODULE_NOT_ENABLED`.

## Permisos de la API (scopes)

| Endpoint | Permiso obligatorio |
| - | - |
| `GET /external/v1/files/quota` | `fm:files:read` |
| `GET /external/v1/files/:id/url` | `fm:files:read` |
| `POST /external/v1/files/upload` | `fm:files:write` |

Sin el permiso adecuado, los endpoints responden `403 INSUFFICIENT_SCOPE`
con el permiso que falta en
`requiredScopes`; lo concede un administrador de la organización. El permiso se exige desde la primera llamada, sin periodo
de gracia (las versiones antiguas de esta página hablaban de catorce
días de tolerancia que no se aplican). El catálogo y las reglas de
concesión están en [Permisos de la API](https://os.utilia.ai/docs/developers/api/scopes).

No hay endpoint para borrar archivos desde fuera: no existe
`DELETE /external/v1/files/:id` ni, por tanto, el permiso
`fm:files:delete`, que se retiró del catálogo el 25 de mayo de 2026.

## Endpoints

**`POST /api/external/v1/files/upload`**

Sube un archivo de la aplicación (multipart/form-data). Permiso: fm:files:write.

**`GET /api/external/v1/files/quota`**

Espacio de almacenamiento usado y disponible para la aplicación. Permiso: fm:files:read.

**`GET /api/external/v1/files/:id/url`**

Dirección firmada para descargar el archivo, válida durante una hora. Permiso: fm:files:read.

Parámetro de ruta: `id`.

## Parámetros

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `X-Api-Key` | `header` | Sí | Clave de API de la aplicación externa. |
| `file` | `multipart` | Sí | En la subida: el archivo (campo `file`). Hasta 50 MB por petición, dentro siempre del tamaño máximo y la cuota configurados para la aplicación. |
| `name` | `multipart` | No | En la subida: nombre personalizado del archivo. |
| `metadata` | `multipart` | No | En la subida: metadatos libres del archivo. |
| `id` | `path` | Sí | En `GET /:id/url`: identificador del archivo. |

## Ejemplos

### curl

```bash
# Subir un archivo
curl -X POST 'https://os.utilia.ai/api/external/v1/files/upload' \
  -H 'X-Api-Key: utl_xxx' \
  -F 'file=@/ruta/local/captura.png'

# Consultar la cuota
curl -X GET 'https://os.utilia.ai/api/external/v1/files/quota' \
  -H 'X-Api-Key: utl_xxx'

# Obtener la dirección de descarga
curl -X GET 'https://os.utilia.ai/api/external/v1/files/file-uuid/url' \
  -H 'X-Api-Key: utl_xxx'
```

### Python

```python
import requests

# Subir un archivo
with open('/ruta/local/captura.png', 'rb') as f:
    resp = requests.post(
        'https://os.utilia.ai/api/external/v1/files/upload',
        headers={'X-Api-Key': 'utl_xxx'},
        files={'file': f},
        timeout=30,
    )
    print(resp.json())
```

## Errores

- `400 FILE_MISSING`: la petición de subida no lleva ningún archivo.
- `400 FILE_INVALID_MIME`: ese tipo de archivo no está permitido (solo imágenes, PDF, documentos de Office y texto).
- `400 FILE_TOO_LARGE`: el archivo supera el tamaño máximo por archivo configurado para la aplicación.
- `403 FILE_QUOTA_EXCEEDED`: la subida superaría la cuota de almacenamiento de la aplicación.
- `403 MODULE_NOT_ENABLED`: el plan de tu organización no incluye el Gestor de archivos.
- `403 INSUFFICIENT_SCOPE`: la aplicación no tiene concedido el permiso necesario.
- `404 FILE_NOT_FOUND`: el archivo no existe o no es de esta aplicación (los archivos son de la aplicación, no de cada usuario externo).

El tamaño máximo por archivo y la cuota de la aplicación se ajustan en
la pestaña Avanzado de los ajustes de la aplicación, en
[Ajustes › Creadas por tu empresa](https://os.utilia.ai/dashboard/settings/custom-apps).

## Seguimiento de peticiones

Envía `X-Request-Id` en cada petición para poder cruzarla con los
registros de UTILIA OS. Si no lo envías, la respuesta trae el que se
haya generado.

## Siguiente paso

Tras subir un archivo, adjúntalo a un ticket con `attachmentIds` en el
cuerpo de `POST /external/v1/tickets` o de
`POST /external/v1/tickets/:id/messages` (ver
[Tickets de soporte](https://os.utilia.ai/docs/developers/api/tickets)). Si
algún identificador no es válido o no es de tu aplicación, la respuesta
es `400 FILE_INVALID_ATTACHMENTS`.

> **Atención: Las facturas no admiten adjuntos**
>
> `POST /external/v1/invoices` **no** acepta `attachmentIds`: el campo no
> existe en su contrato y se descarta sin dar error, así que el archivo no
> quedaría adjunto a la factura. Por la API externa, los adjuntos solo
> funcionan en tickets.
