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.
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./v1/render/intent/deckRender 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
| Name | Type | Required | Description |
|---|---|---|---|
| slides | Intent[] | required | 1–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_id | string | optional | Theme applied to every slide (e.g. slideforge_standard). Or a saved custom theme ID. |
| name | string | optional | Deck name / PowerPoint title. Auto-generated if omitted. |
| detail | string | optional | "full" adds a debug block (per-slide engine/rung, latency rollup, costs). |
Response
{
"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.
# 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.
# 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.)
# 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.
# 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" }'