> For the complete documentation index, see [llms.txt](https://guide.plusai.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://guide.plusai.com/apis-for-presentations/presentation-agent-api.md).

# Presentation Agent API

{% hint style="info" %}
Presentation Agent API requires a **Pro or Team plan**. [Learn more about pricing](/plus-ai/plans-and-billing.md).
{% endhint %}

The **Plus AI Presentation Agent API** lets you generate and edit PowerPoint presentations programmatically over HTTP. Instead of calling a single render endpoint, you delegate the work to an autonomous AI agent: you describe what you want in natural language, and the agent plans the deck, writes the content, builds the slides, and returns a finished `.pptx` (and `.pdf`).

Working with the agent is a simple flow:

1. **Start a session** — send a `POST` request with your prompt (and any optional files). The API responds immediately with a `sessionId` and a polling URL.
2. **Get the result** — poll the session until its status reaches a terminal state, then download the generated presentation.
3. **Continue the conversation** — send a follow-up prompt to the same session, and the agent picks up where it left off, with everything said and done so far. See [Conversations](#conversations).

Alternatively, you can supply a **webhook callback URL** when you create the session, and we'll notify you when the result is ready — no polling required.

### Highlights

* **Natural-language generation** — describe the presentation you want and the agent builds it end to end, from outline to finished slides.
* **Multi-turn conversations** — follow up in the same session (“make slide 3 a chart”, “now translate it to German”): the agent remembers the conversation and keeps editing the same deck.
* **Edit existing decks** — provide an existing PowerPoint file to have the agent revise, extend, restyle, or translate it rather than starting from scratch.
* **Bring your own content** — attach supporting files (spreadsheets, documents, images, and more) and the agent incorporates them into the presentation.
* **Choose your model** — select from a range of leading AI models to balance speed, cost, and quality.
* **Asynchronous by design** — kick off long-running jobs and either poll for progress or receive a webhook callback when the deck is ready.
* **Polished output** — download the result as both PowerPoint (`.pptx`) and PDF, plus a thumbnail preview.
* **Multilingual** — generate or translate presentations in the language of your choice.

### Working with files

The Presentation Agent can do more than generate decks from a blank slate. Using the **Files API**, you can give the agent a **starting presentation** to work from and **attachments** to pull content from — so the agent edits and fills in a real deck instead of creating one from scratch. This unlocks a powerful pattern: **keep a presentation as a reusable template and have the agent refresh it on a schedule with the latest data.** Of course, you can also omit the starting presentation to create something new.

#### How it works

1. **Upload your files.** Send each file to `PUT /r/v0/files/upload`. Each upload returns a file ID.
2. **Reference them when you start a session.** In your `POST /r/v0/agent/sessions` request:
   * Set `pptxFileId` to the file ID of the presentation you want the agent to start from and edit.
   * Add any supporting files to `attachments` as a list of `{ "fileId": "..." }`. The agent reads these and uses their content to fill in or update the deck.
3. **Get the result.** Poll the session (or use a webhook callback) and download the updated presentation.

The agent treats `pptxFileId` as the canvas to edit — preserving your branding, layout, and structure — and treats `attachments` as the source material to incorporate.

#### Example: refresh a weekly analytics report

Suppose you maintain a branded "Weekly Performance Review" deck and want to regenerate it every Monday with last week's numbers.

1. Upload your existing report and this week's data export:

   <pre class="language-bash" data-collapsed-line-count="10"><code class="lang-bash"># Upload the existing report (your template/starting point)
   curl -X PUT "https://api.plusdocs.com/r/v0/files/upload" \
     -H "Authorization: Bearer $PLUSAI_API_KEY" \
     -F "file=@weekly-report.pptx"
   # → { "fileId": "file_abc123", ... }

   # Upload this week's analytics export
   curl -X PUT "https://api.plusdocs.com/r/v0/files/upload" \
     -H "Authorization: Bearer $PLUSAI_API_KEY" \
     -F "file=@analytics-2026-05-25.xlsx"
   # → { "fileId": "file_def456", ... }
   </code></pre>
2. Start a session that updates the report using the new data:

   <pre class="language-bash" data-collapsed-line-count="10"><code class="lang-bash">curl -X POST "https://api.plusdocs.com/r/v0/agent/sessions" \
     -H "Authorization: Bearer $PLUSAI_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "prompt": "Update this weekly performance review with the figures from the attached analytics export. Refresh the charts and KPI numbers for the week of May 25, keep the existing layout and branding, and update the title slide date.",
       "pptxFileId": "file_abc123",
       "attachments": [{ "fileId": "file_def456" }]
     }'
   </code></pre>

The agent opens your existing report, reads the spreadsheet, updates the relevant slides, and returns a finished deck — ready to download or deliver via your webhook.

#### More ideas

* **Monthly board deck** — keep a board-meeting template and attach the month's financials, product metrics, and a notes doc; have the agent populate each section.
* **Sales proposal from a template** — start from your standard pitch deck and attach a customer brief so the agent tailors it to a specific prospect.
* **Translate and localize** — provide an existing deck and ask the agent to produce a version in another language while preserving design.
* **Quarterly review from raw data** — attach CSV exports and a summary document and have the agent build out the data slides on top of your template.

### Conversations

Every session is a **conversation**. The prompt you create it with is its first turn (turn `0`), and each follow-up you post to `POST /r/v0/agent/sessions/{sessionId}/messages` starts the next one. The agent remembers the conversation so far — your earlier requests, its replies and the files you attached (very long conversations are condensed as they grow) — and keeps editing the same deck, so you can refine it step by step instead of starting over. That deck is the session's own copy: a file you passed as `pptxFileId` is never changed.

#### How a conversation works

1. **Create a session** as usual and poll it until the first turn has ended: `status` is `IDLE`, `FAILED` or `INTERRUPTED`.
2. **Post a follow-up.** Send a `prompt` to `POST /r/v0/agent/sessions/{sessionId}/messages`, optionally with `attachments` (the same `[{ "fileId": "..." }]` list as when you create a session). The response tells you the `turn` the message started.
3. **Poll the same URL** until `turn` equals that turn and `status` is `IDLE`, `FAILED` or `INTERRUPTED` — or until `stalled` is `true`. `lastAssistantMessage` is the agent's last reply in the turn and `pptxUrl` is always the latest version of the deck. When the turn ends `IDLE`, `turnPptxUrl` is the deck as that turn left it; it's `null` after a `FAILED` or `INTERRUPTED` turn, and only the current turn's is shown, so save it if you want to keep that version.

#### Example: refine a deck over several turns

{% code collapsedlinecount="10" %}

```bash
# SESSION_ID is the sessionId returned when you created the session.
# Ask for a change to the deck the session built:
MESSAGE_KEY=$(uuidgen)   # reuse it only if you retry this same request
curl -X POST "https://api.plusdocs.com/r/v0/agent/sessions/$SESSION_ID/messages" \
  -H "Authorization: Bearer $PLUSAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $MESSAGE_KEY" \
  -d '{ "prompt": "Turn the revenue table on slide 4 into a bar chart", "expectedTurn": 0 }'
# → { "sessionId": "...", "turn": 1, "pollingUrl": "https://api.plusdocs.com/r/v0/agent/sessions/..." }

# Poll until turn 1 has ended:
curl "https://api.plusdocs.com/r/v0/agent/sessions/$SESSION_ID" \
  -H "Authorization: Bearer $PLUSAI_API_KEY"
# → { "turn": 1, "status": "IDLE", "lastAssistantMessage": "...", "pptxUrl": "...", ... }

# Or stop turn 1 while it's still running:
curl -X POST "https://api.plusdocs.com/r/v0/agent/sessions/$SESSION_ID/interrupt" \
  -H "Authorization: Bearer $PLUSAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "expectedTurn": 1 }'
# → { "sessionId": "...", "turn": 1, "status": "INTERRUPTED" }
```

{% endcode %}

#### Things to know

* **One turn at a time.** A message sent while a turn is still in progress is refused with `409 AGENT_BUSY`. Wait for the turn to end, or stop it with the `interruptUrl` the refusal includes, then post again.
* **Stopping a turn.** `POST /r/v0/agent/sessions/{sessionId}/interrupt` stops the turn in progress, and any sub-agents working on it. Send the turn you want to stop as `expectedTurn`, so a late request can't stop a newer turn. The session then reports `INTERRUPTED` and accepts the next message. If the turn had already ended, nothing is stopped and you get `409 NOT_ACTIVE` with the session's current `status` and `turn`: just post your next message.
* **After a failed or stopped turn.** A `FAILED` or `INTERRUPTED` turn doesn't end the conversation: post another message to retry or carry on. Changes the agent made before the turn ended stay in the deck; nothing is rolled back. For a `FAILED` turn, `statusMessage` says what went wrong.
* **A stalled turn.** If `stalled` is `true`, the turn will never finish. Post your message again with a **new** `Idempotency-Key` and it replaces the stalled turn (resending with the same key only returns the stalled turn again). A message sent after an `AGENT_BUSY` refusal's `staleAt` replaces the busy turn too.
* **Safe retries.** Send an `Idempotency-Key` header (1–255 letters, digits, `.`, `_`, `:` or `-`) with each message, a new one for every new message, and reuse it only when you retry the same request, for example after a timeout or a `503`. A retry of a message that was already accepted returns the same turn instead of starting another one. Without the header, every request is a new message.
* **Stay in sync.** Pass `expectedTurn` (the `turn` you last saw) and a message is refused with `409 CONVERSATION_ADVANCED` if the conversation moved on without you.
* **Webhooks.** The `callbackUrl` you gave when creating the session is called whenever a turn completes or fails (not when you stop one). A `callbackUrl` sent with a message replaces it from that turn on.
* **What a follow-up can't change.** A follow-up keeps the session's deck, model and language: `pptxFileId`, `model` and `language` are ignored on a message. Start a new session to change them.
* **Limits.** A conversation can go on for up to 14 days from when it was created, and for up to 50 turns. PDFs attached across all its turns can total at most 100 pages (`400 PDF_PAGE_LIMIT`). Once a conversation can't go on, follow-ups are refused with `410 SESSION_EXPIRED` (too old or too many turns; `reason` says which) or `409` with `CONTEXT_OVERFLOW` or `SESSION_NOT_CONTINUABLE` (for example, its history has grown too large for the model, or the model is no longer available). Start a new session then; to pick up from the latest deck, download it from `pptxUrl`, upload it with `PUT /r/v0/files/upload`, and pass its file ID as the new session's `pptxFileId`.
* **Credits.** Each turn uses AI agent credits, and `turnCreditsUsed` shows what the current turn has used so far (not counting sub-agents it starts). When your organization is out of credits, a follow-up is refused with `402 INSUFFICIENT_AGENT_CREDITS`.
* **Who can continue a session.** Only the user who created a session can continue or stop it; for anyone else those requests return `404`. Anyone with an API key for the session's organization can read its status, including the agent's replies and download links.

## Authentication

To authenticate with the Presentations API, you must first generate an API key. You can [generate an API key here](https://app.plusdocs.com/api-settings). \
\
*Note: Only one active API key per user is currently allowed.*\
\
Once you have an API key, you can authenticate your requests to the presentations api using [HTTP Bearer authentication](https://swagger.io/docs/specification/v3_0/authentication/bearer-authentication/):

{% code collapsedlinecount="10" %}

```
Authorization: Bearer {PLUSAI_API_KEY}
```

{% endcode %}

## Create and start an AI agent session

> Creates a new agent session, posts the initial prompt, and starts the\
> agent. Returns a polling URL to track progress.\
> \
> Optionally seeds the session with an existing PowerPoint file\
> (\`pptxFileId\`) and/or file attachments (\`attachments\[].fileId\`). File\
> IDs must reference files in the caller's organization that the caller\
> owns or that are shared with one of their teams (e.g. uploaded via\
> \`PUT /r/v0/files/upload\`); other files are reported as not found.<br>

```json
{"openapi":"3.1.0","info":{"title":"PlusAI REST API","version":"v0"},"tags":[{"name":"Presentation Agent","description":"Create and drive AI presentation agent sessions."}],"servers":[{"url":"https://api.plusdocs.com","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key issued for your Plus organization, sent as a bearer token."}},"schemas":{"CreateAgentSessionRequest":{"type":"object","properties":{"prompt":{"type":"string","description":"The initial instruction for the agent."},"pptxFileId":{"type":"string","description":"ID of an existing PowerPoint file (uploaded via the files API) to\nseed the session with. Must reference a `.pptx` file (legacy\n`.ppt` files aren't supported).\n"},"language":{"type":"string","description":"Target language. Defaults to `auto`.","default":"auto"},"attachments":{"type":"array","description":"Files to attach to the initial message.","items":{"type":"object","properties":{"fileId":{"type":"string"}},"required":["fileId"]}},"callbackUrl":{"type":"string","format":"uri","description":"HTTPS URL invoked when the session changes state. Must be a public\nHTTPS address (no localhost/private IPs).\n"},"model":{"type":"string","description":"The model to use.","enum":["claude-fable-5","claude-opus-5-5","claude-opus-5","claude-opus-4-8","claude-opus-4-7","claude-opus-4-6","claude-sonnet-5-5","claude-sonnet-5","claude-sonnet-4-6","claude-haiku-4-5","gpt-5.4","gpt-5.4-nano","gpt-5.4-mini","gpt-5.4-pro","gpt-5.5","gpt-5.5-pro","gpt-5.6-sol","gpt-5.6-terra","gpt-5.6-luna","@cf/moonshotai/kimi-k2.7-code"]}},"required":["prompt"]},"CreateAgentSessionResponse":{"type":"object","properties":{"sessionId":{"type":"string"},"pollingUrl":{"type":"string","format":"uri","description":"URL to poll for session state."}},"required":["sessionId","pollingUrl"]},"Error":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"PaymentRequiredError":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"responses":{"BadRequest":{"description":"The request was malformed or missing required fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/r/v0/agent/sessions":{"post":{"operationId":"createAgentSession","tags":["Presentation Agent"],"summary":"Create and start an AI agent session","description":"Creates a new agent session, posts the initial prompt, and starts the\nagent. Returns a polling URL to track progress.\n\nOptionally seeds the session with an existing PowerPoint file\n(`pptxFileId`) and/or file attachments (`attachments[].fileId`). File\nIDs must reference files in the caller's organization that the caller\nowns or that are shared with one of their teams (e.g. uploaded via\n`PUT /r/v0/files/upload`); other files are reported as not found.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentSessionRequest"}}}},"responses":{"201":{"description":"Session created and started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentSessionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Insufficient AI agent credits for the billing cycle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRequiredError"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Get the current state of an agent session

> Returns the current status of an agent session. Poll this endpoint\
> until \`status\` reaches a terminal value. When the agent finishes,\
> \`pdfUrl\`, \`pptxUrl\`, and \`thumbnailUrl\` are populated with download URLs.\
> \
> A session is a conversation: its first prompt is turn 0, and each\
> message posted with \`POST /r/v0/agent/sessions/{sessionId}/messages\`\
> starts the next turn. \`status\`, \`lastAssistantMessage\`,\
> \`statusMessage\`, \`stalled\`, \`turnCreditsUsed\` and \`turnPptxUrl\` all\
> describe the current \`turn\`.\
> \
> \*\*Polling a turn.\*\* After posting a message that returned turn \`N\`,\
> poll until \`turn\` equals \`N\` and \`status\` is \`IDLE\`, \`FAILED\` or\
> \`INTERRUPTED\`, or until \`stalled\` is \`true\`. The new turn is recorded\
> before the message is acknowledged, so a poll never reports the\
> previous turn's result as the new turn's.\
> \
> Any member of the session's organization can read it; only the user\
> who created it can continue or interrupt it.<br>

```json
{"openapi":"3.1.0","info":{"title":"PlusAI REST API","version":"v0"},"tags":[{"name":"Presentation Agent","description":"Create and drive AI presentation agent sessions."}],"servers":[{"url":"https://api.plusdocs.com","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key issued for your Plus organization, sent as a bearer token."}},"schemas":{"AgentSession":{"type":"object","properties":{"sessionId":{"type":"string"},"status":{"type":"string","description":"The status of the current turn: `READY`, `RUNNING` and\n`PENDING_TOOL_CALLS` are in progress; `IDLE` means the turn\nfinished and the session is waiting for the next message.\n","enum":["READY","IDLE","RUNNING","PENDING_TOOL_CALLS","DONE","INTERRUPTED","FAILED"]},"turn":{"type":"integer","minimum":0,"description":"The conversation turn the session is on: 0 for the prompt it was\ncreated with, then one more for each message posted to it.\n"},"lastAssistantMessage":{"type":["string","null"],"description":"The agent's latest reply in the current turn, once it has ended. Null for sessions not created through this API (e.g. the web app's or the PowerPoint add-in's chats)."},"statusMessage":{"type":["string","null"],"description":"A description of the current status, e.g. why the turn failed."},"stalled":{"type":"boolean","description":"The current turn can no longer finish (it never started, or stopped\nmaking progress). Stop polling it; posting a new message replaces\nthe stalled turn.\n"},"turnCreditsUsed":{"type":["number","null"],"description":"AI agent credits the current turn has used so far."},"turnPptxUrl":{"type":["string","null"],"format":"uri","description":"Download URL for the presentation as of the end of the current\nturn (populated once the turn is finished). `pptxUrl` is always the\nlatest version.\n"},"pdfUrl":{"type":["string","null"],"format":"uri","description":"Download URL for the generated PDF (populated when finished)."},"pptxUrl":{"type":["string","null"],"format":"uri","description":"Download URL for the generated PPTX (populated when finished)."},"thumbnailUrl":{"type":["string","null"],"format":"uri","description":"Download URL for a thumbnail image (populated when finished)."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["sessionId","status","turn","stalled"]},"Error":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}},"responses":{"BadRequest":{"description":"The request was malformed or missing required fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/r/v0/agent/sessions/{sessionId}":{"get":{"operationId":"getAgentSession","tags":["Presentation Agent"],"summary":"Get the current state of an agent session","description":"Returns the current status of an agent session. Poll this endpoint\nuntil `status` reaches a terminal value. When the agent finishes,\n`pdfUrl`, `pptxUrl`, and `thumbnailUrl` are populated with download URLs.\n\nA session is a conversation: its first prompt is turn 0, and each\nmessage posted with `POST /r/v0/agent/sessions/{sessionId}/messages`\nstarts the next turn. `status`, `lastAssistantMessage`,\n`statusMessage`, `stalled`, `turnCreditsUsed` and `turnPptxUrl` all\ndescribe the current `turn`.\n\n**Polling a turn.** After posting a message that returned turn `N`,\npoll until `turn` equals `N` and `status` is `IDLE`, `FAILED` or\n`INTERRUPTED`, or until `stalled` is `true`. The new turn is recorded\nbefore the message is acknowledged, so a poll never reports the\nprevious turn's result as the new turn's.\n\nAny member of the session's organization can read it; only the user\nwho created it can continue or interrupt it.\n","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The current session state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSession"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Session not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Continue an agent session with another prompt

> Posts a follow-up prompt to an existing session, which starts the\
> session's next turn and runs the agent on it. The agent keeps the\
> conversation so far and edits the session's presentation in place.\
> Poll the returned \`pollingUrl\` (the same URL as at creation) until\
> \`turn\` equals the returned \`turn\` and \`status\` is \`IDLE\`, \`FAILED\` or\
> \`INTERRUPTED\`, or until \`stalled\` is \`true\`.\
> \
> Only the user who created the session can continue it. A turn still\
> in progress must finish, or be stopped with\
> \`POST /r/v0/agent/sessions/{sessionId}/interrupt\`, before the next\
> one can start (\`409 AGENT\_BUSY\`).\
> \
> Send an \`Idempotency-Key\` header to retry safely: a retry with the\
> same key after the message was accepted returns the same turn and\
> doesn't start another.<br>

```json
{"openapi":"3.1.0","info":{"title":"PlusAI REST API","version":"v0"},"tags":[{"name":"Presentation Agent","description":"Create and drive AI presentation agent sessions."}],"servers":[{"url":"https://api.plusdocs.com","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key issued for your Plus organization, sent as a bearer token."}},"schemas":{"SendAgentSessionMessageRequest":{"type":"object","properties":{"prompt":{"type":"string","description":"The follow-up instruction for the agent."},"attachments":{"type":"array","description":"Files to attach to the message, as for `POST /r/v0/agent/sessions`.\n","items":{"type":"object","properties":{"fileId":{"type":"string"}},"required":["fileId"]}},"callbackUrl":{"type":"string","format":"uri","description":"HTTPS URL invoked when this turn ends, replacing the session's\ncallback URL. Must be a public HTTPS address (no localhost/private\nIPs).\n"},"expectedTurn":{"type":"integer","minimum":0,"description":"The session's `turn` the caller last saw. The message is refused\nwith `409 CONVERSATION_ADVANCED` if the session has moved on.\n"}},"required":["prompt"]},"SendAgentSessionMessageResponse":{"type":"object","properties":{"sessionId":{"type":"string"},"turn":{"type":"integer","description":"The turn this message started. Poll until the session reports it."},"pollingUrl":{"type":"string","format":"uri","description":"URL to poll for session state (the same for every turn)."}},"required":["sessionId","turn","pollingUrl"]},"Error":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"AgentSessionRefusal":{"type":"object","description":"Why the session didn't accept the message. Nothing was changed.","properties":{"code":{"type":"string","enum":["AGENT_BUSY","CONVERSATION_ADVANCED","SESSION_NOT_CONTINUABLE","CONTEXT_OVERFLOW","INSUFFICIENT_AGENT_CREDITS","NOT_ORG_MEMBER","PRESENTATION_EDIT_FORBIDDEN","SESSION_EXPIRED","INVALID_ATTACHMENT_KEY","ATTACHMENT_FAILED","PDF_PAGE_LIMIT","ORG_ACCESS_UNVERIFIED","TURN_START_FAILED"]},"message":{"type":"string"},"turn":{"type":"integer","description":"The session's current turn."},"reason":{"type":"string","description":"For `SESSION_NOT_CONTINUABLE` and `SESSION_EXPIRED`, why the\nconversation can't continue (e.g. `agent`, `model_retired`, `age`, `turns`).\n"},"staleAt":{"type":["string","null"],"format":"date-time","description":"`AGENT_BUSY` only: when the running turn counts as stalled, after\nwhich a new message replaces it.\n"},"interruptUrl":{"type":"string","format":"uri","description":"`AGENT_BUSY` only: the URL that stops the running turn."}},"required":["code","message"]}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/r/v0/agent/sessions/{sessionId}/messages":{"post":{"operationId":"sendAgentSessionMessage","tags":["Presentation Agent"],"summary":"Continue an agent session with another prompt","description":"Posts a follow-up prompt to an existing session, which starts the\nsession's next turn and runs the agent on it. The agent keeps the\nconversation so far and edits the session's presentation in place.\nPoll the returned `pollingUrl` (the same URL as at creation) until\n`turn` equals the returned `turn` and `status` is `IDLE`, `FAILED` or\n`INTERRUPTED`, or until `stalled` is `true`.\n\nOnly the user who created the session can continue it. A turn still\nin progress must finish, or be stopped with\n`POST /r/v0/agent/sessions/{sessionId}/interrupt`, before the next\none can start (`409 AGENT_BUSY`).\n\nSend an `Idempotency-Key` header to retry safely: a retry with the\nsame key after the message was accepted returns the same turn and\ndoesn't start another.\n","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"A client-generated key for this message (e.g. a UUID), reused on\nretries. 1 to 255 letters, digits, `.`, `_`, `:` or `-`. Without\none, every request is a new message.\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._:-]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendAgentSessionMessageRequest"}}}},"responses":{"202":{"description":"Message accepted; the new turn has started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendAgentSessionMessageResponse"}}}},"400":{"description":"The request is invalid, or an attachment can't be used\n(`INVALID_ATTACHMENT_KEY`, `ATTACHMENT_FAILED`, `PDF_PAGE_LIMIT`).\n","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/AgentSessionRefusal"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Insufficient AI agent credits for the billing cycle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSessionRefusal"}}}},"403":{"description":"The caller may no longer use the session's organization\n(`NOT_ORG_MEMBER`) or edit its presentation\n(`PRESENTATION_EDIT_FORBIDDEN`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSessionRefusal"}}}},"404":{"description":"Session not found, or not created by the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The session can't take this message now:\n- `AGENT_BUSY`: a turn is still in progress. Wait for it to end,\n  or stop it with `interruptUrl`, then post again. `staleAt` is\n  when the running turn counts as stalled, after which a new\n  message replaces it.\n- `CONVERSATION_ADVANCED`: `expectedTurn` is behind the session's\n  `turn`.\n- `SESSION_NOT_CONTINUABLE` (see `reason`) or `CONTEXT_OVERFLOW`:\n  the conversation can't continue; create a new session.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSessionRefusal"}}}},"410":{"description":"`SESSION_EXPIRED`: the conversation is too old or has too many\nturns (see `reason`); create a new session.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSessionRefusal"}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"description":"A temporary failure (`ORG_ACCESS_UNVERIFIED`,\n`TURN_START_FAILED`); retry with the same `Idempotency-Key`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSessionRefusal"}}}}}}}}}
```

## Stop the turn an agent session is working on

> Stops the session's current turn, and any sub-agents working on it,\
> if it is still in progress. The session then reports \`INTERRUPTED\`\
> and accepts the next message. Pass \`expectedTurn\` so a late request\
> can't stop a newer turn.\
> \
> Only the user who created the session can interrupt it.<br>

```json
{"openapi":"3.1.0","info":{"title":"PlusAI REST API","version":"v0"},"tags":[{"name":"Presentation Agent","description":"Create and drive AI presentation agent sessions."}],"servers":[{"url":"https://api.plusdocs.com","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key issued for your Plus organization, sent as a bearer token."}},"schemas":{"InterruptAgentSessionRequest":{"type":"object","properties":{"expectedTurn":{"type":"integer","minimum":0,"description":"Stop only this turn. Without it, whichever turn is in progress is\nstopped.\n"}}},"InterruptAgentSessionResponse":{"type":"object","properties":{"sessionId":{"type":"string"},"turn":{"type":"integer","description":"The turn that was stopped."},"status":{"type":"string","enum":["INTERRUPTED"]}},"required":["sessionId","turn","status"]},"Error":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"AgentSessionNotActive":{"type":"object","properties":{"code":{"type":"string","enum":["NOT_ACTIVE"]},"message":{"type":"string"},"turn":{"type":"integer","description":"The session's current turn."},"status":{"type":"string","description":"The session's status."}},"required":["code","message","turn","status"]}},"responses":{"BadRequest":{"description":"The request was malformed or missing required fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/r/v0/agent/sessions/{sessionId}/interrupt":{"post":{"operationId":"interruptAgentSession","tags":["Presentation Agent"],"summary":"Stop the turn an agent session is working on","description":"Stops the session's current turn, and any sub-agents working on it,\nif it is still in progress. The session then reports `INTERRUPTED`\nand accepts the next message. Pass `expectedTurn` so a late request\ncan't stop a newer turn.\n\nOnly the user who created the session can interrupt it.\n","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterruptAgentSessionRequest"}}}},"responses":{"202":{"description":"The turn was stopped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterruptAgentSessionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Session not found, or not created by the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`NOT_ACTIVE`: nothing was stopped, because the turn had already\nended (or `expectedTurn` isn't the current turn). `status` and\n`turn` are the session's.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSessionNotActive"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Upload a file

> Uploads a file as \`multipart/form-data\`. The upload is validated and\
> accepted synchronously, then processed asynchronously.\
> \
> Constraints:\
> \- The request method must be \`PUT\`.\
> \- \`Content-Type\` must be \`multipart/form-data\`.\
> \- \`Content-Length\` is required (chunked transfer encoding is not\
> &#x20; supported).\
> \- Maximum size is 125 MB.<br>

```json
{"openapi":"3.1.0","info":{"title":"PlusAI REST API","version":"v0"},"tags":[{"name":"Files","description":"Upload, list, and delete user files."}],"servers":[{"url":"https://api.plusdocs.com","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key issued for your Plus organization, sent as a bearer token."}},"schemas":{"UploadAccepted":{"type":"object","properties":{"status":{"type":"string"},"uploadId":{"type":"string"},"message":{"type":"string"}},"required":["status","uploadId","message"]},"Error":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"UploadError":{"type":"object","description":"Error envelope returned by the file-upload endpoint.","properties":{"error":{"type":"string"}},"required":["error"]}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/r/v0/files/upload":{"put":{"operationId":"uploadFile","tags":["Files"],"summary":"Upload a file","description":"Uploads a file as `multipart/form-data`. The upload is validated and\naccepted synchronously, then processed asynchronously.\n\nConstraints:\n- The request method must be `PUT`.\n- `Content-Type` must be `multipart/form-data`.\n- `Content-Length` is required (chunked transfer encoding is not\n  supported).\n- Maximum size is 125 MB.\n","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The file to upload."}},"required":["file"]}}}},"responses":{"202":{"description":"File received; processing will begin shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAccepted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"405":{"description":"Method not allowed (must use PUT).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadError"}}}},"411":{"description":"Content-Length header is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadError"}}}},"413":{"description":"File exceeds the size limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadError"}}}},"415":{"description":"Content-Type must be multipart/form-data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadError"}}}}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://guide.plusai.com/apis-for-presentations/presentation-agent-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
