API Reference
The AgentPreso API provides programmatic access to all platform features. The API is hosted on Cloudflare Workers at https://api.agentpreso.com.
Authentication
All API requests (except /health) require authentication using an API key.
Getting an API Key
- Log in to the AgentPreso Dashboard
- Navigate to Settings > API Keys
- Click “Create New Key”
- Copy and store your key securely - it won’t be shown again
Using Your API Key
Include the API key in the Authorization header:
curl https://api.agentpreso.com/api/decks \
-H "Authorization: Bearer ap_your_api_key_here"
Base URL
https://api.agentpreso.com
Response Format
All responses are JSON. Successful responses have this structure:
{
"data": { ... },
"meta": {
"requestId": "req_abc123"
}
}
Error responses:
{
"error": {
"code": "NOT_FOUND",
"message": "Deck not found"
},
"meta": {
"requestId": "req_abc123"
}
}
Endpoints
Health Check
Check if the API is running.
GET /health
Response:
{
"status": "ok",
"version": "1.0.0"
}
Decks
List Decks
Get all decks for the authenticated user.
GET /api/decks
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | number | Max results (default: 50, max: 100) |
offset | number | Pagination offset |
sort | string | Sort field: updated, created, title |
order | string | Sort order: asc, desc |
Response:
{
"data": [
{
"id": "deck_abc123",
"slug": "quarterly-review",
"title": "Q3 2024 Review",
"themeId": "corporate",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z"
}
],
"meta": {
"total": 42,
"limit": 50,
"offset": 0
}
}
Create Deck
Create a new deck.
POST /api/decks
Request Body:
{
"slug": "my-presentation",
"title": "My Presentation",
"markdown": "---\ntheme: minimal\n---\n\n# Slide 1\n\nContent here",
"themeId": "minimal"
}
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | URL-friendly identifier (unique per user) |
markdown | string | Yes | Full markdown content |
title | string | No | Display title (extracted from markdown if not provided) |
themeId | string | No | Theme ID (default: minimal) |
Response:
{
"data": {
"id": "deck_xyz789",
"slug": "my-presentation",
"title": "My Presentation",
"themeId": "minimal",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
}
Get Deck
Get a deck by ID.
GET /api/decks/:id
Response:
{
"data": {
"id": "deck_abc123",
"slug": "quarterly-review",
"title": "Q3 2024 Review",
"markdown": "---\ntheme: minimal\n...",
"themeId": "corporate",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z"
}
}
Get Deck by Slug
Get a deck by its slug.
GET /api/decks/by-slug/:slug
Update Deck
Update an existing deck.
PUT /api/decks/:id
Request Body:
{
"markdown": "---\ntheme: minimal\n---\n\n# Updated Content",
"title": "Updated Title",
"themeId": "dark"
}
All fields are optional. Only provided fields are updated.
Update Single Slide
Update a single slide by index (0-based).
PATCH /api/decks/:id/slides/:index
Request Body:
{
"content": "# Updated Slide\n\nNew content for this slide"
}
Delete Deck
Delete a deck permanently.
DELETE /api/decks/:id
Response:
{
"data": {
"deleted": true
}
}
Share Deck
Generate a public share link for a deck.
POST /api/decks/:id/share
Response:
{
"data": {
"shareEnabled": true,
"shareToken": "abc123xyz",
"shareUrl": "https://api.agentpreso.com/share/abc123xyz"
}
}
Get Share Status
Get sharing status, share URL, and view count for a deck.
GET /api/decks/:id/share
Response:
{
"data": {
"shareEnabled": true,
"shareToken": "abc123xyz",
"shareUrl": "https://api.agentpreso.com/share/abc123xyz",
"viewCount": 42
}
}
Unshare Deck
Revoke public share link for a deck.
DELETE /api/decks/:id/share
Invalidate Render Cache
Invalidate all cached renders for a deck.
DELETE /api/decks/:id/cache
Response:
{
"data": {
"ok": true
}
}
Rendering
Render Deck
Render a stored deck to HTML, PDF, or editable PowerPoint.
POST /api/decks/:id/render
Request Body:
{
"format": "pdf"
}
| Field | Type | Required | Description |
|---|---|---|---|
format | string | Yes | Output format: html, pdf, pptx |
Response:
{
"data": {
"url": "https://storage.agentpreso.com/renders/abc123.pdf",
"expiresAt": "2024-01-15T11:30:00Z",
"format": "pdf",
"sizeBytes": 524288
}
}
The URL is a presigned R2 URL valid for 1 hour.
PPTX Format Notes:
- PPTX exports produce editable slides — text, bullets, and tables can be modified directly in PowerPoint
- Diagrams (Mermaid) and charts are embedded as high-quality PNG images
- Theme colors and fonts are applied to the PPTX theme
Render Raw Markdown
Render markdown content directly (without storing a deck).
POST /api/render
Request Body:
{
"markdown": "---\ntheme: minimal\n---\n\n# Slide 1",
"format": "html",
"themeId": "minimal"
}
| Field | Type | Required | Description |
|---|---|---|---|
markdown | string | Yes | Markdown content to render |
format | string | Yes | Output format: html, pdf, pptx |
themeId | string | No | Theme to apply |
vars | object | No | Variables to substitute {{key}} placeholders before rendering |
Variables Example:
{
"markdown": "---\ntheme: minimal\n---\n\n# Hello {{company}}!\n\nDeal size: {{deal_size}}",
"format": "pdf",
"vars": {
"company": "Contoso Ltd",
"deal_size": "$1.2M"
}
}
Variables can be strings, numbers, booleans, or arrays (for chart data). See Themes > Variables for full documentation.
Render Slide Preview
Render a single slide from markdown to a PNG screenshot.
POST /api/render/slide
Request Body:
{
"markdown": "---\ntheme: minimal\n---\n\n# Slide 1",
"slide": 1,
"themeId": "minimal"
}
| Field | Type | Required | Description |
|---|---|---|---|
markdown | string | Yes | Full deck markdown content |
slide | number | Yes | Slide number (1-indexed) |
themeId | string | No | Theme to apply |
Response: PNG binary image with image/png content type.
Render Theme Preview (Public)
Render a theme preview as a standalone HTML document. No authentication required.
GET /api/render/preview?themeId=corporate
| Parameter | Type | Required | Description |
|---|---|---|---|
themeId | string | Yes | Theme ID to preview |
Response: Complete HTML document with embedded CSS.
Themes
List Themes
Get available themes (built-in and user’s custom themes).
GET /api/themes
Response:
{
"data": [
{
"id": "minimal",
"name": "minimal",
"description": "Clean, lots of whitespace, sans-serif",
"type": "built-in"
},
{
"id": "tmpl_user123",
"name": "my-brand",
"description": "Custom company branding",
"type": "custom"
}
]
}
Get Theme
Get theme details including CSS.
GET /api/themes/:id
Response:
{
"data": {
"id": "corporate",
"name": "corporate",
"description": "Professional, muted blues/grays, structured layouts",
"css": "/* @theme corporate */\n...",
"scaffold": "---\ntheme: minimal\n...",
"manifest": {
"variables": {
"--primary-color": "#1e40af"
},
"layouts": [
{ "name": "lead", "description": "Title slide" }
]
}
}
}
Upload Custom Theme
Upload a new custom theme.
POST /api/themes
Content-Type: multipart/form-data
Form Fields:
| Field | Type | Required | Description |
|---|---|---|---|
manifest | file | Yes | theme.yaml file |
css | file | No | overrides.css file |
scaffold | file | No | scaffold.md file |
assets | file[] | No | Asset files (logos, backgrounds) |
Update Custom Theme
Update a custom theme’s properties. Cannot modify built-in themes.
PATCH /api/themes/:id
Request Body:
{
"name": "updated-brand",
"description": "Updated company branding",
"css": "/* @theme updated-brand */\n...",
"logoAssetId": "asset_abc123",
"logoPlacement": "{\"default\":{\"position\":\"bottom-right\",\"size\":\"small\"}}"
}
All fields are optional. Only provided fields are updated.
| Field | Type | Description |
|---|---|---|
name | string | New theme name |
description | string | New description |
css | string | New CSS content |
manifest | string | JSON manifest with theme metadata |
logoAssetId | string/null | Asset ID for primary logo, or null to remove |
logoLightAssetId | string/null | Asset ID for light logo variant |
logoPlacement | string/null | JSON string defining logo placement rules |
Delete Custom Theme
Delete a custom theme.
DELETE /api/themes/:id
Render Theme Preview
Render a theme preview using its scaffold markdown.
POST /api/themes/:id/render
Request Body:
{
"format": "embed"
}
| Field | Type | Required | Description |
|---|---|---|---|
format | string | No | Output format: html or embed (default: embed) |
For embed format, returns HTML as text/html. For html format, returns { html, css } JSON. Results are cached.
Assets
List Assets
List all uploaded assets for the authenticated user.
GET /api/assets
Response:
{
"data": [
{
"id": "asset_abc123",
"filename": "logo.png",
"mimeType": "image/png",
"sizeBytes": 15360,
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
Upload Asset
Upload an image or other binary asset.
POST /api/assets
Content-Type: multipart/form-data
Form Fields:
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The file to upload |
filename | string | No | Custom filename |
Response:
{
"data": {
"id": "asset_abc123",
"filename": "logo.png",
"mimeType": "image/png",
"sizeBytes": 15360,
"uri": "asset://asset_abc123"
}
}
Use the uri in your markdown:

Get Asset Metadata
GET /api/assets/:id
Get Asset URL
Get a presigned URL to download the asset.
GET /api/assets/:id/url
Response:
{
"data": {
"url": "https://storage.agentpreso.com/assets/abc123.png",
"expiresAt": "2024-01-15T11:30:00Z"
}
}
Download Asset
Stream the asset file directly with appropriate content-type headers.
GET /api/assets/:id/download
Response: Binary file stream with Content-Type and Content-Disposition headers.
Delete Asset
DELETE /api/assets/:id
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key |
FORBIDDEN | 403 | Access denied to resource |
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 400 | Invalid request body |
CONFLICT | 409 | Resource already exists (e.g., duplicate slug) |
RATE_LIMITED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |
Rate Limits
| Plan | Requests/minute | Renders/hour |
|---|---|---|
| Free | 60 | 10 |
| Pro | 300 | 100 |
| Team | 1000 | 500 |
Rate limit headers are included in all responses:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1705315800