Posters API
The Posters API turns a natural-language prompt into a single-canvas, brand-styled poster — a scientific poster, a conference board, a one-page summary — then lets you revise it by describing the change you want and hands you the finished file.
A poster is one canvas, which shapes the whole surface:
- You choose an orientation and a page size up front.
- An edit addresses a region of the canvas — "row 2", "the pie chart" — in plain language.
- Citations render on the canvas, so a reference format (AMA, APA, Vancouver, Prezent Standard) is part of the request.
All three operations stream. You start the work, then read Server-Sent Events for progress and the result.
Full schemas and per-field documentation live in the interactive API Reference.
Authentication
All endpoints require a Bearer token:
Authorization: Bearer YOUR_API_KEY
See Getting Started → Authentication for details.
Endpoint summary
| Method | Path | Description |
|---|---|---|
POST | /api/v1/posters/generate | Generate a poster |
POST | /api/v1/posters/edit | Revise a poster |
POST | /api/v1/posters/download | Download the file |
POST | /api/v2/streams/sessions | Get a stream URL for a callback_id |
GET | /api/v1/posters/status | Get a turn's status (polling alternative) |
GET | /api/v1/posters/templates | List available poster templates |
GET | /api/v1/posters/languages | List supported languages |
GET | /api/v1/posters/agents | List available agents |
GET | /api/v1/posters/conferences | List available conferences |
GET | /api/v2/integrations | List your apps |
POST | /api/v2/integrations/auth | Connect or disconnect an app |
GET | /api/v2/integrations/auth/status | Check an app's connection |
POST /api/v2/streams/sessions is the shared 2.0 stream-session
service — one endpoint serves every artifact type.
How the streaming flow works
Generate and edit both follow the same three steps:
1. POST /api/v1/posters/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
The event contract
Every event on the stream has the same shape, so one parser handles the whole stream:
event: status
data: {"status":"inprogress","message":"Composing the poster layout","callback_id":"3f8e7d61-…"}
| Field | Notes |
|---|---|
status | inprogress while working; then one of success, failed, cancelled |
message | Human-readable step description — safe to show a user verbatim |
callback_id | The generation or edit turn this event belongs to |
download_url | Only on a success event. The poster is exported as soon as generation completes, in the export_type the request resolved to, so the terminal event carries a ready-to-fetch URL for that format. Use download to get any other format |
The stream closes after the terminal event. Branch on status rather
than on the SSE event: name.
Poster progress messages are drawn from a fixed set, in order:
- Understanding your prompt
- Reading your sources — only when files, links, or text were supplied
- Checking for app references — only when the prompt references a connected app
- Analyzing the key elements
- Composing the poster layout
- Selecting visuals and charts
- Formatting references — only when a
reference_formatapplies - Finalizing your poster
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:
| Endpoint | Success shape |
|---|---|
POST /api/v1/posters/generate | { message, resData, error } |
POST /api/v1/posters/edit | { message, resData, error } |
POST /api/v1/posters/download | flat top-level fields, including download_url |
POST /api/v2/streams/sessions | { success, data } |
GET /api/v1/posters/languages | { status, message, data } |
GET /api/v1/posters/templates | { success, data } |
GET /api/v1/posters/agents | { status, log, data } |
GET /api/v1/posters/conferences | { status, log, data } |
GET /api/v2/integrations | { mcps, count } |
POST /api/v2/integrations/auth | depends on the app — see the endpoint |
See Developer Guide → Error codes for the full code catalog.
POST /api/v1/posters/generate — Generate a poster
Starts a generation and returns a callback_id. Nothing else — the
poster arrives over the stream.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | yes | What to generate, in plain language. Describe the layout as well as the content — see Writing a poster prompt. |
template_id | string | no | Brand template to apply. Takes precedence over settings.template_id; falls back to your key's configured template. |
agent_code | string | no | Agent to generate with — an agent_code from /api/v1/posters/agents. A code that is not available to you is rejected with 422. |
conference_code | string | no | Conference to generate the poster for — a conference_code from /api/v1/posters/conferences. The generator applies that conference's content and design instructions. A code that is not in your list is rejected with 422. See Generating for a conference. |
files | array | no | [{ "file_id": "…" }] — files to ground the poster in. Upload first via POST /api/v1/preprocess, then call POST /api/v1/validate and pass the file_id it returns. |
web_links | array | no | URLs for the generator to read. |
text | string | no | A block of text as source material. |
websearch | boolean | no | Let the generator search the web. Takes precedence over settings.websearch. Default false. |
settings | object | no | Poster settings — see below. |
Supplying any of files, web_links, or text makes the
request multi-source (internally, multimodal): the generator grounds
the poster in what you gave it rather than in its own knowledge. See
Grounding a poster in your own content.
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.
| Key | Type | Accepted values / default |
|---|---|---|
template_id | string | Any poster template you can access. Root template_id wins. |
orientation | string | landscape, portrait, or square. Default landscape. |
size | string | Depends on orientation. Portrait/landscape: a4, a3, a2, letter, tabloid (default a4). Square: small, medium, large, a0_square, xl_square (default medium). See Orientation and size. |
language | string | A locale from /api/v1/posters/languages. Default en. |
websearch | boolean | Default false. Also settable at the root of the request, which takes precedence. |
reference_format | string | ama, apa, vancouver, or prezent. Default vancouver. See Reference formats. |
add_sources_to_footer | boolean | Default true. Renders the source list in the poster footer — set false to suppress it. |
image_library | object | { brand_library, prezent_library, ai_images, my_workspace, additional_context }, each a boolean. Omitted keys default to false, except brand_library and prezent_library, which default to true when image_library itself is omitted. company_library and brand_library are the same library — set brand_library. |
export_type | string | pdf, pptx, jpg, or png. Default pptx. |
enable_mcp_context | boolean | Default false. Pull context from connected apps. |
Orientation and size
orientation and size together resolve to a pixel canvas. The
generator lays content out for that aspect ratio, so a portrait poster is
not a rotated landscape one — the composition differs.
The accepted values of size depend on orientation. Portrait and
landscape take paper sizes; square takes its own set of print dimensions.
Sending a size from the wrong set is rejected with 422.
portrait and landscape — paper sizes:
size | Portrait (w × h) | Landscape (w × h) |
|---|---|---|
a4 | 794 × 1123 | 1123 × 794 |
a3 | 1123 × 1587 | 1587 × 1123 |
a2 | 1587 × 2245 | 2245 × 1587 |
letter | 817 × 1054 | 1054 × 817 |
tabloid | 1054 × 1633 | 1633 × 1054 |
square — sized by print edge length, not paper format:
size | Edge | Canvas (w × h) |
|---|---|---|
small | 60 cm | 2268 × 2268 |
medium | 90 cm | 3402 × 3402 |
large | 100 cm | 3780 × 3780 |
a0_square | 84 cm | 3175 × 3175 |
xl_square | 120 cm | 4535 × 4535 |
Pixel values are at 96 DPI, and match the dimensions the same selection produces in the Prezent app.
size defaults per orientation: a4 for portrait and landscape,
medium for square. So switching to square without naming a size gives
you a 90 cm canvas rather than an error.
Values are case-insensitive — A4 and a4 are the same size.
Reference formats
When the poster cites sources — because you supplied files, links, or
enabled websearch — reference_format controls how the citations are
rendered on the canvas.
| Value | Style |
|---|---|
ama | American Medical Association — superscript numerals, numbered list |
apa | American Psychological Association — author–date, alphabetical list |
vancouver | Vancouver — bracketed numerals, citation-order list (default) |
prezent | Prezent Standard — compact inline attribution |
ama and vancouver are the conventional choices for scientific and
medical posters. The format applies to on-canvas citations regardless of
add_sources_to_footer, which only controls whether the full source list
is also printed in the footer.
Writing a poster prompt
A poster is read all at once, so the layout matters as much as the content — and the prompt is where you specify it. Describe the regions you want and what goes in each:
Create a poster on cloud misconfiguration as the leading cause of enterprise data breaches. Organise content in 3 horizontal rows. Row 1 covers the problem statement with supporting breach statistics and a pie chart of root causes (misconfiguration, credential compromise, insecure APIs, insider threat). Row 2 presents a comparison table of misconfiguration risk across IaaS, PaaS, and SaaS environments, columns for the most common error, average time-to-detection, and business impact. Row 3 covers preventative controls, a ranked list of top 5 remediations by effectiveness, and an emerging AI-assisted detection framework.
That prompt names the structure (3 rows), the content of each region, and the visual type where it matters (pie chart, comparison table, ranked list). A bare topic — "cloud misconfiguration" — produces a valid poster but leaves every layout decision to the generator, and gives a later edit nothing to address by name.
Naming the regions pays off twice: it also gives you the vocabulary for editing, where "row 2" is how you target part of the canvas.
Grounding a poster in your own content
Any combination of files, web_links, and text can be sent; the
generator reads all of them.
Files — upload via
POST /api/v1/preprocess and pass the returned
file_id. Supported types:
| Category | Formats |
|---|---|
| Presentations | .pptx |
| Documents | .pdf, .docx, .txt |
| Spreadsheets | .xlsx, .csv |
| Images | .png, .jpg, .jpeg |
Password-protected and corrupted files are rejected at upload time, not
at generation time — check the preprocess response before generating.
Web links — public URLs, read at generation time:
{ "web_links": ["https://example.com/2026-cloud-security-report"] }
Text — paste source material directly:
{ "text": "Internal Q3 findings: …" }
Grounding and websearch are independent: enable both to have the poster
built from your sources and supplemented with current public data.
Example
- cURL
- Python
- Node.js
curl -X POST https://api.prezent.ai/api/v1/posters/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a poster on cloud misconfiguration as the leading cause of enterprise data breaches. Organise content in 3 horizontal rows. Row 1 covers the problem statement with supporting breach statistics and a pie chart of root causes. Row 2 presents a comparison table of misconfiguration risk across IaaS, PaaS, and SaaS environments. Row 3 covers preventative controls and a ranked list of top 5 remediations by effectiveness.",
"template_id": "your-template-id",
"settings": {
"orientation": "landscape",
"size": "a4",
"language": "en",
"websearch": true,
"reference_format": "vancouver",
"image_library": {
"brand_library": true,
"prezent_library": true,
"ai_images": true
},
"export_type": "pdf"
}
}'
import requests
resp = requests.post(
"https://api.prezent.ai/api/v1/posters/generate",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"prompt": (
"Create a poster on cloud misconfiguration as the leading cause of "
"enterprise data breaches. Organise content in 3 horizontal rows. "
"Row 1 covers the problem statement with breach statistics and a pie "
"chart of root causes. Row 2 presents a comparison table of risk "
"across IaaS, PaaS, and SaaS. Row 3 covers preventative controls."
),
"template_id": "your-template-id",
"settings": {
"orientation": "landscape",
"size": "a4",
"language": "en",
"websearch": True,
"reference_format": "vancouver",
"export_type": "pdf",
},
},
)
resp.raise_for_status()
callback_id = resp.json()["resData"]["callback_id"]
const res = await fetch('https://api.prezent.ai/api/v1/posters/generate', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt:
'Create a poster on cloud misconfiguration as the leading cause of ' +
'enterprise data breaches. Organise content in 3 horizontal rows. ' +
'Row 1 covers the problem statement with breach statistics and a pie ' +
'chart of root causes. Row 2 presents a comparison table of risk ' +
'across IaaS, PaaS, and SaaS. Row 3 covers preventative controls.',
template_id: 'your-template-id',
settings: {
orientation: 'landscape',
size: 'a4',
language: 'en',
websearch: true,
reference_format: 'vancouver',
export_type: 'pdf',
},
}),
});
const { resData } = await res.json();
const callbackId = resData.callback_id;
A grounded request — the same poster, built from an uploaded file, a block of pasted text, and live web search:
{
"prompt": "Create a poster on cloud misconfiguration as the leading cause of enterprise data breaches. Organise content in 3 horizontal rows…",
"template_id": "your-template-id",
"files": [{ "file_id": "0efc0f76-4c51-4564-a671-f0ff0c21d23e" }],
"web_links": ["https://example.com/2026-cloud-security-report"],
"text": "Internal Q3 incident review: 61% of findings traced to storage-bucket policy drift…",
"websearch": true,
"settings": {
"orientation": "landscape",
"size": "a3",
"reference_format": "vancouver",
"add_sources_to_footer": true,
"enable_mcp_context": true,
"image_library": {
"brand_library": true,
"prezent_library": true,
"ai_images": true,
"my_workspace": false,
"additional_context": true
}
}
}
A square poster — note that size comes from the square set, not the
paper sizes:
{
"prompt": "A square poster on cloud misconfiguration, four quadrants",
"template_id": "your-template-id",
"settings": {
"orientation": "square",
"size": "large"
}
}
Generating for a conference
Pass a conference_code from
/api/v1/posters/conferences
to build the poster for that conference. The generator applies the
conference's content and design instructions. It combines with an
agent_code, and with any of the fields above:
{
"prompt": "Create a poster on cancer awareness",
"agent_code": "77a488a8-d8f3-4d80-89f1-baf1a7bb39c4",
"conference_code": "5a431fb7-bb8d-47b8-b88c-d4959861af4f",
"settings": {
"orientation": "landscape",
"reference_format": "apa"
}
}
Both codes are checked against your lists before anything is
generated. A conference that is unknown, no longer active, or belongs
to another industry is rejected with 422. So is an agent that is not
in your agent list.
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.orientation: 'diagonal' is not supported (accepted values: portrait, landscape, square)",
"settings.size: 'a4' is not valid for orientation 'square' (accepted values: small, medium, large, a0_square, xl_square)"
]
}
}
An agent_code or conference_code that is not available to you is
reported the same way:
"details": [
"conference_code: '5a431fb7-bb8d-47b8-b88c-d4959861af4f' is not a valid conference for this user"
]
POST /api/v1/posters/edit — Revise a poster
Applies a natural-language edit to an existing poster.
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.
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
| Field | Type | Required | Description |
|---|---|---|---|
callback_id | string | yes | The poster to edit — the id of the original generation, or of the most recent edit. |
prompt | string | yes, unless action_type is sent | The change to make, in plain language. Name the region you mean. Not allowed with action_type. |
action_type | string | no | Apply a specific change with an exact value instead of a prompt — see Actions. |
value | string | with action_type | The value for action_type. |
files | array | no | [{ "file_id": "…" }] — context for this edit. Not allowed with action_type. |
web_links | array | no | URLs to read as context. Not allowed with action_type. |
text | string | no | A block of text as context. Not allowed with action_type. |
The poster's settings — orientation, size, language, reference format,
image sources — are fixed at generation time and carry over unchanged
through every edit. Describe what you want in the prompt. The template
is the exception: once changed, by a template_change action or by a
prompt, the new template carries over to every later edit.
Actions
A template change can be asked for in a prompt. When you already know
the exact template, send action_type and value instead, and the
change is applied as given.
action_type | value |
|---|---|
template_change | A template code from GET /api/v1/posters/templates. Re-themes the whole poster. |
With action_type, do not send prompt, files, web_links, or
text — they are rejected with 422, as are an unknown action_type
and a missing or empty value. A template code that is not in your
template list is rejected with 400 and error.details set to
The selected template is not available.
What an edit can change
| Intent | Example prompt |
|---|---|
| Change template | "Apply the corporate 2026 template" — or send a template_change action |
| Replace a visual | "Replace the image in row 1 with something from the brand library" |
| Change a chart | "Turn the pie chart of root causes into a horizontal bar chart, sorted descending" |
| Manage sources | "Add the 2026 cloud security report to the sources and switch citations to AMA" |
| Rewrite content | "Shorten every bullet in row 3 to one line" |
| Change layout | "Move the comparison table to row 3 and the remediations to row 2" |
Asking for a different orientation or page size reflows the whole canvas. The content is preserved and recomposed for the new aspect ratio; region positions will move. Do it in its own turn rather than combining it with a content edit, so you can see the reflow before changing anything else.
Example
# Edit a region
curl -X POST https://api.prezent.ai/api/v1/posters/edit \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"prompt": "In row 2, replace the comparison table with a stacked bar chart and keep the same three columns as series"
}'
# Reflow to a portrait A3 board
curl -X POST https://api.prezent.ai/api/v1/posters/edit \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"prompt": "Reflow the poster for a portrait A3 board"
}'
# Add a source and switch citation style
curl -X POST https://api.prezent.ai/api/v1/posters/edit \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"prompt": "Cite the added report in row 1 and use AMA style throughout",
"web_links": ["https://example.com/2026-cloud-security-report"]
}'
# Switch the poster to another template
curl -X POST https://api.prezent.ai/api/v1/posters/edit \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"action_type": "template_change",
"value": "CORP26"
}'
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/v1/posters/download — Download the file
Returns the download_url inline. One request, one URL — the poster
renders synchronously.
If you ask for the format the generation already produced, that file's URL is returned as-is. Any other format is re-exported from the poster's stored canvas before the URL comes back, so allow up to ~25 seconds for that call.
The poster must have finished generating. Requesting a download while it
is still in progress returns 400.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
callback_id | string | yes | The poster to download. |
export_type | string | no | pptx, pdf, png, or jpg. Defaults to your key's configured format, then pptx. |
Each format is a separate export, so the same poster can be downloaded in
several — call the endpoint once per export_type.
export_type | Result |
|---|---|
pptx | A PowerPoint file sized to the canvas — editable downstream. The default. |
pdf | Vector, print-ready at the poster's canvas size. The right choice for printing. |
jpg | Raster image, smaller file, no transparency. For web and previews. |
png | Raster image, lossless, supports transparency. For overlaying or further design work. |
Example
curl -X POST https://api.prezent.ai/api/v1/posters/download \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"export_type": "pdf"
}'
Success response (200)
{
"message": "Poster exported successfully",
"status": "success",
"callback_id": "3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91",
"export_type": "pdf",
"download_url": "https://assets.prezent.ai/protected/…/poster.pdf?callback_id=3f8e7d61-…&token=eyJhbGciOi…&source=prezent-api-framework",
"expires_at": "2026-08-05T12:34:56.000Z"
}
Fields are at the top level, not under data.
download_url carries its own access token, so fetch it as-is and send
no Authorization header.
It expires at expires_at (about 5 minutes). Call the endpoint again for
a fresh URL — that costs nothing extra when the format is unchanged, as
the existing file is reused.
Errors
| Status | error.code | Cause |
|---|---|---|
400 | INVALID_JSON | The body is not valid JSON. |
400 | MISSING_CALLBACK_ID | No callback_id in the body. |
400 | INVALID_DATA_TYPE | callback_id is not a non-empty string, or export_type is not one of the four supported values. |
400 | BAD_REQUEST | The callback_id has no active session (they expire ~3 hours after generation), the poster has not finished generating, or the export itself failed. |
500 | INTERNAL_SERVER_ERROR | The export service was unreachable or returned something unusable. |
{
"success": false,
"data": null,
"error": {
"code": "BAD_REQUEST",
"message": "Bad request.",
"details": "This poster is not ready to download yet; wait for generation to complete"
}
}
POST /api/v2/streams/sessions — Get a stream URL
Exchanges a callback_id from poster generate or edit for an
authenticated SSE URL. This is the shared 2.0 stream-session endpoint.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
callback_id | string | yes | The id from poster 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.
Errors
| Status | error.code | Meaning |
|---|---|---|
400 | MISSING_REQUIRED_FIELD | callback_id absent or not a string. |
404 | STREAM_CALLBACK_NOT_FOUND | No generation found for this id under your key, or its session expired (~3h). |
GET /api/v1/posters/status — Get a turn's status
Reads the state of one poster generation or edit turn. This is the polling alternative to the streaming API — same question, answered as a single stateless call instead of a held-open connection.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
callback_id | string | yes | The id from poster generate or edit. |
Example
curl -s "https://api.prezent.ai/api/v1/posters/status?callback_id=3f8e7d61-9a2b-4c5e-b013-7d4a6f2c8e91" \
-H "Authorization: Bearer YOUR_API_KEY"
Success response (200)
{
"message": "Workflow status polling completed. Your poster is ready to download.",
"status": "success",
"download_url": "https://cdn.prezent.ai/exports/a1b2c3/poster.pdf?token=..."
}
Note this endpoint returns the three fields flat — not wrapped in the
{ success, data } envelope the other endpoints use.
status is one of:
| Value | Meaning |
|---|---|
in_progress | The turn is still running, or the file is still being exported. |
success | The turn finished and download_url is populated. |
failed | The turn is terminal with no artefact. message carries the reason, including a cancellation or a clarifying question. |
download_url is an empty string on any status other than success.
Errors
| Status | error.code | Meaning |
|---|---|---|
400 | MISSING_REQUIRED_FIELD | callback_id absent or not a string. |
400 | BAD_REQUEST | callback_id is not a valid id, or belongs to a presentation — poll GET /api/v2/autogenerator/status instead. |
404 | STREAM_CALLBACK_NOT_FOUND | No poster with this id was issued to your API key. |
GET /api/v1/posters/templates — List poster templates
Returns the brand templates available for poster generation — the values
you pass as template_id.
Always list from this endpoint — a template_id from another surface is
not necessarily valid for a poster.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | no | Case-insensitive name filter. |
sort | string | no | Sort expression (for example name:asc). |
source | string | no | Source filter (brand vs prezent). |
orientation | string | no | Only templates supporting this orientation (landscape, portrait, square). |
enabledFeature | string | no | Optional feature flag filter. |
limit | integer | no | Items per page (1–200, default 50). Enables pagination. |
cursor | string | no | Opaque cursor from a previous response's next_cursor. |
Example
curl -s "https://api.prezent.ai/api/v1/posters/templates?orientation=landscape&limit=50" \
-H "Authorization: Bearer YOUR_API_KEY"
Success response (200)
{
"success": true,
"data": {
"items": [
{
"id": "tmpl_corp_2026",
"name": "Corporate 2026",
"code": "CORP26",
"source": "brand",
"orientations": ["landscape", "portrait", "square"]
}
],
"next_cursor": null
}
}
Results are always scoped to the poster feature server-side; the caller cannot override the scoping. See Developer Guide → Pagination.
GET /api/v1/posters/languages — List supported languages
The languages a poster 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/v1/posters/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" }
]
}
Right-to-left locales such as ar mirror the poster's region order as
well as its text.
GET /api/v1/posters/agents — List agents
The poster agents available to the caller — the values you pass as
agent_code on generate.
| List | Contents |
|---|---|
default_agents | Prezent's agents for your industry, including any you have customized. |
my_agents | Agents you created from scratch. |
Example
curl -s https://api.prezent.ai/api/v1/posters/agents \
-H "Authorization: Bearer YOUR_API_KEY"
Success response (200)
{
"status": "success",
"log": "Agents retrieved successfully",
"data": {
"default_agents": [
{
"agent_code": "scientific_poster",
"agent_name": "Scientific Poster",
"description": "Research posters with methods, results, and conclusions.",
"instructions": "Lay out background, methods, results, and conclusions in reading order."
}
],
"my_agents": [
{
"agent_code": "612a9f7d-c2b8-48ba-8f5a-8acdefade314",
"agent_name": "Conference Board",
"description": "Event posters in our house style.",
"instructions": "Headline across the top, three key takeaways, QR code in the footer."
}
]
}
}
GET /api/v1/posters/conferences — List conferences
The conferences you can generate posters for, with every detail of each
one. Pass a conference_code from this list as conference_code on
generate.
The list holds active conferences for your industry: your profile's
industry, else your company's. It is sorted by short_name.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | no | Page number, starting at 1. Default 1. A page past the last returns an empty items. |
limit | integer | no | Conferences per page, 1–100. Default 10. A value above 100 is rejected with 400. |
This endpoint is always paginated. Without page and limit you
get the first 10 conferences, not the whole list. Keep requesting the
next page until you have total_count conferences; every page except
the last is full.
Example
# First page
curl -s "https://api.prezent.ai/api/v1/posters/conferences?page=1&limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
# Next page
curl -s "https://api.prezent.ai/api/v1/posters/conferences?page=2&limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
Success response (200)
{
"status": "success",
"log": "Conferences retrieved successfully",
"data": {
"items": [
{
"conference_code": "5a431fb7-bb8d-47b8-b88c-d4959861af4f",
"num_id": 42,
"short_name": "ASCO",
"full_name": "American Society of Clinical Oncology Annual Meeting",
"therapy_area": "Oncology",
"region": "US",
"website": "https://conferences.asco.org/am/attend",
"start_date": "2026-05-29",
"end_date": "2026-06-02",
"location": "Chicago, IL",
"instructions": "Use a structured abstract layout; include disclosures in the footer.",
"orientation": "landscape",
"height": 48,
"width": 96,
"size_unit": "in",
"min_body_font_pt": 24,
"references_format": "ama",
"industry": "Life Sciences",
"function": "Medical Affairs",
"created_at": "2026-09-16T10:12:44.000Z",
"updated_at": "2026-09-16T10:12:44.000Z"
}
],
"page": 1,
"limit": 10,
"total_count": 37
}
}
| Field | Description |
|---|---|
items | This page's conferences. |
page | The page returned. |
limit | The page size applied. |
total_count | Conferences available to you across all pages. |
Optional conference fields such as website, the dates, and the canvas
size may be null.
Errors
| Status | error.code | Meaning |
|---|---|---|
400 | INVALID_REQUEST | limit is above 100, or page is too large to be valid. error.details.details says which. |
GET /api/v2/integrations — List your apps
The third-party apps your team has enabled, with whether each one is
connected for you. Use an app's mcp_id to connect it.
Apps are listed for the user who owns the API key.
Example
curl -s "https://api.prezent.ai/api/v2/integrations" \
-H "Authorization: Bearer YOUR_API_KEY"
Success response (200)
{
"mcps": [
{
"mcp_id": "f4623c46-74d3-412e-8213-4f68a9606c97",
"mcp_name": "PubMed",
"description": "Search biomedical literature on PubMed.",
"category": "Life Sciences",
"auth_type": "no_auth",
"sample_prompt": "Build a 4-slide evidence deck. Search PubMed for ...",
"connection_state": "connected"
}
],
"count": 1
}
| Field | Description |
|---|---|
mcp_id | The app's ID. Pass it to /integrations/auth. |
mcp_name | The app's display name, e.g. PubMed. May be null. |
description | What the app does. May be null. |
category | App category, e.g. Life Sciences, Productivity. |
auth_type | How the app connects: dcr / standard (authorization URL), api_key (your API key), no_auth (nothing needed). |
sample_prompt | An example prompt that uses the app. May be null. |
connection_state | connected or not_connected. |
POST /api/v2/integrations/auth — Connect or disconnect an app
Connects a third-party app to the caller's Prezent account, or
disconnects it. Once connected, a poster 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.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
mcp_id | string (UUID) | yes | The app's ID, from GET /api/v2/integrations. |
action | string | no | connect (default) or disconnect. |
api_key | string | for api_key apps | Your API key for the app. |
instance_url | string | for some apps | Your company's MCP URL, for apps that need it (e.g. https://acme.veevavault.com/api/ai/mcp). |
GET with query parameters also works for connecting, except for
api_key apps — an API key is only accepted in a POST body.
Example
curl -s -X POST "https://api.prezent.ai/api/v2/integrations/auth" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mcp_id": "6a511689-7e64-42dc-9ca8-cce9bbebd73d",
"api_key": "YOUR_VEEVA_API_KEY",
"instance_url": "https://acme.veevavault.com/api/ai/mcp"
}'
Responses (200)
Authorization required (dcr / standard apps) — send the user to the URL:
{ "auth_url": "https://accounts.example.com/authorize?client_id=...&state=...", "connected": false }
The URL is single-use and time-limited. After the user authorizes,
confirm with
/auth/status.
Connected (api_key and no_auth apps, or any app that is already connected):
{ "status": "connected", "connected": true, "message": "MCP connection successful" }
If the app was already connected — whatever its auth_type — you get
this same response (no auth_url for dcr / standard apps), the
message says so, and the existing connection is kept. To reconnect an
app or change its API key, disconnect it first, then connect again.
Disconnected (action: "disconnect"):
{ "status": "disconnected", "message": "MCP disconnected successfully" }
Errors
| Status | error.code | Meaning |
|---|---|---|
400 | MISSING_REQUIRED_FIELD | mcp_id missing, or the app needs an api_key or instance_url that was not sent. error.details.field names it. |
400 | MCP_NOT_FOUND | No app with this mcp_id. |
400 | MCP_NOT_CONNECTED | disconnect on an app that is not connected. |
422 | INVALID_INPUT | A field is invalid, e.g. instance_url doesn't match the app's address, or api_key sent to an app that doesn't use one. |
503 | SERVICE_UNAVAILABLE | The 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 poster generation that depends on the app.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mcp_id | string (UUID) | yes | The app's ID. |
Example
curl -s "https://api.prezent.ai/api/v2/integrations/auth/status?mcp_id=f4623c46-74d3-412e-8213-4f68a9606c97" \
-H "Authorization: Bearer YOUR_API_KEY"
Success response (200)
{ "connected": false }
End-to-end example
Generate a poster, stream it to completion, then download it as a PDF.
- Python
- Node.js
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 stream to its terminal event, printing progress as it arrives."""
# 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():
# The server sends a `:ping` comment every 15s to hold the
# connection open. httpx-sse surfaces those as events with an
# empty `data`, so skip them or json.loads raises.
if not sse.data.strip():
continue
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
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/v1/posters/generate", json={
"prompt": (
"Create a poster on cloud misconfiguration as the leading cause of "
"enterprise data breaches. Organise content in 3 horizontal rows…"
),
"template_id": "your-template-id",
"settings": {
"orientation": "landscape",
"size": "a4",
"reference_format": "vancouver",
"websearch": True,
},
})
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()
read_stream(sess.json()["data"]["stream_url"])
# 4. ask for the file. A re-export renders while you wait, so allow for it.
dl = c.post(
"/api/v1/posters/download",
json={"callback_id": callback_id, "export_type": "pdf"},
timeout=180,
)
dl.raise_for_status()
print("poster ready:", dl.json()["download_url"])
import { EventSource } from 'eventsource'; // npm install eventsource
const BASE = 'https://api.prezent.ai';
const HEADERS = {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
};
const post = async (path, body) => {
const res = await fetch(`${BASE}${path}`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${path} → ${res.status} ${await res.text()}`);
return res.json();
};
// Read a stream to its terminal event, logging progress as it arrives.
const readStream = (streamUrl) =>
new Promise((resolve, reject) => {
// The token is in the URL — no Authorization header here.
const es = new EventSource(streamUrl);
es.onmessage = (e) => {
const event = JSON.parse(e.data);
console.log(event.status, '-', event.message);
if (event.status === 'inprogress') return;
es.close();
if (event.status === 'success') resolve();
else reject(new Error(`${event.status}: ${event.message}`));
};
es.onerror = () => { es.close(); reject(new Error('stream failed')); };
});
// 1. start
const { resData } = await post('/api/v1/posters/generate', {
prompt:
'Create a poster on cloud misconfiguration as the leading cause of ' +
'enterprise data breaches. Organise content in 3 horizontal rows…',
template_id: 'your-template-id',
settings: {
orientation: 'landscape',
size: 'a4',
reference_format: 'vancouver',
websearch: true,
},
});
const callbackId = resData.callback_id;
// 2 + 3. stream to completion
const { data } = await post('/api/v2/streams/sessions', { callback_id: callbackId });
await readStream(data.stream_url);
// 4. ask for the file — the URL comes back inline
const dl = await post('/api/v1/posters/download', {
callback_id: callbackId,
export_type: 'pdf',
});
console.log('poster ready:', dl.download_url);
Errors
Every error uses the canonical envelope. The codes you are most likely to see on this surface:
| Status | error.code | Cause |
|---|---|---|
400 | BAD_REQUEST, INVALID_JSON, MISSING_CALLBACK_ID, MISSING_REQUIRED_FIELD, INVALID_DATA_TYPE | Malformed request, unknown callback_id, poster not ready to edit, or a template_change template that is not available to you. |
401 | UNAUTHORIZED, INVALID_API_KEY, EXPIRED_API_KEY | Missing, invalid, or expired key. |
404 | ENDPOINT_NOT_FOUND, STREAM_CALLBACK_NOT_FOUND, MCP_NOT_FOUND | Key not scoped to this path, expired session, or unknown app. |
422 | INVALID_INPUT | Field validation failed — bad orientation, size, reference_format, an unsupported language, an agent_code / conference_code not available to you, or an invalid edit action_type / value. error.details is an array of messages. |
429 | TOO_MANY_REQUESTS, RATE_LIMIT_EXCEEDED, USAGE_LIMIT_EXCEEDED | Gateway throttle, per-category limit, or annual quota. |
500 | INTERNAL_SERVER_ERROR | Unexpected server error. |
503 | SERVICE_UNAVAILABLE | A downstream service is temporarily unreachable. |
Full catalog: Error Reference.
Where to go next
- Streaming (SSE) — reconnection, timeouts, and production-ready client patterns.
- File Upload — how to get the
file_idvalues that ground a poster. - API Reference — every field, interactively.
- Rate Limits — the three limit tiers and how to back off.