---
name: linkedin-carousel
description: Use when asked to make, write or generate a LinkedIn carousel, turn a post, article or URL into a carousel, or export a carousel as a PDF for LinkedIn.
---

# LinkedIn carousel

Make LinkedIn carousels for a person through the LinkedIn Carousel Maker, using its MCP tools or its REST API. Both call the same services. Prefer MCP when the tools are available.

## 1. Connect

The person creates an API key in the app (`lcm_…`, one key per free account). Ask for it; never invent one.

```sh
claude mcp add --transport http lcm https://YOUR_HOST/mcp --header "Authorization: Bearer lcm_…"
```

REST alternative: call `https://YOUR_HOST/api/v1/…` with the header `Authorization: Bearer lcm_…`. Routes:

| Route | Same as MCP tool |
|---|---|
| `GET /api/v1/layouts` | `list_layouts` |
| `GET /api/v1/templates` | `list_templates` |
| `POST /api/v1/carousels` | `create_carousel` |
| `GET /api/v1/carousels/{id}` | `get_carousel` |
| `PUT /api/v1/carousels/{id}` | `update_carousel` |
| `POST /api/v1/carousels/generate` | `generate_carousel` |
| `POST /api/v1/carousels/{id}/edit` | `edit_carousel` |
| `POST /api/v1/carousels/{id}/export` | `export_carousel` |
| `GET /api/v1/carousels` | `list_carousels` |
| `DELETE /api/v1/carousels/{id}` | `delete_carousel` |
| `GET /api/v1/me` | `get_me` |

Full schema: `docs/openapi.yaml`. MCP details: `docs/mcp.md`.

## 2. Workflow

1. Get the source: a topic, pasted text, or a URL. Ask for the audience and tone if unclear (`professional`, `conversational`, `bold`, `storytelling`, `analytical`).
2. `generate_carousel` with `source: { type: "topic" | "text" | "url", value }`, plus optional `slides` (5–12), `tone`, `language`, `preset` (`linkedin-4x5` default, or `linkedin-1x1`). It saves the carousel and returns `{ carousel, saved, truncated, usage }` (`truncated`: the source was over 12,000 characters and only the first part was used; tell the person). If `saved` is false the 10-carousel cap is hit: `list_carousels`, ask the person which one to remove, `delete_carousel` it, then `create_carousel` with the returned carousel.
3. `get_carousel` to read the saved carousel. Review every slide and the caption against section 3.
4. Fix problems with `edit_carousel` (`id`, `prompt` ≤ 500 chars, `mode`):
   - `mode: "slide"` plus `slideId`: one weak slide (wrong wording, too long, no specifics). Cheapest in effect; change one thing.
   - `mode: "carousel"`: a problem across slides (wrong tone, repeated layouts, weak hook plus flow, slide order).
   Each edit costs one AI edit. Batch all fixes for a slide into one prompt. For tiny exact changes (a typo, a number, swapping a layout, adding the caption), skip AI: change the JSON and call `update_carousel`. That costs no AI credit but counts toward the 100 requests/day. Pass `expectedUpdatedAt` from `get_carousel` so a concurrent change returns 409 `conflict` instead of being overwritten.
5. Show the person the slide texts and caption. Apply their changes.
6. `export_carousel` with `format: "pdf"` for a LinkedIn document post (LinkedIn shows the PDF as a swipeable carousel) or `"png"` for images (a ZIP, one PNG per slide). Free keys (`get_me` → `plan: "free"`) get `pdf` only, up to 10 slides; `png` and longer carousels need the Pro plan. Return the `url`; it is temporary (`expiresAt`), so tell the person to download it now.

To start from a pattern instead of AI, call `list_templates` and `create_carousel`. AI-free and costs no credit.

## 3. Writing rules

Check these on every carousel and fix violations.

- Slide 1 is the hook: an intro layout, title ≤ 8 words, creating curiosity or tension (a bold claim, a surprising number, a costly mistake). No "In this post…" or "Here are some tips".
- One idea per slide. Each content slide ≤ 25 words in total, except list layouts, which follow their item limits.
- Specifics over generic: real numbers, named examples, exact actions, before/after. Replace "communicate better" with what to do and what happens.
- Vary layouts: never the same layout twice in a row, at least 4 distinct layouts.
- Accent 1–3 key words per title with `**word**`, e.g. `Stop **discounting** your first deal`. Never leave an asterisk unclosed; use them nowhere else.
- Last slide is a `cta` with a specific call to action tied to the topic ("Follow for weekly pricing teardowns"), not just "Follow me".
- No hashtags, emojis or links on slides.
- Never invent statistics, quotes or results. Use numbers from the source, or label examples as illustrative ("e.g.", "say you…").
- No clichés: "In today's fast-paced world", "game-changer", "unlock", "delve", "leverage", "elevate", "dive in".

Caption (≤ 1,300 characters), in this order:
1. Hook line, ≤ 12 words, worded differently from slide 1.
2. Blank line.
3. 3–6 short lines, one sentence each, expanding the idea.
4. A question or CTA that invites comments.
5. Last line: exactly 3 relevant hashtags, nothing else.

## 4. Choosing layouts

Slide kinds: intro = first slide only, content = middle slides, outro = last slide only. AI generation never picks a layout that needs an image.

| Layout id | Kind | Use when | Image |
|---|---|---|---|
| `hero` | intro | Opening slide, the hook is pure text | no |
| `hero-image` | intro | Opening slide, a strong photo carries the hook | required |
| `headshot` | intro | Opening slide, the author's face and story are the hook | optional photo |
| `statement` | content | One strong claim or insight | no |
| `text-image` | content | A screenshot, chart or photo illustrates the point | required |
| `bullets` | content | 3–5 parallel points or tips (items ≤ 12 words) | no |
| `steps` | content | An ordered process or how-to (3–5 steps) | no |
| `big-number` | content | One number makes the point (number ≤ 6 characters) | no |
| `quote` | content | A memorable quote or testimonial (≤ 30 words) | no |
| `compare` | content | Contrast two options, or before/after (2–4 items per column) | no |
| `checklist` | content | A checklist or requirements (3–6 items) | no |
| `cta` | outro | The final slide | no |
| `canvas` | any | Hand-placed text and icons; AI does not generate it; edit text only | no |

`statement`, `big-number` and `cta` take an optional `icon` field (off by default; `value` is one of the icon names in the OpenAPI `Field` schema). A `canvas` slide may be any kind; its one `elements` field holds text and icon elements positioned in slide pixels from the top-left. AI edits change only the `text` of existing elements and keep their ids, positions and sizes.

Call `list_layouts` for exact field keys and word limits before hand-writing slides. For image layouts, set the image field's `value` to a URL the person gave you; do not guess URLs.

## 5. Slide count

| Content | Slides |
|---|---|
| Listicle (tips, mistakes, tools) | 6–8 |
| Framework or how-to | 7–9 |
| Story or lessons learned | 6–7 |
| Data or results | 6–8 |

Hard cap: 20 slides per carousel. `generate_carousel` accepts 5–12. Default to 8 when unsure.

## 6. Limits and errors

Free account limits: 5 AI generations per month, 30 AI edits per month, 10 saved carousels, 1 API key, 100 API requests per day. Every response from `generate_carousel` and `edit_carousel` includes `usage`; check it before further AI calls and tell the person what is left. Every API or MCP request, including `initialize` and `tools/list`, counts toward the 100/day limit.

Errors are `{ error: { code, message } }` (MCP: tool result with `isError: true`).

| Code | Status | Tell the person |
|---|---|---|
| `limit_reached` | 402 | The monthly AI credits or the 10-carousel cap are used up. They can delete a carousel (cap) or wait for next month (credits). Do not retry. Hand-edit via `update_carousel` costs no AI credit but counts toward the 100 requests/day. |
| `rate_limited` | 429 | The 100 requests/day budget is spent. Honour `Retry-After` (seconds until UTC midnight). Stop and report. |
| `export_unavailable` | 501 | This deployment has server export disabled. Offer the PDF/PNG export from the editor in the browser instead. |
| `pro_required` | 403 | The plan does not allow this export (PNG, or more than 10 slides on free). Offer the PDF/PNG export from the editor in the browser, which has no such limit. Do not retry. |
| `email_unverified` | 403 | The account email is not verified yet, so AI generation and edits are blocked. Ask them to click the link in the verification email (or resend it from the editor), then retry. |
| `conflict` | 409 | The carousel changed. `get_carousel`, re-apply your change, retry once. |
| `invalid_key` | 401 | The API key is wrong or revoked. Ask for a new one. |
| `unauthenticated` | 401 | Session routes only (not the API). The person is not signed in. Ask them to sign in. |
| `invalid_body` | 400 | The request body is malformed or fails validation. Fix the field named in `message`, then retry. |
| `ai_invalid_output` | 422 | The AI returned a carousel that failed validation. Retry once, then tell the person. |
| `ai_unavailable` | 502 | AI provider failed. Credit is refunded; retry once, then report. |

Failed AI calls refund their credit.

AI edits (`edit_carousel`) only rewrite text. Field `style`, `icon` fields, hidden fields (`on: false`) and Canvas element positions, sizes, styles and visibility are kept as they were. To change any of those, use `update_carousel`. On a `canvas` slide the AI may rewrite only the text of its visible text elements; anything else fails with `invalid_body` "Canvas slides accept text edits only" and the credit is refunded.

## 7. Carousel JSON

Every field is `{ type, on, value }`. `type` is `text`, `list` (value is a string array), `number`, `icon` (see below), or `image` (adds `fit`: `cover` or `contain`, optional `credit`). `on: false` hides an optional field. Colors are `#rrggbb`. Slide `kind` must match the layout kind.

`text` and `list` fields take an optional `style` that applies to the whole field: `size` (`xs`..`xl`), `weight`, `italic`, `align`, `color` (`text`, `accent`, `muted` or `#rrggbb`), `font` (`title`, `body` or a Google Fonts family), `lineHeight`, `tracking`; every key is optional. The `statement`, `big-number` and `cta` layouts also accept an optional `icon` field `{ "type": "icon", "on", "value": <Lucide kebab name, e.g. "rocket">, "size"?: 48..400, "color"?: "text"|"accent"|"muted"|"#rrggbb" }`: above the title on `statement` and `cta`, beside the number on `big-number`. It is off by default.

**Canvas.** A `canvas` slide holds hand-placed text and icons in one `elements` field (at most 24, unique `id`s). Text elements are `{ "kind": "text", "id", "x", "y", "w", "role": "title"|"body", "text", "style"?, "visible"? }` (text up to 400 characters); icon elements are `{ "kind": "icon", "id", "x", "y", "size", "name", "color"?, "visible"? }`. `x`, `y`, `w` and `size` are slide pixels measured from the slide's top-left (1080 wide; 1350 or 1080 tall); the editor keeps elements inside the content area, 72 px from each edge with a 120 px band at the bottom for the author and counter. AI never generates canvas slides and edits only their text.

Minimal valid carousel (`create_carousel` / `update_carousel` take it as `carousel`):

```json
{
  "id": "demo-1",
  "version": 1,
  "title": "Pricing mistakes",
  "caption": "Most founders underprice.\n\nHere is the fix.\n\nWhich one did you make?\n\n#pricing #saas #founders",
  "settings": {
    "preset": "linkedin-4x5",
    "theme": { "background": "#ffffff", "text": "#111827", "accent": "#2563eb", "muted": "#6b7280" },
    "fonts": { "title": "Inter", "body": "Inter" },
    "author": { "name": "", "handle": "", "title": "", "avatarUrl": "", "show": "all" },
    "counter": { "show": true, "style": "chip", "includeIntro": false },
    "swipe": { "show": true, "text": "Swipe" },
    "pattern": { "id": "none", "opacity": 0.08 }
  },
  "slides": [
    { "id": "s1", "kind": "intro", "layout": "hero", "fields": {
      "title": { "type": "text", "on": true, "value": "I **doubled** my price and lost nobody" } } },
    { "id": "s2", "kind": "content", "layout": "statement", "fields": {
      "title": { "type": "text", "on": true, "value": "Low prices attract the **worst** clients" } } },
    { "id": "s3", "kind": "outro", "layout": "cta", "fields": {
      "title": { "type": "text", "on": true, "value": "Want weekly pricing **teardowns**?" },
      "cta": { "type": "text", "on": true, "value": "Follow for more" } } }
  ]
}
```

Prefer `generate_carousel` or a template as the starting point and edit its output; hand-built JSON is for small fixes. `docs/openapi.yaml` has the full schema.
