Documents (Markdown & Typst)
Document hosting lets you upload text documents to Picora and get a permanent URL for each. Two markup languages are supported (v0.82.0):
| Format | Extensions | How it renders online |
|---|---|---|
| Markdown (v0.15.0) | .md, .markdown | WYSIWYG rendering, with automatic embedded-image rewriting |
| Typst (v0.82.0) | .typ | Compiled to paginated SVG in your browser |
The killer feature for Markdown: automatic embedded base64 image rewriting — Picora extracts inline images, uploads them as separate files, and replaces the URLs in your markdown.
Combined with MCP integration, this is how AI tools like Claude / Cursor publish your notes to a stable cloud URL with one prompt.
Quick upload via dashboard
- Open Library → Documents
- Drag a
.mdfile or click Upload / Paste text - Wait for the upload + image rewriting (typically <8 seconds for documents with up to 50 images)
- Online viewer opens automatically; copy the share link
Upload via API
curl -X POST https://api.picora.me/v1/docs \ -H "Authorization: Bearer sk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "filename": "release-notes.md", "content": "# v0.15 Release\n\n\n\nFirst paragraph...", "title": "v0.15 Release Notes", "tags": ["release", "v0.15"], "isPublic": false, "rewriteImages": true }'Response:
{ "success": true, "data": { "id": "doc_xK9mR2pQ7vBnE3wF8sCd2", "title": "v0.15 Release Notes", "filename": "release-notes.md", "sizeBytes": 4523, "wordCount": 312, "imageCount": 1, "rewrittenCount": 1, "failedCount": 0, "isPublic": false, "tags": ["release", "v0.15"], "createdAt": "2026-04-27T08:00:00Z" }}Embedded image rewriting {#rewriting}
Picora parses your markdown using a proper AST (not regex) and handles four embedded image syntaxes:
 # inline base64 — REWRITTEN![alt][ref] # reference style — REWRITTEN[ref]: data:image/png;base64,iVBORw...<img src="data:image/png;base64,iVBORw..." /> # HTML inline — REWRITTEN # already on Picora — KEPT # third-party — KEPT # local file — SKIPPED with warningWhat gets rewritten: only data:image/...;base64, URIs are decoded and re-uploaded. Third-party URLs are not transferred (legal gray area; we respect original sources).
Where rewritten images go: into your image storage quota as regular images. They appear in your Library Images tab with a tag indicating they came from a markdown document.
CDN allowlist: Picora maintains an allowlist of trusted CDN domains. URLs matching the allowlist are recognized as “already on Picora” and skipped. The default allowlist includes media.picora.me / *.picora.me / *.picora.cn.
Per-plan limits
| Tier | doc count | max file | max embedded images |
|---|---|---|---|
| trial | 100 | 1 MB | 30 |
| pro | Unlimited | 5 MB | 100 |
| pro_plus | Unlimited | 5 MB | 300 |
If embedding images exceeds your image storage quota, the entire document upload fails (QUOTA_EXCEEDED: img_storage) and any successfully uploaded images are rolled back (best-effort).
Failure handling
If individual images fail to decode or upload (e.g., corrupted base64, R2 timeout), the document still saves — the failed images keep their original data: URLs in the rewritten markdown. The response’s failures[] array tells you which indices failed.
This means you’ll never lose a document due to a single bad image; you can retry / fix later.
Typst documents {#typst}
Typst (.typ) is supported from v0.82.0. Unlike Markdown, Typst is a document compiler — a .typ file is source code that gets compiled to a paginated document, so the two formats are mutually exclusive: a document is either Markdown or Typst, decided by its file extension.
Upload works through the same endpoint as Markdown — just send a .typ filename:
curl -X POST https://api.picora.me/v1/docs \ -H "Authorization: Bearer sk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "filename": "lecture.typ", "content": "#set page(width: 16cm)\n= Lecture 1\n\nBody text.", "isPublic": false }'What differs from Markdown:
- Your source is stored byte-for-byte. Typst is never passed through the Markdown pipeline, so
#let,#setand other#-prefixed syntax survive untouched. Embedded-image rewriting, frontmatter parsing, excerpt and cover extraction are all Markdown-only and are skipped. formatfield. Every document returned by the API carriesformat: "markdown" | "typst". Documents created before v0.82.0 reportmarkdown.- Content type.
GET /v1/docs/:id/rawreturnstext/typst; charset=utf-8for Typst documents (Markdown returnstext/markdown; charset=utf-8). - Online rendering happens in your browser. Opening a
.typdocument in the dashboard downloads the Typst engine (~27 MB) once, then compiles the document locally to SVG. The engine is cached by the browser, so only the first Typst document you ever open pays that cost. If compilation fails, the source is shown along with the compiler diagnostic.
Quotas, size limits, versioning, visibility and deletion behave exactly the same as for Markdown — Typst documents count against the same document-count quota.
Online viewer
Each document has a viewer at https://center.picora.me/library/docs/{id}:
- Markdown rendered with
react-markdown+ GFM (tables, task lists, strikethrough, autolinks) - Code blocks: syntax highlighting (12 common languages, lazy-loaded)
- Math: LaTeX with KaTeX (lazy-loaded when the doc contains
$$) - Diagrams: Mermaid (lazy-loaded when the doc contains
```mermaid) - Lazy-loaded inline images via
<img loading="lazy"> - Floating TOC for documents with H1-H3 (only shown when the doc is over 1500 words)
- Copy toolbar: copy raw URL / copy markdown source / download
.md/ show QR code (for public docs)
Visibility
- Private (default): only you can view;
/rawendpoint requires authentication - Public: anyone with the URL can view;
/rawis freely fetchable
This is the opposite default from images — markdown documents are typically private notes.
Source hash & idempotency
Picora computes SHA-256 of the rewritten markdown. If you upload the same content twice (same hash, same user), the second upload returns 409 DOC_HASH_DUPLICATE with the existing document’s ID. It does not consume your document quota for duplicates.
This is useful for AI tools that may retry uploads — they get back the original ID without creating duplicates.
Updating a document
There’s currently no PATCH-content endpoint. To update a document’s content, re-upload to the same knowledge-base path (kbId + relativePath) — the current content is replaced in place. If version control is enabled, the previous content is automatically kept as a version.
You can PATCH metadata only:
curl -X PATCH https://api.picora.me/v1/docs/{id} \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"title": "New title", "isPublic": true, "tags": ["updated"]}'Version history
Picora can keep a rolling history of your knowledge-base documents so an edit or sync never permanently loses the previous content. It is a per-user, opt-in feature configured under Settings → Account → Document version control — off by default.
When enabled, every content change to a KB document (whether re-uploaded via API or synced from an editor like Moraya) saves a snapshot of the previous content. You choose how many versions to keep per document (default 10, any value 1–500); once the limit is exceeded, the oldest version is pruned automatically.
- Scope: only documents inside a knowledge base (
kbId+relativePath). One-off uploads are not versioned. - Storage: history counts toward your storage usage — the account page shows how many versions and how much space they occupy. More versions = more storage.
- Turning it off keeps existing history but stops adding new versions; a Clear history action reclaims the space on demand.
In the dashboard
Open a knowledge-base document from your library and click Version history in the viewer header. The panel lists every saved version (newest first) with its size and time; select one to preview its content, then Restore this version to make it current. A restore keeps your current content as a new version first, so it is never destructive.
List, read, and restore via API
# List a document's versions (newest first) + total sizecurl https://api.picora.me/v1/docs/{id}/revisions \ -H "Authorization: Bearer sk_live_..."
# Read one historical version's full contentcurl https://api.picora.me/v1/docs/{id}/revisions/{revId} \ -H "Authorization: Bearer sk_live_..."
# Restore: rewrite the document with an older version's content# (the current content is first saved as a new version — never destructive)curl -X POST https://api.picora.me/v1/docs/{id}/revisions/{revId}/restore \ -H "Authorization: Bearer sk_live_..."
# Clear ALL of your version history to reclaim storagecurl -X DELETE https://api.picora.me/v1/user/me/doc-revisions \ -H "Authorization: Bearer sk_live_..."Listing and reading versions stay available even after a plan expires (so you can always review/export your history); restoring requires an active plan. See the Documents API reference for full field details.
Deletion
Same as other resource types — DELETE endpoint or dashboard. Deleting a document does not delete the embedded images that were rewritten (they may be referenced from other documents). Orphan image cleanup is on the v0.20+ roadmap.
Common issues
“22 errors during upload — file:// links skipped” — your markdown referenced local files. Replace them with inline base64 or third-party URLs before uploading.
“403 QUOTA_EXCEEDED: img_storage during upload” — embedded images would exceed your image quota. Free up image storage or upload the document with rewriteImages=false (images stay as-is in the markdown).
“422 DOC_FILE_TOO_LARGE” — your .md file exceeds your plan’s doc_max_file_bytes. Split into multiple documents or upgrade.
“Mermaid / KaTeX not rendering” — make sure you’re viewing the document in Picora’s online viewer (the /library/docs/{id} page), not just the raw markdown. The viewer auto-loads these libraries.
Related
- API Reference — Documents
- AI Workflows — Tool catalog —
upload_docMCP tool - Quota overview — doc count and image side-effects