# Sube un archivo de video

> Sube el archivo directo al storage, en una parte o en varias, y recibe el video_id para generar clips.

Usa la subida cuando el video lo tienes tú y no está en un enlace público. El archivo va directo de tu servidor al storage, por URL firmada, y no pasa por la API. Al final tienes un `video_id` igual al de `POST /clips/info`.

Formatos aceptados: `.mp4`, `.mov`, `.webm` y `.mkv`. El tamaño máximo viene del plan.

### Pide la URL de subida

```bash
curl -X POST https://api.cut.pro/api/v1/videos/upload \
  -H "X-Api-Key: $CUTPRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "file_name": "podcast-ep-42.mp4", "file_size": 734003200, "content_type": "video/mp4" }'
```

```json title="200 OK"
{
  "mode": "single",
  "video_id": "7412911002237764",
  "upload_url": "https://...",
  "expires_in": 3600
}
```

`mode` dice cómo enviar: `single` (un PUT) o `multipart` (varias partes). Un archivo demasiado grande responde `400 FILE_TOO_LARGE` con el tope en `extra.max_size`.

### Envía los bytes

Con `mode: "single"`, un PUT con el mismo `Content-Type` que declaraste:

```bash
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: video/mp4" \
  --upload-file podcast-ep-42.mp4
```

Con `mode: "multipart"`, ve [Archivos grandes](#archivos-grandes).

### Registra el video

Después de que el PUT respondió `200`, informa duración, ancho y alto:

```bash
curl -X POST https://api.cut.pro/api/v1/videos/upload/complete \
  -H "X-Api-Key: $CUTPRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "video_id": "7412911002237764",
    "file_name": "podcast-ep-42.mp4",
    "duration": 3712,
    "width": 1920,
    "height": 1080
  }'
```

La respuesta tiene el mismo formato que `POST /clips/info`, con `credits_cost` y `current_balance`. Envía el `id` en `POST /clips` y sigue como en [Genera clips de un video](/es/clipping#crea-el-envío).

## Archivos grandes

Cuando el archivo supera el límite de un solo PUT, `POST /videos/upload` responde `mode: "multipart"`:

```json title="200 OK"
{
  "mode": "multipart",
  "video_id": "7412911002237764",
  "upload_id": "2~kB1x...",
  "part_urls": ["https://...", "https://...", "https://..."],
  "part_size": 104857600,
  "expires_in": 3600
}
```

1. Corta el archivo en pedazos de `part_size` bytes. Solo el último puede ser menor.
2. Haz un PUT de cada pedazo en la URL de la misma posición en `part_urls`. La posición 0 es la parte 1.
3. Guarda el header `ETag` de cada respuesta.
4. Cierra la subida con las partes en orden:

```bash
curl -X POST https://api.cut.pro/api/v1/videos/upload/multipart/complete \
  -H "X-Api-Key: $CUTPRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "video_id": "7412911002237764",
    "upload_id": "2~kB1x...",
    "parts": [
      { "part_number": 1, "etag": "\"a54357aff0632cce46d942af68356b38\"" },
      { "part_number": 2, "etag": "\"0c78aef83f66abc1fa1e8477f296d394\"" },
      { "part_number": 3, "etag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"" }
    ]
  }'
```

5. Registra con `POST /videos/upload/complete`, como en el paso 3 de arriba.

```js title="multipart.mjs"

const file = await open("podcast-ep-42.mp4");
const parts = [];
for (const [index, url] of upload.part_urls.entries()) {
	const buffer = Buffer.alloc(upload.part_size);
	const { bytesRead } = await file.read(buffer, 0, upload.part_size, index * upload.part_size);
	const response = await fetch(url, { method: "PUT", body: buffer.subarray(0, bytesRead) });
	parts.push({ part_number: index + 1, etag: response.headers.get("ETag") });
}
await file.close();
```

> ¿Desististe a mitad de camino? Llama a `POST /videos/upload/multipart/abort` con `video_id` y `upload_id`. Sin eso, las partes ya enviadas siguen ocupando el storage.

## Errores

| Code | Cuándo |
|---|---|
| `INVALID_FILE_TYPE` | Extensión fuera de la lista. `extra.allowed_extensions` trae las aceptadas. |
| `FILE_TOO_LARGE` | Supera el tope del plan, informado en `extra.max_size`. |
| `FILE_NOT_FOUND` | El complete llegó antes de que el archivo estuviera en el storage. Revisa el PUT. |
| `VIDEO_TOO_LONG`, `VIDEO_TOO_SHORT` | Duración fuera de lo permitido. `extra.duration` y `extra.max_duration` dicen cuánto. |
