Build presentations programmatically
Everything the web editor can do is also a REST endpoint. Create a deck in one POST (optionally with the full slide JSON), then point humans at the shareUrl or hand them the .pptx export.
BASE = $origin (e.g. http://localhost:3000) — all requests/responses are JSON unless noted.
Authentication
- The edit token is returned once when you create/import a deck. Send it as
Authorization: Bearer <token>(orx-edit-token) for PUT/DELETE/asset uploads. - Reading decks, share pages and .pptx export are public (deck ids/share ids are unguessable).
- ¹ If the server sets the
AGENT_API_KEYenv var, deck-creating endpoints additionally require headerx-api-key. On a default local install it is unset.
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/decks | x-api-key¹ | Create a deck. Body: { title?, deck? }. Returns id, editToken (show once — store it), shareUrl, deck. |
| GET | /api/v1/decks/:id | public | Read full deck JSON (doc + title + shareId). |
| PUT | /api/v1/decks/:id | Bearer | Replace deck. Body: { title?, deck }. Send the whole document — this is a full-document API. |
| DELETE | /api/v1/decks/:id | Bearer | Delete the deck permanently (share link dies too). |
| GET | /api/v1/decks/:id/export | public | Download the deck as a real .pptx file. |
| POST | /api/v1/decks/:id/assets | Bearer | multipart/form-data { file: image }. Returns { url } for use in image elements. |
| POST | /api/v1/decks/import | x-api-key¹ | multipart/form-data { file: .pptx }. Imports an existing PowerPoint as a new editable deck. |
| GET | /api/v1/files/:assetId | public | Serves an uploaded image (used as image src). |
Quickstart
# 1) create a deck with content in one call
curl -s $BASE/api/v1/decks -H 'Content-Type: application/json' -d '{
"title": "Q4 Review",
"deck": {
"version": 1,
"aspect": "16:9",
"theme": {
"palette": ["#1a1a2e", "#4f46e5", "#f59e0b", "#ffffff", "#f4f4f5", "#71717a"],
"fontHeading": "Inter",
"fontBody": "Sarabun"
},
"slides": [
{
"id": "s1",
"background": { "type": "solid", "color": "#ffffff" },
"elements": [
{
"id": "t1", "type": "text",
"x": 96, "y": 250, "w": 1088, "h": 160, "rotation": 0,
"valign": "top",
"paragraphs": [
{ "runs": [ { "text": "Q4 Review", "size": 88, "bold": true, "color": "#1a1a2e", "font": "Inter" } ],
"align": "center" }
]
},
{
"id": "r1", "type": "shape", "shape": "rect",
"x": 0, "y": 640, "w": 1280, "h": 80,
"fill": "#4f46e5"
}
]
}
]
}
}'
# → { "id": "…", "editToken": "SAVE_THIS", "shareUrl": "https://…/share/…", … }
# 2) edit later (GET → modify JSON → PUT the whole document)
curl -s $BASE/api/v1/decks/<id> > deck.json
# …edit deck.json…
curl -s -X PUT $BASE/api/v1/decks/<id> \
-H 'Authorization: Bearer <editToken>' \
-H 'Content-Type: application/json' \
-d @update.json # { "deck": { …full document… }, "title": "New title" }
# 3) give a human the link / the PowerPoint file
echo "$SHARE_URL"
curl -sO $BASE/api/v1/decks/<id>/exportDeck document schema
Coordinates live on a logical canvas: 1280×720 for 16:9, 960×720 for 4:3. y grows downward. These map to PowerPoint at 96px = 1 inch (font size is in px; 72px → 54pt).
{
"version": 1,
"aspect": "16:9" | "4:3",
"theme": {
"palette": ["#ink", "#primary", "#accent", "#bg", "#surface", "#muted"],
"fontHeading": "Inter", "fontBody": "Sarabun"
},
"slides": [
{
"id": "s1", // any unique string
"background": { "type": "solid", "color": "#ffffff" }
| { "type": "image", "src": "/api/v1/files/<assetId>", "fit": "cover" },
"elements": [
// shared fields: id, x, y, w, h, rotation (deg clockwise)
{ "type": "text",
"valign": "top" | "middle" | "bottom", // optional
"padding": 8, // optional
"paragraphs": [
{ "runs": [ { "text": "Hello", "size": 48, "bold": true,
"italic": false, "underline": false,
"color": "#1a1a2e", "font": "Inter" } ],
"align": "left" | "center" | "right", // optional
"bullet": false } // optional
] },
{ "type": "image", "src": "/api/v1/files/<assetId>", "fit": "cover" | "contain" },
{ "type": "shape", "shape": "rect" | "roundRect" | "ellipse" | "triangle" | "line" | "arrow",
"fill": "#4f46e5" | "none", "stroke": "#1a1a2e", "strokeWidth": 4 }
]
}
]
}
// element order inside "elements" = z-order (last is on top)
// fonts: Inter, Sarabun, Prompt, Montserrat, Playfair Display,
// Georgia, Arial, Times New Roman, Courier New
// limits: ≤200 slides, ≤120 elements/slide, deck JSON ≤2MBUploading images for slides
ASSET=$(curl -s -X POST $BASE/api/v1/decks/<id>/assets \
-H 'Authorization: Bearer <editToken>' \
-F "file=@photo.png")
# → { "id": "…", "url": "/api/v1/files/…", … }
# use that url as an image element's "src" (or slide background src)Importing an existing .pptx
curl -s -X POST $BASE/api/v1/decks/import -F "file=@deck.pptx" -F "title=My import"
# → { id, editToken, shareUrl, deck, warnings: ["…"] }
# import is best-effort: text boxes, basic shapes, images, solid backgrounds
# and 16:9 / 4:3 sizes round-trip well; charts/SmartArt/groups are skipped
# (listed in "warnings").- Always persist the
editTokenimmediately — it cannot be recovered later. - The PUT is a full-document replace: GET the current doc, mutate, PUT it back. Validate mentally against the schema above (unknown fields fail with a 400 explaining the first issue).
- The same API powers the web editor at
/edit/<id>— anything you write is instantly human-editable. - Slide ids and element ids just need to be unique strings (≤64 chars).