Documentation

Overview

Link.md is a Markdown sharing service. Paste or drop a Markdown file, get a shareable link. Shares created without an account expire after 24 hours. Logged-in users can create permanent shares, set custom slugs, and upload attachments.

Shares support Markdown with extensions: math (LaTeX), syntax-highlighted code blocks, callouts, footnotes, and more.

Authentication

All endpoints live under /api/v1 and accept and return JSON.

Create an API key in Settings > API, or via the tokens API if you already have one. Include it in requests:

Authorization: Bearer YOUR_API_KEY

Unauthenticated requests are allowed for creating anonymous (24-hour) shares, but most operations require a key.

Create a share

Creating a share with files follows a three-step flow: create the share, upload binary files, then finalize.

Step 1 — Create

curl -X POST https://link.md/api/v1/shares \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My Share",
    "slug": "my-share",
    "files": [
      {"filename": "index.md", "size": 256, "content_type": "text/markdown", "content": "# Hello\nWorld"},
      {"filename": "photo.png", "size": 102400, "content_type": "image/png"}
    ]
  }'

Optional fields: title, slug, visibility ("public", "unlisted", "private"; defaults to "public"), expires_in (seconds, minimum 60).

Text files can include content inline. Binary files (attachments) omit it and are uploaded in step 2. If all files have inline content, the share is auto-finalized and you can skip steps 2 and 3.

Returns 201:

{
  "id": "abc123...",
  "status": "draft",
  "files": [
    {"filename": "index.md", "upload_url": "/api/v1/shares/abc123.../upload/index.md", "uploaded": true},
    {"filename": "photo.png", "upload_url": "/api/v1/shares/abc123.../upload/photo.png", "uploaded": false}
  ],
  "edit_token": "TOKEN",
  "url": "https://link.md/abc123...",
  "expires_at": null
}

Step 2 — Upload files

Upload each binary file to its upload_url:

curl -X POST https://link.md/api/v1/shares/abc123.../upload/photo.png \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: image/png" \
  --data-binary @photo.png

Returns 200: {"ok": true, "size": 102400}

Step 3 — Finalize

curl -X POST https://link.md/api/v1/shares/abc123.../finalize \
  -H "Authorization: Bearer YOUR_API_KEY"

Publishes the share and returns the final file list.

Get a share

curl https://link.md/api/v1/shares/abc123... \
  -H "Authorization: Bearer YOUR_API_KEY"

Returns share metadata and a file list with download URLs.

Update a share

curl -X PATCH https://link.md/api/v1/shares/abc123... \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "New Title", "slug": "new-slug"}'

Updatable fields: title, slug, visibility, expiration ("permanent" or "24h"). Slug, visibility, and expiration changes require ownership.

Visibility values: "public", "unlisted", "private". Private shares are only accessible to the owner, edit-token holders, and collaborators.

Delete a share

curl -X DELETE https://link.md/api/v1/shares/abc123... \
  -H "Authorization: Bearer YOUR_API_KEY"

List your shares

curl https://link.md/api/v1/shares \
  -H "Authorization: Bearer YOUR_API_KEY"

Returns all shares owned by the authenticated user.

RSS feeds

Public notes for a user are available as an RSS feed:

curl https://link.md/USERNAME/rss.xml

Feeds include the 25 most recent public published notes. Item descriptions include a plain-text excerpt by default. To omit excerpts:

curl https://link.md/USERNAME/rss.xml?excerpt=false

Only public notes are included. Unlisted, private, draft, and expired notes are excluded.

File endpoints

Operate on individual files within a share by path. Useful for syncing tools like the Obsidian plugin.

Get a file

curl https://link.md/api/v1/shares/abc123.../files/index.md \
  -H "Authorization: Bearer YOUR_API_KEY"

Returns the raw file content with Content-Type set to the file's MIME type (not JSON-wrapped). Metadata is in response headers: X-Filename, X-Size, X-Is-Entry.

Create or update a file

curl -X PUT https://link.md/api/v1/shares/abc123.../files/notes.md \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: text/markdown" \
  --data-binary @notes.md

Creates a file if it doesn't exist, replaces it if it does. Send raw file content in the request body with the appropriate Content-Type header. The same validation rules apply as during share creation (file type, magic bytes, size limits, SVG sanitization).

For anonymous shares, pass X-Edit-Token: TOKEN instead of Authorization.

Returns 200: {"ok": true, "filename": "notes.md", "size": 1024, "created": true}

Delete a file

curl -X DELETE https://link.md/api/v1/shares/abc123.../files/photo.png \
  -H "Authorization: Bearer YOUR_API_KEY"

Deletes a file from the share. The entry file (e.g. index.md) cannot be deleted. For anonymous shares, pass X-Edit-Token: TOKEN instead of Authorization.

Returns 200: {"ok": true}

API tokens

Manage API keys programmatically (requires an existing key).

Method Endpoint Description
POST /api/v1/tokens Create a new API key. Body: {"name": "...", "expires_in": 86400}
GET /api/v1/tokens List your API keys
DELETE /api/v1/tokens/:id Revoke an API key

Attachments

Attachments can be added to shares in two ways:

Supported file types

PNG, JPEG, GIF, WebP, SVG, and PDF.

SVGs are sanitized to remove scripts and other active content.

Multi-file shares

A share can contain multiple files. The entry file (typically index.md or the first markdown file) is rendered when you visit the share. Other files are accessible via relative links.

File URLs follow the pattern:

When a share has multiple files, a file list sidebar appears on the left to navigate between them.

Collaboration

Real-time collaborative editing uses WebSocket connections authenticated via short-lived tokens.

Request a collaboration token

curl -X POST https://link.md/api/v1/shares/abc123.../collab-token \
  -H "Authorization: Bearer YOUR_API_KEY"

Collaboration must be enabled on the share. Returns a one-time token (valid for 30 seconds) and a WebSocket URL:

{
  "token": "TOKEN",
  "ws_url": "wss://link.md/collab/abc123...?token=TOKEN",
  "expires_in": 30
}

Connect to ws_url to join the collaborative editing session.

Agent collaborators

Invite an AI agent (or any HTTP client) to collaborate on a share without giving it your account-level API key. Each agent has a per-share Bearer token and a stable name that shows up in presence and edit attribution.

Agents are issued from the share's invite dropdown. Click Invite agent, pick a name, copy the snippet once. The token is bound to one share, can be revoked at any time, and is tied to your user account (deleting your account or the share revokes it).

Issue an agent token

curl -X POST https://link.md/api/v1/shares/abc123.../agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_name": "claude", "role": "editor"}'

Returns the token exactly once:

{
  "agent_name": "claude",
  "role": "editor",
  "token": "AGENT_TOKEN",
  "prefix": "AGENT_TO",
  "created_at": "2026-05-15T14:30:00Z"
}

agent_name must match [a-z0-9][a-z0-9_-]{0,31} and is unique per share. role is editor (default) or viewer.

List / revoke

curl https://link.md/api/v1/shares/abc123.../agents \
  -H "Authorization: Bearer YOUR_API_KEY"

curl -X DELETE https://link.md/api/v1/shares/abc123.../agents/claude \
  -H "Authorization: Bearer YOUR_API_KEY"

Agent endpoints

All authenticated with the agent's Bearer token. The agent token is bound to a single share. Calls against any other share return 403.

Read state

curl https://link.md/api/v1/shares/abc123.../state \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "X-Agent-Id: claude"

Returns the current doc and all live cursors (humans and agents):

{
  "version": 13,
  "content": "# Hello",
  "title": "Hello",
  "awareness": [["claude", {"agent":{"name":"claude"}, "cursor":{"anchor":4,"head":4}}]]
}

Push an edit

Agents send the full new doc plus the version they based it on. The server applies the change, bumps the version, and broadcasts it to all live editors. If version is stale you get 409 with the current state.

curl -X POST https://link.md/api/v1/shares/abc123.../ops \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "X-Agent-Id: claude" \
  -H "Content-Type: application/json" \
  -d '{"version": 13, "content": "# Hello\n\nNew line.", "title": "Hello"}'

Response: {"version": 14} on success, or 409 with { "error": "conflict", "version": ..., "content": ..., "title": ... } on version mismatch.

Only editor-role agents can push. viewer agents get 403.

Heartbeat presence

curl -X POST https://link.md/api/v1/shares/abc123.../presence \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "X-Agent-Id: claude" \
  -H "Content-Type: application/json" \
  -d '{"cursor": {"anchor": 10, "head": 14}}'

Cursor is optional. Send {} as a liveness ping. Presence times out after 90 seconds without a heartbeat, so agents that want to stay visible should ping every ~60 seconds. cursor: null explicitly clears the agent's cursor while keeping it in the presence list.

Notes

Limits

Limit Anonymous Authenticated
Share expiration 24 hours Permanent or custom
Max file size 2 MB 10 MB
Attachments per share 4 20
Total size per share 5 MB 50 MB
Storage quota 50 MB (default)
Markdown content 1 MB per share 1 MB per share

Error codes

Status Meaning
400 Validation failed (empty content, invalid slug, invalid visibility, bad filename, etc.)
401 Missing or invalid API key / edit token
403 Forbidden (not the owner, CSRF check failed)
404 Share not found
409 Slug or ID collision
410 Share has expired
413 File or share exceeds size limit / storage quota exceeded
429 Rate limited — check Retry-After header
Drop .md file