Skip to content
  • Follow System
  • English
  • 中文

Documents API

Picora supports documents as a first-class resource type alongside images, videos and audio. Two markup languages share this one API (v0.82.0+):

  • Markdown.md / .markdown (v0.15.0+)
  • Typst.typ (v0.82.0+)

The format is decided by the filename extension and reported back in the format field. Markdown-specific processing (embedded base64 image rewriting, frontmatter parsing, excerpt/cover extraction) applies to Markdown only; Typst source is stored byte-for-byte.

This page covers the REST API; for AI-driven workflows see the picora.upload_doc MCP tool.

Resource model

FieldTypeNotes
idstring (nanoid 21)Document ID
titlestringPriority: explicit param → frontmatter title: → first H1 → filename
filenamestringOriginal filename, incl. extension (.md / .markdown / .typ)
formatstringmarkdown or typst (v0.82.0+). Derived from the extension; documents created earlier report markdown
sizeBytesintUTF-8 byte length after image rewrite
wordCountintEstimated word count (Intl.Segmenter word)
imageCountintTotal image references (incl. external + skipped)
rewrittenCountintActual base64 → CDN uploads (post-dedup)
isPublicbooleanPublic docs allow anonymous /raw access
tagsstring[]Up to 10 tags, ≤ 32 chars each
hasInlineContentbooleantrue = stored in DB (≤ 256KB), false = R2
createdAt, updatedAtISO 8601

Quotas

Plandoc_countdoc_max_file_bytesdoc_max_images_per_doc
trial1001 MB30
pro-1 (unlimited)5 MB100
pro_plus-1 (unlimited)5 MB300

doc_count is returned as -1 in planLimits.docCount (GET /v1/user/me/usage) when the plan has no document-count cap. Check for -1 before rendering a usage ratio.

Endpoints

POST /v1/docs — upload

Terminal window
curl -X POST https://api.picora.me/v1/docs \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"filename": "README.md",
"content": "# Hello\n![](data:image/png;base64,iVBOR...)",
"tags": ["readme"],
"isPublic": false,
"rewriteImages": true
}'

Response (201 on new, 200 on duplicate):

{
"success": true,
"data": {
"id": "abc123XYZ_ki1234567890",
"title": "Hello",
"imageCount": 1,
"rewrittenCount": 1,
"failedCount": 0,
"failures": [],
"warnings": [],
"duplicate": false,
"hasInlineContent": true,
"createdAt": "2026-04-28T08:00:00.000Z"
}
}

Inline image rewriting: data:image/{png,jpeg,webp,gif,svg+xml};base64,... URLs are decoded, optionally SVG-sanitized, and uploaded to your image quota. The markdown URL is replaced with https://media.picora.me/{nanoid}.{ext}.

SHA-256 idempotency: Re-uploading identical content returns the existing doc id with duplicate: true and does NOT consume quota.

GET /v1/docs — list

ParamTypeNotes
cursorstringPagination cursor from previous response
limitint1-50, default 20
qstringTitle fuzzy match (LIKE %q%)
tagstringSingle or comma-separated for OR (e.g. tag=ai,note)
isPublicboolFilter by visibility
sortenumcreated_desc (default), created_asc, updated_desc, updated_asc

GET /v1/docs/:id — metadata

Returns metadata only. Use :id/raw for full content.

GET /v1/docs/:id/raw — raw markdown

Returns Content-Type: text/markdown; charset=utf-8.

  • Public docs: anonymous access, Cache-Control: public, max-age=300
  • Private docs: requires Authorization: Bearer ..., no-store

PATCH /v1/docs/:id — update metadata

{ "title": "...", "isPublic": true, "tags": ["ai"] }

Content updates: not supported — re-POST and DELETE old.

DELETE /v1/docs/:id and DELETE /v1/docs (batch)

Single delete or body { ids: string[] } (max 50). Embedded images are NOT cascaded (orphan cleanup is future). A hard delete also removes the document’s version history.

Version history

Optional per-user feature (default off) that keeps a rolling history of knowledge-base documents. Enable it and set the retention count via PATCH /v1/user/me (docVersioningEnabled, docVersioningMax 1–500). When on, any content replacement at the same (kbId, relativePath) saves the previous content as a version; the oldest is pruned once docVersioningMax is exceeded. History storage is reported under docs.revisionCount / docs.revisionBytes in GET /v1/user/me/usage.

GET /v1/docs/:id/revisions — list versions

Returns versions newest-first plus total size. Read-only — available even when a plan has expired.

{ "success": true, "data": {
"revisions": [
{ "id": "Rk9MR2PQ7vB01234567ab", "revNumber": 3, "sizeBytes": 20480,
"origin": "sync", "sourceHash": "e3b0c442…", "createdAt": "2026-07-10T08:00:00.000Z" }
],
"totalBytes": 61440
} }

origin is upload (direct re-upload) · sync (editor/KB sync) · restore (produced by a restore).

GET /v1/docs/:id/revisions/:revId — read a version’s content

Returns the version metadata plus its full content (Markdown or Typst source). Read-only.

POST /v1/docs/:id/revisions/:revId/restore — restore

Rewrites the document with the version’s content. The current content is first snapshotted as a new version (never destructive). Requires an active plan (returns 403 PLAN_INACTIVE otherwise). If the restored content equals the current content, responds with duplicate: true and no side effects.

DELETE /v1/user/me/doc-revisions — clear all history

Deletes every version for the current user to reclaim storage. Processes up to 200 per call — repeat while hasMore is true. Allowed regardless of plan status (reclaiming your own space is always permitted).

{ "success": true, "data": { "deleted": 37, "freedBytes": 8388608, "hasMore": false } }

Error codes

HTTPcodeWhen
401UNAUTHORIZEDMissing / invalid Bearer for private docs
403PLAN_INACTIVERestore attempted without an active plan
403QUOTA_EXCEEDEDdoc_count limit reached
404DOC_REVISION_NOT_FOUNDVersion id not found / not owned
404NOT_FOUNDDoc id not found
409DOC_HASH_DUPLICATESame content already uploaded (returns existing id)
422DOC_FILE_TOO_LARGEcontent > plan’s doc_max_file_bytes
422DOC_IMAGE_LIMIT_EXCEEDEDimages > plan’s doc_max_images_per_doc
422DOC_INVALID_PATTERN(admin) bad CDN whitelist pattern
502STORAGE_ERRORR2 / OSS unavailable

See also

  • Markdown hosting guide — usage scenarios + AI workflow examples
  • MCP — the picora.upload_doc tool and how to connect a client