---
title: "Upload a video"
method: POST
path: "/client/media/video"
tags: ["Account actions"]
---

# Upload a video

`POST /client/media/video`

## Request

- Base URL: `https://api.twittrapi.com`
- URL: `https://api.twittrapi.com/client/media/video`
- Auth: API key in header `X-API-KEY`

Upload a video in 5 MB chunks, wait for X to process it, and get a media ID.

Request bodies are limited to 100 MB at the gateway (413 above that). With base64 overhead that is a video of about 75 MB.

Limits (X): MP4 or MOV, up to 512 MB; up to 140 seconds without Premium, under 4 hours with Premium.

Reference: https://www.twittrapi.com/docs/account-actions/client-media-video

## Query parameters

- `format` 'raw' | 'simple'

## Request body

- object
  - `cookie` string, required: Cookie header of your own logged-in X session: auth_token=...; ct0=... (guest_id is used when present). How to copy it: /docs/x-session.
  - `proxy` string, required: Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request.
  - `media` string, byte, required: The video file, base64-encoded. MP4 or MOV, up to 512 MB (X's limit; the gateway's 100 MB body limit applies first).
  - `media_type` string: MIME type, e.g. video/mp4 or video/quicktime. Optional: the API detects the type from the file's bytes and uses the detected type if this is missing or wrong.
  - `video_duration_ms` integer, required: Length of the video in milliseconds. Must be under 4 hours; X allows 140 seconds without Premium and refuses longer videos itself.

## Response `200`

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With format=simple: {data: {mediaId, mediaKey, expiresAfterSecs}}. Pass mediaId in media_ids of /client/create or /client/reply. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

- RawPassthrough: X's own JSON (raw format, the default), passed through unchanged. The shape is X's and can change; see https://www.twittrapi.com/docs/response-format.

## Other responses

- `400`: The body is not valid JSON. The file breaks X's limits (not MP4 or MOV, over 512 MB, or video_duration_ms not positive or 4 hours or more). Checked before anything is sent to X, refunded. The error names the limit and the file's value, e.g. "Banner must be between 200x100 and 8192x8192 pixels (got 120x60)" or "Profile photo must be at most 2 MB (got 3.4 MB)".
- `401`: Missing or unknown API key. Not charged.
- `402`: No balance left, or the trial expired. Not charged. Top up in the dashboard.
- `403`: Key deactivated, or the endpoint is not in your plan (trial keys and account actions). Not charged.
- `407`: The proxy is unreachable or timed out.
- `408`: X did not finish processing in time. Refunded.
- `422`: A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. media is not valid base64, or X reports that processing failed.
- `429`: Rate limit for your key exceeded. Not charged. No Retry-After header: back off exponentially.
- `500`: Something failed on our side or between us and X. Refunded (400, 407, 408, 409 or a 5xx error are refunded). Retry with backoff.
- `default`: Any status X answers with (for example 401, 403, 404): passed through with X's body.

---

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