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> (or x-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_KEY env var, deck-creating endpoints additionally require header x-api-key. On a default local install it is unset.

Endpoints

MethodPathAuthDescription
POST/api/v1/decksx-api-key¹Create a deck. Body: { title?, deck? }. Returns id, editToken (show once — store it), shareUrl, deck.
GET/api/v1/decks/:idpublicRead full deck JSON (doc + title + shareId).
PUT/api/v1/decks/:idBearerReplace deck. Body: { title?, deck }. Send the whole document — this is a full-document API.
DELETE/api/v1/decks/:idBearerDelete the deck permanently (share link dies too).
GET/api/v1/decks/:id/exportpublicDownload the deck as a real .pptx file.
POST/api/v1/decks/:id/assetsBearermultipart/form-data { file: image }. Returns { url } for use in image elements.
POST/api/v1/decks/importx-api-key¹multipart/form-data { file: .pptx }. Imports an existing PowerPoint as a new editable deck.
GET/api/v1/files/:assetIdpublicServes 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>/export

Deck 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 ≤2MB

Uploading 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").
Agent tips
  • Always persist the editToken immediately — 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).