API reference

Publish, update, and read documents and highlight sets over HTTP. This is the same API Marked uses.

Base URL and credentials

https://share.markedapp.com/api/v1

Published document links use markedb.in, but every API request goes to share.markedapp.com. Create credentials in Settings: Create API token issues a token on its own, while Connect Marked issues a token and a device key together. Both are shown once.

HeaderValueRequired for
Authorization Bearer <api_token> Everything except reading a public document or its highlight sets
X-Device-Key <device_key> Creating and updating documents, and captures
If-Match <content_hash> Updating a document

The device key is what makes conflict detection possible: it lets the server tell you which machine published the version you are about to overwrite.

Errors

StatusBodyMeaning
400 {"error":"device_key_required"} The request writes a document but sent no device key.
401 {"error":"unauthorized"} Missing, malformed, or revoked token.
404 {"error":"not_found"} No such document, or you are not allowed to see it. Private, expired, and password-protected documents look identical to missing ones.
409 {"error":"conflict", …} The If-Match hash is stale. The body carries the current hash.
422 {"error":"…"} Validation failed, for example a blank body or an archive with no Markdown in it.

Documents

A document can be addressed by its public id, its short share code, or its private id (a UUID). Marked stores the private id, since it stays valid regardless of visibility changes.

get /documents

Your documents, most recently updated first, including private ones. Requires a token but no device key. Each entry is the full document JSON below, Markdown body included.

Results are paged: limit defaults to 50 and caps at 200, and page starts at 1. Unusable values fall back to the default rather than erroring. The response stays a plain array, with paging details in the headers — X-Total-Count holds the full count, and Link carries rel="next" and rel="prev" URLs when there are more pages.

curl -i "https://share.markedapp.com/api/v1/documents?limit=25" \
  --header "Authorization: Bearer $MARKED_API_TOKEN"

X-Total-Count: 138
Link: <https://share.markedapp.com/api/v1/documents?limit=25&page=2>; rel="next"

Pass q to search titles and bodies with the same fuzzy full-text ranking as the web library. Search hits omit body_markdown by default and include a short snippet instead. Add include=body when you need the Markdown in the list response. Whitespace-only q is ignored (normal list). Paging (limit, page, X-Total-Count, Link) is unchanged; Link URLs keep q (and include when set).

curl -i "https://share.markedapp.com/api/v1/documents?q=kubernetes&limit=25" \
  --header "Authorization: Bearer $MARKED_API_TOKEN"

Building a document picker? Use ?q= to search, or list without q for the full menu, then download /documents/:id/textpack for the one the reader chooses. The Markdown in this JSON points at images hosted on the server, while the TextPack bundles them for a self-contained import. title can be null, so supply your own "Untitled" fallback.

get /documents/:id

One document with its Markdown. Public and unlisted documents need no token; private ones need yours. Expired and password-protected documents return 404 to everyone but their owner.

curl https://share.markedapp.com/api/v1/documents/$DOCUMENT_ID \
  --header "Authorization: Bearer $MARKED_API_TOKEN"
{
  "id": "abc123",
  "private_id": "8f2c…",
  "url": "https://markedb.in/x7k2m",
  "content_hash": "9b1e…",
  "visibility": "unlisted",
  "reading_style": "swiss",
  "created_at": "2026-08-20T09:14:02.000Z",
  "updated_at": "2026-08-25T12:00:00.000Z",
  "title": "Shared from Marked",
  "body_markdown": "# Hello from Marked",
  "allow_remix": false
}

created_at and updated_at are ISO 8601 timestamps in UTC with millisecond precision, and both come back on every document response, including list and create.

reading_style is the typography the author published — editorial, manuscript, swiss, contrast, typewriter — or null for no published style, which readers see as Editorial. It comes back on every document response, and an unknown value is rejected with 422. See sharing for what each style looks like.

get /documents/:id/textpack

A TextBundle archive with Markdown and embedded images — the canonical import download for connected apps. Accepts a public id, private id, or share code in :id. Same visibility rules as the JSON endpoint above: bearer token required for private documents you own; public and unlisted documents work without a token. No device key. Add ?hl=<set_id> to include that highlight set as highlights.json.

curl "https://share.markedapp.com/api/v1/documents/$PRIVATE_ID/textpack" \
  --header "Authorization: Bearer $MARKED_API_TOKEN" \
  --output MyDoc.textpack

post /documents

Creates a document and returns 201 with its id, private_id, url, content_hash, visibility, created_at, and updated_at. Store the private id and hash for later updates.

Send JSON when you only have Markdown:

curl https://share.markedapp.com/api/v1/documents \
  --request POST \
  --header "Authorization: Bearer $MARKED_API_TOKEN" \
  --header "X-Device-Key: $MARKED_DEVICE_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "document": {
      "title": "Shared from Marked",
      "body_markdown": "# Hello from Marked",
      "visibility": "unlisted",
      "allow_remix": false
    }
  }'

Accepted fields are title, body_markdown, visibility, allow_remix, apex_mode, share_slug, and reading_style. Visibility defaults to private. A body is required and is capped at 500,000 bytes.

TextPack uploads

Send multipart/form-data with a file field to publish a document that has images. The archive is a zipped TextBundle (.textpack) or any .zip containing a Markdown file. Images are stored as document assets and the Markdown image paths are rewritten to hosted URLs. A highlights.json in Marked's format is imported as your own highlight set.

An info.json in the archive can publish the reading style, either under the com.markedapp.share namespace or as a bare readingStyle key. A document[reading_style] form field overrides it, and an unknown style is ignored. Downloaded TextPacks carry the same file, so a round trip keeps the style.

{
  "version": 2,
  "type": "net.daringfireball.markdown",
  "transient": false,
  "creatorIdentifier": "com.markedapp.share",
  "com.markedapp.share": {
    "documentId": "abc123",
    "url": "https://markedb.in/x7k2m",
    "readingStyle": "swiss"
  }
}
curl https://share.markedapp.com/api/v1/documents \
  --request POST \
  --header "Authorization: Bearer $MARKED_API_TOKEN" \
  --header "X-Device-Key: $MARKED_DEVICE_KEY" \
  --form "file=@./MyDoc.textpack" \
  --form "document[visibility]=unlisted" \
  --form "document[allow_remix]=false"

patch /documents/:id

Replaces the document. Owner only, and If-Match must carry the content_hash you last saw. Accepts the same JSON or multipart shapes as create.

curl "https://share.markedapp.com/api/v1/documents/$PRIVATE_ID" \
  --request PATCH \
  --header "Authorization: Bearer $MARKED_API_TOKEN" \
  --header "X-Device-Key: $MARKED_DEVICE_KEY" \
  --header "If-Match: $CONTENT_HASH" \
  --form "file=@./MyDoc.textpack"

A missing or stale hash is a conflict rather than an overwrite:

{
  "error": "conflict",
  "content_hash": "current-hash",
  "updated_by_device_label": "Marked on Sisyphus"
}

Recover by fetching the document (or reading the hash out of the 409 body), deciding whether to keep the server's version, and retrying with the current hash. Each body change also records a version snapshot in the document's history.

Captures

post /captures

Turns a web page into a private document, re-hosting its images and recording the page as the permanent source_url. Needs a token and a device key.

curl https://share.markedapp.com/api/v1/captures \
  --request POST \
  --header "Authorization: Bearer $MARKED_API_TOKEN" \
  --header "X-Device-Key: $MARKED_DEVICE_KEY" \
  --header "Content-Type: application/json" \
  --data '{"url":"https://example.com/article"}'

Returns 201 with id, private_id, url, content_hash, source_url, visibility, created_at, and updated_at. A missing URL returns 422 with {"error":"url_required"}.

Highlight sets

Each set is one reader's highlights and notes on a document. See Highlights and reviews for the concepts behind them. Reading requires only that you can read the document; writing requires a token.

get /documents/:id/highlight_sets

Every set on the document, newest first, without the highlight payloads.

[
  {
    "id": "hl_abc123",
    "document_id": "abc123",
    "url": "https://markedb.in/x7k2m?hl=hl_abc123",
    "label": "Anonymous reviewer",
    "highlight_count": 12,
    "note_count": 3,
    "updated_at": "2026-08-25T12:00:00.000Z",
    "owner": false,
    "parent_set_id": null,
    "content_signature": "4c9f…"
  }
]
FieldMeaning
labelAccount display name, else the reviewer's signature, else "Anonymous reviewer".
ownertrue when the set belongs to the token's user. Filter these out to list other people's reviews.
parent_set_idSet this one was forked from, if any.
content_signatureFingerprint of the whole set. Compare it to detect changes without downloading the payload.

get /documents/:id/highlight_sets/:hl_id

The same fields plus payload, which holds the highlights themselves.

{
  "id": "hl_abc123",
  "payload": {
    "version": 1,
    "highlights": [
      {
        "id": "hl_9f21",
        "kind": "highlight",
        "text": "the passage that was highlighted",
        "color": "yellow",
        "note": "needs a citation",
        "signature": "ED",
        "fingerprint": "b3d1…",
        "range": { "start": {}, "end": {} }
      }
    ]
  }
}

kind is highlight or comment. note is the reader's note (also mirrored as comment for Marked's format), signature is the label the reader typed, and fingerprint identifies the passage so the same highlight can be recognised across sets. Anchoring fields (range, marked_anchor, prefix, suffix) may also be present.

post /documents/:id/highlight_sets

Creates or replaces the token user's own set on that document. Send a highlights array; pass parent_set_id to record that you started from someone else's set.

curl https://share.markedapp.com/api/v1/documents/$DOCUMENT_ID/highlight_sets \
  --request POST \
  --header "Authorization: Bearer $MARKED_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "highlights": [
      { "kind": "highlight", "text": "a passage", "color": "yellow", "note": "nice" }
    ]
  }'

patch /documents/:id/highlight_sets/:hl_id

Replaces the highlights in a set you own.

post /documents/:id/highlight_sets/:hl_id/import

Merges someone else's set into your own, deduplicating by fingerprint and leaving their set untouched. Document owners only. Returns your set with its payload.

curl https://share.markedapp.com/api/v1/documents/$DOCUMENT_ID/highlight_sets/$REVIEW_ID/import \
  --request POST \
  --header "Authorization: Bearer $MARKED_API_TOKEN"

Downloads and change checks

Browser-friendly exports live on the document host rather than under /api/v1. Connected apps should prefer GET /api/v1/documents/:id/textpack (above) so bearer tokens unlock private documents; these URLs follow the same visibility rules but only honor a signed-in browser session, not an API token.

URLReturns
/d/:id.md The raw Markdown source.
/d/:id.textpack A TextBundle archive: Markdown plus images. Add ?hl=<set_id> to include that set as highlights.json.
/d/:id/meta content_hash and updated_at — a cheap way to poll for changes.

Tokens are bearer credentials: treat them like passwords, keep them out of logs and analytics, and revoke anything you suspect has leaked. Revoking a token has no effect on your documents.