How to Automate Your Slide Decks with AI and APIs
A working walkthrough of the Presentation Agent API, from uploading a template to shipping native PowerPoint files on a cron.
There is something appealing about having your own presentation generator: one that knows your template, reads your data, and produces a fresh PowerPoint file without making you rebuild the same 12 slides every Monday.
AI tools such as Kimi and Genspark can already generate a deck from a prompt. They are useful when you need slides quickly.
But deploying your own workflow gives you a different kind of control. You decide where the source files come from, what instructions are always applied, who can trigger a run, where the finished deck goes, and when the whole thing happens.
So, how would you build it?
For this walkthrough, I am using the Plus AI Presentation Agent API. We will make a small Python job that starts from an existing company template, adds the latest data, asks the agent to update the deck, and downloads an editable `.pptx`. At the end, we will put that job on a weekly cron schedule.
You do not need to build a full app to follow along. Think of this as the smallest useful version of an internal deck generator.
Plus AI Presentation Agent API
Plus AI currently exposes two presentation APIs. The regular Presentations API is the direct prompt-to-deck option. You submit a prompt, poll the job, and download the generated presentation.
The Presentation Agent API can open an existing PowerPoint, read attachments, edit the slides, and return PowerPoint and PDF versions.
That means the input can be more specific than “make a deck about Q3.” I can give it a branded weekly report and say:
Update this deck using the attached analytics export. Replace the KPI values, refresh the charts, keep the current slide order, and update the reporting date.
The .pptx acts as the canvas and the attachments provide source material. The result is a native PowerPoint file that remains editable. Someone will always ask to change a number five minutes before the meeting.
The API uses an asynchronous job model. A POST request starts a session and returns a sessionId and pollingUrl. A client can poll until the session reaches a terminal state, or provide a public HTTPS callback URL and wait for a webhook.
I will use polling in this walkthrough because it keeps the entire example inside one script.
How to Use the API
1. Create an API Key
You need a Plus AI plan that includes API access. Generate a key from the API settings page, then expose it to the script through an environment variable:
export PLUSAI_API_KEY=”your-key-here”The key is sent with every request using bearer authentication:
Authorization: Bearer YOUR_KEYKeep it out of the source code and Git history. On a deployed service, I would load it from the platform’s secret store. Plus AI currently permits one active API key per user, so replacing the key can affect every job running under that user.
2. Upload the PowerPoint Template
My starting file is a normal .pptx that already contains the layout I want the agent to follow. For a weekly performance report, it might have:
A title slide with the reporting date
A KPI summary
Revenue and acquisition charts
A slide for wins, risks, and next steps
The correct logo, fonts, colors, and footer
Before uploading it, I would remove speaker notes, hidden slides, and old data. A template should provide structure without conflicting content.
The Files API accepts uploads as multipart/form-data:
curl -X PUT “https://api.plusdocs.com/r/v0/files/upload” \
-H “Authorization: Bearer $PLUSAI_API_KEY” \
-F “file=@weekly-performance-template.pptx”The Files API reference currently lists a maximum upload size of 125 MB. It also says uploads are processed asynchronously, so an accepted upload is not necessarily ready to use in an agent session immediately.
Once processing finishes, the file appears in the file list:
curl “https://api.plusdocs.com/r/v0/files?limit=100” \
-H “Authorization: Bearer $PLUSAI_API_KEY”Find the template and save its id:
export PLUSAI_TEMPLATE_FILE_ID=”file_abc123”I would reuse that ID across scheduled runs. When the design changes, I can upload a new version and update the configuration.
3. Upload the Latest Source Data
The template provides the design, but the agent still needs current information. In this example, that information comes from an Excel export generated by an analytics system.
Here is a helper that uploads the file and waits for it to appear in the file list:
import os
import time
from pathlib import Path
import requests
BASE_URL = “https://api.plusdocs.com/r/v0”
HEADERS = {
“Authorization”: f”Bearer {os.environ[’PLUSAI_API_KEY’]}”
}
def upload_file(path: str) -> str:
source = Path(path)
with source.open(”rb”) as handle:
response = requests.put(
f”{BASE_URL}/files/upload”,
headers=HEADERS,
files={”file”: (source.name, handle)},
timeout=120,
)
response.raise_for_status()
accepted = response.json()
if accepted.get(”fileId”):
return accepted[”fileId”]
for _ in range(30):
listing = requests.get(
f”{BASE_URL}/files?limit=100”,
headers=HEADERS,
timeout=30,
)
listing.raise_for_status()
matches = [
item
for item in listing.json()[”files”]
if item.get(”name”) == source.name
]
if matches:
return matches[0][”id”]
time.sleep(2)
raise TimeoutError(
“The uploaded file did not finish processing”
)The helper checks for an immediate fileId, then falls back to the list endpoint. Each export needs a unique filename, such as analytics-2026-08-10.xlsx, so the lookup cannot select an older upload.
In production, I would retry network errors and selected 5xx responses. A 401 needs a credential fix, while a malformed request will keep failing regardless of backoff.
4. Start an Agent Session
The session request connects the template, the attachment, and the instructions:
def start_agent(data_file_id: str) -> str:
payload = {
“prompt”: (
“Update the weekly performance review using the attached “
“analytics export. Refresh every KPI and chart. Update the “
“reporting date, preserve the existing branding and slide “
“order, and add a short source note to every data slide. “
“If a metric is missing, label it as unavailable instead “
“of estimating a value.”
),
“pptxFileId”: os.environ[
“PLUSAI_TEMPLATE_FILE_ID”
],
“attachments”: [
{”fileId”: data_file_id}
],
“language”: “en”,
}
response = requests.post(
f”{BASE_URL}/agent/sessions”,
headers={
**HEADERS,
“Content-Type”: “application/json”,
},
json=payload,
timeout=30,
)
response.raise_for_status()
return response.json()[”pollingUrl”]I treat the prompt as part of the application, not as disposable text. It contains business rules that should be reviewed and versioned with the code.
The rule about missing metrics is especially important. If a value is absent, I want the slide to say so. A plausible number in a polished chart can survive several reviews before anyone realizes it was inferred.
The request also accepts a model parameter. I would start with the default, then compare other supported models using the same input. I would measure execution time, credits, factual accuracy, and manual cleanup alongside slide quality.
5. Poll the Session and Download the PowerPoint
The session can move through states such as READY, RUNNING, and PENDING_TOOL_CALLS. The script should continue polling until it receives DONE, FAILED, or INTERRUPTED.
from datetime import date
def wait_for_deck(polling_url: str) -> dict:
deadline = time.time() + (20 * 60)
while time.time() < deadline:
response = requests.get(
polling_url,
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
session = response.json()
if session[”status”] == “DONE”:
return session
if session[”status”] in {
“FAILED”,
“INTERRUPTED”,
}:
raise RuntimeError(
f”Agent stopped: {session[’status’]}”
)
time.sleep(10)
raise TimeoutError(
“The presentation took longer than 20 minutes”
)
def download_powerpoint(url: str) -> Path:
output = (
Path(”output”)
/ f”weekly-review-{date.today()}.pptx”
)
output.parent.mkdir(exist_ok=True)
response = requests.get(url, timeout=120)
response.raise_for_status()
if not response.content.startswith(b”PK”):
raise ValueError(
“The downloaded file does not look like a PPTX”
)
output.write_bytes(response.content)
return output
if __name__ == “__main__”:
data_id = upload_file(
“exports/analytics-latest.xlsx”
)
polling_url = start_agent(data_id)
result = wait_for_deck(polling_url)
saved_to = download_powerpoint(
result[”pptxUrl”]
)
print(f”Saved {saved_to}”)The timeout stops a stuck job from running forever. The signature check catches HTML or JSON error bodies saved with a .pptx filename. A PowerPoint file is a ZIP container, so it normally begins with PK.
A completed session can include pptxUrl, pdfUrl, and thumbnailUrl. I would save the PowerPoint for editing, use the PDF for review, and include the thumbnail in a notification.
I would generate several decks and compare every figure with the source before automating delivery. Any recurring error belongs in the prompt or validation logic.
6. Run It on a Cron Schedule
Cron does not usually inherit the environment from an interactive shell. A job that works in the terminal can fail on Monday morning because it cannot find the API key, working directory, or Python environment.
I would put those details in a wrapper script:
#!/usr/bin/env bash
set -euo pipefailcd /opt/deckbot
set -a
source /etc/deckbot.env
set +aexec /opt/deckbot/.venv/bin/python build_deck.pyThe /etc/deckbot.env file should be readable only by the account running the job. The crontab can then stay simple:
0 7 * * 1 /opt/deckbot/run.sh >> /var/log/deckbot.log 2>&1This runs every Monday at 7:00 a.m. Cron uses the server’s timezone, so verify that setting before relying on it for a customer-facing workflow.
For a serverless deployment, polling keeps the function alive unnecessarily. Use callbackUrl instead. Plus AI requires a public HTTPS URL and rejects localhost or private IP addresses. The webhook handler can download the output after the state changes.
Cost and Limitations
At the time of writing, the Plus AI pricing page lists Pro at $20 per user per month when billed annually, or $25 when billed monthly. It includes 3,000 AI credits.
Team costs $30 per user per month annually, or $40 monthly, with 6,000 credits.
Max costs $200 annually, or $240 monthly, and includes unlimited AI credits. These are monthly per-user prices.
New customers can start with a seven-day trial containing 1,000 credits.
Plus says a typical agent task consumes around 40 to 170 credits. I would benchmark the real workflow several times, including failures, retries, and discarded outputs.
There are also a few operational limits to account for:
API access requires an eligible paid plan.
A file upload can be up to 125 MB.
Upload processing and presentation generation are asynchronous.
A user can currently have only one active API key.
Agent jobs consume credits and can fail or be interrupted.
Output is nondeterministic, even when the prompt and attachments stay the same.
A template is guidance, not a pixel-level contract. Complex charts, custom fonts, animations, and unusual master layouts need to be tested with real files.
Privacy depends on the complete pipeline. I can use a service account, store the key outside the repository, remove unnecessary fields, delete old files, and control delivery.
The prompt, template, and attachments are still processed by Plus AI. Plus says it uses AES-256 encryption for data at rest, TLS for data in transit, and has completed a SOC 2 Type II audit.
For regulated or highly sensitive material, I would review the security documentation, privacy policy, retention terms, and model-provider arrangement before sending production data.
Final Thoughts
I hope you found this guide helpful. The API call is the easy part. A useful generator also needs a stable template, clean data, versioned instructions, retries, secure credentials, and a review process.
Plus AI handles the presentation-specific work inside that pipeline. It can edit an existing PowerPoint and return a file that remains editable, which is exactly what I would want for weekly reports, proposals, board updates, and other decks that still need a final human pass.
I would start with one presentation that already follows a predictable format. Run the script manually, compare the numbers with the source, and adjust the prompt until the failures are boring and understood. Then schedule it.
If you run into issues or have any questions, please let me know in the comments.
Hi there! Thanks for making it to the end of this post! If you enjoyed this content and would like to support my work, consider becoming a paid subscriber. Your support means a lot!





