Render a Deck

One call renders N structured intents into a single themed .pptx — slides fill in parallel. A 10-slide deck completes in ~5 seconds, a preview image per slide included. $0.05 per usable slide.

The legacy POST /v1/deck (convergence render) is 410 Gone (ADR 2026-06-14). Render decks via POST /v1/render/intent/deck; the /v1/deck/* sub-paths below (outline, PATCH, from-jobs, fork) are live and unchanged.
POST/v1/render/intent/deck

Render a deck from 1–30 structured intents (form + typed fields, or briefs). Slides fill in parallel and merge into one themed .pptx. Synchronous 200 — no polling. $0.05 per usable slide; unusable slides don't bill. Mirrors the MCP create_deck tool.

Parameters

NameTypeRequiredDescription
slidesIntent[]required1–30 intent objects. Each is the same shape as POST /v1/render/intent — a form + typed fields (binder, zero-LLM), or a prose brief. Discover a form's fields via GET /v1/catalog/forms/{form}.
theme_idstringoptionalTheme applied to every slide (e.g. slideforge_standard). Or a saved custom theme ID.
namestringoptionalDeck name / PowerPoint title. Auto-generated if omitted.
detailstringoptional"full" adds a debug block (per-slide engine/rung, latency rollup, costs).

Response

200
{
  "job_id": "deck-a1b2c3d4",
  "status": "complete",
  "name": "Q4 2026 Strategy Review",
  "cost": 0.15,
  "slides": [
    {
      "slide_number": 1,
      "status": "complete",
      "form": "hero_statement"
    },
    {
      "slide_number": 2,
      "status": "complete",
      "form": "market_landscape_2x2"
    },
    {
      "slide_number": 3,
      "status": "complete",
      "form": "roadmap_timeline"
    }
  ],
  "latency_ms": 4703
}

Examples

curl -X POST https://api.slideforge.dev/v1/render/intent/deck \
  -H "Authorization: Bearer sf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slides": [
      {"form": "hero_statement", "variant": "cover", "headline": "Q4 2026 Strategy Review"},
      {"brief": "2x2 matrix: market opportunity vs effort, four initiatives plotted"},
      {"brief": "Roadmap timeline of key Q4 milestones with owners"}
    ],
    "theme_id": "dark_keynote",
    "name": "Q4 2026 Strategy Review"
  }'

# Each slide is a /v1/render/intent object: a form + typed fields, or a brief.
# Discover a form's typed fields at GET /v1/catalog/forms/{form}.

Free pre-flight: POST /v1/deck/outline

Send the same slides to get a free, deterministic preview of how each slide routes — form pick, engine, and any predicted refusals. Slides that would refuse contribute $0 to total_cost_usd, so you can budget exactly before rendering.

curl
# Free preview — per-slide routing + predicted refusals + exact cost, no render
curl -X POST https://api.slideforge.dev/v1/deck/outline \
  -H "Authorization: Bearer sf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slides": [
      {"form": "hero_statement", "headline": "Q4 2026 Strategy Review"},
      {"brief": "2x2 market opportunity vs effort"},
      {"brief": "thoughts"}
    ]
  }'
{
  "status": "complete",
  "routing_summary": {
    "hero_statement": 1,
    "market_landscape_2x2": 1,
    "roadmap_timeline": 1
  },
  "refused_count": 1,
  "renderable_summary": {
    "count": 3,
    "total_cost_usd": 0.15
  },
  "total_cost_usd": 0.15,
  "confidence": 0.78,
  "slides": [
    {
      "slide_number": 1,
      "form": "hero_statement",
      "engine": "intent",
      "estimated_cost_usd": 0.05,
      "predicted_refusal": null
    },
    {
      "slide_number": 2,
      "form": "market_landscape_2x2",
      "engine": "intent",
      "estimated_cost_usd": 0.05,
      "predicted_refusal": null
    },
    {
      "slide_number": 3,
      "form": null,
      "engine": null,
      "estimated_cost_usd": 0,
      "predicted_refusal": {
        "code": "vague_brief",
        "message": "Brief lacks groundable facts."
      }
    }
  ]
}

Downloads & partial decks

The sync response carries no download credentials — just the deck job_id. Fetch the merged .pptx with GET /v1/jobs/{id}/pptx using your normal Bearer key, or mint a short-lived single-use link via POST /v1/jobs/{id}/download-url. A tiled preview of the whole deck is at GET /v1/jobs/{id}/contact_sheet. If any slide failed every attempt, GET /v1/jobs/{id} reports a partial status with failed_slides[] — recompile by PATCHing a replacement.

PATCH /v1/deck/{id}

Update a deck — swap individual slides by position and recompile. Reuses already-rendered blobs (~300ms); complete, partial, and failed decks are all recompilable.

curl
# Swap slide at position 2 with a new job, recompile (reuses rendered blobs)
curl -X PATCH https://api.slideforge.dev/v1/deck/DECK_JOB_ID \
  -H "Authorization: Bearer sf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "replace": [{"position": 2, "job_id": "NEW_SLIDE_JOB_ID"}],
    "name": "Updated Q4 Strategy"
  }'

POST /v1/deck/from-jobs

Assemble a deck from existing completed slide jobs — no render, just merge and compile. Free. (/v1/compile is an alias.)

curl
# Assemble a deck from existing slide jobs (no render — merge + compile)
curl -X POST https://api.slideforge.dev/v1/deck/from-jobs \
  -H "Authorization: Bearer sf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "job_ids": ["slide-job-1", "slide-job-2", "slide-job-3"],
    "name": "Compiled Deck",
    "theme_id": "slideforge_standard"
  }'

POST /v1/deck/{id}/fork

Create a variant of an existing deck. The fork shares slide blobs (no duplication) but has its own metadata, so changes to the fork don't affect the original. Free. Use for A/B tests, per-audience versions, or alternative narratives.

curl
# Fork a deck for a different audience (shares slide blobs, own metadata)
curl -X POST https://api.slideforge.dev/v1/deck/DECK_JOB_ID/fork \
  -H "Authorization: Bearer sf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Q4 Strategy — Board Version" }'