Documents API guide
Create a document, embed the editor in your own page, change it with AI, then publish and share it, all over the REST API. Each step uses what the previous one returned, and the editor you embed in step 2 shows every change as it happens.
Click Run to call the API live with your signed-in session, or copy the curl snippets, which carry your API token and the ids this guide creates. The complete endpoint list is in the REST API docs.
Setup
Run uses your FluidDocs session (credentials: include); if you are not
signed in it sends you to sign in and back here. Scripts, servers and CI use an
API token instead (Authorization: Bearer lit_live_…). This page loads
yours and puts it in every curl snippet. Every endpoint accepts either.
Keep the token on your server: it acts as you. The embedded editor in step 2 never uses it; it runs on the session of whoever is viewing the page.
1Create a document
One endpoint creates every document type. Send the type and a description of what you
want; the document exists as soon as the call returns (run.projectId), and AI fills it in
the background. The response is a run, which you poll until it is
done.
Send an Idempotency-Key header so a retried request (a timeout, a crashed worker) never
creates a second document: the same key returns the same run with 200.
/api/v1/docs/create
A page or a text document takes one to three minutes; a deck a few minutes. Run keeps polling
the run for you and shows its step and run.result as they change. The second button
repeats the request with the same Idempotency-Key: you get the same
runId back, and no second document.
Check the run
Poll every couple of seconds until run.status is done, error
or cancelled. run.currentStep names the stage in progress, and
run.result carries what the stages report so far (a deck's slideCount
counts up as slides are saved). Prefer a push? GET …/stream sends the same run as
server-sent events.
/api/v1/workflows/runs/:runId
Needs a runId: run step 1 first, or paste one below.
while :; do RUN=$(curl -s "$BASE/api/v1/workflows/runs/$RUN_ID" -H "Authorization: Bearer $TOKEN") STATUS=$(echo "$RUN" | jq -r .run.status) case "$STATUS" in done|error|cancelled) break;; esac sleep 2 done
GET …/stream sends server-sent events: run whenever the run
changes (its steps, and the plan's sections once planned), log for each progress line of a
step, and end when it finishes. The panel below is built from this stream.
curl -N '$BASE/api/v1/workflows/runs/$RUN_ID/stream' -H 'Authorization: Bearer $TOKEN'
Sections
- Planning the document…
Log
2Embed the editor
Put the document inside your own product with one <iframe> pointing at
/p/_iframe/:projectId/?embed=native. This is a native embed: the document runs
its own editing UI (a text document's toolbar and A4 pages, a spreadsheet's grid, a deck's slide bar), with no
FluidDocs app around it. Editing, undo/redo and saving are always there; ?features= adds
Edit with AI (edit-ai) and Export PDF (export).
Changes made through the API show up in it live.
?del=lit_del_… from POST /docs/projects/:id/delegate-ownership.
Never put your API token in the iframe. Only allowed sites can frame the editor; see
Embedding options.
/p/_iframe/:projectId/?embed=native
Needs a projectId: wait for step 1 to finish, or paste one below.
Embed loads the editor in the panel below. Click into the document and type: your edits save to its draft. We publish them in step 4. Steps 3 and 5 reload it when they change the document.
3Update it with AI
The builder chat edits a document from a plain-language request: the same agent as
Edit with AI in the editor. Send the projectId and a message; you get a
jobId back at once, then poll the job until it is done. Watch the editor
above: Run reloads it when the job finishes.
/api/v1/jobs/chat
Needs a projectId: run step 1 first, or paste one below.
A new chat job for the same document cancels the one still running. To continue a conversation,
send the earlier turns in history.
curl -s '$BASE/api/v1/jobs/$JOB_ID/result' -H 'Authorization: Bearer $TOKEN' curl -N '$BASE/api/v1/jobs/$JOB_ID/stream?afterSeq=-1' -H 'Authorization: Bearer $TOKEN'
4Publish & share
A new document is private, and only you can open it. Sharing it takes three calls: commit the draft so it becomes the live version, give the document a public address (a slug), and choose who may open that address.
4.1 Commit the draft
/api/v1/docs/projects/:projectId/commit
Needs a projectId from step 1.
Promotes the draft to the live version; the editor's DRAFT badge goes away. Commit again after every later change you want people to see.
4.2 Publish at a slug
/api/v1/docs/projects/:projectId/publish
Needs a projectId from step 1.
Returns the public url. Store it as returned rather than building it yourself: its
shape depends on your plan and domain, and the slug may be cleaned up. A slug already in use returns
409.
4.3 Choose who can open it
unlisted is the usual choice for sharing: anyone with the returned shareUrl
can open the document, it isn't listed or indexed, and switching away from unlisted
turns the link off at once. public opens the plain url to everyone.
/api/v1/docs/projects/:projectId/set-visibility
Needs a projectId from step 1.
4.4 Read back the links
/api/v1/docs/projects/:projectId
Needs a projectId from step 1.
The document's current state: visibility, url, shareUrl (when
unlisted) and hasPendingDraft (uncommitted changes waiting).
5Download a PDF
A PDF export is a job, like the AI edit in step 3: start it, poll it until it is done, then
download its file. No AI runs and it costs no credits. You export the version you can edit: the draft,
with any changes not yet committed. Someone who can only read the document exports the published one.
Text documents print on A4 sheets, page by page as in the editor; pages and spreadsheets print the way
Chrome prints them. Decks have their own route, /jobs/export-deck-pdf, with one
1440×810 page per slide; the guide picks it when your document is a deck. It is the same export as the
editor's Export PDF button (the export embed feature).
/api/v1/jobs/export-pdf
Needs a projectId from step 1.
Run starts the job, polls GET /jobs/:jobId every couple of seconds, then
downloads GET /jobs/:jobId/artifact (application/pdf). A document takes
10–30 seconds.
curl -s '$BASE/api/v1/jobs/$PDF_JOB_ID' -H 'Authorization: Bearer $TOKEN' | jq .status curl -s '$BASE/api/v1/jobs/$PDF_JOB_ID/artifact' -H 'Authorization: Bearer $TOKEN' -o document.pdf
That's the whole loop. From here, repeat steps 3 and 4.1 for each change: the slug and the share link stay the same and show the newest committed version.
Reference
The details behind the steps above, and the calls the guide didn't need. The snippets use the ids
from your run of the guide. Base path: /api/v1. Every endpoint accepts a session or
Authorization: Bearer lit_live_….
Creation runs
| Call | What it does |
|---|---|
POST /docs/create | Create a document of any type. 201 { run }; with a repeated Idempotency-Key (up to 200 characters), 200 and the original run. |
GET /workflows/runs/:runId | The run: status (running, done, error, cancelled), currentStep, steps, result, projectId, error. |
GET /workflows/runs/:runId/stream | The same run as server-sent events, until it finishes. |
POST /workflows/runs/:runId/cancel | Stop a running run. The document stays as far as it got. |
POST /workflows/runs/:runId/retry | Resume a failed run (status: "error") on the same document, from the step that failed. Finished work is not redone or charged again. |
POST /workflows/runs/:runId/restart | Run a stopped run again (failed, cancelled or done), from the start or from { "fromStep": "…" }. A running run must be cancelled first. |
GET /workflows/runs?projectId=… | The runs of one document. Without projectId: your own runs, newest first (status, workflow, page, limit). |
GET /workflows | Every workflow with its documentType and input JSON Schema. |
Document types & inputs
Every type takes description (3–2000 characters), name (kept as the
document's name; otherwise it is named from the plan), folderId, and
blank: true (or the description "Empty doc") for the type's empty document, with no AI.
type | Creates | Extra input | run.result |
|---|---|---|---|
text-document | A paginated A4 document with headings, tables and images | images: "stock" or "both" (stock, then AI-generated) | { sectionCount, pageCount } |
page | A web page in sections | — | — |
deck | A slide deck | see below | { templateReference, slideCount } |
spreadsheet-document | A spreadsheet with sheets and formulas | — | { sheetCount } |
notes-app | A notes notebook | — | { noteCount } |
Deck input:
| Field | Description |
|---|---|
outline | The slides in order, [{ title, body? }] (1–40). With an outline, description is optional and no outline is written for you. |
templateReference | A design family to use, or "custom" (default) for a design composed for this deck. |
stylePreset | A colour palette for a custom design. Not combinable with a design family. |
creativeBrief | Art direction for every slide (up to 2000 characters). |
tone | casual, professional, funny, educational or sales_pitch. |
detail | concise, balanced or text-heavy. |
Read, rename, delete
| Call | What it does |
|---|---|
GET /docs?flat=true | Your documents (name, folderId, page, limit up to 100, sort=updatedAt|name). GET /docs/org lists your organization's. |
GET /docs/projects/:projectId | One document: name, type, visibility, perms, url, shareUrl, hasPendingDraft, dates. |
PATCH /docs/projects/:projectId | Rename or move: { name?, folderId? } (folderId: null = top level). |
POST /docs/projects/:projectId/copy | Duplicate a document ({ name }); add remix with a prompt to have AI rewrite the copy. |
GET /docs/projects/:projectId/thumbnail | A JPEG of the first screen (?source=draft|published). |
DELETE /docs/projects/:projectId | Deletes the document and all its files. Cannot be undone. Its public and share links stop working. |
Remove the document this guide created:
Drafts & other ways to edit
| Call | What it does |
|---|---|
POST /docs/projects/:id/commit | Make the draft the live version. |
POST /docs/projects/:id/discard | Throw the draft away and go back to the live version. |
POST /docs/projects/:id/draft?path=… | Write a whole file to the draft (raw body; &autocommit=true commits too). |
POST /jobs/chat | Builder chat, as in step 3. model: "flash" for a faster, cheaper model. |
POST /jobs/agent-task | One-shot agent run with a prompt; readOnly: true to only read and answer. |
POST /jobs/:jobId/cancel | Stop a running job. |
POST /docs/projects/:id/ops/apply | Precise edits to single elements, without AI. |
GET /docs/projects/:id/ops/actions | A text document's, spreadsheet's or notebook's own operations (client "server"): text_insert_blocks, sheet_write_range, notes_create… with their input schemas. |
POST /docs/projects/:id/ops/actions/:name | Run one of them, without AI and without an editor open: { "input": { … } }. The change shows up live in open editors. |
Examples for each are in the REST API docs: write a file, partial updates, agent task.
Visibility & permissions
| Visibility | Who can open it | Link to share |
|---|---|---|
private | Only you | none |
org | Signed-in members of your organization | url |
unlisted | Anyone with the link; not indexed | shareUrl (includes ?t=…; the bare url returns 403) |
public | Anyone | url |
- Password: unlisted documents can ask for a password —
{ "visibility": "unlisted", "requirePassword": true, "password": "…" }(6–128 characters). Setting a new one locks every visitor out until they enter it. - Email gate:
requireEmail: trueasks visitors of an unlisted document for their email first. - New share link: switch to
privateand back tounlisted; the old link stops working. - Unpublish:
DELETE /docs/projects/:id/publishremoves the slug. Visibility is unchanged. - Fine-grained permissions (Pro):
POST /docs/projects/:id/set-perms { org, others }, each a sum ofLIST=1,WRITE=2,READ=4. For exampleorg: 6lets your organization read and edit.
Embedding options
Two ways to embed a document. The guide uses the first.
| Embed | What the viewer gets |
|---|---|
/p/_iframe/:id/?embed=native&features=edit-ai|export | Native: the document's own editing UI in one iframe. features: edit-ai (the builder chat), export (Export PDF). No theme or toolbar messages. |
/app/embed/:id?features=a|b|c&theme=light|dark&edit-bar=on|off | Wrapper: the FluidDocs app's editor without its logo and breadcrumb, with the features below and postMessage control of the toolbar. |
Wrapper features | Adds |
|---|---|
edit-ai | The "Edit with AI" chat |
jobs | Running AI jobs and their history |
export | Export to PDF / HTML |
publish | Publish, share and discard-draft controls |
comments | Comments |
code | Source code editor |
files | The document's files |
db | The document's data collection |
fluidify | Apply libraries to the document |
block-kit, insert-blocks | Insert blocks into pages and slides |
| Wrapper message to the iframe | Effect |
|---|---|
{ _fdEmbed: true, type: "setTheme", theme: "dark" } | Switch the toolbar theme (light / dark). |
{ _fdEmbed: true, type: "setEditBar", editBar: "off" } | Hide or show the toolbar (on / off). |
- Where it can be framed: only allowed sites can put the editor in an iframe
(FluidDocs itself,
localhost, and partner domains). Ask us to add yours. A read-only view of a published document can go anywhere: frame itsurl. - Sign-in: the viewer signs in to FluidDocs from the embed's Log in button (it opens a new tab), then reloads. Safari blocks the session in cross-site iframes, and Chrome keeps it per site.
- Viewers without a FluidDocs account: your server can mint a short-lived token that lets
the embed act as the document's owner for that document only:
POST /docs/projects/:id/delegate-ownershipreturns anembedUrlwith?del=lit_del_…(24 hours; revoke with…/delegate-ownership/revoke). - Reacting to changes on your side: the iframe doesn't post save or publish events.
Read
GET /docs/projects/:id(hasPendingDraft,url) from your server.
MCP tools
The FluidDocs MCP server exposes the same calls to agents:
| Tool | Same as |
|---|---|
create_document | POST /docs/create (type, input, idempotencyKey) |
get_workflow_run | GET /workflows/runs/:runId |
Errors
| Status | When |
|---|---|
400 | Missing or unknown type (details.types lists the valid ones), or invalid input (details has the problems), or a bad folderId. |
401 | No session or token, or an expired one. |
402 | Your plan's AI credits are used up. |
403 | Not allowed on this document (or a lit_del_ token on /docs/create). |
404 | No such document, run or job, or not yours. |
409 | The slug is taken, or the Idempotency-Key is being used by another request at the same moment. |