---
title: "/creatives/upload"
method: POST
path: "/projects/{project_id}/creatives/upload"
tags: ["Creatives"]
---

# /creatives/upload

`POST /projects/{project_id}/creatives/upload`

## Request

- Base URL: `https://api.productbyte.com/v1`
- URL: `https://api.productbyte.com/v1/projects/{project_id}/creatives/upload`
- Auth: HTTP bearer

Creates a creative from media you supply. Send `multipart/form-data` with a `file` field: a JPEG, PNG, WebP, HEIC, or HEIF image, or an MP4 video.

For a video, also send `width` and `height` in pixels, and optionally `duration_seconds`. Images are measured for you; a video cannot be, and the frame size decides how the creative renders — so an MP4 without them is rejected rather than guessed at.

This is one of the ways to create a creative — see `/creatives/generate` for the other. Either way the returned `id` is a creative id, reusable as `creative_id` on as many posts as you like.

Send an `Idempotency-Key` header so a retry after a timeout collects the original creative instead of uploading the file a second time.

Requires the `creatives:write` scope.

## Path parameters

- `project_id` string, required: The project to create the creative in.

## Response `201`

The creative was created.

- object
  - `id` string, required: Pass this as `creative_id` when creating a post.
  - `name` string, required: What the creative is called in ProductByte.
  - `kind` 'image' | 'carousel' | 'video', required: What the creative is built from. It decides what `formats` means — see below.
  - `formats` object[], required: For an **image** creative these are alternatives: pick the ones whose aspect ratio suits each channel. For a **carousel** they are the deck’s slides, in order, and are published together — pass all of them, not a selection.
    - `id` string, required: Pass this in `format_ids` when creating a post.
    - `kind` 'image' | 'video', required: What kind of media backs this format.
    - `width` number, required: Frame width in pixels.
    - `height` number, required: Frame height in pixels.
    - `language` 'en-GB' | 'en-US' | 'nl-NL' | 'fr-FR' | 'de-DE' | 'es-ES' | 'it-IT' | 'pt-PT' | 'pl-PL' | 'sv-SE' | 'da-DK' | 'fi-FI' | 'no-NO', required: Language of any copy rendered into it.
  - `created_at` string, date-time, required: When the creative was created.

## Other responses

- `400`: The file is missing, too large, or not a supported type (`API_REQUEST_INVALID`).
- `401`: Missing, unknown, revoked, or expired key (`API_KEY_UNAUTHORIZED`).
- `403`: The key lacks `creatives:write`, or the project is outside its allowlist.
- `409`: The `Idempotency-Key` was already used with a different file (`API_IDEMPOTENCY_KEY_REUSED`), or the first request using it is still running (`API_IDEMPOTENCY_KEY_IN_FLIGHT`) — retry in a moment to collect its response.
- `429`: Too many requests for this key (`API_RATE_LIMITED`). Back off and retry; `Retry-After` gives the window length.

---

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