# Upload a video file

> Upload the file straight to storage, in one part or in several, and get the video_id to clip.

Use the upload when the video is with you and not at a public link. The file goes straight from your server to storage, through a signed URL, and does not pass through the API. At the end you have a `video_id` identical to the one from `POST /clips/info`.

Accepted formats: `.mp4`, `.mov`, `.webm` and `.mkv`. The maximum size comes from the plan.

### Request the upload URL

```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` says how to send: `single` (one PUT) or `multipart` (several parts). A file that is too large returns `400 FILE_TOO_LARGE` with the ceiling in `extra.max_size`.

### Send the bytes

With `mode: "single"`, one PUT with the same `Content-Type` you declared:

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

With `mode: "multipart"`, see [Large files](#large-files).

### Register the video

After the PUT responded `200`, report the duration, width and height:

```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
  }'
```

The response has the same format as `POST /clips/info`, with `credits_cost` and `current_balance`. Send the `id` in `POST /clips` and continue as in [Clip a video](/en/clipping#create-the-submission).

## Large files

When the file exceeds the limit of a single PUT, `POST /videos/upload` responds `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. Cut the file into pieces of `part_size` bytes. Only the last one can be smaller.
2. Make a PUT for each piece to the URL in the same position in `part_urls`. Position 0 is part 1.
3. Keep the `ETag` header from each response.
4. Close the upload with the parts in order:

```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. Register with `POST /videos/upload/complete`, as in step 3 above.

```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();
```

> Gave up halfway? Call `POST /videos/upload/multipart/abort` with `video_id` and `upload_id`. Without it, the parts already sent keep taking up storage.

## Errors

| Code | When |
|---|---|
| `INVALID_FILE_TYPE` | Extension outside the list. `extra.allowed_extensions` carries the accepted ones. |
| `FILE_TOO_LARGE` | Exceeds the plan ceiling, reported in `extra.max_size`. |
| `FILE_NOT_FOUND` | The complete call arrived before the file was in storage. Check the PUT. |
| `VIDEO_TOO_LONG`, `VIDEO_TOO_SHORT` | Duration outside what is allowed. `extra.duration` and `extra.max_duration` say by how much. |
