# Memes.ai API Reference

> Generate on-brand ad creative — meme-style ad images, edits, short-form video, multi-slide carousels, storyboards, and brand kits — from a prompt, a website URL, and reference images. One key. One async pattern. A handful of calls to a finished campaign.

**Base URL:** `https://api.memes.media/v1`
**Docs:** `https://studio.memes.media/developers`
**Recipes:** Use-case cookbook → [recipes.md](recipes.md) — product-feed ads, Meta creative tests, Reels video, feed-scale automation.
**Status:** Beta — v1 endpoints are open for early integrations; additive fields may ship before GA.

---

## Table of contents

- [Introduction](#introduction)
- [Quickstart](#quickstart)
- [A complete campaign](#a-complete-campaign)
- [Authentication](#authentication)
- [Making requests](#making-requests)
- [Generations (the async model)](#generations)
- [Models](#models)
- [Errors](#errors)
- [Webhooks](#webhooks)
- **Resources**
  - [Images](#images)
  - [Memes](#memes)
  - [Videos](#videos)
  - [Carousels](#carousels)
  - [Storyboards](#storyboards)
  - [Brand Kits](#brand-kits)
  - [Prompt Enhancement](#prompt-enhancement)
  - [Images as inputs](#images-as-inputs)
  - [Account & Usage](#account--usage)
- [SDKs & MCP](#sdks--mcp)

---

## Introduction

The Memes.ai API turns a brand and an idea into finished ad creative. It is the same engine behind [studio.memes.media](https://studio.memes.media), exposed as a clean, key-authed REST API.

You can:

- **Generate ad images** from a prompt, a brand profile, and up to four reference images (product shots, logos, style templates).
- **Generate memes** — the flagship branded-ad format, 1–4 variants per call, with full edit/reframe lineage.
- **Generate video** — text-to-video and image-to-video (animate an existing asset).
- **Build carousels** — a free strategy *plan* step, then a billable per-slide *render*.
- **Compose storyboards** — a single 3–4 panel narrative ad image.
- **Persist brand kits** — reusable brand profiles (logo, colors, voice, audience, products) that ground every generation.
- **Enhance prompts** — turn a rough idea into a production-ready creative brief.

Three principles run through the whole API:

1. **One async pattern.** Every creative operation returns a [Generation](#generations) — a job you poll (or receive via [webhook](#webhooks)). You never hold an HTTP connection open waiting for a render.
2. **One asset shape.** Every output — image, video, carousel slide, analysis — is a normalized [Asset](#the-asset-object). Write your handling code once.
3. **One brand context.** Pass a `brand_kit_id` (or inline `brand_context`) to any endpoint and the output is on-brand by construction.

---

## Quickstart

### 1. Get an API key

Create a secret key in the [API settings](https://studio.memes.media/settings/api) of your workspace. Keys look like `sk_live_…` (production) or `sk_test_…` (test mode). Treat them like passwords — the full key is shown **once**.

### 2. Generate your first meme

```bash
curl https://api.memes.media/v1/memes \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f1c9a52-0e9b-4b8d-9a1a-1f2e3d4c5b6a" \
  -d '{
    "prompt": "When the coffee finally kicks in on a Monday",
    "brand_context": { "company_name": "Dawn Roasters", "industry": "coffee" },
    "aspect_ratio": "1:1",
    "count": 2
  }'
```

The API responds immediately with a queued **Generation**:

```json
{
  "generation": {
    "id": "gen_8f3k1m2p7q",
    "object": "generation",
    "type": "meme",
    "status": "queued",
    "model": "forge-image",
    "requested_count": 2,
    "completed_count": 0,
    "output": [],
    "created_at": "2026-06-18T17:04:22Z"
  },
  "usage": { "credits_used": 0, "credits_remaining": 480, "interval": "rolling_30_days" },
  "meta": { "request_id": "req_b2c4", "livemode": true }
}
```

### 3. Poll for the result

```bash
curl https://api.memes.media/v1/generations/gen_8f3k1m2p7q \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

When `status` is `succeeded`, `output[]` holds your finished assets:

```json
{
  "generation": {
    "id": "gen_8f3k1m2p7q",
    "type": "meme",
    "status": "succeeded",
    "completed_count": 2,
    "output": [
      { "id": "asset_a1", "type": "image", "url": "https://cdn.memes.media/a1.png", "aspect_ratio": "1:1" },
      { "id": "asset_a2", "type": "image", "url": "https://cdn.memes.media/a2.png", "aspect_ratio": "1:1" }
    ],
    "completed_at": "2026-06-18T17:04:41Z"
  },
  "usage": { "credits_used": 2, "credits_remaining": 478, "interval": "rolling_30_days" },
  "meta": { "request_id": "req_d4e1", "livemode": true }
}
```

That's the whole loop: **submit → poll → use `output[]`**. Prefer push over polling? [Register a webhook](#webhooks) and we'll call you on `generation.completed`.

---

## A complete campaign

A realistic flow: save a brand once, generate variants, then resize the winner for Stories. Notice how each id threads into the next call — a `brand_kit_id` grounds the generation, and an `output[].id` (`asset_…`) becomes the input to the reframe.

**1 · Save the brand once.** The returned `id` is reusable forever.

```bash
curl https://api.memes.media/v1/brand-kits \
  -H "Authorization: Bearer sk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "Dawn Roasters", "company_name": "Dawn Roasters",
        "website_url": "https://dawnroasters.example",
        "brand_colors": ["#3B2A1A", "#E8C39E"], "tone_of_voice": "warm, witty" }'
# -> { "brand_kit": { "id": "bk_dawn_roasters", "object": "brand_kit", ... } }
```

**2 · Generate four on-brand variants.**

```bash
curl https://api.memes.media/v1/memes \
  -H "Authorization: Bearer sk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9b1d…" \
  -d '{ "prompt": "POV: cold brew the night before a deadline",
        "brand_kit_id": "bk_dawn_roasters", "count": 4 }'
# -> { "generation": { "id": "gen_8f3k1m2p7q", "status": "queued" } }
```

**3 · Poll until terminal.**

```bash
curl https://api.memes.media/v1/generations/gen_8f3k1m2p7q \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
# -> status "succeeded", output: [ { "id": "asset_a1", ... }, { "id": "asset_a2", ... }, ... ]
```

**4 · Reframe the winner for Stories.** Any `output[].id` can be reused as an input.

```bash
curl https://api.memes.media/v1/images/reframe \
  -H "Authorization: Bearer sk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{ "image_id": "asset_a1", "aspect_ratio": "9:16" }'
# Reframe is synchronous (fast) -> 200 with the finished asset, no polling:
# -> { "generation": { "id": "gen_re55", "type": "reframe", "status": "succeeded", "output": [ { "id": "asset_re55", ... } ] } }
```

From here, [animate](#videos) the winner into video (`POST /videos/animate`), or fan out a [carousel](#carousels) from the same brand kit. Every output asset is reusable as the next call's input.

---

## Authentication

The API authenticates with secret keys sent as a bearer token:

```bash
curl https://api.memes.media/v1/account \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

| | |
|---|---|
| **Header** | `Authorization: Bearer <key>` |
| **Live keys** | `sk_live_…` — bill and affect your live workspace |
| **Test keys** | `sk_test_…` — run the same code paths in a sandbox; outputs are watermarked |
| **Visibility** | The full secret is shown once at creation. Store only the key; we store a one-way hash. |
| **Rotation** | Create a new key, deploy it, then revoke the old one. Revocation is immediate. |

Every key belongs to a **workspace**. The `livemode` boolean on every response tells you whether a live or test key was used.

> **Keep keys server-side.** Secret keys grant full access to a workspace's generation and billing. Never embed them in browser, mobile, or other client-side code. If a key leaks, revoke it immediately.

`401 authentication_error` is returned for a missing, malformed, expired, or revoked key.

---

## Making requests

### Base URL & versioning

All requests go to `https://api.memes.media/v1`. The major version is in the path. Breaking changes ship as a new major version (`/v2`); we won't break `/v1` under you.

For dated, backwards-compatible refinements you can pin a version:

```
Memes-Version: 2026-06-18
```

Omit it to use your workspace's pinned default, or the latest if none is set.

### Request & response format

Requests are JSON (`Content-Type: application/json`). Responses are JSON and always include a `meta` object:

```json
"meta": { "request_id": "req_b2c4f9", "livemode": true }
```

Include the `request_id` in any support conversation — it pins the exact call.

### Idempotency

Safely retry any `POST` by sending an `Idempotency-Key` header (use a UUID per logical operation):

```bash
-H "Idempotency-Key: 3f1c9a52-0e9b-4b8d-9a1a-1f2e3d4c5b6a"
```

We cache the first response for a key for **24 hours** and replay it on retries, so a dropped connection never double-charges or double-generates. Reusing a key with a *different* body returns `409 idempotency_conflict`.

### Rate limits

Each key gets a sliding-window limit. Every response carries:

```
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 41
```

Exceeding it returns `429 rate_limited` with a `Retry-After` header (seconds). This is independent of your credit quota — hitting your plan's credit ceiling returns `429 quota_exceeded` instead.

### Pagination

List endpoints are cursor-paginated:

```bash
curl "https://api.memes.media/v1/memes?limit=40&starting_after=asset_a2" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

```json
{ "data": [ ... ], "has_more": true, "next_cursor": "asset_xy9" }
```

| Param | Type | Description |
|---|---|---|
| `limit` | integer | Page size, 1–100. Default 40. |
| `starting_after` | string | Object id to start after (from `next_cursor`). |
| `q` | string | Free-text search, ≤200 chars (where supported). |
| `order` | string | `newest` (default) or `oldest`. |

The cursor is always the `id` of the last object in the page you received — pass it as `starting_after` to fetch the next page, and stop when `has_more` is `false`.

---

## Generations

A **Generation** is the single resource for every long-running operation. Whether you're rendering one image or a ten-slide carousel, you get back a Generation and read results from it.

### Lifecycle

```
POST /v1/<resource>          ->  202  { generation: { status: "queued" } }
GET  /v1/generations/{id}    ->  200  { generation: { status, output } }
webhook generation.completed ->  push (optional)
```

### Status values

| Status | Meaning |
|---|---|
| `queued` | Accepted and credits reserved; not started yet. |
| `processing` | Actively rendering. |
| `succeeded` | All requested outputs produced. |
| `partial` | Some of N outputs succeeded (e.g. 3 of 4 variants); unfilled credits are refunded. |
| `failed` | Terminal failure; reserved credits are refunded. |
| `canceled` | Canceled before completion. |

`succeeded`, `partial`, `failed`, and `canceled` are terminal. Stop polling once you reach one.

### Credits

Credits are **reserved when a job is queued** and **settle when it completes** — you're charged only for delivered outputs, and reserved credits are refunded on `failed`, `canceled`, or the unfilled portion of a `partial`. That's why a job shows `credits_used: 0` at submit and the final figure once terminal. Every response carries a `usage` block:

```json
"usage": { "credits_used": 2, "credits_remaining": 478, "interval": "rolling_30_days", "reset_at": "2026-07-01T00:00:00Z" }
```

### The Generation object

```json
{
  "id": "gen_8f3k1m2p7q",
  "object": "generation",
  "type": "meme",
  "status": "succeeded",
  "model": "forge-image",
  "requested_count": 4,
  "completed_count": 4,
  "failed_count": 0,
  "output": [ /* Asset objects */ ],
  "error": null,
  "created_at": "2026-06-18T17:04:22Z",
  "completed_at": "2026-06-18T17:04:53Z"
}
```

`type` is one of `meme`, `image`, `edit`, `reframe`, `video`, `carousel`, `storyboard`.

### The Asset object

Every output, for every endpoint, has the same shape:

```json
{
  "id": "5f2b1c34-7a90-4c0e-9b21-2e6f3a8d1c44",
  "type": "image",
  "url": "https://cdn.memes.media/a1.png",
  "aspect_ratio": "1:1",
  "slide_index": null,
  "parent_id": null,
  "model": "forge-image",
  "created_at": "2026-06-18T17:04:41Z"
}
```

`type` is `image`, `video`, `carousel_slide`, or `analysis`. `slide_index` is set for carousel slides; `parent_id` links edits/reframes back to their source. All resource ids (`id`, generation ids, brand-kit ids, etc.) are **UUID strings** — the short `asset_…` / `gen_…` ids shown elsewhere in this doc are illustrative only. Pixel `width`/`height` and `thumbnail_url` are not currently returned; `aspect_ratio` is the size hint.

### Endpoints

| Method · Path | Purpose |
|---|---|
| `GET /generations` | List jobs. Filter by `type` and `status`. |
| `GET /generations/{id}` | Poll any job; returns the Generation with normalized `output[]`. |
| `POST /generations/{id}/cancel` | Cancel a `queued` or `processing` job. |

```bash
curl https://api.memes.media/v1/generations/gen_8f3k1m2p7q \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

**Polling guidance:** poll every 2–3 seconds and stop at the first terminal status. Images and memes typically finish in 10–30s, video in 1–4 minutes. For production, prefer [webhooks](#webhooks).

---

## Models

Choose a model with the `model` parameter, or omit it to use the smart default for each endpoint. Models belong to the **Forge** family — each named by capability, with a faster `-fast` variant where one is offered.

| Model | Best for |
|---|---|
| `forge-image` | Primary text-to-image ad creative; reference, product, and logo conditioning. *(default for Images & Memes)* |
| `forge-image-fast` | Faster, cheaper image generation — quick variants and iteration. |
| `forge-edit` | Instruction edits, mask inpainting, and aspect-ratio reframing. |
| `forge-video` | Text-to-video and multi-reference (2–4 image) video. |
| `forge-motion` | Image-to-video — animate a single existing asset. |
| `forge-prompt` | Prompt enhancement — rough idea to production-ready brief. |
| `forge-plan` | Carousel strategy planning. |
| `forge-copy-fast` | Fast brand/creative analysis (e.g. URL analysis). |

Most endpoints pick the right model for you — you only set `model` to choose between `forge-image` and `forge-image-fast`. Models route to the best available engine internally and fail over automatically; your code references only the stable Forge id. We may improve the engine behind a Forge id at any time — the id and its contract stay constant.

---

## Errors

The API uses conventional HTTP status codes and returns a single error envelope:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_required_param",
    "message": "Either prompt, brand_context, or reference_images is required.",
    "param": "prompt",
    "request_id": "req_8f2a1c"
  }
}
```

| HTTP | `type` | Common `code`s | When |
|---|---|---|---|
| `400` | `invalid_request_error` | `missing_required_param`, `invalid_aspect_ratio`, `invalid_mask`, `count_out_of_range` | Malformed or missing input. |
| `401` | `authentication_error` | `invalid_api_key`, `expired_api_key`, `revoked_api_key` | Bad or missing key. |
| `403` | `permission_error` | `insufficient_scope`, `plan_feature_unavailable` | Key lacks a scope, or the feature isn't on your plan. |
| `404` | `not_found_error` | `resource_not_found` | Unknown or unowned id. |
| `409` | `conflict_error` | `idempotency_conflict`, `already_finished` | Idempotency reuse with a new body; cancel after terminal. |
| `422` | `generation_error` | `content_blocked`, `generation_failed`, `generation_timed_out` | Safety block or render failure. |
| `429` | `rate_limit_error` | `rate_limited` | Per-key rate limit. See `Retry-After`. |
| `429` | `quota_error` | `quota_exceeded`, `feature_quota_exceeded` | Credit or feature quota exhausted. |
| `500` | `api_error` | `internal_error` | Unexpected error on our side. |
| `503` | `service_unavailable` | `temporarily_unavailable` | At capacity; retry shortly. |

`request_id` is always present and matches `meta.request_id`. Build retries around `429` (respect `Retry-After`) and `503` (exponential backoff); `400`–`404` are not retryable without changes.

---

## Webhooks

Rather than polling, register an HTTPS endpoint and we'll POST a signed event when a job reaches a terminal state.

### Events

| Event | Fires when |
|---|---|
| `generation.completed` | A job `succeeded`. |
| `generation.partial` | A job finished with some outputs (`partial`). |
| `generation.failed` | A job `failed` or timed out. |
| `generation.canceled` | A job was canceled. |

### Payload

```json
{
  "id": "evt_2x9k",
  "type": "generation.completed",
  "created_at": "2026-06-18T17:04:53Z",
  "livemode": true,
  "data": { "generation": { /* full Generation object */ } }
}
```

### Verifying signatures

Every delivery is signed with your endpoint's secret (`whsec_…`, shown once when the endpoint is created) and carries three headers: `svix-id`, `svix-timestamp`, and `svix-signature`. The simplest, recommended way to verify is the official libraries — available for JavaScript/TypeScript, Python, Go, Rust, Ruby, PHP, Java, C#, Kotlin, and Elixir:

```js
import { Webhook } from "svix";

const wh = new Webhook(process.env.MEMES_WEBHOOK_SECRET); // whsec_…
// Pass the RAW request body and the svix-* headers.
const event = wh.verify(rawBody, {
  "svix-id": req.headers["svix-id"],
  "svix-timestamp": req.headers["svix-timestamp"],
  "svix-signature": req.headers["svix-signature"],
}); // throws on an invalid signature or a stale timestamp; returns the parsed event
```

```bash
npm install svix      # or: pip install svix
```

To verify manually: `svix-signature` is a space-separated list of `v1,<base64>` signatures (a list so secrets can rotate), where each is `HMAC-SHA256(secret, "{svix-id}.{svix-timestamp}.{raw_body}")` and `secret` is the base64 bytes following the `whsec_` prefix. Reject the request if `|now − svix-timestamp| > 5 minutes` (replay protection).

Deliveries are **at-least-once** — dedupe on `svix-id` (or the event `id`). Failed deliveries retry with exponential backoff for ~24 hours, and every attempt is logged and replayable from your webhook portal.

### Managing endpoints

| Method · Path | Purpose |
|---|---|
| `GET /webhooks` | List endpoints. |
| `POST /webhooks` | Register an endpoint; returns the signing secret once. |
| `GET /webhooks/{id}` | Fetch an endpoint. |
| `PATCH /webhooks/{id}` | Update url, events, or enabled state. |
| `DELETE /webhooks/{id}` | Remove an endpoint. |
| `POST /webhooks/{id}/test` | Send a test event. |

```bash
curl https://api.memes.media/v1/webhooks \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/memes",
    "events": ["generation.completed", "generation.failed"]
  }'
```

---

## Images

Generate and edit ad images. Pass a prompt, a brand context, and up to four reference images.

| Method · Path | Purpose |
|---|---|
| `POST /images` | Text-to-image / reference-conditioned ad image (1–4). |
| `POST /images/edits` | Instruction edit or mask inpaint → a child asset. |
| `POST /images/reframe` | Outpaint to a new aspect ratio. |
| `GET /images` | List generated images. |
| `GET /images/{id}` | Fetch one image asset. |

### Create an image

`POST /images`

| Param | Type | Required | Description |
|---|---|---|---|
| `prompt` | string | one of* | Creative direction. |
| `brand_kit_id` | string | one of* | A saved [brand kit](#brand-kits) to ground the image. |
| `brand_context` | object | one of* | Inline brand profile if you don't have a kit. |
| `reference_images` | array | no | Up to 4 `{ type: "ref" \| "template" \| "logo", source }`, where `source` is an [https URL or a workspace asset id](#images-as-inputs). |
| `aspect_ratio` | enum | no | `auto`, `1:1`, `2:1`, `3:1`, `2:3`, `3:2`, `3:4`, `4:5`, `4:3`, `16:9`, `9:16`, `21:9`. Default `1:1`. |
| `count` | integer | no | Variants, 1–4. Default 1. |
| `quality` | enum | no | `fast`, `standard` (default), `premium`. |
| `model` | enum | no | `forge-image` (default) or `forge-image-fast`. |
| `controls` | object | no | Advanced fine-tuning knobs (`platform`, `visual_taste`, `product_role`, `brand_strictness`, `copy_density`, `reference_strength`, `risk_level`). Optional; accepted values are evolving during Preview. |

\* At least one of `prompt`, `brand_kit_id` / `brand_context`, or `reference_images`.

> **Reuse outputs as inputs.** Any `output[].id` (`asset_…`) from a previous generation can be passed as a `reference_images[].source`, an `image_id` for edits/reframes, or an `image_id` to [animate](#videos) — so you can iterate on generated creative without re-uploading.

```bash
curl https://api.memes.media/v1/images \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Minimalist product hero on a sunlit kitchen counter",
    "brand_kit_id": "bk_dawn_roasters",
    "reference_images": [{ "type": "logo", "source": "https://yourcdn.example/logo.png" }],
    "aspect_ratio": "4:5",
    "count": 3
  }'
```

Returns `202` with a Generation (`type: "image"`). Credits: 1 per delivered image.

### Edit an image

`POST /images/edits` — apply a natural-language instruction, optionally within a mask.

| Param | Type | Required | Description |
|---|---|---|---|
| `image_id` | string | yes | The asset to edit. |
| `instruction` | string | yes | What to change, ≤2000 chars. |
| `mask` | string | no | A PNG mask as a `data:` URL; enables inpainting within the masked region. |
| `reference_images` | array | no | Up to 4 references. |
| `aspect_ratio` | enum | no | Output ratio. |

Returns `202` (`type: "edit"`). The new asset's `parent_id` points to `image_id`. Credits: 1.

### Reframe an image

`POST /images/reframe` — outpaint into a new aspect ratio.

| Param | Type | Required | Description |
|---|---|---|---|
| `image_id` | string | yes | The asset to reframe. |
| `aspect_ratio` | enum | yes | One of `9:16`, `16:9`, `1:1`, `4:5`, `2:3`, `3:2`. Must differ from the source. |

Returns `202` (`type: "reframe"`). Credits: 1.

---

## Memes

Memes are the flagship branded-ad format: a brand-grounded image with full edit/reframe lineage. Functionally, Memes are Images with meme defaults plus lineage fields.

| Method · Path | Purpose |
|---|---|
| `POST /memes` | Generate 1–4 brand-grounded meme ads. |
| `GET /memes` | List memes (search, exclude carousel-derived). |
| `GET /memes/{id}` | Fetch one meme. |
| `GET /memes/{id}/lineage` | Full edit/reframe family tree. |
| `DELETE /memes/{id}` | Soft-delete a meme. |

### Create memes

`POST /memes`

| Param | Type | Required | Description |
|---|---|---|---|
| `prompt` | string | one of* | The joke, angle, or message. |
| `brand_kit_id` | string | one of* | Brand kit to ground with. |
| `brand_context` | object | one of* | Inline brand profile. |
| `template_prompt` | string | no | A meme-template directive to anchor the format. |
| `reference_images` | array | no | Up to 4 typed references (`ref`/`template`/`logo`). |
| `aspect_ratio` | enum | no | Default `1:1`. |
| `count` | integer | no | 1–4. Default 1. |
| `enhance` | boolean | no | Run [prompt enhancement](#prompt-enhancement) first. |

\* At least one of `prompt`, `brand_kit_id` / `brand_context`, or `template_prompt`.

```bash
curl https://api.memes.media/v1/memes \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "POV: you discover cold brew the night before a deadline",
    "brand_kit_id": "bk_dawn_roasters",
    "aspect_ratio": "1:1",
    "count": 4,
    "enhance": true
  }'
```

Returns `202` (`type: "meme"`, `model: "forge-image"`). Credits: 1 per delivered variant.

### Meme lineage

`GET /memes/{id}/lineage` returns the root meme plus every edit and reframe derived from it, in chronological order — the data behind an edit-history view.

---

## Videos

Generate short-form video from text, references, or an existing image.

| Method · Path | Purpose |
|---|---|
| `POST /videos` | Text-to-video or reference-to-video. |
| `POST /videos/animate` | Image-to-video from an existing asset. |
| `GET /videos` | List video jobs. |
| `GET /videos/{id}` | Fetch one video. |
| `DELETE /videos/{id}` | Delete a video (refunds its credits). |

### Create a video

`POST /videos`

| Param | Type | Required | Description |
|---|---|---|---|
| `prompt` | string | one of* | What the video shows. |
| `business_url` | string | one of* | A website to ground the concept in. |
| `brand_kit_id` / `brand_context` | string / object | one of* | Brand grounding. |
| `reference_images` | array | no | Up to 4 references. Their presence routes to reference-to-video. |
| `aspect_ratio` | enum | no | `1:1`, `16:9` (default), `9:16`, `4:3`, `3:4`, `3:2`, `2:3`. |
| `duration` | integer | no | Seconds, 1–15. Default 5. |
| `resolution` | enum | no | `480p` or `720p` (default). |

\* At least one of `prompt`, `business_url`, or `brand_kit_id` / `brand_context`.

The model (`forge-video` or `forge-motion`) is selected automatically from your inputs — you don't set it.

```bash
curl https://api.memes.media/v1/videos \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A latte being poured in slow motion, steam rising, warm morning light",
    "brand_kit_id": "bk_dawn_roasters",
    "aspect_ratio": "9:16",
    "duration": 6
  }'
```

Returns `202` (`type: "video"`). Credits: 10 per completed video. Video typically completes in 1–4 minutes — use a [webhook](#webhooks).

### Animate an image

`POST /videos/animate`

| Param | Type | Required | Description |
|---|---|---|---|
| `image_id` | string | yes | A workspace-owned image asset to animate. |

Runs at 5s / 720p with `forge-motion`. Returns `202` (`type: "video"`).

---

## Carousels

Carousels are two steps by design: a **free** strategy plan, then a billable render that fans out one image per slide.

| Method · Path | Purpose |
|---|---|
| `POST /carousels/plan` | Generate slide strategy (no images, free). |
| `POST /carousels` | Render: persist the set and fan out N slide jobs. |
| `GET /carousels` | List carousel sets with hydrated slides. |
| `GET /carousels/{id}` | One set with per-slide status and URLs. |
| `DELETE /carousels/{id}` | Soft-delete a set. |

### Plan a carousel

`POST /carousels/plan`

| Param | Type | Required | Description |
|---|---|---|---|
| `brief` | string | yes | What the carousel should accomplish, ≤2500 chars. |
| `business_website` | string | no | URL to ground the plan. |
| `goal` | enum | no | `awareness`, `leads` (default), `sales`, `followers`, `engagement`. |
| `platform` | enum | no | `ig-portrait`, `ig-square`, `linkedin`, `tiktok`. |
| `aspect_ratio` | enum | no | Slide ratio. |
| `slide_count` | integer | no | 3–10. Default 5. |
| `brand_kit_id` / `brand_context` | string / object | no | Brand grounding. |

```bash
curl https://api.memes.media/v1/carousels/plan \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brief": "5 myths about cold brew, debunked, for first-time buyers",
    "goal": "leads",
    "platform": "ig-portrait",
    "slide_count": 5,
    "brand_kit_id": "bk_dawn_roasters"
  }'
```

Returns `200` synchronously (planning is free) with a plan:

```json
{
  "plan": {
    "id": "plan_71kd",
    "concept_title": "Cold Brew Myths, Busted",
    "viral_mechanic": "myth/fact reveal",
    "audience": "first-time cold brew buyers",
    "cta": "Try the starter kit",
    "slides": [
      { "slide_index": 0, "role": "hook", "on_image_text": "5 cold brew myths", "visual_direction": "..." }
    ]
  },
  "model": "forge-plan"
}
```

### Render a carousel

`POST /carousels` with either a `plan_id` (from `POST /carousels/plan`) or inline `plan` fields, plus optional `quality`, `controls`, and `reference_images`. Returns `202` with a `carousel_set` whose `id` is the pollable set id. Poll `GET /carousels/{id}`; each slide's `asset` fills in as it completes (and each slide id is independently pollable via `GET /generations/{id}`). Credits: 1 × `slide_count`.

---

## Storyboards

A storyboard is a single composite 3–4 panel narrative ad image (not a fan-out).

| Method · Path | Purpose |
|---|---|
| `POST /storyboards` | Generate one multi-panel storyboard image. |
| `GET /storyboards/{id}` | Fetch the result. |

`POST /storyboards`

| Param | Type | Required | Description |
|---|---|---|---|
| `goal` | string | one of* | What the story should land. |
| `brand_kit_id` / `brand_context` | string / object | one of* | Brand grounding. |
| `reference_images` | array | no | Typed references. |
| `aspect_ratio` | enum | no | `16:9`, `9:16`, `1:1`. |
| `preset` | enum | no | `auto`, `comic-strip`, `problem-solution`, `before-after`, `how-it-works`, `customer-journey`, `testimonial-sequence`, `founder-story`. |
| `panel_count` | integer | no | 3 or 4. Default 3. |
| `tone` | enum | no | Narrative tone, e.g. `playful`, `bold`, `warm`, `professional`. |
| `text_density` | enum | no | `minimal`, `balanced`, `rich`. |
| `product_role` | enum | no | How prominently the product features. |
| `visual_style` | enum | no | `comic-strip` or `clean-storyboard`. |

Returns `202` (`type: "storyboard"`). Credits: 4.

---

## Brand Kits

Brand kits are reusable brand profiles — logo, colors, voice, audience, products — that ground any generation. They're **free and unlimited** on every plan.

| Method · Path | Purpose |
|---|---|
| `GET /brand-kits` | List kits. |
| `POST /brand-kits` | Create a kit. |
| `GET /brand-kits/{id}` | Fetch a kit. |
| `PATCH /brand-kits/{id}` | Partial update. |
| `DELETE /brand-kits/{id}` | Delete a kit. |
| `POST /brand-kits/{id}/logo` | Upload or replace the logo. |
| `DELETE /brand-kits/{id}/logo` | Remove the logo. |
| `POST /brand-kits/{id}/product-references` | Add a product reference (max 5). |
| `DELETE /brand-kits/{id}/product-references/{ref_id}` | Remove one. |
| `POST /brand-kits/{id}/set-default` | Mark the default kit. |
| `POST /brand-kits/analyze-url` | Extract a brand profile from a URL (does not persist). |

### Create a brand kit

`POST /brand-kits`

| Param | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | A label for the kit. |
| `company_name` | string | no | Brand name. |
| `website_url` | string | no | Brand site. |
| `website_context` | string | no | Notes/positioning. |
| `brand_colors` | string[] | no | Hex colors. |
| `tone_of_voice` | string | no | Voice description. |
| `website_context` | string | no | Positioning notes. |
| `target_audience` | string | no | Audience description. |
| `industry` | string | no | Industry. |
| `auto_attach_product_references` | boolean | no | Auto-pull product images from the site. Default `true`. |

```bash
curl https://api.memes.media/v1/brand-kits \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dawn Roasters",
    "company_name": "Dawn Roasters",
    "website_url": "https://dawnroasters.example",
    "brand_colors": ["#3B2A1A", "#E8C39E"],
    "tone_of_voice": "warm, witty, unpretentious",
    "target_audience": "remote workers who care about good coffee"
  }'
```

Returns the created kit — the `id` is what you pass as `brand_kit_id` everywhere else:

```json
{ "brand_kit": { "id": "bk_dawn_roasters", "object": "brand_kit", "name": "Dawn Roasters", "is_default": false, "created_at": "2026-06-18T17:00:00Z" } }
```

### Analyze a URL

`POST /brand-kits/analyze-url` with `{ "url": "https://..." }` returns an extracted profile (`company_name`, `industry`, `tone_of_voice`, `brand_colors`, `target_audience`, `website_context`) without saving it — useful for pre-filling a kit. Synchronous `200`, `model: forge-copy-fast`.

---

## Prompt Enhancement

Turn a rough idea into a polished, production-ready creative prompt.

| Method · Path | Purpose |
|---|---|
| `POST /prompts/enhance` | Rough idea → polished image prompt. |

`POST /prompts/enhance`

| Param | Type | Required | Description |
|---|---|---|---|
| `prompt` | string | yes | The rough idea. |
| `website_url` | string | no | Site to ground in. |
| `has_logo` | boolean | no | Whether you'll attach a logo. |
| `has_reference_image` | boolean | no | Whether you'll attach references. |
| `aspect_ratio` | enum | no | Intended output ratio. |
| `reference_images` | array | no | https URLs or workspace asset ids to consider. |

```bash
curl https://api.memes.media/v1/prompts/enhance \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "funny ad about mondays and coffee", "website_url": "https://dawnroasters.example" }'
```

Returns `200` synchronously: `{ "enhanced_prompt": "...", "original_prompt": "...", "model": "forge-prompt" }`.

---

## Images as inputs

You don't upload files to use an image as a reference, mask, or edit source — **pass an `https` URL or a workspace asset id** and the API fetches and normalizes it server-side.

A `source` (in `reference_images[]`) or an `image_id`/`mask` accepts:

- **An `https` URL** — your own hosted asset URL, or any public image URL.
- **A workspace asset id** — any `asset_…` returned in a previous generation's `output[]` (or a meme/image id you own). This lets you iterate: feed a generated image straight back in as a reference, an edit source, or an animate source — no re-upload.

```jsonc
"reference_images": [
  { "type": "logo", "source": "https://yourcdn.example/logo.png" },
  { "type": "ref",  "source": "asset_5Tk8wQ" }          // a prior output asset id
]
```

> **Presigned uploads (`POST /files`) for raw local bytes are coming soon.** Until then, host the image and pass its URL — every input path already fetches + normalizes URLs for you.

---

## Account & Usage

| Method · Path | Purpose |
|---|---|
| `GET /account` | Workspace info, plan, key prefix, livemode. |
| `GET /account/usage` | Credit consumption and quota by interval. |

```bash
curl https://api.memes.media/v1/account/usage \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

```json
{
  "plan": "pro",
  "credits_remaining": 480,
  "intervals": {
    "images": { "limit": 1000, "used": 520, "remaining": 480, "interval": "rolling_30_days", "reset_at": "2026-07-01T00:00:00Z" },
    "video":  { "limit": 50,   "used": 4,   "remaining": 46,  "interval": "rolling_30_days", "reset_at": "2026-07-01T00:00:00Z" }
  }
}
```

---

## SDKs & MCP

- **TypeScript & Python SDKs** — typed clients with automatic retries, idempotency, and a webhook-signature helper. *Coming soon.*
- **MCP server** — the same operations exposed as Model Context Protocol tools (`generate_meme`, `generate_image`, `generate_video`, `plan_carousel`, `get_generation`, …) so agents can create campaigns directly. *Coming soon.*

Until the SDKs ship, the API is plain REST — any HTTP client works. Want early access to a key, an SDK, or the MCP server? Email **members@memes.com**.
