---
title: "Upload a video"
method: POST
path: "/v1/videos/upload"
tags: ["Videos"]
---

# Upload a video

`POST /v1/videos/upload`

## Request

- Base URL: `https://www.postquick.ai/api`
- URL: `https://www.postquick.ai/api/v1/videos/upload`
- Auth: HTTP bearer

Get a video file into PostQuickAI hosted storage. The returned `videoUrl` is on a domain whitelisted by our TikTok developer app, so it can be passed straight to /v1/posts/create as `videoUrl` for TikTok / YouTube / Instagram Reels publishing without URL ownership errors.

Two modes:

A) Inline base64 (MCP / LLM friendly, ≤ 25MB decoded):
   POST `{ data: base64String, contentType?, fileName? }` → get back `{ videoUrl, contentType, size }` in one call. No follow-up PUT needed.

B) Signed URL (required for videos > 25MB, up to 500MB):
   1. POST `{ contentType, size }` → get back `{ uploadUrl, videoUrl, headers }`.
   2. PUT the raw video bytes to `uploadUrl` using the returned `headers`.

Limits: video/mp4, video/webm, video/quicktime. Counts against your storage quota at request time.

Typical MCP flow: call `uploadVideo` with inline base64 `data` → take the returned `videoUrl` → pass it as `videoUrl` to `createPost`.

## Request body

- object
  - `data` string: Raw video bytes encoded as base64 (or a full data: URL). When present, the server uploads inline and returns just `videoUrl` — no follow-up PUT required. Use this for MCP / LLM tool calls. Max 25MB decoded; for larger videos omit `data` and use the signed-URL flow instead.
  - `contentType` 'video/mp4' | 'video/webm' | 'video/quicktime': MIME type of the video. Required for the signed-URL flow. Optional when `data` is provided (sniffed from bytes or data-URL prefix), but supplying it is the most reliable.
  - `size` integer: Size of the video in bytes. Required for the signed-URL flow (storage quota enforcement). Ignored when `data` is provided. Max 500MB.
  - `fileName` string: Optional original file name. Only used to derive a fallback extension.

## Response `201`

Signed upload URL issued.

- object
  - `success` true, required
  - `data` object, required
    - `uploadUrl` string, uri: Pre-signed Supabase Storage URL — only returned in the signed-URL flow. PUT the raw video bytes here within `expiresIn` seconds. Omitted when `data` was uploaded inline.
    - `videoUrl` string, uri, required: Final public URL of the video. For inline uploads it is already populated; for signed-URL uploads it becomes valid once the PUT succeeds. Pass to /v1/posts/create as videoUrl.
    - `method` 'PUT': HTTP method to use for the upload. Only present in the signed-URL flow.
    - `headers` object: Headers that must be sent on the PUT request. Only present in the signed-URL flow.
    - `expiresIn` integer: Seconds until the uploadUrl expires (default 7200 / 2 hours). Only present in the signed-URL flow.
    - `contentType` string, required
    - `size` integer, required

## Other responses

- `400`: Invalid request (missing/invalid contentType, size, or oversized).
- `403`: Storage limit exceeded.

---

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