Skip to content
  • Follow System
  • English
  • 中文

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):

FormatExtensionsHow it renders online
Markdown (v0.15.0).md, .markdownWYSIWYG rendering, with automatic embedded-image rewriting
Typst (v0.82.0).typCompiled 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

  1. Open Library → Documents
  2. Drag a .md file or click Upload / Paste text
  3. Wait for the upload + image rewriting (typically <8 seconds for documents with up to 50 images)
  4. Online viewer opens automatically; copy the share link

Upload via API

Terminal window
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![logo](data:image/png;base64,iVBORw0KGgo...)\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:

![alt](data:image/png;base64,iVBORw...) # inline base64 — REWRITTEN
![alt][ref] # reference style — REWRITTEN
[ref]: data:image/png;base64,iVBORw...
<img src="data:image/png;base64,iVBORw..." /> # HTML inline — REWRITTEN
![alt](https://media.picora.me/abc.png) # already on Picora — KEPT
![alt](https://example.com/photo.jpg) # third-party — KEPT
![alt](file:///Users/me/photo.png) # local file — SKIPPED with warning

What 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

Tierdoc countmax filemax embedded images
trial1001 MB30
proUnlimited5 MB100
pro_plusUnlimited5 MB300

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:

Terminal window
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, #set and other #-prefixed syntax survive untouched. Embedded-image rewriting, frontmatter parsing, excerpt and cover extraction are all Markdown-only and are skipped.
  • format field. Every document returned by the API carries format: "markdown" | "typst". Documents created before v0.82.0 report markdown.
  • Content type. GET /v1/docs/:id/raw returns text/typst; charset=utf-8 for Typst documents (Markdown returns text/markdown; charset=utf-8).
  • Online rendering happens in your browser. Opening a .typ document 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; /raw endpoint requires authentication
  • Public: anyone with the URL can view; /raw is 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:

Terminal window
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 controloff 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

Terminal window
# List a document's versions (newest first) + total size
curl https://api.picora.me/v1/docs/{id}/revisions \
-H "Authorization: Bearer sk_live_..."
# Read one historical version's full content
curl 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 storage
curl -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.