---
title: "Start a media upload"
method: POST
path: "/uploads"
tags: ["Uploads"]
---

# Start a media upload

`POST /uploads`

## Request

- Base URL: `https://api.shortsync.app/v1`
- URL: `https://api.shortsync.app/v1/uploads`
- Auth: HTTP bearer

Reserves an upload slot and returns a presigned `PUT` URL targeting
ShortSync's media storage. The request body is **empty** — no
filename or content type is required at this step. Your `PUT` may
set whatever `Content-Type` header you want; ShortSync stores
whatever bytes arrive.

The URL is valid for **1 hour** (see `expires_at`). If your upload
doesn't start within that window, call `POST /uploads` again to
mint a fresh URL — the prior `upload_id` is harmless and will be
garbage collected.

Type validation is **deferred to finalize**. The first time you
call `GET /uploads/{upload_id}` or `POST /posts` with this
`upload_id`, ShortSync reads the stored `Content-Type` and rejects
it with `415 UNSUPPORTED_FILE_TYPE` if it isn't in the allow-list
(`video/mp4`, `video/quicktime`, `video/webm`, `video/x-msvideo`,
`video/x-matroska`, `image/jpeg`, `image/png`, `image/webp`,
`image/heic`, `image/gif`). Bad uploads waste the PUT bandwidth, so
set the `Content-Type` header correctly on your `PUT`.

The same finalize step also validates per-kind size caps (2 GB
video, 20 MB image) and the workspace storage quota, then marks
the upload `ready`. There is no explicit "complete" call.

## Request body

- UploadCreate: `POST /uploads` accepts an empty body or an optional `filename` hint. When provided, `filename` is baked into the presigned URL as an `x-amz-meta-filename` header that the PUT must echo, and surfaces on the final `Upload.filename` field.
  - `filename` string: Optional. Original filename for display only. ShortSync derives container/codec from the PUT's `Content-Type`; the filename does not affect validation.

## Response `201`

Upload reserved. Use `presigned_url` to PUT the bytes.

- UploadTicket
  - `upload_id` string, required
  - `presigned_url` string, uri, required
  - `method` 'PUT', required
  - `expires_at` string, date-time, required
  - `workspace_id` string, required
  - `required_headers` object: Only present when `filename` was supplied to `POST /uploads`. The PUT to `presigned_url` MUST include every header in this map verbatim (currently `x-amz-meta-filename`), otherwise S3 rejects the upload with a signature mismatch.

## Other responses

- `400`: Request body or parameters failed validation.
- `401`: Missing, malformed, or revoked token.
- `403`: Token is valid but cannot access this resource. Common cases: - `INSUFFICIENT_SCOPE` - token lacks the required scope. - `WORKSPACE_NOT_ALLOWED` - account token is allowlisted to other workspaces.
- `429`: Per-key sliding-window rate limit (60 req/min), per-org monthly request quota, or per-user upload cap was hit. `RATE_LIMIT_EXCEEDED` carries the window headers; `QUOTA_EXCEEDED` carries `details.quota` (which counter), `used`, `limit`, `resets_at`, and `upgrade_url`. `UPLOAD_QUOTA_EXCEEDED` (Free tier on `POST /posts` only) carries the same shape plus `details.remaining`.

---

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