{"openapi":"3.1.0","info":{"title":"Rendra REST API","version":"1.0.0","summary":"Agents draft, Rendra renders.","description":"Rendra is a docs workspace for the AI era. This API lets agents, CLIs and third-party tools read and write the same documents humans see in the UI. Every request is scoped to a single workspace via a Bearer token.","contact":{"name":"Rendra support","url":"https://rendra.dev/"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://www.rendra.design","description":"Production"}],"tags":[{"name":"Workspace","description":"Active workspace handshake."},{"name":"Documents","description":"CRUD over the doc library."},{"name":"Search","description":"Cross-doc substring search."},{"name":"Rendering","description":"Print-ready HTML for PDF export."},{"name":"Refresh","description":"Agent-assisted updates that land in the Suggested updates queue for human approval — never written to the live document directly."}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/workspace":{"get":{"tags":["Workspace"],"operationId":"getWorkspace","summary":"Get the active workspace","description":"Whoami + handshake. Returns the workspace the caller's token (or session) is scoped to, plus simple doc counts. A good first call for agents to confirm they're talking to the right place.","security":[{"bearerAuth":["docs:read"]}],"responses":{"200":{"description":"Active workspace and auth context.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceEnvelope"},"example":{"workspace":{"slug":"acme","name":"Acme","plan":"pro","createdAt":"2026-01-04T10:22:11.000Z"},"docs":{"total":42,"published":18},"auth":{"kind":"token","scopes":["docs:read","docs:write"]}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/documents":{"get":{"tags":["Documents"],"operationId":"listDocuments","summary":"List documents","description":"Returns a paginated list of document summaries (no `html` body — use `GET /documents/{slug}` to fetch the body).","security":[{"bearerAuth":["docs:read"]}],"parameters":[{"name":"status","in":"query","description":"Filter by status. `active` (default) returns draft+review+published. `all` returns every status including archived.","required":false,"schema":{"type":"string","enum":["active","all","draft","review","published","archived"],"default":"active"}},{"name":"type","in":"query","description":"Filter by doc type.","required":false,"schema":{"type":"string","enum":["guide","playbook","policy","runbook","brief","spec"]}},{"name":"collection","in":"query","description":"Filter by collection (exact match).","required":false,"schema":{"type":"string"}},{"name":"favorite","in":"query","description":"If `1` or `true`, only favorites.","required":false,"schema":{"type":"string","enum":["1","true"]}},{"name":"limit","in":"query","description":"Page size, clamped to 1..200.","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","description":"Row offset, clamped to 0..10000.","required":false,"schema":{"type":"integer","minimum":0,"maximum":10000,"default":0}}],"responses":{"200":{"description":"Paginated list of summaries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentListEnvelope"},"example":{"documents":[{"slug":"onboarding-playbook","title":"Onboarding Playbook","subtitle":"Ship a new hire in week one","status":"published","docType":"playbook","collection":"People ops","audience":"internal","ownerName":"Lena Park","tags":["hiring"],"favorite":false,"summary":"Calm, structured ramp-up.","stats":{"words":842,"tables":3,"panels":2,"readingMinutes":5},"lastReviewedAt":"2026-03-14","reviewDueAt":"2026-06-14","shareUrl":"https://app.rendra.dev/share/abc123","updatedAt":"2026-04-01T09:12:33.000Z","createdAt":"2026-02-14T12:00:00.000Z"}],"pagination":{"limit":50,"offset":0,"returned":1}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Documents"],"operationId":"createDocument","summary":"Create a document","description":"Creates a new document from raw HTML. Rendra will normalize the markup, compute stats, and generate a collision-free slug from the title. Requires the `docs:write` scope.","security":[{"bearerAuth":["docs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentCreate"},"example":{"title":"Onboarding Playbook","subtitle":"Ship a new hire in week one","html":"<h1>Welcome</h1><p>Day one…</p>","status":"draft","docType":"playbook","audience":"internal","collection":"People ops","ownerName":"Lena Park","tags":["hiring","week-one"],"accent":"moss"}}}},"responses":{"201":{"description":"The newly created document (full body included).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentEnvelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/documents/{slug}":{"get":{"tags":["Documents"],"operationId":"getDocument","summary":"Get one document","description":"Returns the full document, including the HTML body.","security":[{"bearerAuth":["docs:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The document slug, unique within the workspace."}],"responses":{"200":{"description":"The document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Documents"],"operationId":"updateDocument","summary":"Update a document","description":"Partial update — send only the fields you want to change. Invalid status transitions return 409. Changing the title regenerates the slug with collision avoidance.","security":[{"bearerAuth":["docs:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The document slug, unique within the workspace."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentPatch"},"example":{"status":"published","tags":["hiring","week-one","people-ops"]}}}},"responses":{"200":{"description":"The updated document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentEnvelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}},"delete":{"tags":["Documents"],"operationId":"deleteDocument","summary":"Delete a document","description":"Hard-deletes the document. Irreversible.","security":[{"bearerAuth":["docs:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The document slug, unique within the workspace."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/documents/{slug}/pdf":{"get":{"tags":["Rendering"],"operationId":"getDocumentPdf","summary":"Get print-ready HTML for PDF export","description":"Returns a self-contained, print-optimized HTML page that auto-invokes window.print(). Send Accept: application/pdf to skip the auto-print for headless-browser workflows.","security":[{"bearerAuth":["docs:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Print-ready HTML.","content":{"text/html":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/documents/{slug}/propose-update":{"post":{"tags":["Refresh"],"operationId":"proposeDocumentUpdate","summary":"Propose a section rewrite","description":"Agent-facing hook for proposing a rewrite of one section of a document. The proposal lands in the Suggested updates queue — the live document is never modified directly. The human still approves from the UI before the rewrite is applied.","security":[{"bearerAuth":["docs:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The document slug, unique within the workspace."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["section_id","proposed_html"],"properties":{"section_id":{"type":"string","minLength":1,"maxLength":64,"description":"The id of a `<section data-rendra-refresh>` block inside the document."},"proposed_html":{"type":"string","minLength":1,"maxLength":200000,"description":"The replacement HTML for that section."},"diff_summary":{"type":"string","maxLength":2000,"description":"Optional one-line note shown to the human reviewer."}}},"example":{"section_id":"pricing-table","proposed_html":"<section data-rendra-refresh id=\"pricing-table\">…</section>","diff_summary":"Bumped Pro tier to $24/mo per the 2026 update."}}}},"responses":{"201":{"description":"Proposal queued for review.","content":{"application/json":{"schema":{"type":"object","required":["ok","proposal_id"],"properties":{"ok":{"type":"boolean","example":true},"proposal_id":{"type":"integer","example":142}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/documents/{slug}/sections/{sectionId}/refresh":{"post":{"tags":["Refresh"],"operationId":"refreshDocumentSection","summary":"Run a section refresh on demand","description":"Counterpart to the cron-driven Rendra Refresh tick — same worker, same proposal queue, but kicked off from outside (typically the MCP `trigger_section_refresh` tool). The section must already have a configured refresh job. Like propose-update, the result lands in Suggested updates for human approval.","security":[{"bearerAuth":["docs:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The document slug, unique within the workspace."},{"name":"sectionId","in":"path","required":true,"schema":{"type":"string"},"description":"The id of the `<section data-rendra-refresh>` block to refresh."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"All fields optional. Empty body = refresh using the section's configured source and model.","properties":{"source_text":{"type":"string","minLength":1,"maxLength":200000,"description":"Caller-supplied source content. Skips the configured URL fetch and feeds this text to the LLM instead."},"source_title":{"type":"string","maxLength":500,"description":"Optional title passed to the LLM for context."},"model":{"type":"string","minLength":1,"maxLength":64,"description":"Whitelisted model id to use for THIS run only. Doesn't change the section's stored model."}}},"example":{"source_text":"Pricing changed Apr 2026 — Pro is now $24/mo, Team $48/mo.","source_title":"Pricing FAQ — Apr 2026"}}}},"responses":{"201":{"description":"Refresh ran; proposal queued for review.","content":{"application/json":{"schema":{"type":"object","required":["ok","proposal_id"],"properties":{"ok":{"type":"boolean","example":true},"proposal_id":{"type":"integer","example":143},"summary":{"type":"string","example":"Updated pricing to match Apr 2026 source."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/documents/search":{"get":{"tags":["Search"],"operationId":"searchDocuments","summary":"Search documents","description":"Case-insensitive substring match across title, subtitle, summary, owner, and collection. Returns summaries only.","security":[{"bearerAuth":["docs:read"]}],"parameters":[{"name":"q","in":"query","required":true,"description":"Search text.","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"description":"Result cap, clamped to 1..100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"status","in":"query","required":false,"description":"Same semantics as on `GET /documents`.","schema":{"type":"string","enum":["active","all","draft","review","published","archived"],"default":"active"}}],"responses":{"200":{"description":"Matching summaries (most recent first).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentSearchEnvelope"},"example":{"query":"onboarding","documents":[],"pagination":{"limit":20,"returned":0}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Rendra personal access token","description":"Send `Authorization: Bearer rendra_<prefix>_<body>`. Mint a token at `/settings/api-tokens`. Each token carries a set of scopes — see below."}},"schemas":{"Workspace":{"type":"object","required":["slug","name","plan","createdAt"],"properties":{"slug":{"type":"string","example":"acme"},"name":{"type":"string","example":"Acme"},"plan":{"type":"string","enum":["free","pro","team"],"example":"pro"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-01-04T10:22:11.000Z"}}},"WorkspaceEnvelope":{"type":"object","required":["workspace","docs","auth"],"properties":{"workspace":{"$ref":"#/components/schemas/Workspace"},"docs":{"type":"object","properties":{"total":{"type":"integer","example":42},"published":{"type":"integer","example":18}}},"auth":{"type":"object","properties":{"kind":{"type":"string","enum":["token","session"]},"scopes":{"type":"array","items":{"type":"string"}}}}}},"DocumentSummary":{"type":"object","required":["slug","title","subtitle","status","docType","collection","audience","ownerName","tags","favorite","summary","stats","lastReviewedAt","reviewDueAt","shareUrl","updatedAt","createdAt"],"properties":{"slug":{"type":"string","example":"onboarding-playbook"},"title":{"type":"string","example":"Onboarding Playbook"},"subtitle":{"type":"string","example":"Ship a new hire in week one"},"status":{"type":"string","enum":["draft","review","published","archived"],"example":"published"},"docType":{"type":"string","enum":["guide","playbook","policy","runbook","brief","spec"],"example":"playbook"},"collection":{"type":"string","example":"People ops"},"audience":{"type":"string","enum":["internal","cross_functional","leadership","customer_facing"],"example":"internal"},"ownerName":{"type":"string","example":"Lena Park"},"tags":{"type":"array","items":{"type":"string"},"example":["hiring","week-one"]},"favorite":{"type":"boolean","example":false},"summary":{"type":"string","example":"A calm, structured ramp-up for anyone joining the team."},"stats":{"type":"object","properties":{"words":{"type":"integer","example":842},"tables":{"type":"integer","example":3},"panels":{"type":"integer","example":2},"readingMinutes":{"type":"integer","example":5}}},"lastReviewedAt":{"type":"string","description":"YYYY-MM-DD or empty string","example":"2026-03-14"},"reviewDueAt":{"type":"string","description":"YYYY-MM-DD or empty string","example":"2026-06-14"},"shareUrl":{"type":"string","format":"uri","example":"https://app.rendra.dev/share/abc123"},"updatedAt":{"type":["string","null"],"format":"date-time","example":"2026-04-01T09:12:33.000Z"},"createdAt":{"type":["string","null"],"format":"date-time","example":"2026-02-14T12:00:00.000Z"}}},"Document":{"allOf":[{"$ref":"#/components/schemas/DocumentSummary"},{"type":"object","required":["html"],"properties":{"html":{"type":"string","description":"Normalized HTML body of the document.","example":"<h1>Welcome</h1><p>Week one in five steps.</p>"}}}]},"DocumentEnvelope":{"type":"object","required":["document"],"properties":{"document":{"$ref":"#/components/schemas/Document"}}},"DocumentListEnvelope":{"type":"object","required":["documents","pagination"],"properties":{"documents":{"type":"array","items":{"$ref":"#/components/schemas/DocumentSummary"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"returned":{"type":"integer"}}}}},"DocumentSearchEnvelope":{"type":"object","required":["query","documents","pagination"],"properties":{"query":{"type":"string"},"documents":{"type":"array","items":{"$ref":"#/components/schemas/DocumentSummary"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"returned":{"type":"integer"}}}}},"DocumentCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":240},"subtitle":{"type":"string","maxLength":400,"default":""},"html":{"type":"string","default":""},"status":{"type":"string","enum":["draft","review","published","archived"],"default":"draft"},"docType":{"type":"string","enum":["guide","playbook","policy","runbook","brief","spec"],"default":"guide"},"audience":{"type":"string","enum":["internal","cross_functional","leadership","customer_facing"],"default":"internal"},"collection":{"type":"string","maxLength":80,"default":"General"},"ownerName":{"type":"string","maxLength":120,"default":""},"tags":{"type":"array","items":{"type":"string"},"default":[]},"accent":{"type":"string","enum":["moss","berry","slate","ink"],"default":"moss"}}},"DocumentPatch":{"type":"object","description":"All fields optional. Unsent fields are left untouched. Changing `title` re-derives a unique slug. Changing `status` is gated by the state machine.","properties":{"title":{"type":"string","minLength":1,"maxLength":240},"subtitle":{"type":"string","maxLength":400},"html":{"type":"string"},"status":{"type":"string","enum":["draft","review","published","archived"]},"docType":{"type":"string","enum":["guide","playbook","policy","runbook","brief","spec"]},"audience":{"type":"string","enum":["internal","cross_functional","leadership","customer_facing"]},"collection":{"type":"string","maxLength":80},"ownerName":{"type":"string","maxLength":120},"tags":{"type":"array","items":{"type":"string"}},"accent":{"type":"string","enum":["moss","berry","slate","ink"]},"favorite":{"type":"boolean"},"lastReviewedAt":{"type":"string","maxLength":10},"reviewDueAt":{"type":"string","maxLength":10}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","example":"Not authenticated"}},"additionalProperties":true}},"responses":{"BadRequest":{"description":"Malformed JSON or validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Invalid input","issues":[{"path":["title"],"message":"Required"}]}}}},"Unauthorized":{"description":"Missing, malformed or revoked token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Not authenticated"}}}},"PaymentRequired":{"description":"Free plan document limit reached. Upgrade to Pro to continue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Free plan limit reached (10 docs). Upgrade to continue.","upgradeUrl":"/settings/billing"}}}},"Forbidden":{"description":"Token lacks the required scope (or no workspace).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Missing docs:write scope"}}}},"NotFound":{"description":"Resource not found in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Document not found"}}}},"Conflict":{"description":"Invalid state transition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Cannot transition from archived to published"}}}}}}}