---
title: "Step 1 of the compliant upload: get a presigned PUT for the user's video FILE"
method: POST
path: "/api/v1/videos/upload"
---

# Step 1 of the compliant upload: get a presigned PUT for the user's video FILE

`POST /api/v1/videos/upload`

## Request

- Base URL: `https://everpop.app`
- URL: `https://everpop.app/api/v1/videos/upload`
- Auth: HTTP bearer

Everpop clips from the creator's file, never from a YouTube download. PUT exactly expectedBytes to the returned putUrl (15-min validity, ≤5 GB — the most one PUT can carry, files up to 32 GB go through browserUploadUrl — .mp4/.mov/.webm/.mkv/.m4v), send every requiredHeaders entry, and let the HTTP client supply Content-Length. Then call /api/v1/videos/complete. If the PUT times out or the connection drops, or a retried PUT answers 412 (If-None-Match: * means the bytes are already stored), call /api/v1/videos/complete with the same key before anything else: a 400 there means the bytes never landed (PUT again while putUrl is valid, else cancel the slot). At most 3 uploads can be open at once (429 upload_slots).

## Request body

- object
  - `filename` string, required
  - `sizeBytes` integer, required: Exact byte length bound into the signed upload request; one PUT carries at most 5 GB (a bigger file, up to 32 GB, goes through browserUploadUrl); files over 32 GB are not accepted on any path

## Response `200`

Exact signed upload instructions.

- UploadSlotEnvelope
  - `ok` unknown, required
  - `data` object, required
    - `putUrl` string, uri, required
    - `key` string, required
    - `requiredHeaders` object, required
      - `Content-Type` 'video/mp4' | 'video/quicktime' | 'video/webm' | 'video/x-matroska' | 'video/x-m4v', required
      - `If-None-Match` '*', required
    - `expectedBytes` integer, required
    - `expiresInSeconds` unknown, required
    - `next` string, required
    - `browserUploadUrl` string, uri, required: The sign-in-gated Everpop upload page. If you can't send file bytes yourself, give this to the user instead, then list videos to continue. GET /api/v1/videos carries the same link without opening a slot, which counts against the user's open uploads until it expires.

## Other responses

- `401`: Missing, expired, or revoked API key.
- `402`: Subscription or plan does not allow API upload.
- `403`: Account verification, suspension, or write scope blocks upload.
- `409`: Account deletion is in progress; no upload slot was opened.
- `413`: code=file_too_large — files over 32 GB aren't accepted on any Everpop path (re-export smaller, or trim). With reason=single_put the file is over 5 GB, the most one PUT can carry: give the user browserUploadUrl (the dashboard uploader takes up to 32 GB).
- `422`: Missing/invalid size or unsupported file type.
- `429`: code=rate_limited: wait Retry-After (60). code=upload_slots: 3 uploads are already open — finalize or cancel one (a canceled slot counts until its 15-minute window ends), then retry. code=upload_cleanup_backlog or upload_retention_backlog (retryable: false, no Retry-After): earlier canceled uploads are still being cleaned up; stop and tell the user.
- `503`: Storage upload is temporarily unavailable.

---

[API](https://skmtc.dev/everpop/apis/everpop-api.md) · [All operations](https://skmtc.dev/everpop/apis/everpop-api/llms.txt) · [OpenAPI document](https://skmtc.dev/everpop/apis/everpop-api/revisions/1a9fb20867f5?raw)
