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:
- Editor — paste from clipboard or drag and drop onto the editor
- API — upload via the create share flow
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:
/:id/:filename— anonymous shares/:username/:slug/:filename— shares with slugs
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
- The
X-Agent-Idheader is informational. The server derives the agent identity from the token. It's included in the snippet for parity with other agent-API conventions. - Edits authored by agents include
by: { agent: "<name>" }in the broadcastupdatemessage. Authenticated human edits includeby: { user: "<id>" }. Anonymous edit-token edits omitby. - Agent tokens are not account-scoped: they cannot list your shares, create shares, or touch any share other than the one they were issued for.
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 |