New: generate on-brand decks from the headless presentation API.Explore the API
Docs
Presentation API

Presentation API

Generate on-brand decks from your own code. A REST API, authenticated with a key, charged in credits.

What it is

A REST API that turns a prompt into a finished, on-brand deck. Your app, your scripts, or a cron job can build a presentation the moment the data is ready, without anyone opening the editor.

The API is included on Growth and above. Generation spends the same credits as the app: roughly 60 credits a deck, and one credit is one cent. Reading your decks is free.

The base URL is https://www.trypreso.com/api/v1.

Authentication

Create a key at Dashboard → Developers. You see the secret exactly once, so store it somewhere safe: we keep only a hash of it and cannot show it to you again.

A key is bound to one workspace. It can only read and write that workspace's decks, brands, and files, and it spends that workspace's plan and credits. If you run several workspaces, create a key in each.

curl https://www.trypreso.com/api/v1/me \
  -H "Authorization: Bearer sk_live_your_key_here"

X-API-Key: sk_live_… works too, if a bearer header is awkward in your client.

Generate a deck

curl -X POST https://www.trypreso.com/api/v1/decks \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Q3 sales QBR: pipeline, wins, risks, and the plan for next quarter. Confident, data-heavy, 10 slides."
  }'
{
  "id": "dsn_9f2c41ab77e3d0164b58",
  "title": "Q3 Sales QBR",
  "slides": 10,
  "url": "/dashboard/designs/dsn_9f2c41ab77e3d0164b58",
  "deck": { "title": "Q3 Sales QBR", "theme": {}, "slides": [] }
}

The response is a 201, and the whole deck comes back in deck: every slide with its layout, title, bullets, and speaker notes, plus the deck's theme. Open the url to edit or present it.

Body

FieldRequiredWhat it does
promptyesWhat the deck is for, who it is for, and the tone. Up to 8,000 characters.
brand_idnoThe brand kit to design in. Defaults to the workspace's brand when it has exactly one.
project_idnoGrounds the deck in a project, so it inherits that project's instructions, brand, and files.
modelnoModel id. Defaults to the model the app uses.

On brands

If you do not pass brand_id and the workspace has exactly one brand kit, that brand is used: there is nothing ambiguous about which one you meant. With several brands and no brand_id, Preso does not guess. List them with GET /api/v1/brands and pass the id.

Read your decks

# The workspace's decks, most recent first. Slide content is not included.
curl "https://www.trypreso.com/api/v1/decks?limit=20" \
  -H "Authorization: Bearer sk_live_your_key_here"
 
# One deck, with every slide.
curl https://www.trypreso.com/api/v1/decks/dsn_9f2c41ab77e3d0164b58 \
  -H "Authorization: Bearer sk_live_your_key_here"
 
# Delete it.
curl -X DELETE https://www.trypreso.com/api/v1/decks/dsn_9f2c41ab77e3d0164b58 \
  -H "Authorization: Bearer sk_live_your_key_here"

The list route leaves the slides out on purpose. A workspace with 200 decks would otherwise return several megabytes to answer "what decks do I have".

Check the key, the plan, and the balance

curl https://www.trypreso.com/api/v1/me \
  -H "Authorization: Bearer sk_live_your_key_here"
{
  "workspace_id": "wsp_3a1f",
  "plan": "growth",
  "plan_name": "Growth",
  "credits": 11840,
  "credits_usd": "118.40",
  "usage": {
    "brands": { "used": 2, "limit": 5 },
    "projects": { "used": 7, "limit": 50 },
    "storage_bytes": { "used": 418234880, "limit": 107374182400 }
  }
}

Worth calling before a batch: it tells you whether you can pay for it.

Projects

A project carries standing instructions, a brand, and files. Every deck generated inside it inherits all three, so you stop repeating the brief on every call.

# Create a project with standing instructions.
curl -X POST https://www.trypreso.com/api/v1/projects \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "memory": "Acme sells warehouse robotics to mid-market logistics firms. Always lead with ROI. Never use the word synergy."
  }'
 
# Point it at a brand, so everything in it comes out in Acme's colours.
curl -X PATCH https://www.trypreso.com/api/v1/projects/prj_123 \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"brand_id": "bk_456"}'
 
# Then generate inside it. No brief needed: the project already knows.
curl -X POST https://www.trypreso.com/api/v1/decks \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Q3 business review", "project_id": "prj_123"}'

GET /api/v1/projects lists them, GET /api/v1/projects/{id} fetches one, and DELETE removes one.

Deleting a project detaches its decks and files rather than destroying them. Losing a deck because you tidied up a folder would be indefensible.

On PATCH: omit a field to leave it alone, and send null to clear it. {"brand_id": null} detaches the brand.

Files

Files are the raw material a deck is written from: a report, a set of notes, a spreadsheet. If your system already holds the document, hand it straight to Preso and generate from it.

# Upload a document, filed into a project.
curl -X POST https://www.trypreso.com/api/v1/files \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -F "[email protected]" \
  -F "project_id=prj_123"
 
# See what Preso will actually read, before you spend credits on a generation.
curl https://www.trypreso.com/api/v1/files/fil_789 \
  -H "Authorization: Bearer sk_live_your_key_here"

The upload is multipart/form-data with the file under file. The limit is 50 MB, and your plan's storage allowance is checked before the bytes are stored, so a file that does not fit is refused rather than uploaded and then complained about.

GET /api/v1/files/{id} returns the extracted text, which is exactly what the model reads when it writes a deck from that document. Extraction is lazy and cached: the first read pays for it, every read after is free.

GET /api/v1/files lists them and reports the storage used. PATCH attaches a file to a project ({"project_id": "prj_123"}) or detaches it (null). DELETE removes it.

Brands

# Create a client's brand kit.
curl -X POST https://www.trypreso.com/api/v1/brands \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme",
    "website": "https://acme.com",
    "colors": ["#5506FD", "#160A3A", "#FFFFFF"]
  }'
 
# Give it a voice. brand_md is the written guide the model reads.
curl -X PATCH https://www.trypreso.com/api/v1/brands/bk_456 \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"brand_md": "# Voice\nPlain, confident, no jargon. Open on the customer problem. One idea per slide."}'

An agency onboarding a client can create the brand, push the colours and the voice, and start generating in it without anyone opening the dashboard.

GET /api/v1/brands lists them, GET /api/v1/brands/{id} fetches one with its brand_md, and DELETE removes it.

Credits

curl https://www.trypreso.com/api/v1/credits \
  -H "Authorization: Bearer sk_live_your_key_here"

The balance, plus the purchases and the charges behind it. The balance is purchases minus usage, computed the same way the app computes it, so a billing integration and the dashboard can never disagree about what a customer has left.

Errors

Every failure is a JSON body with a stable code. Switch on the code, not on the English, which we reserve the right to improve.

{
  "error": {
    "code": "insufficient_credits",
    "message": "Out of credits. Top up at /dashboard/billing."
  }
}
StatusCodeWhat happened
400invalid_requestMissing prompt, or a body that is not JSON.
401unauthorizedNo key, or a key that is unknown or revoked.
402insufficient_creditsOut of credits. Top up and retry.
403forbiddenThe workspace's plan does not include the API.
403workspace_lockedThe workspace is paused: a trial ended, or a payment failed.
404not_foundNo such deck, or it belongs to another workspace.
500server_errorOur fault. Retry.

A deck belonging to another workspace returns 404, not 403. A 403 would confirm the id exists, which is a free enumeration oracle.

What it costs

Generation is charged at the real cost of the model call behind it, so a short deck costs less than a long one. Around 60 credits for a typical deck, which is about $0.60. Listing, reading, and deleting decks are free.

If the balance hits zero, generation returns 402 until you top up. Nothing is deleted and no deck is lost.

Discovering the API

GET /api/v1 needs no key and describes itself: the endpoints, the auth scheme, the error codes, and what a deck costs. It is the right thing to curl first.

Next

Agents get their own front door. See the MCP server to let Claude, or any MCP-aware agent, build decks as a tool call.