---
title: "Prepare media for a platform"
method: POST
path: "/v1/media/prepare"
tags: ["Media"]
---

# Prepare media for a platform

`POST /v1/media/prepare`

## Request

- Base URL: `https://zernio.com/api`
- URL: `https://zernio.com/api/v1/media/prepare`
- Auth: HTTP bearer

Turn any image or video into a version the target platform accepts, using the same per-platform processing Zernio runs before publishing. Each team can prepare up to 60 files per hour; past that the endpoint answers 429 with `retryAfterSeconds`.

What each target does to a video: `instagram`, `facebook_reel` re-encode to H.264 Main 4.1, 30 fps, 1080 wide (HDR sources only get the size step, so they are not flattened to SDR). `instagram_story` re-encodes a video over 100MB to fit 60 seconds and 100MB. `facebook_story` reframes anything wider than 9:16 onto a 1080x1920 canvas with a blurred background (Facebook otherwise publishes such stories with 0 seconds of video and no error). `threads` re-encodes to the conservative H.264 Baseline profile Threads processes reliably. `bluesky` re-encodes to 1080 wide and at most 180 seconds, then compresses under 50MB if the result is still larger. `telegram` compresses to 50MB and moves the index to the start of the file. The other targets compress only when the file is over the platform limit.

Images are converted to a format the platform ingests (Instagram takes only JPEG, LinkedIn and Google Business reject WebP and AVIF) and compressed under its size limit; `telegram` also fits the width plus height limit and `googlebusiness` enlarges images under its minimum size.

When the file already complies, `changed` is false and `url` is the input URL (after redirects). Otherwise `url` points to the prepared file in Zernio storage, kept under the same retention as uploaded media. The request is synchronous: a long video can take a few minutes. Videos over 200MB are refused with 413, and a video URL whose host sends no Content-Length is refused with 400.

## Request body

- object
  - `url` string, uri, required: Public http(s) URL of the image or video.
  - `target` 'instagram' | 'instagram_story' | 'facebook' | 'facebook_reel' | 'facebook_story' | 'threads' | 'tiktok' | 'linkedin' | 'twitter' | 'bluesky' | 'pinterest' | 'reddit' | 'snapchat' | 'telegram' | 'googlebusiness' | 'youtube_thumbnail', required: Where the media will be published. `twitter`, `googlebusiness` and `youtube_thumbnail` take images only; `facebook_reel` takes videos only.

## Response `200`

Prepared media

- object
  - `target` string, required
  - `url` string, uri, required: URL to publish: the prepared file, or the input URL when nothing needed to change.
  - `changed` boolean, required: Whether the media was processed.
  - `mediaType` 'image' | 'video', required
  - `originalSize` integer, nullable, required: Input size in bytes, null when the host does not report it.
  - `outputSize` integer, nullable, required: Size of the returned file in bytes.

## Other responses

- `400`: Invalid request
- `401`: Missing or invalid API key. `code` is `missing_credentials` when no Authorization header was sent and `invalid_credentials` when the key is unknown, revoked or expired.
- `403`: The API key is a restricted key (zrk_ prefix) and may not perform this operation. Three cases. (1) The operation's resource group (see the operation's x-resource-group) is disabled on the key: fix it by creating a key with the group enabled in the dashboard API keys tab and revoking the old one. (2) The operation is admin-plane (x-resource-group admin-plane: API keys, invites, connected apps, member identity), which is never grantable to restricted keys; the error reads "Restricted API keys cannot manage API keys, invites, or member identity." and the fix is a full-access key or the dashboard, never a new restricted key. (3) On webhook subscription writes, delivery-log reads and replays, a named event maps to a resource group the key does not hold, so a restricted key can never create or edit a subscription broader than itself (a no-messages key cannot subscribe to, test-fire, redeliver or read logs for message.* events).
- `413`: The video is over the 200MB this endpoint prepares.
- `422`: The media could not be processed (for example a corrupt file).
- `429`: The connected account's upstream platform quota is exhausted. Reddit rate-limits per connected Reddit user (1000 requests per 10-minute window), and that budget is shared by every operation using that account. Retry after the window resets rather than retrying immediately; repeated calls while exhausted do not succeed and keep the budget spent. Google Ads: writes and reports run on one developer token shared by every Google Ads account on Zernio. The token holds Standard access (no daily operations cap), so this only happens when Google throttles the token or your ad account. The envelope has `code: rate_limited`, `platform: google`, `details.quotaScope: DEVELOPER` (`ACCOUNT` when it is your own ad account's quota), `details.resetsAt` (ISO instant when Google accepts requests again) and `Retry-After` counting down to it. Retrying earlier cannot succeed. Meta ads: every Meta throttle (codes 4, 17, 32, 613 and the business-use-case codes 80000-80014) returns 429 `rate_limited`, even when Meta itself answers HTTP 400. `Retry-After` comes from Meta's `x-business-use-case-usage` estimate when Meta sends one, otherwise it is Meta's documented 60-second minimum (30 seconds for the one-write-per-30-seconds limit on a single object). Pinterest ads: Pinterest rate-limits per user and app, per category (ads_read, ads_write, ads_analytics). `Retry-After` comes from Pinterest's `x-ratelimit-reset` when it sends one, otherwise 60 seconds.

## Changes

- **2026-10-22** `4e74db179e73`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/media/prepare/post.md)

---

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