Skip to main content
Version: 2.0

AI Builder API

The AI Builder API turns a natural-language prompt into a Prezent-quality deck, lets you revise it by describing the change you want, and hands you the finished file.

All three operations stream. You start the work, then read Server-Sent Events for progress and the result — there is no status endpoint to poll.

Full schemas and per-field documentation live in the interactive API Reference.

Coming from API 1.0?

The AI Builder API changed substantially in 2.0 — polling became streaming, and the many editing endpoints collapsed into one. See What changed from 1.0 below. The 1.0 API is unchanged and still served; its docs are under version 1.0.

Authentication

All endpoints require a Bearer token:

Authorization: Bearer YOUR_API_KEY

See Getting Started → Authentication for details.

Endpoint summary

MethodPathDescription
POST/api/v2/generateGenerate a presentation
POST/api/v2/autogenerator/editRevise a presentation
POST/api/v2/autogenerator/downloadDownload the file
POST/api/v2/streams/sessionsGet a stream URL for a callback_id
GET/api/v1/autogenerator/templatesList available templates
GET/api/v2/autogenerator/languagesList supported languages
GET/api/v2/integrations/authConnect a third-party app
GET/api/v2/integrations/auth/statusCheck an app's connection

GET /api/v1/autogenerator/templates keeps its v1 path — it is unchanged in 2.0 and there is no v2 equivalent.

How the streaming flow works

Generate and edit both follow the same three steps:

1. POST /api/v2/generate            →  { resData: { callback_id } }
2. POST /api/v2/streams/sessions → { data: { stream_url } }
with that callback_id
3. open stream_url (SSE) → progress events…
terminal event + download_url

Download is two steps, not threePOST /api/v2/autogenerator/download returns a stream_url directly, so it skips step 2.

The event contract

Every event on a v2 stream has the same shape, so one parser handles the whole stream:

event: status
data: {"status":"inprogress","message":"Analyzing the key elements","callback_id":"3f8e7d61-…"}
FieldNotes
statusinprogress while working; then one of success, failed, cancelled
messageHuman-readable step description — safe to show a user verbatim
callback_idThe generation or edit turn this event belongs to
download_urlOnly on a success event. May be null on a generation stream if the file has not been exported yet

The stream closes after the terminal event. Branch on status rather than on the SSE event: name — it carries the same information and is simpler.

Progress messages are drawn from a fixed set, in order:

  1. Understanding your prompt
  2. Checking for app references — only when the prompt references a connected app
  3. Analyzing the key elements
  4. Adjusting structure for clarity
  5. Optimizing spacing and alignment
  6. Crafting your speaker notes — only when notes were requested
  7. Finalizing your slides

Steps that do not apply are skipped, so do not treat this as a fixed-length progress bar.

Reconnection, timeouts, and full client code are in Streaming (SSE).

Response envelopes

Errors use the canonical envelope on every endpoint:

{ "success": false, "data": null, "error": { "code": "...", "message": "..." } }

Success shapes are not uniform — each endpoint documents its own:

EndpointSuccess shape
POST /api/v2/generate{ message, resData, error }
POST /api/v2/autogenerator/edit{ message, resData, error }
POST /api/v2/autogenerator/downloadflat top-level fields
POST /api/v2/streams/sessions{ success, data }
GET /api/v2/autogenerator/languages{ status, message, data }
GET /api/v1/autogenerator/templates{ success, data }
GET /api/v2/integrations/authone of two shapes — see the endpoint

See Developer Guide → Error codes for the full code catalog.


POST /api/v2/generate — Generate a presentation

Starts a generation and returns a callback_id. Nothing else — the deck arrives over the stream.

Request body

FieldTypeRequiredDescription
promptstringyesWhat to generate, in plain language.
template_idstringnoBrand template to apply. Takes precedence over settings.template_id; falls back to your key's configured template.
audience_idstringnoAudience profile to tailor tone and detail to. See Audiences.
audienceobjectnoObject form, { "id": "…" }. audience_id takes precedence.
filesarrayno[{ "file_id": "…" }] — files to ground the deck in. Upload first via POST /api/v1/preprocess.
web_linksarraynoURLs for the generator to read.
textstringnoA block of text as source material.
textsarraynoSeveral blocks of text. Takes precedence over text.
websearchbooleannoLet the generator search the web. Takes precedence over settings.websearch. Default false.
externalContextbooleannoAlias for websearch.
settingsobjectnoGeneration settings — see below.

Be specific in the prompt. "A 10-slide deck on renewable-energy market trends for a board audience" produces a better result than "renewable energy".

Settings

Every key in settings is optional. An omitted key falls back to the default configured on your API key, then to the documented default — so a fully configured key needs no settings object at all.

KeyTypeAccepted values / default
template_idstringAny template you can access. Root template_id wins.
languagestringA locale from /api/v2/autogenerator/languages. Default en.
websearchbooleanDefault false. Also settable at the root of the request, which takes precedence.
add_sources_to_footerbooleanDefault false.
add_sources_to_slides_notebooleanDefault false. Takes precedence over add_speaker_notes_to_slides_note.
generate_speaker_notesbooleanDefault false.
add_speaker_notes_to_slides_notebooleanDefault false. Ignored when add_sources_to_slides_note is enabled.
image_libraryobject{ brand_library, prezent_library, ai_images, my_workspace, additional_context }, each a boolean. Omitted keys default to false, except ai_images, which defaults to true when image_library itself is omitted. company_library and brand_library are the same library — set brand_library.
voice_settingsobject{ "type": "standard" } — brand-voice profile.
deck_default_sizeinteger1–200, default 7. The prompt can override it.
deck_max_sizeinteger1–200, default 100. Must be ≥ deck_default_size.
export_typestringpptx or pdf. Default pptx.
enable_mcp_contextbooleanDefault false. Pull context from connected apps.

Example

curl -X POST https://api.prezent.ai/api/v2/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A 10-slide deck on renewable-energy market trends for 2026",
"template_id": "your-template-id",
"settings": {
"language": "en",
"websearch": true,
"generate_speaker_notes": true
}
}'

Success response (200)

{
"message": "Job submitted successfully",
"resData": {
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91"
},
"error": null
}

The payload is under resData, not data.

callback_id is valid for about 3 hours and is what you pass to streams, edit, and download.

Validation errors (422)

Field-level failures return INVALID_INPUT with error.details as an array of messages, one per failed field:

{
"success": false,
"data": null,
"error": {
"code": "INVALID_INPUT",
"message": "The request contains invalid input.",
"details": [
"prompt is required",
"settings.language: 'xx' is not a supported language locale"
]
}
}

POST /api/v2/autogenerator/edit — Revise a presentation

Applies a natural-language edit. One endpoint replaces 1.0's regenerate, node-change, and slide-actions — describe the change instead of choosing an operation.

Edits are conversational: each builds on the last. Pass the callback_id of the most recent turn, and use the new id you get back for the next edit.

The generation must have finished

A slide can only be addressed once the deck size is known, so editing a callback_id that is still generating returns 400. Wait for the stream's terminal event first.

Only one turn runs at a time — if a previous edit is still in flight, the request is rejected with that reason in error.message.

Request body

FieldTypeRequiredDescription
callback_idstringyesThe presentation to edit — the id of the original generation, or of the most recent edit.
promptstringyesThe change to make, in plain language.
slideIndexintegernoWhich slide to edit, counting from 0. Omit to edit the whole deck.
filesarrayno[{ "file_id": "…" }] — context for this edit.
web_linksarraynoURLs to read as context.
textstringnoA block of text as context.

slideIndex is validated against the actual deck — a value past the last slide returns 400 reporting the real slide count.

Example

# Edit the whole deck
curl -X POST https://api.prezent.ai/api/v2/autogenerator/edit \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"prompt": "Make the tone more formal and shorten every bullet"
}'

# Edit slide 5 (index 4)
curl -X POST https://api.prezent.ai/api/v2/autogenerator/edit \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"prompt": "Replace the chart with a comparison table",
"slideIndex": 4
}'

Success response (200)

{
"message": "Edit submitted successfully",
"resData": {
"callback_id": "8c1d4b92-6f37-4e2a-a95c-1b0e7d3f6a48"
},
"error": null
}

This callback_id is new — not the one you sent. Stream it to watch the edit, and pass it as the callback_id of your next edit to keep building on this version.


POST /api/v2/autogenerator/download — Download the file

Returns a stream_url directly — no /streams/sessions step. Open it and read the terminal event for the download_url.

A stream rather than an inline URL because the file may not exist yet:

CaseWhat you see
Already exported in this formatterminal event immediately, with the download_url
Needs exportingprogress events, then the terminal event with the new URL

An export can take several minutes — far longer than an HTTP response allows — which is why both cases are delivered the same way.

Request body

FieldTypeRequiredDescription
callback_idstringyesThe presentation to download.
export_typestringnopptx or pdf. Defaults to your key's configured format, then pptx.

Example

curl -X POST https://api.prezent.ai/api/v2/autogenerator/download \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"export_type": "pptx"
}'

Success response (200)

{
"message": "Connect to stream_url to receive the download URL on the done event",
"status": "success",
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"export_type": "pptx",
"stream_url": "https://stream-api-prod.myprezent.com/v2/streams/a1b2c3?token=eyJhbGciOi...&turn_id=3f8e7d61&stream_type=export&export_type=pptx",
"expires_at": "2026-08-05T12:34:56.000Z"
}

Fields are at the top level, not under data.

status: "success" confirms the stream was minted — it does not mean the file is ready. Read the stream's terminal event for that.

stream_url expires at expires_at (about 5 minutes). Call the endpoint again for a fresh one.

Quota (429)

Download quota is checked before the stream is minted, so a key without room never receives a URL it cannot redeem:

{
"success": false,
"data": null,
"error": {
"code": "USAGE_LIMIT_EXCEEDED",
"message": "Usage limit exceeded.",
"details": "This presentation has 12 slide(s), but only 5 download slide(s) remain on your plan (limit 50000)."
}
}

POST /api/v2/streams/sessions — Get a stream URL

Exchanges a callback_id from generate or edit for an authenticated SSE URL.

Request body

FieldTypeRequiredDescription
callback_idstringyesThe id from generate or edit.

Example

curl -X POST https://api.prezent.ai/api/v2/streams/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91" }'

Success response (201)

{
"success": true,
"data": {
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"stream_url": "https://stream-api-prod.myprezent.com/v2/streams/a1b2c3?token=eyJhbGciOi...&turn_id=3f8e7d61",
"expires_at": "2026-08-05T12:34:56.000Z"
}
}

The token is in the URL, so send no Authorization header when opening it — browsers' EventSource cannot set headers anyway.

stream_url expires in about 5 minutes. Calling this endpoint again mints a fresh URL, and the stream replays from the start of the turn — so a late reconnect loses nothing.

For an edit, the URL also replays from the point the edit was queued, so you see the whole edit even if you connect a moment late.

Errors

Statuserror.codeMeaning
400MISSING_REQUIRED_FIELDcallback_id absent or not a string.
404STREAM_CALLBACK_NOT_FOUNDNo generation found for this id under your key, or its session expired (~3h).

GET /api/v1/autogenerator/templates — List templates

Returns the brand templates available for generation — the values you pass as template_id. Unchanged from 1.0, and still on the v1 path.

Query parameters

ParameterTypeRequiredDescription
namestringnoCase-insensitive name filter.
sortstringnoSort expression (for example name:asc).
sourcestringnoSource filter (brand vs prezent).
enabledFeaturestringnoOptional feature flag filter.
limitintegernoItems per page (1–200, default 50). Enables pagination.
cursorstringnoOpaque cursor from a previous response's next_cursor.

Success response (200)

{
"success": true,
"data": {
"items": [
{ "id": "tmpl_corp_2024", "name": "Corporate 2024", "code": "CORP24", "source": "brand" }
],
"next_cursor": null
}
}

Results are always scoped to the auto_generator feature server-side; the caller cannot override the scoping. See Developer Guide → Pagination.


GET /api/v2/autogenerator/languages — List supported languages

The languages a deck can be generated in. Pass a locale from this list as settings.language; an unsupported value is rejected with 422.

The list is static — cache it rather than calling this before every generation.

Example

curl -s https://api.prezent.ai/api/v2/autogenerator/languages \
-H "Authorization: Bearer YOUR_API_KEY"

Success response (200)

{
"status": "success",
"message": "Supported languages retrieved successfully",
"data": [
{ "language": "Arabic", "locale": "ar" },
{ "language": "Chinese (Simplified)", "locale": "zh" },
{ "language": "English (UK)", "locale": "en-uk" },
{ "language": "English (US)", "locale": "en" },
{ "language": "French", "locale": "fr" },
{ "language": "German", "locale": "de" },
{ "language": "Hindi", "locale": "hi" },
{ "language": "Indonesian", "locale": "id" },
{ "language": "Italian", "locale": "it" },
{ "language": "Japanese", "locale": "ja" },
{ "language": "Korean", "locale": "ko" },
{ "language": "Portuguese (Brazil)", "locale": "pt" },
{ "language": "Spanish", "locale": "es" }
]
}

GET /api/v2/integrations/auth — Connect an app

Connects a third-party app (Google Drive, Notion, …) to the caller's Prezent account. Once connected, a generation can pull context from it by setting settings.enable_mcp_context to true.

The connection is made for the user who owns the API key — there is no parameter to connect an app on someone else's behalf.

Query parameters

ParameterTypeRequiredDescription
mcp_idstringyesIdentifier of the app to connect.

Two response shapes

Which you get is a property of the app, not something you choose. Branch on whether status is present.

Authorization required — send the user to the URL:

{ "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=..." }

The URL is single-use and time-limited; request a fresh one rather than storing it. After the user authorizes, confirm with /auth/status.

No authorization needed — already done:

{ "status": "connected", "message": "MCP connection successful" }

Errors

Statuserror.codeMeaning
400MISSING_REQUIRED_FIELDmcp_id not supplied.
404MCP_NOT_FOUNDNo app registered under this mcp_id.
503SERVICE_UNAVAILABLEThe integrations service is temporarily unreachable.

GET /api/v2/integrations/auth/status — Check an app's connection

Whether an app is currently connected for the caller. Use it after sending a user through an authorization URL, and before a generation that depends on the app.

Query parameters

ParameterTypeRequiredDescription
mcp_idstringyesIdentifier of the app to check.

Example

curl -s "https://api.prezent.ai/api/v2/integrations/auth/status?mcp_id=google-drive" \
-H "Authorization: Bearer YOUR_API_KEY"

Success response (200)

{ "connected": false }

End-to-end example

Generate a deck, stream it to completion, then download it.

import json
import httpx # pip install httpx httpx-sse
from httpx_sse import connect_sse

BASE = "https://api.prezent.ai"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}


def read_stream(stream_url):
"""Read a v2 stream to its terminal event; return the download_url."""
# The token is in the URL — send no auth header here.
with httpx.Client(timeout=None) as sc:
with connect_sse(sc, "GET", stream_url) as events:
for sse in events.iter_sse():
event = json.loads(sse.data)
print(event["status"], "-", event["message"])
if event["status"] == "inprogress":
continue
if event["status"] != "success":
raise RuntimeError(f"{event['status']}: {event['message']}")
return event.get("download_url")
raise RuntimeError("stream closed without a terminal event")


with httpx.Client(base_url=BASE, headers=HEADERS, timeout=30) as c:
# 1. start the generation
gen = c.post("/api/v2/generate", json={
"prompt": "A 10-slide deck on renewable-energy market trends for 2026",
"template_id": "your-template-id",
})
gen.raise_for_status()
callback_id = gen.json()["resData"]["callback_id"]

# 2. mint a stream URL, 3. read it to completion
sess = c.post("/api/v2/streams/sessions", json={"callback_id": callback_id})
sess.raise_for_status()
download_url = read_stream(sess.json()["data"]["stream_url"])

# 4. the generation stream may finish before the file is exported.
# A null download_url means "ask for the export", not "failed".
if not download_url:
dl = c.post("/api/v2/autogenerator/download", json={
"callback_id": callback_id,
"export_type": "pptx",
})
dl.raise_for_status()
download_url = read_stream(dl.json()["stream_url"])

print("deck ready:", download_url)
Why step 4 exists

On a generation stream, download_url can be null — the deck is built but not yet exported to a file. Treat null as "call download", not as an error. On a download stream it is always populated.

What changed from 1.0

1.02.0
GeneratePOST /api/v1/autogeneratorPOST /api/v2/generate
Track progresspoll GET /api/v1/autogenerator/statusopen an SSE stream
Reviseregenerate, node-change, slide-actionsPOST /api/v2/autogenerator/edit
DownloadPOST /api/v1/autogenerator/downloadPOST /api/v2/autogenerator/download
TemplatesGET /api/v1/autogenerator/templatesunchanged

There is no status endpoint in 2.0. Progress arrives as events, so there is no polling loop to write and no interval to tune — and the progress messages are written for display, so you can show them to a user directly.

Editing is one endpoint. Rather than choosing between regenerate, node-change, and slide-actions, describe the change you want. Edits chain, so a sequence of revisions is a conversation.

These 1.0 endpoints have no 2.0 equivalent: status-bulk, meta, slide-data, extract-images, brand-image-search, library-image-search, replace-image, and reaction-feedback. If you depend on any of them, stay on 1.0 — both versions run side by side and there is no forced migration.

Errors

Every error uses the canonical envelope. The codes you are most likely to see on this surface:

Statuserror.codeCause
400BAD_REQUEST, INVALID_JSON, MISSING_CALLBACK_ID, MISSING_REQUIRED_FIELD, INVALID_DATA_TYPEMalformed request, unknown callback_id, deck not ready to edit, slideIndex out of range.
401UNAUTHORIZED, INVALID_API_KEY, EXPIRED_API_KEYMissing, invalid, or expired key.
404ENDPOINT_NOT_FOUND, STREAM_CALLBACK_NOT_FOUND, MCP_NOT_FOUNDKey not scoped to this path, expired session, or unknown app.
422INVALID_INPUTField validation failed — error.details is an array of messages.
429TOO_MANY_REQUESTS, RATE_LIMIT_EXCEEDED, USAGE_LIMIT_EXCEEDEDGateway throttle, per-category limit, or annual quota.
500INTERNAL_SERVER_ERRORUnexpected server error.
503SERVICE_UNAVAILABLEA downstream service is temporarily unreachable.

Full catalog: Error Reference.

Where to go next