> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shortkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Image Carousel

> Create a finished image carousel in one request, from uploaded files, image URLs, or both.

Create an image carousel — a single image or up to 10 — in one request. Send each image as an uploaded file, as a public URL that ShortKit downloads, or a mix of both. ShortKit stores and checks every image and returns the carousel `ready`, published if your organization auto-publishes. A single image is a one-image carousel.

This endpoint requires a **secret key**.

> [!NOTE]
>
> Prefer this endpoint for new integrations. [Upload](https://docs.shortkit.dev/api/content/upload.md#uploading-a-carousel) plus finalize remains available for carousels whose files total more than 30 MB, and [Import](https://docs.shortkit.dev/api/content/import.md) for batches of up to 100 items.

## Request format

Send `multipart/form-data` with:

* a `metadata` part — a JSON string containing the fields below
* one file part per uploaded image, named however you like; each image's `file` field refers to it by that name

If every image is a `sourceUrl`, you can send the same fields as `application/json` instead.

## Request body

**title** `string` *required*

Display title. Max 200 characters.

**images** `object[]` *required*

The carousel's images, in display order. Min 1, max 10. Each object has exactly one of `file` or `sourceUrl`:

* `file` (string) — Name of a file part in this request.
* `sourceUrl` (string) — Public `http` or `https` URL. ShortKit downloads it with a 15-second limit and at most 3 redirects. Private and internal addresses are refused.
* `altText` (string) — Accessibility text for the image. Max 500 characters.

**description** `string`

Optional description. Max 2000 characters.

**tags** `string[]`

Tags for categorization and filtering.

**customMetadata** `object`

Key-value pairs passed through to the feed. Values must be strings.

**section** `string`

Editorial section (e.g., `"Sports"`, `"News"`). Max 200 characters.

**author** `string`

Content author name. Max 200 characters.

**externalId** `string`

Your own identifier for this carousel. Max 255 characters. Repeating a request with the same `externalId` returns the existing carousel with `200` and `replayed: true` instead of creating a second one, so a timed-out request is safe to retry.

**callbackUrl** `string`

Receives a `content.imported` callback once the carousel is created. See [Callback events](https://docs.shortkit.dev/api/content/upload.md#callback-events).

**protected** `boolean`

When `true`, playback is gated behind a short-lived signed URL. When omitted, defaults to your organization's default. See [Protected Content](https://docs.shortkit.dev/guides/protected-content.md).

## Image requirements

* JPEG, PNG, WebP, or GIF. The format is read from the image itself, not from the declared content type.
* At most 10 MB and 40 megapixels per image.
* The whole request body can be at most 30 MB. For larger carousels, send some images as `sourceUrl`.

```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
curl -X POST https://api.shortkit.dev/v1/content/images \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -F 'metadata={
    "title": "Spring drop hero",
    "externalId": "post-98765",
    "customMetadata": {"productId": "SKU-12345"},
    "images": [
      {"file": "hero", "altText": "Red sneaker"},
      {"sourceUrl": "https://cdn.example.com/spring/side.jpg", "altText": "Side view"}
    ]
  };type=application/json' \
  -F hero=@hero.jpg
```

```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import json
import requests

metadata = {
    "title": "Spring drop hero",
    "externalId": "post-98765",
    "customMetadata": {"productId": "SKU-12345"},
    "images": [
        {"file": "hero", "altText": "Red sneaker"},
        {"sourceUrl": "https://cdn.example.com/spring/side.jpg", "altText": "Side view"},
    ],
}

with open("hero.jpg", "rb") as hero:
    resp = requests.post(
        "https://api.shortkit.dev/v1/content/images",
        headers={"Authorization": "Bearer sk_live_your_secret_key"},
        files={
            "metadata": (None, json.dumps(metadata), "application/json"),
            "hero": ("hero.jpg", hero, "image/jpeg"),
        },
    )

carousel = resp.json()["data"]
```

```javascript Node.js theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { readFile } from "fs/promises";

const form = new FormData();
form.append("metadata", JSON.stringify({
  title: "Spring drop hero",
  externalId: "post-98765",
  customMetadata: { productId: "SKU-12345" },
  images: [
    { file: "hero", altText: "Red sneaker" },
    { sourceUrl: "https://cdn.example.com/spring/side.jpg", altText: "Side view" },
  ],
}));
form.append("hero", new Blob([await readFile("hero.jpg")], { type: "image/jpeg" }), "hero.jpg");

const response = await fetch("https://api.shortkit.dev/v1/content/images", {
  method: "POST",
  headers: { "Authorization": "Bearer sk_live_your_secret_key" },
  body: form,
});

const { data: carousel } = await response.json();
```

```json 201 theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "data": {
    "id": "3f2a9c1e-7b4d-4e8a-9f10-2c6d8e1b5a77",
    "externalId": "post-98765",
    "contentType": "carousel",
    "status": "ready",
    "publishStatus": "published",
    "title": "Spring drop hero",
    "description": null,
    "tags": [],
    "customMetadata": {"productId": "SKU-12345"},
    "section": null,
    "author": null,
    "protected": false,
    "images": [
      {
        "id": "b50322db-20dd-47b2-be24-2fb7e3c2b279",
        "position": 0,
        "imageUrl": "https://image.media.shortkit.dev/carousels/5b85b33b78c048ca.jpg",
        "altText": "Red sneaker"
      },
      {
        "id": "e1c8a7f2-9d3b-4c56-8a01-7f2e6b9d4c33",
        "position": 1,
        "imageUrl": "https://image.media.shortkit.dev/carousels/bfd496b145d5416a.jpg",
        "altText": "Side view"
      }
    ],
    "replayed": false,
    "createdAt": "2026-10-27T18:58:09.541869+00:00"
  },
  "meta": {
    "request_id": "req_09eeb351006b"
  }
}
```

```json 422 theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "data": null,
  "meta": {
    "request_id": "req_5c852d7559cc"
  },
  "errors": [
    {
      "code": "invalid_images",
      "message": "1 of 2 images could not be used; nothing was created",
      "details": [
        {
          "position": 1,
          "code": "unsupported_image",
          "message": "File is not a supported image (JPEG, PNG, WebP or GIF)"
        }
      ]
    }
  ]
}
```

## Response

Returns `201` with the created carousel. A repeated `externalId` returns `200` with the existing carousel and `replayed: true`.

**status** `string`

`"ready"` for a newly created carousel.

**publishStatus** `string`

`"published"` when your organization auto-publishes, otherwise `"unpublished"`.

**images** `object[]`

The stored images in display order: `id`, `position`, `imageUrl`, and `altText`.

**replayed** `boolean`

`true` when the response is an existing carousel returned for a repeated `externalId`.

## Errors

The request is all-or-nothing. If any image can't be used, nothing is created and the response lists every failed image.

| Status | Error code | When |
| - | - | - |
| **400** | `invalid_request` | Invalid metadata JSON, a missing `metadata` part, a form field other than `metadata` and the file parts, an unknown field (such as `contentType`), a `file` that names no part or a part no image uses, 0 or more than 10 images, an image with both or neither of `file` and `sourceUrl`, or a non-string `customMetadata` value. Field errors are listed in `details`. |
| **401** | `invalid_api_key` | The `Authorization` header is missing or isn't a valid secret key. A publishable key is rejected. |
| **408** | `request_timeout` | The request body wasn't received within 120 seconds. |
| **409** | `conflict` | `externalId` already belongs to content that isn't an image carousel. |
| **413** | `payload_too_large` | The request body is larger than 30 MB. |
| **415** | `unsupported_media_type` | The request `Content-Type` isn't `multipart/form-data` or `application/json`. |
| **422** | `invalid_images` | One or more images can't be used. `details` lists each failed `position` with a `code`: `too_large`, `unsupported_image`, `fetch_failed`, `blocked_address`, `timeout`, or `invalid_url`. |
| **429** | `rate_limited` | Too many carousels are being created at once. Retry after the number of seconds in the `Retry-After` header. |
| **503** | `service_unavailable` | Image storage is briefly unavailable. Retry the request. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.