# Markdrop Docs
> Guides for publishing documents and files, sharing them safely, running workspaces on your own domain, and using Markdrop from your editor, terminal and AI assistant.
---
# What is Markdrop
Source: https://docs.markdrop.in/get-started/what-is-markdrop
Markdrop gives you a link that renders what you drop into it. Paste a Markdown write-up and people see a formatted page. Upload `deck.pdf`, a spreadsheet or a screen recording and people see the PDF, the grid or the video, in their browser, without downloading anything or installing an app.
Anything you would otherwise paste as raw text, or send as an attachment someone can't open on their phone, becomes one address like `markdrop.in/q3-plan`.
## Two kinds of things you can publish
### Documents
A document is text you write or paste: notes, specs, an AI assistant's answer, a README. Markdrop renders it with headings, tables, task lists, code highlighting, math and diagrams.
You don't need an account to publish a document. You get a link right away, plus an edit secret that lets you change or delete it later.
### Files
A file (the app also calls these *artifacts*) is something you upload: an HTML page, a PDF, an Excel or CSV sheet, a Word document, images, video, audio, or a whole website in a `.zip`. Each one opens in a viewer made for it: a PDF reader, a spreadsheet with tabs, a video player.
Files need a free account, because they're stored against it.
## What happens after you share a link
Readers open it in any browser. Nobody needs an account to read something you've shared with "Anyone with the link".
You stay in control of the link after you share it:
- **Edit it.** Change a document's text or swap the file behind a link. The address stays the same, and pages that are open update in place.
- **Decide who can open it.** Keep it open to anyone with the link, limit it to people you add by email, or to your team. See [Who can open what](https://docs.markdrop.in/sharing/who-can-open).
- **Lock it down further.** Add a password, set it to expire, or encrypt a document in your browser so Markdrop stores only ciphertext it has no key for.
- **See who reads it.** Signed in, you get views, read depth and, for video, how much people watched. See [Analytics](https://docs.markdrop.in/analytics/overview).
## When teams use it
A workspace gives a team shared folders, roles and its own domain, so a link reads `docs.acme.com/guides/setup` instead of `markdrop.in/…`, with your logo on it. See [Create a workspace](https://docs.markdrop.in/workspaces/create).
You can also publish from where you already work: the [CLI](https://docs.markdrop.in/cli/install), the [VS Code extension](https://docs.markdrop.in/vscode/sync), and AI assistants like Claude through [MCP](https://docs.markdrop.in/ai/overview).
## Start here
**[Publish your first document](https://docs.markdrop.in/get-started/first-document)**
Paste some Markdown and get a link in under a minute. No account needed.
**[Publish your first file](https://docs.markdrop.in/get-started/first-file)**
Upload a PDF, page, sheet or video and get a link that opens it.
**[Do you need an account?](https://docs.markdrop.in/get-started/accounts)**
What works without signing in, and what an account adds.
**[A tour of the app](https://docs.markdrop.in/get-started/tour)**
Where your files, shared items, analytics and settings live.
---
# Publish your first document
Source: https://docs.markdrop.in/get-started/first-document
You can publish a document in under a minute, without an account. You paste text, Markdrop gives you a link, and anyone who opens it sees a formatted page.
## Publish it
### Open the editor
Go to [markdrop.in/new](https://markdrop.in/new), or choose **Create › Markdown document** from any page.
### Add your text
Paste or type Markdown in the editor. The **Title** field above it is optional: leave it empty and Markdrop uses the document's first `#` heading, or its first line.
Switch between **Write**, **Split** and **Preview** to see the result as readers will. Not sure what to try? Choose **Try the diagrams sample** in the empty editor.
### Check the settings (optional)
The chips above the editor say how the document is published. By default it's **Anyone with the link**, has **No password**, **Never expires** and gets a **Random link**. Choose a chip to change it. For example, set **Link** to `q3-plan` to publish at `markdrop.in/q3-plan`.
### Choose Publish
Choose **Publish**, or press ⌘ Enter (Ctrl Enter on Windows and Linux).
The document opens with a **Published** panel. Choose **Copy link** and share it anywhere: Slack, email, a ticket. Choose **Show QR code** to open it on a phone.
## Keep your edit secret
If you published without signing in, the panel also shows your **Edit secret**, a code that starts with `sk_`. It's the only way to edit or delete the document without an account.
> **Warning: Copy it now**
>
> The secret stays visible in this browser tab, but nowhere else. Choose **Copy secret** and keep it somewhere safe. Markdrop can't show it to you again.
If you're signed in, the document is already in **My files** and you don't need the secret. Signed out, choose **Sign in to keep it in your account**, then **Save to my account** when you're back on the document. See [Edit with your edit secret](https://docs.markdrop.in/documents/edit-key).
## What readers see
Readers get the rendered page: headings, tables, code with syntax highlighting, math and Mermaid diagrams. They don't need an account. They can copy the link, read full screen (press F), view the raw Markdown, or export a PDF from **More actions**.
If you change the text later, anyone with the page open sees the update appear.
## Things to know
- A document can be up to 500,000 characters. The meter above the editor counts down as you type.
- The editor keeps a draft on this device until you publish, so closing the tab doesn't lose your text. Turning on encryption stops drafts.
- If you publish many documents in a short time, Markdrop asks you to wait a minute and try again.
## Next steps
**[Write and format](https://docs.markdrop.in/documents/writing)**
Everything the renderer supports, from tables to diagrams and math.
**[Add a password or an expiry](https://docs.markdrop.in/documents/passwords-and-expiry)**
Ask readers for a password, or have the link stop working on a date.
**[Encrypt it in your browser](https://docs.markdrop.in/sharing/end-to-end-encryption)**
Publish so that Markdrop stores only ciphertext it has no key for.
---
# Publish your first file
Source: https://docs.markdrop.in/get-started/first-file
Upload a file and Markdrop gives you a link that opens it, rendered, in any browser. A PDF opens as pages, a spreadsheet as a grid with tabs, a video in a player. Readers don't download anything and don't need an account.
Publishing a file needs a free Markdrop account. You can fill everything in first: signed out, the button reads **Sign in and publish**, and everything you entered is still there after you sign in.
## Upload it
### Open the upload page
Go to [markdrop.in/upload](https://markdrop.in/upload), or choose **Create › Artifact**.
### Add your file
Drag a file onto the page, or choose **Choose a file**. One file per link. To publish a whole site, upload it as a `.zip`; see [Publish a zipped website](https://docs.markdrop.in/files/websites).
Markdrop shows a **Preview** of how the file will render. It's drawn in your browser, before anything uploads.
### Check the title and link
The **Title** and **Link** fill in from the file name: `Q3 Board Deck.pdf` becomes the title `Q3 Board Deck` and the link `markdrop.in/q3-board-deck`. Change either one. The title shows in the browser tab and in link previews.
### Choose who can open it (optional)
**Who can open it** starts as **Anyone with the link**. You can choose **Only people added** instead, add a **Password**, or set an **Expires** date. Members of a workspace can also pick a **Location** to file it in a workspace folder.
### Choose Publish
The page shows upload progress. Keep the tab open until it finishes. When it's done, the file opens at its new link with a **Published** panel and **Copy link**.
## What you can upload
| Kind | Opens as |
| --- | --- |
| HTML page | A live web page |
| PDF | Readable pages |
| Excel (`.xlsx`, `.xls`) or CSV | A sheet with tabs |
| Word (`.docx`) | A formatted document |
| Images, including SVG | The full-size image |
| Video and audio | A player that streams and seeks |
| JSON and text | Text with line numbers |
| Zipped site | A multi-page site, opening at its `index.html` |
Most files can be up to 25 MB. Video and audio can be up to 2 GB. See [Supported file types](https://docs.markdrop.in/files/supported-types) and [File size and storage limits](https://docs.markdrop.in/files/limits).
## Files render on a separate domain
Markdrop serves what you upload from a different domain than markdrop.in, inside a sandboxed frame. An HTML page's scripts run, but they can't reach Markdrop or your account. See [How files are served](https://docs.markdrop.in/security/how-files-are-served).
## Change it later
The file is in **My files**, under **Artifacts**. Open it and choose **More artifact actions** (the `⋯` button):
- **Settings** changes the title, link, password and expiry, and lets you replace the file. The link stays the same. See [Replace a file and restore versions](https://docs.markdrop.in/files/replace-and-versions).
- **Share** chooses who can open it and adds people by email. See [Share with specific people](https://docs.markdrop.in/sharing/share-with-people).
- **Delete artifact** removes it and its link for everyone.
## Next steps
**[Supported file types](https://docs.markdrop.in/files/supported-types)**
Every format Markdrop renders, and how each one looks.
**[Upload large files](https://docs.markdrop.in/files/large-uploads)**
Recordings up to 2 GB, and resumable uploads from the terminal.
**[Video and audio](https://docs.markdrop.in/files/video-and-audio)**
The player, keyboard shortcuts, and what to do when a video won't play.
---
# Do you need an account?
Source: https://docs.markdrop.in/get-started/accounts
Writing and publishing a document never needs an account, and neither does reading anything shared with "Anyone with the link". An account is for the things that have to belong to someone: files you upload, documents only certain people can open, and analytics about your readers.
## Without an account
You can:
- Publish documents at [markdrop.in/new](https://markdrop.in/new), with a custom link, a password, an expiry, and end-to-end encryption.
- Publish a README from the [README builder](https://docs.markdrop.in/documents/readme-builder).
- Edit or delete what you published, using its [edit secret](https://docs.markdrop.in/documents/edit-key).
- Send files peer to peer from [markdrop.in/share](https://markdrop.in/share). See [Send files peer to peer](https://docs.markdrop.in/share/send-files).
- Read anything open to anyone with the link.
Everything you publish without an account is open to anyone with the link. "Only people added" needs an owner to add the people, so it needs an account.
## What an account adds
| With an account you can | Where |
| --- | --- |
| Upload files: HTML, PDF, sheets, Word, images, video, zipped sites | [Publish your first file](https://docs.markdrop.in/get-started/first-file) |
| Keep everything you publish in one list | **My files** |
| Limit a document to people you add, and add them by email | [Share with specific people](https://docs.markdrop.in/sharing/share-with-people) |
| Open documents shared with your address, and ask for access | **Shared with me**, [Request access](https://docs.markdrop.in/sharing/request-access) |
| See views, read depth and video watch time | [Analytics](https://docs.markdrop.in/analytics/overview) |
| Create workspaces, folders and custom domains | [Create a workspace](https://docs.markdrop.in/workspaces/create) |
| Publish from VS Code, the CLI and AI assistants | [API tokens](https://docs.markdrop.in/account/api-tokens), [AI and MCP](https://docs.markdrop.in/ai/overview) |
| Save a copy of someone else's document | **More actions › Save a copy** |
## Sign in
There is no password to remember. Go to [markdrop.in/login](https://markdrop.in/login) and choose one:
- **Continue with Google.**
- **Email me a code.** Markdrop emails you a 6-digit code and a sign-in link. Enter the code, or open the link on the same device.
Your first sign-in creates the account. You stay signed in on that browser for 30 days, or until you choose **Sign out**.
> **Note**
>
> Sign in with the address people share things with. Markdrop matches shared documents to the email address of the account you're signed in with. Letter case doesn't matter, but `alice+docs@acme.com` and `alice@acme.com` are different addresses.
## Move anonymous documents into your account
A document you published while signed out isn't attached to any account. To attach it, sign in in the same tab you published from and choose **Save to my account** on the document. See [Edit with your edit secret](https://docs.markdrop.in/documents/edit-key#save-it-to-your-account).
Once it's yours, it appears in **My files** with its analytics, and you can choose who can open it.
## Your profile
Open **Settings** in the sidebar to change your **Display name**, which people in your workspaces see. Your email is your sign-in address and can't be changed there.
## Related
- [A tour of the app](https://docs.markdrop.in/get-started/tour)
- [Account limits](https://docs.markdrop.in/account/limits)
---
# A tour of the app
Source: https://docs.markdrop.in/get-started/tour
Once you sign in, Markdrop works like a drive for things that render. This page walks through the sidebar, the **Create** menu, and the bar on top of every document and file.
## Create something
**Create** sits at the top of the app. It has four choices:
| Choice | What it does |
| --- | --- |
| **Markdown document** | Write or paste Markdown and publish a link. Opens [markdrop.in/new](https://markdrop.in/new). |
| **Artifact** | Upload an HTML page, PDF, spreadsheet, Word document or video. Opens [markdrop.in/upload](https://markdrop.in/upload). |
| **README builder** | Assemble a README from ready-made sections. See [README builder](https://docs.markdrop.in/documents/readme-builder). |
| **Send a file** | Send files peer to peer; they aren't stored on Markdrop's servers. See [Send files peer to peer](https://docs.markdrop.in/share/send-files). |
## The sidebar
| Item | What's there |
| --- | --- |
| **My files** | Everything you own. Filter by **All**, **Documents** or **Artifacts**, and search by title or link. |
| **Shared with me** | Documents and files people added you to by email, and **Access requests** waiting for your answer. |
| **Recent** | What you opened lately, signed in. |
| **Analytics** | Views and engagement across everything you own. See [Analytics](https://docs.markdrop.in/analytics/overview). |
| **Workspaces** | Each workspace you belong to, with its folders. Drop files onto a folder to upload them there. **New workspace** creates one. |
| **VS Code** | Your [API tokens](https://docs.markdrop.in/account/api-tokens), which the VS Code extension and the CLI use. |
| **Connected apps** | AI assistants and other apps you've allowed in. See [Connected apps](https://docs.markdrop.in/account/connected-apps). |
| **Settings** | Your display name, theme and the weekly analytics email. |
Each row in **My files** has a menu with **Open**, **Copy link**, **Edit**, **Analytics**, **Share…**, **Change URL** and **Delete**, plus **Move to folder…** or **Add to a workspace…** where they apply.
## On a document
The bar above a document has:
- **Copy link**: copies the address. For an encrypted document, it includes the key.
- **Edit**: shown to the owner, to people who can edit it, and to whoever holds the edit secret in this tab. Everyone else finds **Edit** in **More actions**, where it asks for the edit secret.
- **Share**: shown to the owner, and to people allowed to share it on. See [Share with specific people](https://docs.markdrop.in/sharing/share-with-people).
- **Read full screen**: hides everything but the text. Press F; press Esc to come back.
- **More actions**: **View raw**, **Export PDF**, **Save a copy** (on other people's documents), **Report** and, on your own documents once you've connected Google, **Publish to Google Docs**.
**Details** lists when it was published, when it expires, its views and who can open it. On a wide screen it sits beside the text, with **Contents** built from the headings.
## On a file
A file page has **Copy link**, **Download**, **Read full screen**, and **More artifact actions**. The owner's menu has **Share**, **Settings** and **Delete artifact**. Someone allowed to edit sees **Replace file**, and readers see **Report**.
## Search and shortcuts
Press ⌘K (Ctrl K on Windows and Linux) anywhere in the app to search your files and jump to pages.
Single-key shortcuts like F for full screen are on by default. Turn them off under **Settings › Keyboard shortcuts**.
## Related
- [Do you need an account?](https://docs.markdrop.in/get-started/accounts)
- [Who can open what](https://docs.markdrop.in/sharing/who-can-open)
---
# Write and format documents
Source: https://docs.markdrop.in/documents/writing
A Markdrop document is Markdown. You write it at [markdrop.in/new](https://markdrop.in/new), paste it from an AI assistant, or publish it from VS Code, and readers get a formatted page. This page covers what the renderer supports and how the editor helps.
## What renders
Markdrop supports GitHub-flavoured Markdown:
- Headings, **bold**, _italic_, ~~strikethrough~~, links and images.
- Bulleted, numbered and task lists (`- [x] Done`).
- Tables, which scroll sideways on a phone instead of squashing.
- Footnotes and autolinked URLs.
- Fenced code blocks with syntax highlighting and a copy button.
Two small extras: an inline code span that holds only a colour, like `` `#3B82F6` ``, shows a swatch next to it. Image links ending in `#gh-dark-mode-only` or `#gh-light-mode-only` show only in that theme, as they do on GitHub.
## Math
Write inline math between single dollar signs and display math between double ones. Markdrop renders it with KaTeX.
```markdown
The loss is $L = -\sum_i y_i \log \hat{y}_i$.
$$
\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2}
$$
```
## Diagrams
A fenced block marked `mermaid` renders as a diagram instead of code:
````markdown
```mermaid
flowchart LR
Draft --> Review --> Publish
```
````
Flowcharts, sequence, class, state and entity-relationship diagrams, Gantt and pie charts, user journeys, Git graphs, mind maps, timelines and more all work. To see several at once, open the editor and choose **Try the diagrams sample**.
## What doesn't render
HTML tags inside a document aren't rendered: Markdrop drops them, so a document can't run scripts or change how the page around it looks. To publish an HTML page, upload it as a file. See [Publish an HTML page](https://docs.markdrop.in/files/html-pages).
## The editor
The editor at [markdrop.in/new](https://markdrop.in/new) has three views: **Write**, **Split** (text and preview side by side, scrolling together) and **Preview**. Markdrop remembers which one you used last.
The toolbar adds **Bold**, **Italic**, **Heading**, **Inline code**, **Code block**, **Link** and **List item** around your selection.
| Shortcut | What it does |
| --- | --- |
| ⌘ Enter / Ctrl Enter | Publish |
| ⌘ S / Ctrl S | Save, while editing a published document |
Under **More actions** you'll find **Export PDF**, **Load the diagrams sample** and **Clear editor**.
### Drafts
Until you publish, the editor keeps a draft on this device and restores it when you come back ("Restored your draft from this device"). Drafts are never kept while **Encrypt** is on.
### Size
A document can be up to 500,000 characters. The meter above the editor shows how many are left, and turns to a warning when fewer than 1,000 remain. With encryption on, it counts the encrypted size, which is larger than the text.
## Titles
The **Title** field is optional. Without one, Markdrop uses the document's first `#` heading, or else its first line, cut to 120 characters. The title shows in the browser tab, in **My files** and in link previews.
## Edit after publishing
Open the document and choose **Edit**. The editor opens with the same views and toolbar, plus **Settings** for the password and expiry. Choose **Save changes**, or press ⌘ S.
Readers who have the page open see the change appear, marked "Updated just now".
If you aren't signed in as the owner, you'll need the document's edit secret. See [Edit with your edit secret](https://docs.markdrop.in/documents/edit-key).
## What readers can do
Readers can copy a link to any heading with its **Copy link to section** button, read full screen, see the raw Markdown with **View raw**, and save a PDF with **Export PDF**. Signed-in readers can choose **Save a copy** to put their own copy in **My files**.
## More about documents
**[Choose a link](https://docs.markdrop.in/documents/addresses)**
Pick an address like markdrop.in/q3-plan, and change it later.
**[Edit with your edit secret](https://docs.markdrop.in/documents/edit-key)**
Edit or delete a document you published without an account.
**[Passwords and expiry](https://docs.markdrop.in/documents/passwords-and-expiry)**
Ask for a password, or have the link stop working on a date.
**[Link previews](https://docs.markdrop.in/documents/link-previews)**
What the card shows in Slack and other apps, and what it never shows.
**[Build a README](https://docs.markdrop.in/documents/readme-builder)**
Assemble a README from ready-made sections and publish it.
---
# Choose a link for your document
Source: https://docs.markdrop.in/documents/addresses
Every document and file has an address. By default Markdrop picks a short random one, like `markdrop.in/7Hk2mPq`. You can choose your own instead, such as `markdrop.in/q3-plan`, and change it later.
## Choose a link when you publish
In the editor, choose the **Link** chip (it says **Random link** until you set it) and type the part after `markdrop.in/`. Leave it blank for a short random link, made of 7 letters and digits that are hard to confuse (no `0`, `1`, `O`, `I` or `l`).
A link can be 3 to 50 characters long, using letters, numbers, hyphens (`-`) and underscores (`_`). Links are case-sensitive: `markdrop.in/Q3-Plan` and `markdrop.in/q3-plan` are different addresses.
When you upload a file, the link fills in from the file name. `Q3 Board Deck.pdf` becomes `q3-board-deck`: accents are folded (`résumé` → `resume`), spaces and punctuation become hyphens, and the result is cut to 40 characters. You can change it before you publish.
### When a link is taken
If someone else already uses the link, Markdrop says so and keeps everything you entered. Choose another one.
### Reserved links
Some words are the addresses of Markdrop's own pages, so no document can use them. They include `new`, `upload`, `share`, `login`, `dashboard`, `settings`, `builder`, `admin`, `api`, `mcp`, `privacy`, `shared`, `recent` and `requests`, in any letter case. If you pick one, Markdrop asks you to choose another.
## Change a link
If you're signed in and own the document:
### Open My files
Find the document in **My files**.
### Choose Change URL
Open the row's menu and choose **Change URL**.
### Type the new link
Type it after `markdrop.in/` and choose **Change URL**.
For a file, you can also open it and choose **More artifact actions › Settings**, then edit **Link** and choose **Save changes**.
Views and analytics stay with the document.
### What happens to the old link
The old address keeps pointing at the document for anyone who can open it: they're redirected to the new one. People who can't open the document get the same answer as before, and the old address never tells them the new one.
As long as the document exists, nobody else can take the old address. It stays attached to this document.
> **Note**
>
> Only the document's owner can change its link from the app. The API also accepts the document's edit secret. See [Edit with your edit secret](https://docs.markdrop.in/documents/edit-key).
## After you delete a document
When a document is deleted or expires, its link is held for 30 days. During that time only you, as its owner, can publish at that address again. A document published without an account has no owner, so its link is held for everyone.
## Links on your own domain
A document filed in a workspace that has its own domain also has an address there, made of the folder path and the link: `docs.acme.com/guides/setup`. The `markdrop.in/setup` address keeps working too. See [Put your workspace on your own domain](https://docs.markdrop.in/workspaces/custom-domain) and [Folders](https://docs.markdrop.in/workspaces/folders).
## Related
- [Publish your first document](https://docs.markdrop.in/get-started/first-document)
- [Passwords and expiry](https://docs.markdrop.in/documents/passwords-and-expiry)
---
# Edit with your edit secret
Source: https://docs.markdrop.in/documents/edit-key
When you publish a document, Markdrop gives you an **edit secret**: a code that starts with `sk_`. Whoever holds it can edit and delete the document, with or without an account. It's how documents published without signing in stay yours.
## Find your secret
Right after you publish, the **Published** panel shows the **Edit secret** with a **Copy secret** button. If you're signed in and own the document, it's folded away behind **Show edit secret**, because your account already lets you edit.
> **Warning: Keep a copy**
>
> The secret stays in the browser tab you published from, and nowhere else. Markdrop stores only a one-way hash of it, so nobody, Markdrop included, can show it to you again. If you lose it and you aren't signed in as the owner, you can't edit or delete the document.
A password manager or a private note is a good place for it. Treat it like a password: anyone who has it can change or delete the document.
## Edit with the secret
### Open the document
Go to its link, for example `markdrop.in/q3-plan`.
### Choose Edit
Choose **More actions › Edit**. Markdrop asks you to **Enter your edit secret**.
### Paste the secret
Paste the `sk_…` code and choose **Unlock editor**.
### Make your changes
Edit the text, then choose **Save changes**. **Settings** in the editor holds the password, expiry and **Delete document**.
## What the secret can and can't do
| With the edit secret you can | Only the signed-in owner can |
| --- | --- |
| Edit the title and text | Choose who can open it |
| Add, change or remove the password | Add people by email |
| Set or remove the expiry | Change the link from **My files** |
| Delete the document | Rotate the edit secret |
| Open it past its password | See its analytics |
## Save it to your account
Attach an anonymous document to your account and you won't need the secret again. It then shows in **My files** with its analytics, and you can choose who can open it.
In the tab you published from, sign in and choose **Save to my account** on the document. Markdrop uses the secret held in that tab to prove the document is yours.
If that tab is gone but you still have the secret, you can claim it through the API with your [API token](https://docs.markdrop.in/account/api-tokens):
```bash title="Claim a document"
curl -X POST https://api.markdrop.in/api/v1/documents/q3-plan/claim \
-H "Authorization: Bearer mdk_••••" \
-H "X-Edit-Secret: sk_••••"
```
A document that already belongs to another account can't be claimed.
## Use the secret from scripts
The API takes the secret in the `X-Edit-Secret` header. For example, to replace a document's text:
```bash title="Update a document"
curl -X PUT https://api.markdrop.in/api/v1/documents/q3-plan \
-H "Content-Type: application/json" \
-H "X-Edit-Secret: sk_••••" \
-d '{"title": "Q3 plan", "content": "# Q3 plan\n\nUpdated."}'
```
`DELETE` on the same address deletes the document, and `PATCH …/slug` with `{"new_slug": "q3-plan-final"}` changes its link.
## If the secret leaks
If you own the document, you can replace its secret. The old one stops working at once, and the new one is returned once:
```bash title="Rotate the edit secret"
curl -X POST https://api.markdrop.in/api/v1/documents/q3-plan/edit-secret/rotate \
-H "Authorization: Bearer mdk_••••"
```
Rotating needs the owner's account; the secret alone can't rotate itself. If an anonymous document's secret leaks, delete the document and publish it again.
## Related
- [Publish your first document](https://docs.markdrop.in/get-started/first-document)
- [Do you need an account?](https://docs.markdrop.in/get-started/accounts)
- [Passwords and expiry](https://docs.markdrop.in/documents/passwords-and-expiry)
---
# Passwords and expiry
Source: https://docs.markdrop.in/documents/passwords-and-expiry
A password makes readers type something before a link opens. An expiry makes the link stop working at a time you choose. Both work on documents and files, and you can set them when you publish or change them later.
## Add a password
When you publish, choose the **Password** chip (or the **Password** field on the upload page) and type it. Passwords can be up to 100 characters.
Readers then see "This document is password protected" and a password box before anything loads. The page title and link preview don't show the title or the text either. See [Link previews](https://docs.markdrop.in/documents/link-previews).
### Who the password applies to
The password is for strangers holding the link. People who already have access skip it:
- you, signed in as the owner;
- anyone holding the [edit secret](https://docs.markdrop.in/documents/edit-key);
- people you added by email, and your workspace team when the document is open to them.
A password only matters when **Who can open it** is **Anyone with the link**. A document set to **Only people added** or to a workspace team stays closed to strangers whatever the password. See [Who can open what](https://docs.markdrop.in/sharing/who-can-open).
### Wrong guesses
After 10 wrong passwords in a minute, or 50 in an hour, from the same IP address, Markdrop stops checking for a while and says "Too many wrong passwords. Try again in a few minutes."
## Set an expiry
When you publish, choose the **Expires** chip and pick **In 1 day**, **In 7 days**, **In 30 days** or **On a date…** to choose an exact day and time. The hint underneath says when that is, in your time zone.
When the time comes, the link stops working for everyone and Markdrop deletes the document. For a file, that includes the file and its old versions. An expiry has to be at least a minute in the future.
> **Danger: Expiry deletes**
>
> An expired document is gone, not hidden. You can't extend it afterwards or get it back. If you might need it later, keep your own copy.
## Change them later
You need to be the owner, or hold the edit secret. The share dialog and file settings are for the signed-in owner; with only the edit secret, use the document editor.
**Share dialog**
Choose **Share** on the document, then **More options: password and expiry**.
- Type a new **Password** and choose **Save options**. Anyone without access now needs the new password.
- Choose **Remove password** to open it to anyone with the link again.
- Pick a new **Expires** value: **Never**, **In 1 hour**, **In 1 day**, **In 7 days**, **In 30 days** or **On a date…**, then **Save options**.
**Document editor**
Choose **Edit**, then **Settings**. Under **Password**, choose **Add a password** or **Remove password**. Under **Expiry**, choose **Remove expiry**, a new period, or **On a date…**. Settings are saved when you choose **Save changes**.
**File settings**
Open the file and choose **More artifact actions › Settings**. Change **Password** (or **Remove the password**) and **Expires**, then choose **Save changes**.
Changing the password takes effect at once. Readers who opened it with the old password are asked again, and links to a protected file's bytes that were already handed out stop working.
## Open a protected document from code
Send the password in the `X-Read-Password` header:
```bash title="Read a password-protected document"
curl https://api.markdrop.in/api/v1/documents/q3-plan \
-H "X-Read-Password: correct horse battery staple"
```
## Related
- [Who can open what](https://docs.markdrop.in/sharing/who-can-open)
- [End-to-end encryption](https://docs.markdrop.in/sharing/end-to-end-encryption)
- [Edit with your edit secret](https://docs.markdrop.in/documents/edit-key)
---
# Link previews
Source: https://docs.markdrop.in/documents/link-previews
When you paste a Markdrop link into Slack, Teams, WhatsApp, LinkedIn, X or iMessage, the app shows a preview card. Markdrop draws that card for each document, and it shows only what a stranger opening the link could already see.
## What the card shows
For a document anyone with the link can open, the card has:
- the title, or the document's opening heading, or else its address;
- the first lines of the text, as plain text;
- a reading time, like "4 min read";
- the address, like `markdrop.in/q3-plan`.
For a file, the card shows the title (or the file name), its type and size. A video that anyone with the link can open also shows its poster frame.
The page's own title and description, which some apps show instead of the card, follow the same rules.
## What it never shows
Anything a stranger couldn't open gets a card with no content: no title, no text, nothing from the document except the address the link already shows. The card says only what kind of lock it has, such as "Private document" or "Password-protected document". That covers documents that are:
- set to **Only people added** or to a workspace team;
- protected by a password;
- in a workspace where visitors must sign in;
- end-to-end encrypted.
An encrypted document's title and text are ciphertext to Markdrop, so its card reads "Encrypted document" and nothing more. A link to a document that doesn't exist, or has expired, gets a plain Markdrop card that doesn't say whether it ever existed.
> **Note: Previews already posted**
>
> Chat apps store a card when the link is first posted. If you add a password or make a document private afterwards, Markdrop asks for a fresh card on future pastes, but a card already sitting in a conversation is kept by that app. Markdrop can't recall it.
## Cards on your own domain
A document filed in a workspace wears that workspace's brand on its card, its site name and accent colour, wherever the link is pasted. A workspace can also hide the Markdrop name from its cards. See [Brand your workspace](https://docs.markdrop.in/workspaces/branding).
For a document strangers can't open, the workspace's branding shows only when the link is on the workspace's own domain, like `docs.acme.com/…`. On a `markdrop.in` link, even the workspace's name would be more than a stranger could learn, so it's left off.
## Previews count as shares
Each time an app fetches a card, Markdrop notes which app asked: Slack, LinkedIn, WhatsApp, Facebook, Telegram, X, Discord, Teams, Google Chat, or another. It counts as a share signal in your analytics, never as a view, and repeat fetches from the same app within 10 minutes count once. See [What gets counted](https://docs.markdrop.in/analytics/what-gets-counted).
## Change what the card says
Edit the document's title, or its opening heading and first lines. Apps refetch cards on their own schedule, so a new paste can take a few minutes to show the change, and old pastes keep their old card.
## Related
- [Who can open what](https://docs.markdrop.in/sharing/who-can-open)
- [End-to-end encryption](https://docs.markdrop.in/sharing/end-to-end-encryption)
- [Passwords and expiry](https://docs.markdrop.in/documents/passwords-and-expiry)
---
# Build a README
Source: https://docs.markdrop.in/documents/readme-builder
The README builder at [markdrop.in/builder](https://markdrop.in/builder) puts a README together from ready-made sections: a title, badges, installation steps, a feature list, a license. You fill in the blanks, drag the sections into order, and watch the result render. Then you copy the Markdown, download `README.md`, or publish it as a Markdrop link. No account needed.
## Build it
### Add sections
Under **Add a section**, choose any section to add it to **Your README**. Search with **Search sections**, or filter by group:
| Group | Includes |
| --- | --- |
| Project basics | Title and Description, Badges, Logo, Table of Contents, Demo, Screenshots, Features, Tech Stack |
| Documentation | Installation, Run Locally, Usage / Examples, Environment Variables, Deployment, API Reference, Running Tests, Documentation |
| Community | Contributing, License, Authors, Acknowledgements, Support, Feedback, FAQ, Used By, Related, Roadmap |
| Diagrams | Flowchart, Sequence Diagram, Gantt Chart, Pie Chart, Bar / Line Chart, Class Diagram, State Diagram, Entity Relationship, Git Graph, Mindmap, User Journey |
| Math & data | Math (KaTeX), Table, Color Reference |
| GitHub profile | Profile Intro, About Me, Skills, Profile Links, Other (working on…) |
| More | Optimizations, Lessons Learned, Appendix, Custom section |
### Fill them in
Select a section in **Your README** to edit its Markdown. The editor has the same formatting toolbar as [markdrop.in/new](https://markdrop.in/new). Type a title at the top, in **README title (optional)**.
### Put them in order
Drag a section by its handle. With a keyboard, press Space or Enter on the handle, move it with the arrow keys, and press Space or Enter again to drop it. Each section's menu also has **Move up**, **Move down**, **Duplicate** and **Remove**.
### Check the preview
The preview shows the **Full README** or **This section**, rendered the way readers will see it, diagrams and math included.
The **Table of Contents** section can rebuild itself from your other sections' headings, so it stays in step as you add and reorder.
On a phone, the builder switches between **Sections**, **Edit** and **Preview** tabs.
## Take it with you
Open **Builder actions** (the `⋯` button):
- **Copy markdown** copies the whole README.
- **Download README.md** saves it as a file, ready to commit.
- **Reset builder…** clears everything and starts over.
Your work is saved in this browser as you go, so you can close the tab and come back to it.
> **Note**
>
> Diagram and math sections render on Markdrop. GitHub renders Mermaid and math too, but other sites that show READMEs may show them as code.
## Publish it as a link
Choose **Publish…**. The **Publish README** dialog has the same settings as any document: **Link**, **Who can open it**, **Encrypt in this browser**, **Password** and **Expires**, plus **Location** if you're in a workspace. Choose **Publish** and the README opens at its new link.
From then on it's an ordinary Markdrop document: edit it, share it, or check who read it. Later changes in the builder don't reach the published copy, and publishing again makes a new link. To update the same link, open the document and choose **Edit**.
## Related
- [Write and format documents](https://docs.markdrop.in/documents/writing)
- [Publish your first document](https://docs.markdrop.in/get-started/first-document)
- [Choose a link for your document](https://docs.markdrop.in/documents/addresses)
---
# Supported file types
Source: https://docs.markdrop.in/files/supported-types
Each file you upload opens in a viewer made for its type, so readers see the content rather than a download prompt. This page lists what Markdrop accepts and what readers get for each.
## The full list
| File | Extensions | Readers see | Size limit |
| --- | --- | --- | --- |
| HTML page | `.html`, `.htm` | The page itself, running live | 25 MB |
| PDF | `.pdf` | Pages with zoom and page count | 25 MB |
| Spreadsheet | `.xlsx`, `.xls`, `.csv` | A grid with row numbers, column letters and a tab per sheet | 25 MB |
| Word document | `.docx` | The document's text, headings, lists and tables | 25 MB |
| Image | `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.svg` | The full-size image | 25 MB |
| Text | `.txt`, `.json` | Text with line numbers and a **Wrap lines** toggle | 25 MB |
| Website | `.zip` | A multi-page site, opening at its `index.html` | 25 MB zipped |
| Video | `.mp4`, `.mov`, `.webm`, `.ogv` | A player with speed control and keyboard shortcuts | 2 GB |
| Audio | `.mp3`, `.m4a`, `.wav`, `.ogg` | The browser's audio player | 2 GB |
Markdrop checks the type when you upload. A file that isn't on this list is refused, with the list of accepted extensions.
Readers can always choose **Download** to get the original file, byte for byte.
## How each type renders
### HTML pages
An HTML file is served as the page, so its scripts, styles and fonts work. It runs inside a sandboxed frame on a separate domain, so it can't read anything from Markdrop or the reader's account. To publish HTML you have as text rather than a file, see [Publish an HTML page](https://docs.markdrop.in/files/html-pages).
### PDFs
PDFs open in a built-in reader with page-by-page scrolling and zoom. It shows the first 50 pages; for a longer PDF it says "Showing the first 50 of N pages. Download to see the rest."
### Spreadsheets
Excel workbooks open with a tab for each sheet; use the arrow keys to move between tabs. CSV files open as a single sheet. Markdrop shows the values in each cell; charts and pictures in a workbook aren't drawn.
### Word documents
A `.docx` is converted to clean web text: headings, paragraphs, lists, tables, links and images. The structure comes through; exact page layout, fonts and spacing don't. If the layout matters, export a PDF and upload that instead.
### Images
Images open at full size. An SVG can contain scripts, so it gets the same sandbox on a separate domain as an HTML page.
### Text and JSON
Text opens with line numbers. Very large files are cut at 2,000,000 characters or 100,000 lines, and the viewer says so; **Download** still gives the whole file.
### Video and audio
Video plays in Markdrop's player, which streams and seeks without downloading the whole file first. Audio uses the browser's own player. See [Video and audio](https://docs.markdrop.in/files/video-and-audio).
### Zipped websites
A `.zip` of a built site is unpacked and served as a site, so links between its pages work. See [Publish a zipped website](https://docs.markdrop.in/files/websites).
## Not supported
PowerPoint (`.pptx`) and other formats not in the table aren't accepted. Files can't be end-to-end encrypted; only documents can.
Markdown isn't uploaded as a file. Paste it into [a document](https://docs.markdrop.in/get-started/first-document) instead, where it renders with headings, diagrams and math. In a workspace, a `.md` file you drop onto a folder becomes a document there.
## More about files
**[Size and storage limits](https://docs.markdrop.in/files/limits)**
25 MB per file, 2 GB for video and audio, 20 GB per account.
**[Upload large files](https://docs.markdrop.in/files/large-uploads)**
Resumable, verified uploads from the terminal.
**[Publish an HTML page](https://docs.markdrop.in/files/html-pages)**
Turn HTML from an AI assistant into a live link.
**[Publish a zipped website](https://docs.markdrop.in/files/websites)**
Serve a whole static site at one link.
**[Replace a file](https://docs.markdrop.in/files/replace-and-versions)**
Swap the file behind a link and restore earlier versions.
**[Video and audio](https://docs.markdrop.in/files/video-and-audio)**
The player, shortcuts, and what to do when a video won't play.
---
# File size and storage limits
Source: https://docs.markdrop.in/files/limits
Markdrop limits how big one file can be and how much each account stores in total. The upload page shows both: the per-file limits above the drop area, and a **Storage** meter with how much you've used.
## Per file
| What | Limit |
| --- | --- |
| Most files: HTML, PDF, spreadsheets, Word, images, text | 25 MB |
| Video and audio | 2 GB |
| A zipped website | 25 MB zipped; see below |
| HTML published by an AI assistant (MCP `publish_page`) | 25 MB |
| HTML sent as text to the API | 10 MB |
Markdrop checks a file's size before uploading, and again once it has arrived. If it's too big, you see "That file is 31.2 MB — the limit is 25 MB." and nothing is stored.
### Zipped websites
A website in a `.zip` has extra limits, checked when Markdrop unpacks it:
| What | Limit |
| --- | --- |
| The `.zip` itself | 25 MB |
| Files inside | 500 |
| Any one file inside | 25 MB |
| Everything inside, unpacked | 100 MB |
See [Publish a zipped website](https://docs.markdrop.in/files/websites).
## Per account
Each account can store **20 GB** of files. That counts every file you own, plus the earlier versions Markdrop keeps when you replace one. Documents don't count against it.
When an upload would go over:
- If you're replacing a file, Markdrop first drops that file's oldest kept versions to make room.
- If that's not enough, or it's a new upload, the upload is refused with "Storage quota exceeded" and how much you're using.
To free space, delete files you no longer need from **My files**. Replacing a file with a smaller one doesn't free space while older versions are kept. See [Replace a file and restore versions](https://docs.markdrop.in/files/replace-and-versions).
When someone you've given edit access replaces one of your files, the new file counts against your storage, not theirs. If it would only fit by dropping your old versions, their upload is refused instead.
## What viewers show
Some viewers show part of a very large file, and readers can choose **Download** for the rest:
| Viewer | Shows |
| --- | --- |
| PDF | The first 50 pages |
| Text and JSON | Up to 2,000,000 characters or 100,000 lines |
## Uploading in a hurry
If you upload a lot in a short time, Markdrop asks you to wait: "Too many uploads in a short time". Wait a minute and publish again; your file and settings stay on the page.
## Related
- [Upload large files](https://docs.markdrop.in/files/large-uploads)
- [Supported file types](https://docs.markdrop.in/files/supported-types)
- [Account limits](https://docs.markdrop.in/account/limits)
---
# Upload large files
Source: https://docs.markdrop.in/files/large-uploads
Screen recordings and long audio are big. Markdrop accepts video and audio up to 2 GB, and there are two ways to get them up: the browser, for a one-off, and the `markdrop` CLI, for anything you'd hate to start over.
## From the browser
Upload at [markdrop.in/upload](https://markdrop.in/upload) as with any file. The file goes straight from your browser to storage, with a progress bar that says how much has gone, for example "412 MB of 1.6 GB. Keep this tab open until it finishes."
A browser upload is one continuous transfer. If the connection drops or you close the tab, it stops and you start again. For a large file on a shaky connection, use the CLI.
## From the terminal
The CLI uploads in parts, several at a time, and picks up where it stopped if anything goes wrong.
### Install the CLI and sign in
See [Install the CLI](https://docs.markdrop.in/cli/install). Then save an API token, which you create at [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens):
```bash
markdrop login
```
### Upload the file
```bash
markdrop upload demo.mp4
```
The CLI prints the link when it's done. It shows progress on the way.
### If it stops, run it again
If your laptop sleeps or the network drops, run the same command. The upload resumes from the parts that already arrived.
### What happens during a CLI upload
1. The CLI hashes the file with SHA-256.
2. It sends the file in parts, 4 at a time, retrying any part that fails.
3. Markdrop puts the parts together and checks the size and the SHA-256 against what the CLI declared. If they don't match, nothing is published.
4. The file goes live at its link.
Checking a 2 GB file takes a little while after the last part lands. The CLI waits for it.
An interrupted upload can be resumed for 24 hours. After that it expires and the parts are discarded; run the command again to start fresh.
### Useful options
```bash
markdrop upload demo.mp4 --slug q3-demo # markdrop.in/q3-demo
markdrop upload demo.mp4 --title "Q3 demo" --access private
markdrop upload demo.mp4 --workspace Acme --folder guides/setup
url=$(markdrop upload demo.mp4) # stdout is only the link
```
See [Upload and replace from the CLI](https://docs.markdrop.in/cli/upload-and-replace) for every flag.
## From an AI assistant
An assistant connected through MCP can upload large files for you. The local server (`markdrop mcp`) uploads straight from your disk, and the hosted server can hand an assistant that has a shell the same part-by-part upload the CLI uses. See [AI and MCP](https://docs.markdrop.in/ai/overview).
## Zipped websites go through the browser
The CLI and the upload API don't take `.zip` sites: upload those at [markdrop.in/upload](https://markdrop.in/upload). See [Publish a zipped website](https://docs.markdrop.in/files/websites).
## Related
- [File size and storage limits](https://docs.markdrop.in/files/limits)
- [Replace a file and restore versions](https://docs.markdrop.in/files/replace-and-versions)
- [Video and audio](https://docs.markdrop.in/files/video-and-audio)
---
# Publish an HTML page
Source: https://docs.markdrop.in/files/html-pages
AI assistants are good at writing whole web pages: a dashboard, a slide deck, a one-page report with charts. Markdrop turns that HTML into a link that opens the page, running, in anyone's browser.
There are three ways to publish HTML you have as text. All of them need a Markdrop account.
## Save it as a file and upload it
This works for any page, from any source.
### Save the HTML
Paste it into a text editor and save it with an `.html` extension, for example `q3-dashboard.html`.
### Upload it
Go to [markdrop.in/upload](https://markdrop.in/upload) and drop the file in. The **Preview** shows the page running, in your browser, before anything uploads.
### Publish
Check the **Title** and **Link** (both come from the file name), then choose **Publish**.
The page can be up to 25 MB. A page with images or fonts inlined as data can get large; if it's over, move the big assets out and publish the page and its assets as a [zipped website](https://docs.markdrop.in/files/websites).
## Ask your AI assistant
If your assistant is connected to Markdrop through MCP, ask it to publish the page. It uses the `publish_page` tool, which takes the page as text and returns the link. Pages can be up to 25 MB this way too.
> Publish this page to Markdrop as q3-dashboard and give me the link.
`publish_page` also takes `.txt`, `.csv`, `.json` and `.svg` content. See [Connect Claude to Markdrop](https://docs.markdrop.in/ai/hosted-mcp) and [MCP tools](https://docs.markdrop.in/ai/tools).
## Send it to the API
Scripts can post the HTML directly, signed in with an [API token](https://docs.markdrop.in/account/api-tokens). This route takes pages up to 10 MB:
```bash title="Publish HTML from a script"
curl -X POST https://api.markdrop.in/api/v1/artifacts/paste \
-H "Authorization: Bearer mdk_••••" \
-H "Content-Type: application/json" \
-d '{"title": "Q3 dashboard", "custom_slug": "q3-dashboard", "content": "
Q3
"}'
```
The response includes the `url` and an `edit_secret`. You can also set `read_password`, `expires_in` and `access_level` (`link` or `private`).
## Why not paste it into a document?
A Markdrop document is Markdown, and Markdrop drops HTML tags from documents, so pasted HTML wouldn't run there. A page published as a file runs as a page.
## What the page can do
The page runs with its scripts, styles and web fonts, and can load scripts and images from other sites, such as a charting library from a CDN. Forms, pop-ups and downloads work.
It runs inside a sandbox on a separate domain, never on markdrop.in. It can't read Markdrop's cookies or storage, or the reader's Markdrop session. See [How files are served](https://docs.markdrop.in/security/how-files-are-served).
## Update the page
To change it, open the page and choose **More artifact actions › Settings**, choose the **File** tile, pick the new `.html` and choose **Save changes**. The link stays the same and readers with the page open get the new version. Your assistant can also update it in place through MCP. See [Replace a file and restore versions](https://docs.markdrop.in/files/replace-and-versions).
## Related
- [Supported file types](https://docs.markdrop.in/files/supported-types)
- [Publish a zipped website](https://docs.markdrop.in/files/websites)
---
# Publish a zipped website
Source: https://docs.markdrop.in/files/websites
Some pages don't fit in one file: a built docs site, a notebook export, a chart with its own CSS and JavaScript. Zip the folder and upload it, and Markdrop serves the whole thing as a site. It opens at its `index.html`, and links between its pages work.
## Publish it
### Zip the folder
Zip the folder that holds your `index.html` and its files. On a Mac, right-click the folder and choose **Compress**. On Windows, right-click and choose **Send to › Compressed (zipped) folder**. From a terminal:
```bash
cd dist && zip -r ../site.zip .
```
### Upload the .zip
Go to [markdrop.in/upload](https://markdrop.in/upload) and drop `site.zip` in. Markdrop says it will render as "a multi-page site".
### Publish
Choose a **Title** and **Link**, then **Publish**. Markdrop unpacks the site ("Unpacking site…") and opens it at its new link.
## Which page opens first
Markdrop opens `index.html` at the top of the zip. If there isn't one, it opens the `.html` file closest to the top.
If everything in the zip sits inside one folder, like `my-site/index.html`, Markdrop treats that folder as the top. Zipping a folder or zipping its contents gives the same result.
## How links and assets work
Every file keeps its path from the zip, side by side under the site's address. Relative links work as they do on your computer:
```html
Setup
```
Two things to check before you zip:
- **Use relative paths.** A path starting with `/`, like `/css/site.css`, points at the root of the domain, not your site, and won't find the file.
- **Link to files, not folders.** Markdrop serves the exact file you name, so link to `guides/index.html`, not `guides/`.
## What's included
Markdrop keeps the files a web page uses:
| Kind | Extensions |
| --- | --- |
| Pages | `.html`, `.htm` |
| Styles and scripts | `.css`, `.js`, `.mjs`, `.map` |
| Data | `.json`, `.csv`, `.txt`, `.md`, `.webmanifest` |
| Images | `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.avif`, `.svg`, `.ico` |
| Fonts | `.woff`, `.woff2`, `.ttf`, `.otf` |
| Documents | `.pdf` |
Other files in the zip, including video, are skipped. So are macOS `__MACOSX` folders and hidden files.
## Limits
| What | Limit |
| --- | --- |
| The `.zip` itself | 25 MB |
| Files inside | 500 |
| Any one file inside | 25 MB |
| Everything inside, unpacked | 100 MB |
If a zip breaks a rule, Markdrop says which, for example "Archive has too many files (limit 500)." or "No .html file found — a bundle needs an index.html."
## Update a site
Open the site, choose **More artifact actions › Settings**, pick the new `.zip` under **File**, and choose **Save changes**. The link stays the same and the old files are removed.
A few things work differently from single files:
- Only the owner can replace a zipped site.
- Zipped sites don't keep earlier versions, so you can't restore an old one.
- Upload and replace zips in the browser. The CLI and the upload API don't take them.
## Related
- [Publish an HTML page](https://docs.markdrop.in/files/html-pages)
- [Supported file types](https://docs.markdrop.in/files/supported-types)
- [How files are served](https://docs.markdrop.in/security/how-files-are-served)
---
# Replace a file and restore versions
Source: https://docs.markdrop.in/files/replace-and-versions
You shared `markdrop.in/q3-deck` and then found a typo on slide 4. Replace the file and the link stays the same: everyone who has it sees the new version, and people with the page open get it without reloading. The earlier file is kept, so you can put it back.
## What stays and what changes
When you replace a file, these stay the same:
- the link, including any address on your own domain;
- who can open it, the password and the expiry;
- the title (unless you change it);
- views and analytics. Analytics can compare versions.
The new file can be a different size, and even a different type: a PDF can replace an HTML page.
## Replace it in the browser
### Open the file
Go to its link while signed in.
### Open Settings
Choose **More artifact actions › Settings**. If you can edit the file but don't own it, choose **Replace file** instead.
### Choose the new file
Under **File**, choose the current file and pick the new one. It says "Replaces the current file when you save". Choose **Keep the current file** to change your mind.
### Save
Choose **Save changes** (or **Replace file**). The upload progress shows in the drawer, then the new version goes live.
## Replace it from the terminal
```bash
markdrop replace q3-deck q3-deck-v2.pdf
```
You can pass the link or the full URL. The CLI uploads in parts and resumes if interrupted, like `markdrop upload`. Add `--title "Q3 deck (v2)"` to change the title too. See [Upload and replace from the CLI](https://docs.markdrop.in/cli/upload-and-replace).
## Who can replace a file
- The owner.
- People you added with **Can edit**.
- Members of the file's workspace who can edit, unless the file is set to **Only people added**.
Only the owner changes the title, link, password and expiry, or deletes the file. A replacement counts against the owner's storage.
## Versions
Markdrop keeps the last **5 versions** of each file, the current one included. Each replace adds a version; when there are more than 5, the oldest goes.
Old versions count against the owner's 20 GB of storage. If a replacement wouldn't fit, Markdrop drops that file's oldest versions first. See [File size and storage limits](https://docs.markdrop.in/files/limits).
Old versions are never public. Only the current file is served at the link.
### See and restore versions
The web app doesn't list versions. Use the CLI, or ask an AI assistant connected through MCP.
```bash
markdrop versions q3-deck
```
```text
VERSION SIZE TYPE SHA256 CREATED
* 3 2.1 MB application/pdf 9f2c4e1a7b3d 2026-10-29 14:02 UTC
2 2.0 MB application/pdf 41be07c2d9aa 2026-10-28 09:15 UTC
1 1.9 MB application/pdf c03f5a88e612 2026-10-27 18:40 UTC
```
The `*` marks the version that's live. To bring back version 2:
```bash
markdrop restore q3-deck 2
```
A restore doesn't delete anything: it adds a new version that's a copy of version 2. So version 2 becomes version 4, and you can undo the restore by restoring version 3.
Seeing and restoring versions is for the owner and people who can edit the file. With MCP, the tools are `list_file_versions` and `restore_file_version`. See [MCP tools](https://docs.markdrop.in/ai/tools).
## Zipped websites
A [zipped website](https://docs.markdrop.in/files/websites) has no versions. Only its owner can replace it, in the browser, and replacing it removes the old site.
## Related
- [Upload large files](https://docs.markdrop.in/files/large-uploads)
- [Share with specific people](https://docs.markdrop.in/sharing/share-with-people)
- [Video and audio](https://docs.markdrop.in/files/video-and-audio)
---
# Video and audio
Source: https://docs.markdrop.in/files/video-and-audio
Send a link instead of a 900 MB attachment. Upload a screen recording or a voice memo and readers press play in their browser: it streams, seeks instantly, and never asks them to download the whole file.
## What you can upload
| Kind | Extensions | Size limit |
| --- | --- | --- |
| Video | `.mp4`, `.mov`, `.webm`, `.ogv` | 2 GB |
| Audio | `.mp3`, `.m4a`, `.wav`, `.ogg` | 2 GB |
Upload at [markdrop.in/upload](https://markdrop.in/upload), or with `markdrop upload demo.mp4` from the terminal, which resumes if the connection drops. See [Upload large files](https://docs.markdrop.in/files/large-uploads).
Markdrop doesn't convert your file. Readers get exactly the bytes you uploaded, so what plays depends on how the file was encoded. **H.264 MP4** plays in every current browser; it's the safe choice.
## The video player
Markdrop's player looks and works the same in every browser. It has play and pause, a scrubber that shows what has loaded, back and forward 10 seconds, volume, playback speed from 0.25× to 2×, picture in picture where the browser supports it, and full screen.
### Keyboard shortcuts
Click the video first, then:
| Key | Does |
| --- | --- |
| Space or K | Play or pause |
| ← / → | Back or forward 5 seconds |
| J / L | Back or forward 10 seconds |
| ↑ / ↓ | Volume up or down |
| M | Mute |
| F | Full screen |
| , / . | Back or forward one frame, while paused |
| 0 to 9 | Jump to 0% to 90% of the way through |
## Poster frames and details
After an upload, Markdrop reads the video's length, resolution and codecs, and takes a poster frame for the player and the link preview. If it can't read the file, the video still publishes and plays, without a poster.
`markdrop info demo-clip` shows what Markdrop found:
```text
https://markdrop.in/demo-clip
slug demo-clip
type video/mp4
size 412.6 MB
version 1
media 2560×1440 · 4m12.3s · h264/aac
```
(Trimmed: it also prints the SHA-256, dates and the poster's address.)
## Audio
Audio plays in the browser's own audio player, centred on the page. It streams and seeks the same way video does.
## When a video won't play
The player tells you which of three things went wrong:
| Message | What it means | What to do |
| --- | --- | --- |
| "This video can't be played here." | The browser can't decode the codec inside the file. This is common with `.mov` recordings. | **Download** it to play locally, or re-encode it as H.264 MP4 and [replace the file](https://docs.markdrop.in/files/replace-and-versions). |
| "You don't have access to this video." | The video is protected and the reader isn't signed in with an address it was shared with. | Open it from its Markdrop page while signed in. See [Who can open what](https://docs.markdrop.in/sharing/who-can-open). |
| "This video is no longer available." | The file was deleted or has expired. | Ask whoever shared it for a new link. |
## Private videos
A video set to **Only people added**, a workspace team, or a password plays through a signed link that lasts 4 hours, long enough to watch a long recording without interruption. If you narrow who can open it, by making it private or adding a password, links already handed out stop working straight away.
## See who watched
Signed in, the file's analytics show how far people watched, second by second, which parts they rewatched or skipped, and whether any plays failed. See [Video and audio analytics](https://docs.markdrop.in/analytics/video-and-audio).
## Related
- [Upload large files](https://docs.markdrop.in/files/large-uploads)
- [Replace a file and restore versions](https://docs.markdrop.in/files/replace-and-versions)
- [Link previews](https://docs.markdrop.in/documents/link-previews)
---
# Who can open what
Source: https://docs.markdrop.in/sharing/who-can-open
Every document and file has one setting that decides who can open it: **Who can open it**. It has three choices. Passwords, encryption and named people work on top of that setting, and this page explains how they fit together.
## The three choices
| Choice | Who can open it | Sign-in |
| --- | --- | --- |
| **Anyone with the link** | Anyone who has the address. | Not needed. |
| **{Team} team**, for example **Acme team** | Everyone in the workspace it's filed in, plus people you add by email. Nobody else, link or not. | Needed. |
| **Only people added** | You and the people you add by email. | Needed. |
**Anyone with the link** is the default. Everything published without an account uses it, and it's how Markdrop links have always worked.
The team choice is named after your workspace, so a workspace called Acme shows **Acme team**. It's only available once the document is in a workspace.
People you add by email always get in, whatever the choice, as long as they sign in with that address. See [Share with specific people](https://docs.markdrop.in/sharing/share-with-people).
## Change it
You need to be the document's owner, signed in.
### Open Share
On the document, choose **Share**. From **My files**, choose **Share…** in the row's menu. On a file, choose **More artifact actions › Share**.
### Pick a choice
Under **Who can open it**, choose one of the three cards. It saves straight away: "Who can open it is updated."
The dialog lists every address the setting covers, for example `docs.acme.com/guides/setup` and `markdrop.in/setup`. One setting covers them all.
Narrowing takes effect at once. Make a document private and people who aren't on the list can't open it on their next visit, and links to a protected file's bytes that were already handed out stop working.
## What readers see when they can't open it
| They see | Because |
| --- | --- |
| "This document is private" and **Sign in to open** | They aren't signed in, and the document isn't open to anyone with the link. |
| "You don't have access yet" and **Request access** | They're signed in, but not with an address that has access. They can ask you. See [Request access](https://docs.markdrop.in/sharing/request-access). |
| "This document is password protected" | It's open to anyone with the link, behind a password. |
## How the extras fit
### Passwords
A password only applies to **Anyone with the link**, and only to strangers. You, people you added, your workspace team and anyone holding the edit secret open it without the password. See [Passwords and expiry](https://docs.markdrop.in/documents/passwords-and-expiry).
### Encryption
An end-to-end encrypted document needs the full link, with its `#k=` key, on top of everything else. Adding someone by email lets them fetch it; it doesn't give them the key. See [End-to-end encryption](https://docs.markdrop.in/sharing/end-to-end-encryption).
### Workspaces that require sign-in
A workspace admin can turn on **Visitors must sign in to open anything**. Then nothing in that workspace is open to strangers, even items set to **Anyone with the link**: only members and people added by email get in. See [Workspace privacy](https://docs.markdrop.in/workspaces/privacy).
### Filing doesn't change access
Putting a document in a workspace folder doesn't change who can open it. A document set to **Only people added** stays that way inside a workspace; even workspace admins can't open it unless you add them.
## What each person can do
| Person | Read | Edit the text or replace the file | Password, expiry, link, delete | Who can open it, add people |
| --- | --- | --- | --- | --- |
| Owner | Yes | Yes | Yes | Yes |
| Edit secret holder | Yes | Yes | Yes | No |
| Added with **Can edit** | Yes | Yes | No | Add others, if allowed |
| Added with **Can view** | Yes | No | No | Add others, if allowed |
| Workspace member, team choice or link | Yes | Yes, except **Can view** members | No | Add others, if allowed |
| Anyone else, link choice | Yes | No | No | No |
"If allowed" means you turned on **Let people I share with add others**. See [Share with specific people](https://docs.markdrop.in/sharing/share-with-people#let-people-share-it-on).
## More about sharing
**[Share with specific people](https://docs.markdrop.in/sharing/share-with-people)**
Add people by email with Can view or Can edit.
**[Request access](https://docs.markdrop.in/sharing/request-access)**
Ask an owner for access, or answer someone who asked you.
**[End-to-end encryption](https://docs.markdrop.in/sharing/end-to-end-encryption)**
Encrypt a document so Markdrop stores only ciphertext.
**[Workspace privacy](https://docs.markdrop.in/workspaces/privacy)**
Set defaults for a whole team, or require sign-in for everything.
---
# Share with specific people
Source: https://docs.markdrop.in/sharing/share-with-people
Add people by email when a document or file is for them and not for everyone with the link. They sign in with that address and it opens. You can give each person **Can view** or **Can edit**, and take it back whenever you like.
## Add people
### Open Share
On the document, choose **Share**. From **My files**, choose **Share…** in the row's menu. On a file, choose **More artifact actions › Share**.
### Type their addresses
Under **People with access**, type an address in **Add people by email** and press Enter. Add as many as you like. Suggestions come from people you've shared with before and from your workspaces, never from a search of other Markdrop users.
### Choose their access
Pick **Can view** or **Can edit** for the people you're adding.
### Decide whether to email them
**Email them** is on by default. Choose **Add a message** to include a note of up to 500 characters, like "Here's the Q3 plan for Thursday." Turn **Email them** off to add people quietly.
### Choose Share
Markdrop confirms what happened, for example "Shared with alice@acme.com and emailed them."
If the email couldn't be sent, they still have access, and the toast says "the email didn't send". Send them the link yourself.
> **Tip: Make it only for them**
>
> Adding people doesn't close the link to everyone else. If the document is set to **Anyone with the link**, switch **Who can open it** to **Only people added** in the same dialog. See [Who can open what](https://docs.markdrop.in/sharing/who-can-open).
## What they need to do
Nothing up front. They don't need an account before you share. When they open the link, Markdrop asks them to sign in, and they sign in with the address you typed: with Google, or with a code emailed to that address. Their first sign-in creates their account.
Everything shared with them appears under **Shared with me** in the sidebar.
They must sign in with exactly that address. If you added `alice@acme.com` and she signs in as `alice@gmail.com`, she sees "You don't have access yet" and can [request access](https://docs.markdrop.in/sharing/request-access).
## What Can view and Can edit allow
| | Can view | Can edit |
| --- | --- | --- |
| Open and read it | Yes | Yes |
| Edit the text, or replace the file | No | Yes |
| Change the password, expiry or link, or delete it | No | No |
| Change who can open it | No | No |
When an editor opens the editor, Markdrop tells them: "You can edit the text. Its password, expiry, link and who can open it stay with its owner."
## The list of people
**People with access** shows:
- you, as **Owner**;
- **Everyone in {workspace}**, when the document is open to its workspace team (**Members can edit**);
- each person you added, with **Can view** or **Can edit**.
A person marked "Added without an email" was added with **Email them** off. One marked "No longer active: whoever shared it lost access" was added by someone who no longer has access themselves, so their access went with it.
To remove someone, open the `⋯` menu on their row and choose **Remove**. They lose access straight away. People you added can remove themselves with **Remove me**.
## Let people share it on
Turn on **Let people I share with add others** to let the people you added share it with others. They can never give more access than they have: someone with **Can view** can only add people with **Can view**.
If you remove someone, everyone they added loses the access that came through them.
It's on by default for documents outside a workspace, and off by default for documents filed in one.
## Limits
- A document can be shared with up to 200 people.
- Sharing can be passed on through at most 10 people in a row. Past that, Markdrop asks them to ask the owner.
- You can add up to 60 people an hour.
## Encrypted documents
Adding someone to an end-to-end encrypted document lets them fetch it, but they still need the full link, including the part after `#`. Send them the whole link. See [End-to-end encryption](https://docs.markdrop.in/sharing/end-to-end-encryption).
## Related
- [Who can open what](https://docs.markdrop.in/sharing/who-can-open)
- [Request access](https://docs.markdrop.in/sharing/request-access)
- [Members and roles](https://docs.markdrop.in/workspaces/members-and-roles)
---
# Request access
Source: https://docs.markdrop.in/sharing/request-access
Opened a link and got "You don't have access yet"? You can ask the owner from that page. They get an email with your note and decide with one click, and you get an email when they do. Nothing opens until they say yes.
## Ask for access
You need to be signed in.
### Open the link
Signed in, you see "You don't have access yet" and the address you're signed in as.
### Check the account
If you were sent the link at another address, choose **Switch account** and sign in with that one first. It may open straight away.
### Add a note (optional)
Under **Message (optional)**, say who you are or why you need it, in up to 500 characters: "Hi, I need this for Thursday's review."
### Choose Request access
The page says "Request sent. You'll get an email when the owner decides."
If you've already asked, the page says "You've already asked for this" instead. You can have one open request per document.
### When Request access doesn't appear
| You see | Why |
| --- | --- |
| "This document is private" and **Sign in to open** | You aren't signed in. Sign in first. |
| "This document is password protected" | It's open to anyone with the link and the password. Ask whoever shared it for the password. |
| "You don't have access" with no button | The document can't take requests, for example because nobody owns it. |
On a workspace's own domain, like `docs.acme.com`, **Request access** takes you to the same document on markdrop.in, where you can send the request.
You can send up to 10 requests an hour.
## Answer a request
When someone asks, you get an email with their name, address and note, and three buttons: **Let them view**, **Let them edit** and **Decline**.
You can also answer in the app: open **Shared with me** in the sidebar. **Access requests** lists what's waiting; choose **Review** on one.
| Choose | What happens |
| --- | --- |
| **Let them view** | They're added to the document with **Can view**, as if you'd shared it with them. |
| **Let them edit** | They're added with **Can edit**. |
| **Decline** | Nothing is shared. |
Either way, they get an email with your answer. Only the document's owner can answer, and only while they own it.
After you approve, the person shows in the document's **People with access** list like anyone you added. To undo it, remove them there. See [Share with specific people](https://docs.markdrop.in/sharing/share-with-people).
> **Note**
>
> Approving never lowers someone's access. If they already had **Can edit** and you choose **Let them view**, they keep **Can edit**.
## Related
- [Who can open what](https://docs.markdrop.in/sharing/who-can-open)
- [Share with specific people](https://docs.markdrop.in/sharing/share-with-people)
---
# End-to-end encryption
Source: https://docs.markdrop.in/sharing/end-to-end-encryption
Turn on encryption and your browser encrypts the document's title and text before anything is sent. The key goes in the link, after the `#`, which browsers never send to a server. Markdrop stores ciphertext it has no key for.
The trade-off is plain: **the link is the key**. Anyone with the full link can read the document, and if every copy of the link is lost, so is the document.
## Encrypt a document
### Open the editor
Go to [markdrop.in/new](https://markdrop.in/new), and write or paste your text.
### Turn on encryption
Choose the **Encrypt** chip and turn on **Encrypt in this browser**. The chip changes to **Encrypted**.
### Publish
Choose **Publish**. The button says "Encrypting in your browser…", then "Publishing…".
### Copy the whole link
The **Published** panel says "End-to-end encrypted. The key is in this link." Choose **Copy link**: it copies the address with the key, like `markdrop.in/q3-plan#k=9fQx2LmA…`.
Encryption works with or without an account, and alongside a custom link, a password and an expiry. You can also turn it on in the [README builder](https://docs.markdrop.in/documents/readme-builder).
## How it works
- Your browser makes a new 256-bit AES-GCM key for the document.
- It encrypts the title and the text together into one sealed envelope, and sends only that. Markdrop stores no title for the document.
- The key is added to the link as `#k=…`. The part after `#` stays in the browser: it isn't sent when someone opens the link, and browsers leave it out of the `Referer` they send to other sites.
- When someone opens the full link, their browser fetches the ciphertext and decrypts it, locally.
Markdrop can't verify, read, search or recover the text. Signing in doesn't unlock it either, not even for the owner: if signing in could unlock it, the encryption wouldn't mean anything.
## Keep the link safe
> **Danger: No recovery**
>
> Lose every copy of the full link and the document can't be opened again, by anyone. Markdrop has nothing to recover it with.
As a safety net, the browser you published from remembers the key. If you open the document there without the key, it opens anyway and says "Opened with the key saved in this browser", with **Copy full link** so you can get the link back. On a shared or borrowed computer, choose **More actions › Forget key on this device**.
People who open the link in a browser without the key see "This document is end-to-end encrypted. Ask whoever shared it for the full link."
## What's turned off
Because Markdrop can't read the text, some features don't work on encrypted documents:
- **Link previews** show "Encrypted document" with no title or text.
- **Google Docs export** and **VS Code sync** are off.
- **Save a copy** isn't offered, and AI assistants connected through MCP can't read or change it.
- The editor doesn't keep drafts while **Encrypt** is on.
- Search in **My files** can't find it by title; it shows the link and an **E2E** badge.
The 500,000-character limit counts the encrypted size, which is larger than your text. The meter accounts for it.
## Who can open it
Encryption sits on top of [who can open it](https://docs.markdrop.in/sharing/who-can-open). Set it to **Only people added** and only those people can fetch the ciphertext, and they still need the full link with the key. Adding someone by email never gives them the key; send them the whole link.
## Edit and change the key
To edit, open the full link and choose **Edit**. Your browser decrypts the text, you change it, and it's encrypted again before it's saved. Without the key you can still change the password and expiry, or delete the document, but not the text.
If the link has spread further than you wanted, issue a new key: choose **Edit**, then **Settings**, turn on **Issue a new key**, and choose **Save with new key**. Markdrop shows "New key generated — this is the link now." The old link stops working. Anyone who already read the document keeps what they read.
## Files aren't encrypted
Encryption is for documents. Uploaded files, such as PDFs, pages and videos, aren't end-to-end encrypted. To limit who can open a file, use [who can open it](https://docs.markdrop.in/sharing/who-can-open) and a [password](https://docs.markdrop.in/documents/passwords-and-expiry).
## Related
- [Security and privacy](https://docs.markdrop.in/security/privacy)
- [Link previews](https://docs.markdrop.in/documents/link-previews)
- [Who can open what](https://docs.markdrop.in/sharing/who-can-open)
---
# Create a workspace
Source: https://docs.markdrop.in/workspaces/create
A workspace is a shared home for a team's documents and files. It holds the people you invite, the folders you file things into, your own domains like `docs.acme.com`, and your logo and colours.
Documents you publish outside a workspace keep working exactly as they do now. Nothing moves into a workspace until you put it there.
> **Note: Before you start**
>
> You need a Markdrop account. You can own up to 10 workspaces, and be a member of others that people invite you to.
## Create it
### Open New workspace
In the sidebar, under **Workspaces**, choose **New workspace**. You can also go to [Settings › Workspaces](https://markdrop.in/settings/workspaces) and choose **New workspace**.
### Name it
Type a name, like `Acme Docs`, and choose **Create workspace**. Names can be up to 80 characters, and you can change the name later.
### Follow Get set up
The new workspace opens with a **Get set up** checklist: name your workspace, invite your team, add your first file, and (optional) use your own domain. Each step ticks itself off when it's done. You can hide the checklist; it stays hidden on that device.
You are the workspace's **Owner**. Only the owner can delete it.
## Add things to it
Open the workspace and choose **New**:
| Choice | What it does |
| --- | --- |
| **New document** | Write Markdown straight into the workspace or the folder you're in. |
| **Upload** | Add videos, PDFs, pages and documents. You can also drop files onto the workspace. |
| **New folder** | Make a folder here. See [Folders](https://docs.markdrop.in/workspaces/folders). |
| **Add existing…** | File something you already published. |
When you add existing items, you choose **Who can open them**:
- **Keep as they are**: each item keeps its own setting. An item open to "Only people added" stays that way, even inside the workspace.
- **Acme team** (your workspace's name): anyone in the workspace can open them after signing in. Nobody else can, link or not.
New items start with the workspace's default. You set that with [Privacy](https://docs.markdrop.in/workspaces/privacy).
## Invite your team
Admins invite people. Choose **Share** at the top of the workspace, or open the workspace's settings and choose **Invite people** on the **People** tab. See [Members and roles](https://docs.markdrop.in/workspaces/members-and-roles).
## Leave or delete a workspace
Both live in the workspace's settings, on the **Advanced** tab.
- **Leave workspace** is for everyone except the owner. You lose access right away. Files you added stay in the workspace, and you still own them. To come back, an admin has to invite you again.
- **Delete workspace** is for the owner only. You type the workspace's name to confirm.
Deleting a workspace removes its members, folders and domains. It doesn't delete anyone's files: each file goes back to the person who owns it, with the same link and analytics. Anything that was open to the team becomes "Only people added", and your domain stops serving.
> **Warning**
>
> Deleting a workspace can't be undone. If you only want to stop serving your domain for a while, detach the domain instead. See [Put your workspace on your own domain](https://docs.markdrop.in/workspaces/custom-domain#take-a-domain-off-the-air).
## Next steps
**[Invite people](https://docs.markdrop.in/workspaces/members-and-roles)**
Choose who can view, who can edit, and who runs the workspace.
**[Choose who can open things](https://docs.markdrop.in/workspaces/privacy)**
Pick Open, Team or Locked for the whole workspace.
**[Use your own domain](https://docs.markdrop.in/workspaces/custom-domain)**
Serve the workspace from an address like docs.acme.com.
---
# Members and roles
Source: https://docs.markdrop.in/workspaces/members-and-roles
Everyone in a workspace has one role. The role decides what they can open and change across the whole workspace. You can change a person's role at any time.
## The roles
| Role | What they can do |
| --- | --- |
| **Can view** | Open everything the team can open. |
| **Can edit** | Also add files, change files the team can open, and make folders. |
| **Admin** | Also invite people, manage folders, and change the workspace's privacy, domains and branding. |
| **Owner** | Everything an admin can do, plus manage admins and delete the workspace. Each workspace has one owner: the person who created it. |
A few things only the owner can do:
- Invite someone as **Admin**, or withdraw an invitation sent as Admin.
- Change an admin's role, or remove an admin.
- Delete the workspace.
> **Note**
>
> A role never opens items set to "Only people added". Those stay with their owner and the people they added by name, even for admins. See [Who can open a link](https://docs.markdrop.in/sharing/who-can-open).
## Invite people
Only admins can invite people.
### Open People
Open the workspace's settings and go to the **People** tab. Choose **Invite people**.
### Add their addresses
Type one or more email addresses. Separate several with commas.
### Choose their access
Pick **Can view**, **Can edit** or **Admin**, then choose **Send invitation**.
Each person gets an email with a link. Once they accept, they can open everything the team can open. If you invite someone from a folder's **Share** dialog, they still join the whole workspace, not only that folder.
### How invitations work
- **Nobody joins without saying yes.** The person accepts or declines from the link in the email.
- **The link only works for the address it was sent to.** They sign in to Markdrop as that address to accept. Signed in as someone else, they see which address the invitation was for.
- **Invitations expire after 7 days.** Choose **Resend** next to a pending invitation to send a fresh one.
- **You can take an invitation back.** Open the row's menu and choose **Revoke invitation**. The link in their email stops working, and you can invite them again later.
A workspace can have up to 200 members and up to 100 invitations waiting for a reply at once.
## Change a role or remove someone
On the **People** tab, admins change a person's role from the select next to their name. To remove someone, open the menu on their row and choose **Remove from workspace**.
When someone is removed, they lose access to the workspace right away. Files they added stay in the workspace, and they still own them. People they shared things with through the workspace lose that access too.
You can't change your own role or remove yourself from this list. To leave, use **Leave workspace** on the **Advanced** tab. The owner can't leave; the owner can delete the workspace instead.
## Who sees what in settings
Everyone in the workspace can open its settings, but only admins change them. Controls that need the Admin role show "Admins only". Members see which domains the workspace has and whether each one is live, but not the DNS records behind them.
## Related
**[Workspace privacy](https://docs.markdrop.in/workspaces/privacy)**
Decide what new items start as, and whether visitors must sign in.
**[Share a folder](https://docs.markdrop.in/workspaces/share-a-folder)**
Give people a link to a folder page.
---
# Workspace privacy
Source: https://docs.markdrop.in/workspaces/privacy
Every item in a workspace has its own "Who can open it" setting: **Only people added**, **Acme team** or **Anyone with the link**. ("Acme" stands for your workspace's site name, or its name if it has none.) Workspace privacy sets two things on top of that: what new items start as, and whether visitors must sign in to open anything at all.
Admins choose privacy on the **People** tab of the workspace's settings.
## The three presets
| Preset | New items start as | Visitors must sign in | In short |
| --- | --- | --- | --- |
| **Open** | Anyone with the link | No | Links work for anyone. Each item still chooses who can open it. |
| **Team** | Acme team | No | New items are for the team. Public links stay possible, one by one. |
| **Locked** | Acme team | Yes | Only members and people added by name. No public links. |
A new workspace starts as **Team**.
Changing the preset never changes items that are already in the workspace, with one exception: Locked. When visitors must sign in, that applies to every item at once, including ones set to "Anyone with the link".
> **Warning: Locked applies everywhere**
>
> In a Locked workspace, sign-in is enforced on every address an item has, including its markdrop.in link, not only on your own domain. Someone who isn't a member and wasn't added by name can't open anything, even with the link.
## What each item setting means
- **Only people added**: the person who owns it, and people they added by name. Workspace roles don't open it, not even Admin.
- **Acme team**: everyone in the workspace, after signing in. Can view members read it; Can edit members, admins and the owner can also change it.
- **Anyone with the link**: anyone who has the link, unless the workspace is Locked. A password, if the item has one, still applies.
To change one item, open its menu in the workspace and choose **Who can open it**.
## The switches behind the presets
The **Advanced** tab has the individual switches. Most workspaces never need them.
- **New items start as**: Only people added, Acme team, or Anyone with the link. A starting point only; each item keeps its own setting.
- **Members can share folders with a link**: on by default. Admins always can. See [Share a folder](https://docs.markdrop.in/workspaces/share-a-folder).
- **Visitors must sign in to open anything**: the Locked switch. Enforced everywhere, including markdrop.in links.
If the switches don't match a preset, the People tab shows **Custom**. Pick a preset to replace them.
A folder can have its own starting point too. An admin opens the folder, chooses **More folder actions**, then **New items start as…**. Items filed into that folder start with the folder's choice instead of the workspace's.
## Check what someone else sees
Before you share, open the workspace or a folder and choose **Preview as**. It's there for Can edit members, admins and the owner. Pick who to preview as:
- **Anyone with the link**
- **An Acme member**
- **Someone by email…** (admins only)
Markdrop shows the folder as that person would see it, and how many of its items and folders they'd see. Items they can't open are listed with the reason, like **People added**, **Password** or **Sign-in needed**.
## Related
**[Members and roles](https://docs.markdrop.in/workspaces/members-and-roles)**
What Can view, Can edit, Admin and Owner can do.
**[Sign-in on your domain](https://docs.markdrop.in/workspaces/sign-in-on-your-domain)**
How readers sign in on docs.acme.com.
---
# Folders
Source: https://docs.markdrop.in/workspaces/folders
Folders keep a workspace tidy, and on your own domain they shape the address. A document filed under **Guides › Setup** is served at `docs.acme.com/guides/setup/`.
Folders decide where something appears and what its address is. They never decide who can open it: that stays with each item's own setting and the workspace's [privacy](https://docs.markdrop.in/workspaces/privacy).
## Make a folder
Open the workspace (or the folder you want it inside), choose **New**, then **New folder**. Members with **Can edit**, admins and the owner can make folders.
| Limit | Value |
| --- | --- |
| Folders per workspace | 500 |
| How deep folders can nest | 8 levels |
| Folder name | Up to 80 characters |
Two folders in the same place can't share a name that gives the same address. A few words are reserved for the top level because the app uses them, such as `settings`, `dashboard` and `api`.
## File things into folders
- **Add new things straight into a folder**: open the folder and choose **New**, then **New document**, **Upload** or **Add existing…**.
- **Move something you already filed**: open the item's menu and choose **Move to…**. Can view members can't move items.
## Rename, move or delete a folder
Admins and the owner manage folders. Open the folder and choose **More folder actions**:
- **Rename folder…** shows the new address as you type. Links that use the old name redirect to the new address.
- **Move folder to…** moves it with everything inside. The move is refused if it would nest anything deeper than 8 levels.
- **Delete folder…** removes only the folder. What's inside stays in the workspace, and nobody loses access. If the folder is inside another one, you choose whether everything moves up one level or the items move to the top of the workspace.
markdrop.in links never change when you rename, move or delete a folder. Only addresses on your own domain include folders.
## Take something out of the workspace
Open the item's menu and choose **Remove from workspace**. The item goes back to its owner's files. Nothing is deleted. The item's owner, admins and the workspace owner can do this.
## Next
**[Share a folder](https://docs.markdrop.in/workspaces/share-a-folder)**
Send one link that lists a folder's contents.
**[Use your own domain](https://docs.markdrop.in/workspaces/custom-domain)**
Turn folders into addresses on docs.acme.com.
---
# Share a folder
Source: https://docs.markdrop.in/workspaces/share-a-folder
A folder link is one address, like `markdrop.in/f/…`, that opens a page listing a folder's contents and its subfolders. It's handy for a set of handouts, a release's files, or a team's guides.
A folder link never opens anything by itself. Each visitor sees only the items they could already open on their own. Sharing a folder doesn't change any item's setting.
## Share it
### Open the folder
Open the folder in the workspace and choose **Share**, then the **Folder link** tab.
### Choose who can open the folder's page
- **Not shared**: only people in the workspace see this folder, inside Markdrop.
- **Acme team**: a link for teammates. They sign in to open it. People outside the workspace get a sign-in page, then nothing.
- **Anyone with the link**: no sign-in. Visitors see only the items open to anyone.
### Copy the link
Choose **Copy link** and send it.
Admins can always share folders. Members with **Can edit** can too, unless an admin turned off **Members can share folders with a link** on the **Advanced** tab of the workspace's settings.
## When some items won't show
If you choose **Anyone with the link** and some items are team-only or private, Markdrop tells you, for example "3 of 12 items won't show for visitors". Those items stay hidden from people who can't open them.
To show them, change each item's **Who can open it** to **Anyone with the link**. To check the result first, choose **See it as a visitor →** in the same dialog, or **Preview as** on the folder.
> **Note**
>
> In a [Locked](https://docs.markdrop.in/workspaces/privacy#the-three-presets) workspace, nothing is open to anyone, so a folder link only works for members and people added by name.
## Change or stop sharing
Open **Share › Folder link** again and pick another option. Switching between **Acme team** and **Anyone with the link** keeps the same link.
Choose **Not shared** to turn the link off. If you share the folder again later, it gets a new link, and the old one stays off.
## Folder pages on your own domain
With [your own domain](https://docs.markdrop.in/workspaces/custom-domain), every folder also has an address on it, like `docs.acme.com/guides`. That page follows the same rule: a visitor sees a folder only when it holds at least one item they can open, and sees only those items.
---
# Put your workspace on your own domain
Source: https://docs.markdrop.in/workspaces/custom-domain
Serve every document and folder in a workspace from an address you own, like `docs.acme.com`. A document filed under **Guides** opens at `docs.acme.com/guides/`. Existing markdrop.in links keep working.
> **Note: Before you start**
>
> You need to be an **Admin** or the **Owner** of the workspace, and able to add DNS records for the domain (or know who can). A workspace can have up to 25 domains.
## Add your domain
### Add the domain
Open the workspace's settings and go to the **Look and domain** tab. Under **Your own domain**, choose **Add a domain**. Type the hostname, like `docs.acme.com`, leave **What it serves** on **Documents and files**, and choose **Add domain**.
### Prove it's yours
Markdrop shows two records. The first is the **Ownership record**, a TXT record. Add it where you manage DNS for `acme.com`.
### Point it at Markdrop
The second is the **Routing record**. A subdomain like `docs.acme.com` uses a CNAME. An apex domain like `acme.com` uses an A record instead, because DNS doesn't allow a CNAME there.
### Check DNS
Choose **Check DNS**. When both records are found, the domain is verified and goes live on its own. The status changes to **Live**, and readers can open it.
For `docs.acme.com`, the records look like this. Copy the exact values Markdrop shows you; the TXT value is unique to your domain.
| Type | Name | Value |
| --- | --- | --- |
| TXT | `_markdrop-verify.docs` | `markdrop-verify=…` (your value) |
| CNAME | `docs` | `edge.markdrop.in` |
For an apex domain, `acme.com`:
| Type | Name | Value |
| --- | --- | --- |
| TXT | `_markdrop-verify` | `markdrop-verify=…` (your value) |
| A | `@` | `76.76.21.21` |
Markdrop works out which panel your DNS lives in from your nameservers. When it knows, it says so ("Your DNS is at Cloudflare"), offers a button to open that provider's DNS page, and writes each name the way that provider expects it.
**Cloudflare**
Add both records in your domain's DNS settings. In **Name**, type only the part before your domain: `docs`, not `docs.acme.com`.
Set **Proxy status** to **DNS only** (grey cloud) for the CNAME. An orange cloud breaks certificate issuance.
**GoDaddy**
GoDaddy adds your domain to the name for you. In **Name**, type only the sub-part: `_markdrop-verify.docs` and `docs`. For an apex domain, use `@` for the A record.
**Namecheap**
Use the **Advanced DNS** tab, not **Domain › Redirect**. In **Host**, type only the sub-part: `_markdrop-verify.docs` and `docs`.
**Route 53**
Open the hosted zone for your domain and create each record. In **Record name**, type only the sub-part: `_markdrop-verify.docs` and `docs`.
**Other**
Any DNS provider works. Most panels add your domain to the name for you, so type only the part before it. If yours wants the full hostname, use the names exactly as Markdrop shows them, like `_markdrop-verify.docs.acme.com`.
If your provider offers an ALIAS or ANAME record at the apex, you can point it at `edge.markdrop.in` instead of using the A record.
> **Tip: Someone else runs your DNS?**
>
> Under the records, choose **Send to your DNS admin**. You get a ready-to-send message with both records, what they do and what they don't touch. You can copy it, open it in your email, or choose **Download CSV**.
## Use the API
Prefer the terminal? The same steps work through the API with an [API token](https://docs.markdrop.in/account/api-tokens). Your workspace id is in the address of its settings page, `markdrop.in/settings/workspaces/`. `kind` is `app` for **Documents and files**, or `cdn` for **Files only**.
**curl**
```bash title="Add docs.acme.com"
curl -X POST "https://api.markdrop.in/api/v1/workspaces/$WORKSPACE_ID/domains" \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"host": "docs.acme.com", "kind": "app"}'
```
**JavaScript**
```js title="Add docs.acme.com"
const res = await fetch(
`https://api.markdrop.in/api/v1/workspaces/${process.env.WORKSPACE_ID}/domains`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ host: "docs.acme.com", kind: "app" }),
},
);
const domain = await res.json();
```
**Python**
```python title="Add docs.acme.com"
import os, requests
res = requests.post(
f"https://api.markdrop.in/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/domains",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"host": "docs.acme.com", "kind": "app"},
)
domain = res.json()
```
The response includes the records to add: `dns_record_name` and `dns_record_value` for the TXT record, and `dns_target_type`, `dns_target_name` and `dns_target_value` for the routing record. After you add them, check DNS with `POST /api/v1/workspaces//domains//verify`.
## Watch it once
## It can take a while
> **Warning**
>
> DNS changes can take up to an hour to show up, sometimes longer. If **Check DNS** says a record isn't visible yet, wait and check again. Each record shows **Found** or **Not found yet**, so you know which one to look at.
Markdrop doesn't check a new domain on its own: choose **Check DNS** when you've added the records. A domain that's never verified is dropped after 7 days, and you can add it again.
## Make it the default
The first domain you verify becomes the workspace's address on its own. With more than one, open a domain's menu and choose **Make default**. The default is the address Markdrop uses for shared links and preview cards. Every live domain keeps serving.
## Files only domains
When you add a domain, **What it serves** has a second option, **Files only**: a plain file host for raw bytes, with no document pages. Use it for something like `files.acme.com`.
A Files only domain serves files your members upload, and anything it serves can set cookies on its parent domain. Prefer a domain you use for nothing else, not one that also hosts your app or marketing site.
## Keep it live
Keep both records in place after the domain is live. Markdrop checks them again every day.
If the records stop resolving to Markdrop for three days, the domain stops serving and shows **Lapsed**. Put both records back, then choose **Check DNS again**. Links on markdrop.in keep working throughout.
To see the records again later, open the domain's menu and choose **Show DNS records**. **Download records (CSV)** is in the same menu.
## Take a domain off the air
Open the domain's menu:
- **Detach** stops serving at once, and links to the domain fail. The domain stays verified, so you can bring it back later without touching DNS.
- **Remove domain** stops serving and also discards the verification. Adding it back means adding the DNS records and waiting for them again.
Documents keep working on markdrop.in either way.
## If it doesn't verify
**It says the TXT record wasn't found**
Most DNS panels add your domain to the name for you. If you typed `_markdrop-verify.docs.acme.com`, the record may have ended up at `_markdrop-verify.docs.acme.com.acme.com`. Change the name to `_markdrop-verify.docs`, then check again.
**It found a TXT record, but says none matched**
The value must match exactly, starting with `markdrop-verify=`. Copy it again with the copy button next to **Value**, and check that your panel didn't add quotes or spaces.
**The domain is verified but not live yet**
The routing record isn't visible yet. The domain goes live as soon as it is. Check that the CNAME points at `edge.markdrop.in` (or the A record at `76.76.21.21` for an apex domain), and that no other record for the same name points somewhere else.
**Cloudflare shows the domain, but pages don't load**
Turn the proxy off for the CNAME: set **Proxy status** to **DNS only** (grey cloud).
**It says the domain is already claimed**
Another workspace has verified it. A domain can belong to one workspace at a time. If it's yours, remove it from the other workspace first.
**Will my markdrop.in links break?**
No. They keep working. Your domain is an extra address for the same documents.
---
# Brand your workspace
Source: https://docs.markdrop.in/workspaces/branding
Branding makes your own domain look like yours: your name in page titles, your favicon in the browser tab, your logo and colour on pages and link preview cards. Anything you leave blank falls back to Markdrop's.
Branding shows on your own domains. Admins and the owner change it on the **Look and domain** tab of the workspace's settings. A preview on the right, **How your links look**, updates as you edit, on a light or dark background.
## Names
- **Workspace name** is what members see inside Markdrop. Up to 80 characters.
- **Site name on your pages** is shown in the header and link previews on your domain, like `Acme Docs`. Up to 60 characters. Members also see it as the team's name, as in "Acme team".
## Logo, colours and favicon
| Setting | What to know |
| --- | --- |
| **Favicon** | PNG, JPEG, WebP or GIF, under 2 MB. Resized to 128 pixels. |
| **Logo** | Same file types and size. Shown on link preview cards and in your domain's header. Resized so its longest side is at most 512 pixels. |
| **Logo background** | The ground behind your logo. **Automatic** follows the reader's theme. Pick **Light** for a dark logo, **Dark** for a light one, or **Checker**. |
| **Accent colour** | Buttons, links and highlights on your domain use it. Blank uses Markdrop's blue. |
| **White-label preview cards** | Removes "Published with Markdrop" from cards and titles. |
The favicon and logo save as soon as they upload. Everything else waits for **Save changes** in the bar at the bottom; **Discard** throws away unsaved edits.
Markdrop re-encodes every uploaded image as a fresh PNG and keeps only the pixels, so an animated GIF keeps only its first frame.
> **Tip**
>
> Logos drawn in white disappear on a light background. Check both themes in the preview, and set **Logo background** to **Dark** if you need to.
## Viewer controls
Under **Viewer**, choose what readers get around a document on your own domain:
- **Full**: visitors can leave full screen and see document details.
- **View only**: no controls for signed-out visitors. The page is the document and nothing else.
This setting only ever applies on your own verified domains, never on markdrop.in.
## Related
**[Use your own domain](https://docs.markdrop.in/workspaces/custom-domain)**
Branding shows once the workspace has a live domain.
**[Link previews](https://docs.markdrop.in/documents/link-previews)**
How preview cards look when you paste a link.
---
# Sign-in on your domain
Source: https://docs.markdrop.in/workspaces/sign-in-on-your-domain
Readers sign in to Markdrop on markdrop.in. A browser keeps sign-ins separate for each site, so on `docs.acme.com` a reader looks signed out, however recently they signed in on markdrop.in. Markdrop carries the sign-in across for them, with their say-so.
Readers only need to sign in for things that aren't open to anyone: items for the team, items shared with them by name, and everything in a [Locked](https://docs.markdrop.in/workspaces/privacy#the-three-presets) workspace.
## What readers see
### Choose Sign in
On your domain, the header shows **Sign in to read Acme Docs** (on a phone, **Sign in**). In a Locked workspace, signed-out visitors see **Sign in to read Acme Docs** in the middle of the page instead.
### Sign in on markdrop.in
The reader goes to markdrop.in and signs in there, if they aren't already.
### Confirm the site
Readers who aren't members of the workspace are asked once: **Continue to docs.acme.com as Dana?** The page says that the site can see their display name, never their email address, and can't change anything in their account. **Don't ask again for docs.acme.com** is ticked by default. They choose **Continue**.
### Back on your domain
They land back on the page they started from, signed in, and can read what's been shared with them.
Members of the workspace skip the question: it's their own site.
## What the sign-in on your domain can do
The sign-in Markdrop hands to your domain is deliberately small:
- It works only on that one domain, and only for that workspace's documents.
- It can only read. It can't edit, share or delete anything, or touch the reader's own documents, other workspaces or account.
- It lasts 12 hours. After that, the reader chooses **Sign in** again.
- It doesn't contain the reader's email address.
Every document still runs its normal access check for that person. Signing in on a domain never opens anything the reader couldn't already open on markdrop.in.
To sign out on your domain, a reader opens the avatar menu in the header and chooses **Sign out**.
> **Note: If the domain changes hands**
>
> A reader's "Don't ask again" answer belongs to the workspace that owned the domain when they gave it. If the domain is removed, or another workspace verifies it later, readers are asked again.
## Related
**[Workspace privacy](https://docs.markdrop.in/workspaces/privacy)**
Make a workspace Locked so every visitor signs in.
**[Share with people](https://docs.markdrop.in/sharing/share-with-people)**
Add people by email to a single item.
---
# Analytics overview
Source: https://docs.markdrop.in/analytics/overview
Analytics shows what happens after you share a link: how many people opened it, how far they read, where they stopped watching, and where they came from. You see totals and patterns, never who someone is.
Open **Analytics** in the sidebar, or go to [markdrop.in/dashboard/analytics](https://markdrop.in/dashboard/analytics). You need to be signed in, and you see analytics only for things you own.
## The home board
The home board covers everything you've published.
| Tile | What it counts |
| --- | --- |
| **Views** | Times your links were opened, across all of them. |
| **People** | Distinct visitors. |
| **Watch time** | Time spent playing your videos and audio. |
| **Read to the end** | Share of reading sessions on documents and PDFs that reached the end. |
Below the tiles:
- **Your links** lists each link with its views, a trend line, how engaged people were, and the last view. A green dot means someone is on it now.
- **Worth a look** points out things that stand out, like where people stop watching. Insights appear once your links have about 20 visits.
Choose the period with the range picker: **7 days**, **30 days** (the default), **90 days** or **All time**. Each number is compared with the period of the same length right before it, like "+12% vs previous 30 days". Days run in UTC.
Choose **CSV** to download the numbers for the period.
## One link in detail
Choose a link in **Your links** to open its own page. You can also open the menu on any item you own and choose **Analytics**.
The tabs depend on what the link is:
| Kind | Tabs |
| --- | --- |
| Document | **Overview**, **Content**, **Audience** |
| PDF | **Overview**, **Pages**, **Audience** |
| Video | **Overview**, **Video**, **Audience** |
| Audio | **Overview**, **Listening**, **Audience** |
| Other files | **Overview**, **Audience** |
Files you've replaced with a new version also get a **Versions** tab that compares them.
- **Overview** has the headline numbers for that kind, **Views per day**, **Where views come from**, and **What stands out**.
- **Audience** shows **New and returning** visitors, **Devices**, **Countries**, **Channels**, **Share signals** (times an app showed a preview of your link) and **Campaigns**.
The other tabs are covered in [Documents and PDFs](https://docs.markdrop.in/analytics/documents-and-pdfs) and [Video and audio](https://docs.markdrop.in/analytics/video-and-audio).
## Watch it once
## Ask your AI assistant
If you've connected an AI assistant to Markdrop, it can read these numbers too, for example: "Where do readers drop off in my getting-started guide? Rewrite that section." See [AI tools](https://docs.markdrop.in/ai/tools).
## Next
**[What gets counted](https://docs.markdrop.in/analytics/what-gets-counted)**
What a view is, whose views are left out, and what's never stored.
**[Live now](https://docs.markdrop.in/analytics/live)**
See who's reading or watching right now.
---
# What gets counted
Source: https://docs.markdrop.in/analytics/what-gets-counted
Analytics is built to answer "is this working?" without watching anyone. This page lists exactly what is counted, what is left out, and what is never stored.
## Views and people
- **A view** is one time someone opens your link. Opening it again later is another view.
- **People** counts distinct visitors, worked out from a hashed IP address. Two people on the same network can count as one person, and one person on two networks as two.
- **Returning** people opened the link on at least two different days.
Each open also starts a reading or watching session, which records how far people got. A session is kept only once it has at least 2 seconds of active time, or the visitor played some media.
**Active time** counts a second only while the tab is visible and the reader did something in the last minute, or while media is playing. A tab left open in the background doesn't add time.
## Whose views are left out
- **Yours.** When you're signed in, your own views of your own links aren't counted at all. If you open your link while signed out, Markdrop can't tell it's you, and the view counts.
- **Your workspace's members.** For an item in a workspace, views from its members are recorded as internal and hidden by default. On the item's **Audience** tab, turn off **Hide views from Acme members** to include them. The home board, live view and CSV downloads always leave them out.
- **Bots and crawlers.** Requests that identify as bots, crawlers or scripts aren't counted.
- **Link previews.** When Slack, LinkedIn, WhatsApp or another app fetches your link to show a preview, it's counted under **Share signals**, not as a view.
## Global Privacy Control
If a visitor's browser sends [Global Privacy Control](https://globalprivacycontrol.org/), Markdrop sends no reading or watching data for them at all. Their visit still counts as a view, but without where they came from.
## What you see, and what you don't
You see totals and patterns. You never see who someone is, their IP address, or a list of individual visitors.
- **Countries** with fewer than 5 people are grouped as **Other**. **Campaigns** are grouped the same way.
- Live view shows only countries and device types, never who someone is.
- Devices are coarse, like "phone" and the browser's name.
## What is never stored
- **Raw IP addresses.** Markdrop stores a salted hash of the IP address, never the address itself.
- **Copied text.** For a code block, Markdrop counts that it was copied and which block it was, using a hash of the block. What readers copy is never stored. Other text selections aren't tracked.
- **Anything typed**, and the query strings or fragments of links people click. For clicked links, only the host and path are kept.
- **Content of encrypted documents.** For an [end-to-end encrypted](https://docs.markdrop.in/sharing/end-to-end-encryption) document, only time and how far people scrolled are recorded.
Markdrop looks up an approximate location from the IP address when a view comes in. It stores the country, region and city with the view, and shows you countries only.
## How long it's kept
| Data | Kept for |
| --- | --- |
| Reading and watching sessions (time, depth, sections, pages, playback) | 395 days, then deleted |
| Live presence | About 45 seconds after a visitor's last signal; never saved |
| View records | No fixed deletion date |
## Related
**[Privacy at Markdrop](https://docs.markdrop.in/security/privacy)**
What Markdrop collects across the whole product.
**[Documents and PDFs](https://docs.markdrop.in/analytics/documents-and-pdfs)**
How reading depth and pages are measured.
---
# Video and audio analytics
Source: https://docs.markdrop.in/analytics/video-and-audio
For a video or audio file, analytics shows how much of it people watched or listened to, where they stopped, and whether playback worked. Open the file's analytics page from [Analytics](https://markdrop.in/dashboard/analytics). Audio uses the same views, with "listen" in place of "watch".
## Overview
| Number | What it means |
| --- | --- |
| **Views** and **People** | Times it was opened, and distinct visitors. |
| **Watch time** | Total time played, across everyone. |
| **Avg watched** | On average, how much of the video each person who pressed play watched. |
| **Played** | Share of visits where someone pressed play. |
Markdrop counts the parts of the file someone actually played. Watching the same minute twice still covers that minute once, and skipping ahead doesn't count what was skipped. Someone counts as having finished when playback reaches the end or they've watched at least 90% of it.
## Video tab
The **Video** tab (**Listening** for audio) goes second by second.
### Who's still watching, second by second
A curve of how many people who pressed play were still watching at each point. Markdrop marks moments worth a look under it, such as:
- how many people are still watching at 0:30,
- where most viewers leave, and
- the part that's replayed most.
### Rewatched and skipped
A strip across the length of the video: darker means more rewatching, and gaps mean parts people skipped.
### How they watch
Playback speeds people use, like 1.5×, and how many go fullscreen, watch muted, or use picture-in-picture.
### Could people actually watch it?
For video, Markdrop checks whether playback went well, and labels it **Plays smoothly**, **Slow starts on phones** or **Plays failing**.
| Tile | What it means |
| --- | --- |
| **Time to start (typical)** | How long the typical viewer waited for playback to start. The note shows the slowest 5%. |
| **Sessions that stalled** | Viewers who waited for the video to load mid-play. |
| **Plays that failed** | Plays that didn't work, and why. |
| **Phone viewers waiting > 5 s** | Phone viewers who waited more than 5 seconds to start, with the countries most affected. |
When there's a clear fix, a **Suggestion** tells you what to change.
## Versions
When you [replace a file](https://docs.markdrop.in/files/replace-and-versions) with a new version, each version keeps its own numbers. Pick a version on the Video tab to see its curve. The **Versions** tab lists **Every version** with when it went live, its views and sessions, and **Avg watched**. For video, it also compares the first minute of the current and previous version.
The Versions tab always covers each version's whole life, whatever range you picked.
## Watch it once
---
# Document and PDF analytics
Source: https://docs.markdrop.in/analytics/documents-and-pdfs
For documents and PDFs, analytics answers one question above all: did people read it? Open the link's analytics page from [Analytics](https://markdrop.in/dashboard/analytics) to see the numbers on this page.
## Documents
### Document overview
| Number | What it means |
| --- | --- |
| **Views** | Times the document was opened. |
| **People** | Distinct visitors. |
| **Read to the end** | Share of reading sessions that got through the last 10% or so of the document. |
| **Median read time** | Active reading time of the typical reader. |
| **Copies & link clicks** | Code blocks copied and links clicked. |
How far someone read is measured by scrolling: the furthest point of the document that reached the bottom of their screen.
### Content tab
The **Content** tab breaks the document down by its headings.
- **How far people read, section by section** shows, for each section, how many readers reached it and how long they spent there. Flags point out the section that's **Most copied**, where people spend **Most time**, and where **Readers leave here**.
- **Most copied** lists the code blocks people copy most. Markdrop counts which of your blocks get copied; what readers copy is never stored.
- **Links people click** lists the links readers follow from the document.
- If people print the document, you see how many times.
The tiles at the top are **Readers** (reading sessions), **Read it all** (reached the last section), **Time spent reading** (typical reader, active time only) and **Copies and clicks**.
A section counts as reached when its heading passes about a third of the way down the reader's screen. Time in a section is active time while that heading is the current one.
> **Tip**
>
> Long sections that most people never finish are a good place to split or trim. The section where **Readers leave here** shows up is usually the one to rewrite first.
## PDFs
### PDF overview
| Number | What it means |
| --- | --- |
| **Views** and **People** | As for documents. |
| **Reached the last page** | Share of reading sessions that got to the last page. |
| **Median read time** | Active reading time of the typical reader. |
| **Pages read** | The furthest page the typical reader reached, out of the total. |
### Pages tab
- **Viewers who reached each page** shows how many readers got to each page. Markdrop tells you where the biggest drop is, like "The biggest drop is after page 3".
- **Time spent on each page** shows where readers slow down.
- **Every page** lists each page with **Reached**, **Typical time** and **Came back to it** (readers who returned to that page).
A page counts as current when it's about a third of the way down the reader's screen.
> **Note: Long PDFs**
>
> Markdrop's PDF viewer shows the first 50 pages and offers the rest as a download. Page analytics cover the pages the viewer shows, so a longer PDF reads "50+ pages", and reaching page 50 counts as reaching the end.
## Other files
For files that aren't documents, PDFs, video or audio, like a spreadsheet or a web page, the overview shows **Median time** and **Returning** (people who came back on another day).
## Encrypted documents
For an [end-to-end encrypted](https://docs.markdrop.in/sharing/end-to-end-encryption) document, Markdrop records only time and how far people scrolled. Sections, copies and links aren't recorded, because Markdrop can't read the document.
---
# Live now
Source: https://docs.markdrop.in/analytics/live
Live view shows what's happening on your links at this moment: how many people are reading or watching, and where they are in it. It's handy right after you share something, or during a talk or a launch.
## Open it
Go to [markdrop.in/dashboard/live](https://markdrop.in/dashboard/live), or open [Analytics](https://markdrop.in/dashboard/analytics) and choose **Live**. When someone is on your links, the button shows the count instead, like **3 people live**.
On a single link's analytics page, a badge such as **2 reading now** or **1 watching now** shows who's on that link.
## What you see
The header says how many people are on how many links, like **5 people across 2 links**, or **Nobody right now**.
- **Each video or audio file** gets its own panel, with a dot on the timeline for each viewer and a line for each, like "2:14 · someone in the US, on a phone".
- **Reading now** lists documents, PDFs and other files with a count, and where most readers are: a heading, or a page number.
- **Last 30 minutes** is a short feed of recent events, such as someone opening a link, a link being previewed in Slack, how far someone read, or the page someone reached in a PDF.
The page updates every 10 seconds while the tab is open.
## How live is worked out
A visitor's page sends a small signal every 15 seconds while it's visible. Someone counts as live for 45 seconds after their last signal, so a reader who closes the tab drops off within a minute. Signals pause after 5 minutes without any activity, unless media is playing.
Live presence is kept in memory only and never saved.
## Privacy
- Only countries and device types are shown, never who someone is.
- Your own visits aren't shown.
- Views from your workspace's members aren't shown.
See [What gets counted](https://docs.markdrop.in/analytics/what-gets-counted) for the full list.
---
# Weekly analytics email
Source: https://docs.markdrop.in/analytics/weekly-email
The weekly analytics email is a short summary of last week, sent on Mondays, so you don't have to open the dashboard to know how your links did.
> **Note: Not sending yet**
>
> Markdrop isn't sending the weekly email yet. You can already choose whether you want it, and your choice applies when sending starts.
## What's in it
- **Views** for last week, and the change on the week before.
- **People**, **Watch time** and **Read to the end**.
- **Top links**: your three busiest links, each with a one-line note, like how much of a video people watched and where the biggest drop was.
- **Worth a look**: up to two things that stand out.
- An **Open analytics** button that takes you to your dashboard.
"Last week" runs Monday to Sunday in UTC. As on the dashboard, your own views and your workspace members' views aren't counted.
## When it's sent
The email goes out on Mondays, from 03:30 UTC. You get it only if at least one of your links was viewed last week. A quiet week means no email.
## Turn it on or off
Go to [Settings › Account](https://markdrop.in/settings/account). Under **Email**, switch **Weekly analytics email** on or off. It's on unless you turn it off.
You can also choose **Turn off weekly emails** at the bottom of any weekly email. Markdrop asks you to confirm, then stops sending it. To get it again, switch it back on in Settings.
---
# Send files peer to peer
Source: https://docs.markdrop.in/share/send-files
Send lets you hand files straight to someone else's device. You drop files, share a link, and they stream from your device to theirs, encrypted, with no copy left on any server. Neither of you needs an account.
It's different from [publishing a file](https://docs.markdrop.in/get-started/first-file): a published file lives on Markdrop and anyone with access can open it later. A peer-to-peer transfer exists only while your tab is open.
| | Send peer to peer | Publish a file |
| --- | --- | --- |
| Where the file lives | On your device | On Markdrop |
| Size | Any size | Up to 25 MB, or 2 GB for video and audio |
| Works when you close the tab | No | Yes |
## Send files
### Open Send a file
Go to [markdrop.in/share](https://markdrop.in/share).
### Add your files
Drop files or a whole folder onto the page, or choose **Choose files** or **Choose a folder**. Any type, any size. Nothing leaves your device until someone opens the link.
### Share the link
Choose **Copy link** (or **Share…** on devices that have it), or let them scan the QR code with a phone camera.
### Keep the tab open
The files come from your device, so keep the tab open until everyone has what they need. Closing it ends the share.
While you're sharing, the page shows who's connected under **People**, with up to 10 at once, and how far each one has got. Forgot something? Drop more files on the page or choose **Add files**; everyone connected sees them right away.
When you're done, choose **End sharing**. The link stops working. If people are still downloading, Markdrop asks first; they keep what they've already saved.
## Receive files
Open the link. Markdrop connects your device to the sender's, then shows the files.
- Choose **Download all** to get everything, or **Save** next to a single file.
- On a computer, each file saves to your Downloads as it arrives. If your browser asks to download multiple files, choose **Allow**.
- On an iPhone, files collect in the page as they arrive, then one tap saves them to Photos or Files.
The sender's tab must stay open while you download. If they close it, anything you already saved is yours to keep.
## Use the terminal
The [Markdrop CLI](https://docs.markdrop.in/cli/send-and-receive) sends and receives too, and it works with the browser in both directions.
```bash title="Receive from a browser link"
markdrop get https://markdrop.in/share/
```
```bash title="Send a file or a folder"
markdrop send ./report.pdf
```
The CLI writes straight to disk, so very large files are easier to receive there than in a browser. A CLI sender serves one recipient at a time.
## How it stays private
- **Your files are never stored on Markdrop's servers.** They go from one device to the other.
- **Encrypted in transit.** The connection between the two devices is encrypted with DTLS, the standard encryption for browser-to-browser connections.
- **Direct when the network allows.** When two networks can't reach each other directly, an encrypted relay forwards the bytes. The relay can't read them. The page shows **Direct** or **Relayed** while a file is moving.
Markdrop's server introduces the two devices to each other. It keeps the name, type and size of the files, how many there were, a hashed IP address, your account if you're signed in, and connection details (route, timing, browser). Choose **What we record** on the page to see this list.
> **Note**
>
> Files inside a folder you drop are sent as separate files. Between two browsers, the folder structure isn't kept. Between two CLIs, a folder arrives as a folder.
## Watch it once
---
# Troubleshooting peer-to-peer transfers
Source: https://docs.markdrop.in/share/troubleshooting
Most transfers work on the first try. When one doesn't, the message on the page tells you what happened. Find it below.
## Messages and what to do
**This link isn't active**
Files are only available while the sender keeps their Markdrop tab open. The page keeps trying for about a minute before it shows this. Ask the sender to open their share page again, or to send a new link.
**Waiting for the sender to reconnect**
The sender's page isn't connected right now, often because their phone put it in the background. Ask them to bring the page back to the front. For the next 10 minutes, the transfer picks up on its own when they do.
**Couldn't connect the two devices**
Neither a direct nor a relayed route worked. A VPN or a very strict network is the usual cause. Switch off the VPN on either device, or try another network, then choose **Try again**.
**Too many people are downloading right now**
A link serves up to 10 people at once. Try again in a moment, once someone has finished.
**Someone else is receiving right now**
The sender is using the Markdrop CLI, which serves one recipient at a time. Try again once they're done, or ask the sender for a new link.
**The sender stopped sharing / The sender closed Markdrop**
The link is closed. Anything you already saved is yours to keep. Ask the sender to share again if you need the rest; they'll get a new link.
**The transfer was interrupted**
The connection dropped partway through. Files that finished are saved. Reload the link to fetch the rest. A file that was cut off starts again from the beginning.
**Can't reach Markdrop**
Your connection to markdrop.in dropped. Check you're online and try again. On the sender's side, people already connected keep downloading while the page reconnects.
**The recipient's CLI is out of date**
An older CLI can only receive one file at a time. Ask them to update (`brew upgrade markdrop`), or send a single file.
## Slow or stuck transfers
**Check the route.** While a file is moving, the page shows **Direct** or **Relayed**. A relayed transfer passes through an encrypted relay because the two networks couldn't reach each other, and it's usually slower than a direct one.
**Two devices on the same Wi-Fi can still be relayed.** Some networks, like office, hotel and café Wi-Fi, keep devices from talking to each other. Switching one device to mobile data often gives a direct route.
**Turn off VPNs** on both devices if you can. A VPN often hides the direct route.
**"This network is making it hard"** means connecting is taking longer than usual. It can take up to half a minute.
**"Waiting on the network…"** on the sender's page means data has stopped flowing for the moment. It continues on its own if the connection recovers.
## Large files and phones
There's no size limit, but a browser holds each incoming file in memory until it's saved. Very large files may not fit, especially on phones.
- On an iPhone, Markdrop warns you when the total is large: download a few files at a time.
- To receive something really large, use the [Markdrop CLI](https://docs.markdrop.in/cli/send-and-receive). It writes straight to disk, so it has no size limit.
## Before you share
- **Keep the sender's tab open** and in the front on a phone. Closing or reloading it ends the share, and a reload starts a new link.
- **Send one link to everyone.** Up to 10 people can download at once, and each picks what they want.
---
# Publish and sync from VS Code
Source: https://docs.markdrop.in/vscode/sync
The Markdrop Sync extension publishes the file you're editing in VS Code and keeps it in step with its link. Save the file and the link updates; edit it on markdrop.in and the change comes back into your file.
## Install the extension
Install **Markdrop Sync** from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=HimanshuKukreja.markdrop), or search for "Markdrop Sync" in the Extensions view. From a terminal:
```bash
code --install-extension HimanshuKukreja.markdrop
```
It needs VS Code 1.85 or later.
## Sign in
### Start the sign-in
Open the Command Palette (⌘⇧P on macOS, Ctrl+Shift+P elsewhere) and run **Markdrop: Sign in**.
### Approve in your browser
Markdrop opens a **Connect VS Code** page that shows the account the extension will publish as. Choose **Authorize VS Code**.
### Return to VS Code
Your browser hands you back to VS Code, which shows "Signed in to Markdrop as …".
Signing in creates an API token named "VS Code" on your account, and the extension keeps it in VS Code's Secret Storage. You can see and revoke it on [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens).
If the browser hand-off doesn't come back to VS Code (for example in a remote or web-based VS Code), create a token on that page and run **Markdrop: Sign in with API token** to paste it.
## Publish a file
Open the file and run **Markdrop: Publish / link current file**. You can also choose **Publish to Markdrop** in the status bar, or the publish button in the editor title bar of a Markdown file.
When it's done, VS Code shows the link with **Open in browser** and **Copy link**.
- **The address** is based on the file name. If that address is taken, Markdrop picks a free one close to it.
- **The title** is the file's first `# Heading`. For HTML it is the ``, then the first `
`. Otherwise it is the file name.
- **Who can open it.** Anyone with the link. Change that on markdrop.in: see [Who can open a link](https://docs.markdrop.in/sharing/who-can-open).
The document belongs to your account. Only you can sync it from VS Code.
### What you can publish
| Files | Publish as |
|---|---|
| `.md`, `.markdown`, `.mdx` | A Markdown document, rendered with diagrams and math. Up to 500,000 characters. |
| `.html` | A live web page at its own link. Up to 25 MB. |
| `.txt`, `.csv`, `.json`, `.svg` | A file that opens rendered for its type: CSV as a table, SVG as an image. Up to 25 MB. |
Use the `.html` extension for web pages: a `.htm` file is published as a Markdown document.
PDFs, Word documents, spreadsheets and zipped sites can't be edited as text, so they don't sync from VS Code. Upload them at [markdrop.in/upload](https://markdrop.in/upload) or with [`markdrop upload`](https://docs.markdrop.in/cli/upload-and-replace).
## How syncing works
**From VS Code to Markdrop.** When you save a linked file, the extension sends it a moment later (0.8 seconds, so quick saves are sent once). Nothing is sent when the content hasn't changed. Line endings and trailing blank lines don't count as changes.
**From Markdrop to VS Code.** While VS Code is the focused window, the extension checks the file in the active editor for changes on markdrop.in every 10 seconds, and again when you switch to that file or back to VS Code. When the web version is newer:
- If you haven't changed the file since it last synced, the extension writes the web version into it.
- If you have, both sides changed. The extension opens a comparison and asks you what to keep. See [Resolve sync conflicts](https://docs.markdrop.in/vscode/conflicts).
Files in other tabs, or in a VS Code window that isn't focused, are checked when you come back to them.
**Sync now.** **Markdrop: Sync now (push)** sends your saved changes and then checks for web changes straight away.
### The status bar
| Status bar | Meaning | Click to |
|---|---|---|
| Markdrop: sign in | You aren't signed in. | Sign in |
| Publish to Markdrop | This file isn't linked yet. | Publish it |
| Markdrop: synced | The file matches its link. | Open it in the browser |
| Markdrop: unsynced | You have changes that haven't been sent. | Open it in the browser |
| Markdrop: conflict | Both sides changed; a choice is waiting. | |
The tooltip on a linked file shows its address and revision.
## Stop syncing a file
Run **Markdrop: Unlink current file**. The file stops syncing; the document on markdrop.in stays as it is. Publish again to make a new link.
If the document is deleted on markdrop.in, the extension unlinks the file the next time it checks and tells you.
## Where links are kept
Links are stored in `.markdrop.json` at the root of the workspace folder. It maps each file's path to its document id, address and last synced revision. Commit it to share the links with your team, or add it to `.gitignore`.
A file opened outside any workspace folder keeps its link in VS Code's own storage on that computer instead.
Sync follows the document's id, not its address, so [renaming its address](https://docs.markdrop.in/documents/addresses) on markdrop.in doesn't break the link.
> **Warning: Sign in with the account that published**
>
> Only the owner can sync a document. If you sign in to the extension as someone else, files linked under the other account can't be found and are unlinked when they are next checked. Their documents on markdrop.in aren't affected.
## Related
- [Resolve sync conflicts](https://docs.markdrop.in/vscode/conflicts)
- [Extension settings and commands](https://docs.markdrop.in/vscode/settings)
- [API tokens](https://docs.markdrop.in/account/api-tokens)
---
# Resolve sync conflicts
Source: https://docs.markdrop.in/vscode/conflicts
A conflict happens when a linked file changed in VS Code and on markdrop.in since it last synced: for example, you edited the file locally while a teammate or an AI app edited the document on the web. The extension never overwrites either side on its own. It shows you both and asks.
## How the extension spots a conflict
Every document on Markdrop has a revision number that goes up with each change. The extension remembers the revision your file last synced at.
- **When you save**, it sends your file along with that revision. If the document on markdrop.in has moved on, Markdrop refuses the change and sends back the current web version instead.
- **When it checks for web changes** and finds a newer revision while your file has changes it hasn't sent yet, it has the same two versions in hand.
Either way, the status bar shows **Markdrop: conflict**, a comparison opens titled "Markdrop (web) ↔ Local" with the web version on the left and your file on the right, and VS Code asks:
> This document changed both on Markdrop and locally.
## Choose what to keep
| Choice | What happens |
|---|---|
| **Keep my local version** | Your file replaces the web version. The web edits are overwritten on markdrop.in. |
| **Use the web version** | The web version replaces your file on disk. Your local edits are discarded. |
| **Merge manually** | Nothing is overwritten yet. Edit your file, using the comparison to bring in what you want from the web, then save. Your saved file becomes the new web version. |
> **Warning: Keep my local version and Use the web version can't be undone from the extension**
>
> Whichever side you don't keep is replaced. If you aren't sure, choose **Merge manually** and copy what you need from the left side of the comparison before you save.
If you close the question without choosing, nothing changes. The conflict comes back the next time you save or the extension checks.
### Merge manually, step by step
### Choose Merge manually
The extension notes that you have seen the web version and says "Edit to merge using the diff, then save to push your merged version."
### Edit your file
In the comparison, the right side is your file and you can edit it there. Copy in the web changes you want from the left side.
### Save
Your merged file is sent and becomes the web version. The status bar returns to **Markdrop: synced**.
If someone changes the document on the web again before you save, you get a new conflict with their latest version.
## Avoid conflicts
- Save often while you work, so changes reach Markdrop quickly.
- Before a long edit of a file others also edit on the web, run **Markdrop: Sync now (push)** so you start from the latest web version.
- If you only ever want VS Code to send changes, turn off `markdrop.autoPull`. Web edits are then not pulled in, and you see the conflict when you next save. See [Settings](https://docs.markdrop.in/vscode/settings).
## Other messages you may see
| Message | Meaning |
|---|---|
| "This document no longer exists on Markdrop — unlinked." | The document was deleted on markdrop.in, or isn't owned by the account you're signed in with. The local file is kept and stops syncing. |
| "Markdrop sync failed" | Markdrop refused the change: for example, the file is over 25 MB, or the document is end-to-end encrypted, which can't sync from VS Code. Changes sent on save fail quietly and the status bar stays on **Markdrop: unsynced**; run **Markdrop: Sync now (push)** to see the message. |
| "Session expired — sign in again." | The API token was revoked. Run **Markdrop: Sign in**. |
---
# VS Code settings and commands
Source: https://docs.markdrop.in/vscode/settings
Markdrop Sync works without any setup beyond signing in. Use these settings to change when it sends and fetches changes.
## Settings
Open **Settings** (⌘, on macOS, Ctrl+, elsewhere) and search for "Markdrop", or add them to `settings.json`.
| Setting | Default | What it does |
|---|---|---|
| `markdrop.pushOnSave` | `true` | Send a linked file to Markdrop when you save it. Turn it off to send only when you run **Markdrop: Sync now (push)**. |
| `markdrop.autoPull` | `true` | Bring changes made on markdrop.in into the local file. Turn it off and web edits stay on the web until you resolve them as a [conflict](https://docs.markdrop.in/vscode/conflicts) on your next save. |
| `markdrop.pollSeconds` | `10` | How often, in seconds, to check the file in the active editor for web changes. The minimum is `5`. |
| `markdrop.apiUrl` | `https://api.markdrop.in` | The Markdrop API. Change it only for your own Markdrop server. |
| `markdrop.webUrl` | `https://markdrop.in` | The Markdrop website, used for sign-in and to open documents. |
```json title="settings.json"
{
"markdrop.pushOnSave": true,
"markdrop.autoPull": true,
"markdrop.pollSeconds": 30
}
```
> **Note**
>
> A new `markdrop.pollSeconds` value takes effect after you reload the window (**Developer: Reload Window**). The other settings apply straight away.
The extension only checks for web changes while VS Code is the focused window, and only for the file in the active editor. A longer interval means fewer requests; switching to a file or back to VS Code always checks at once.
## Commands
Run these from the Command Palette (⌘⇧P on macOS, Ctrl+Shift+P elsewhere).
| Command | What it does |
|---|---|
| Markdrop: Sign in | Sign in through your browser. |
| Markdrop: Sign in with API token | Paste an API token from [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens) instead. |
| Markdrop: Sign out | Forget the token on this computer. To end it everywhere, revoke it on the API tokens page. |
| Markdrop: Publish / link current file | Publish the current file and link it. |
| Markdrop: Sync now (push) | Send your saved changes, then check for web changes. |
| Markdrop: Open current document in browser | Open the linked document on markdrop.in. |
| Markdrop: Unlink current file | Stop syncing this file. The web document stays as it is. |
## Status bar
The Markdrop item on the right of the status bar appears for files the extension can publish. It shows whether you're signed in, whether the file is linked, and whether it is synced. The full list is in [Publish and sync from VS Code](https://docs.markdrop.in/vscode/sync#the-status-bar).
## Sign-in and tokens
The extension keeps its API token in VS Code's Secret Storage, not in your settings or in `.markdrop.json`. Signing out removes it from this computer only. Revoke it on [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens) when you stop using a computer: every VS Code window using that token stops syncing at once.
If Markdrop answers that the token is no longer valid, the extension forgets it and asks you to sign in again.
---
# Limits
Source: https://docs.markdrop.in/account/limits
These are the limits Markdrop applies today. Sizes in MB and GB are binary units (1 MB = 1,024 KB).
## Documents
| What | Limit |
| --- | --- |
| Document length | 500,000 characters |
| Title | 200 characters |
| Custom address (slug) | 3 to 50 characters: letters, numbers, `-` and `_` |
| Read password | Up to 100 characters |
| Expiry | Never, 1 day, 7 days, 30 days, or a date you choose |
Some addresses are reserved because the app uses them, like `settings` or `login`. See [Addresses](https://docs.markdrop.in/documents/addresses).
## Files
| What | Limit |
| --- | --- |
| One file | 25 MB |
| One video or audio file | 2 GB |
| Pasted HTML | 10 MB |
| Storage per account | 20 GB, old versions included |
| Versions kept per file | 5, the current one included |
| Zipped website | 500 files, 100 MB unpacked, 25 MB for any one file |
| Uploads in progress at once | 20 |
When you're signed in, the upload page shows your storage, like "1.2 GB of 20 GB used". If an upload would take you over, Markdrop first drops the oldest old versions of that file; if that isn't enough, the upload is refused and you need to delete files or old versions. See [Replace a file and versions](https://docs.markdrop.in/files/replace-and-versions).
The PDF viewer shows the first 50 pages of a PDF and offers the rest as a download. That's a viewer limit, not an upload limit.
## Sharing
| What | Limit |
| --- | --- |
| People added to one document | 200 |
| Recipients of a peer-to-peer transfer at once | 10 |
| Peer-to-peer file size | No limit (the receiving browser must hold each file in memory) |
## Workspaces
| What | Limit |
| --- | --- |
| Workspaces you own | 10 |
| Members per workspace | 200 |
| Invitations waiting for a reply | 100 per workspace |
| Invitation link | Expires after 7 days |
| Folders | 500 per workspace, nested up to 8 deep, names up to 80 characters |
| Custom domains | 25 per workspace |
| Unverified domain | Dropped after 7 days |
| Logo or favicon upload | 2 MB |
## Sign-in and tokens
| What | Limit |
| --- | --- |
| Sign-in on markdrop.in | Lasts 30 days |
| Email sign-in code | 6 digits, expires after 15 minutes, 5 tries |
| Sign-in on a workspace's own domain | Lasts 12 hours |
| API tokens | No expiry; revoke them in Settings |
| Connected AI apps | Access renews every hour on its own. An app that isn't used for 60 days has to connect again. |
## Request rates
Markdrop limits how fast requests can come in, to keep the service fast for everyone. Most people never reach these. Limits are counted per IP address, except where it says per account.
| What | Limit |
| --- | --- |
| Publishing and changing documents | 10 a minute |
| Reading documents through the API | 60 a minute |
| Creating API tokens | 20 an hour |
| Sending workspace invitations | 20 an hour |
| AI assistant (MCP) changes | 60 a minute, per account |
| AI assistant (MCP) reads | 300 a minute, per account |
If you go over, the request is refused with HTTP status `429`. Wait a moment and try again.
---
# API tokens
Source: https://docs.markdrop.in/account/api-tokens
An API token lets a program act as you: the VS Code extension, the Markdrop CLI, a script, or a call to the API. Tokens start with `mdk_`.
Manage them at [Settings › API tokens](https://markdrop.in/settings/tokens).
> **Warning: Treat a token like a password**
>
> A token can do anything your account can: publish, change and delete your documents, and manage your workspaces. Keep it out of shared files and repositories. If one leaks, revoke it.
## Create a token
### Open API tokens
Go to [Settings › API tokens](https://markdrop.in/settings/tokens) and choose **Create token**.
### Name it
Name it after the device or script that will use it, like `My laptop` or `Nightly report`. Names can be up to 80 characters.
### Copy it now
Choose **Create token**, then **Copy token**. This is the only time the full token is shown. Markdrop keeps only a hash of it, so it can't show it to you again.
Make one token for each device or script. Then you can revoke one without breaking the others.
> **Tip: VS Code makes its own**
>
> You don't need to create a token for the VS Code extension. Install Markdrop Sync, run **Markdrop: Sign in**, and approve in the browser. Markdrop creates a token named "VS Code" for you. See [Sync with VS Code](https://docs.markdrop.in/vscode/sync).
## Use a token
Send it in the `Authorization` header:
```bash title="Check who the token belongs to"
curl https://api.markdrop.in/api/v1/auth/me \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
The CLI reads the same token from the `MARKDROP_TOKEN` environment variable. See [Sign in and tokens](https://docs.markdrop.in/cli/login-and-tokens).
Tokens don't expire. A revoked or mistyped token gets `401 Invalid API token`; it's never treated as a signed-out visitor.
> **Note: AI assistants use a different sign-in**
>
> The hosted MCP server at `https://api.markdrop.in/mcp` doesn't accept API tokens. AI apps connect with their own sign-in, and you approve each one. See [Connected apps](https://docs.markdrop.in/account/connected-apps). The local MCP server in the CLI does use an API token.
## See and revoke tokens
The list shows each token's **Name**, the start of the **Token**, when it was **Created**, and when it was **Last used** ("Never" if it hasn't been).
To revoke one, open its menu and choose **Revoke**, then **Revoke token**. Anything using it stops working at once; a VS Code extension using it stops syncing. This can't be undone. Create a new token if you need one again.
---
# Connected apps
Source: https://docs.markdrop.in/account/connected-apps
Connected apps are AI apps you've allowed to use your Markdrop account, like Claude, ChatGPT or Cursor. Each one connected through Markdrop's MCP server, and you approved it on Markdrop.
Manage them at [Settings › Connected apps](https://markdrop.in/settings/connections). To connect a new one, see [Hosted MCP server](https://docs.markdrop.in/ai/hosted-mcp).
## What an app can do
When an app connects, Markdrop asks you to approve what it may do. It can ask for one or both:
| Shown as | What it allows |
| --- | --- |
| **Read** | Read your documents: see your documents and files, their titles, contents, links and view counts. |
| **Publish and edit** | Publish and change documents: publish, update, change access to and delete documents and files for you. |
An app gets only what you approved, never your whole account.
## See your apps
Each row shows the app's name and web address, badges for what it may do, when you connected it, and when it was last used ("Not used yet" if it hasn't been).
## Disconnect an app
Open the app's menu and choose **Disconnect**, then confirm. The app loses access to your Markdrop account, and everything it was given to stay signed in is revoked. What it published stays yours, with the same links.
To use it again, connect it from the app and approve it on Markdrop again.
## How long access lasts
An app's access renews every hour on its own while you use it. If an app isn't used for 60 days, it has to connect again.
## Not listed here
- **The VS Code extension** and **the CLI** use [API tokens](https://docs.markdrop.in/account/api-tokens). Revoke them on the API tokens page.
- **Google Docs** is connected and disconnected from your documents dashboard. Choose **Disconnect** next to "Google Docs is connected." Docs you already exported stay in your Google Drive but stop syncing.
---
# How files are served
Source: https://docs.markdrop.in/security/how-files-are-served
Markdrop shows two kinds of things: documents written in Markdown, and files you upload, like HTML pages, PDFs, spreadsheets and videos. Files can contain anything, including scripts, so Markdrop serves them in a way that keeps them away from your account.
## Files open from a separate site
Documents render on markdrop.in. Uploaded files don't: they're served from a separate site, a different domain from markdrop.in, and shown inside a frame on the document page.
That separation is the main protection. Your browser keeps your Markdrop sign-in, and the edit keys and passwords you've used, in storage that belongs to markdrop.in. A script in an uploaded HTML page runs on the other site, so it can't read any of that.
The frame is sandboxed, and the file site adds protective headers to every response: browsers must not guess file types, links don't pass on the page's address, and search engines are asked not to index files.
## Only safe types open in the page
Markdrop accepts a fixed list of file types (see [Supported file types](https://docs.markdrop.in/files/supported-types)). The file site displays those it knows how to show safely. Anything else is offered as a download instead of opening in the browser.
## Protected files use short-lived links
A file is public only when all of these are true:
- it's set to **Anyone with the link**,
- it has no password,
- its workspace isn't [Locked](https://docs.markdrop.in/workspaces/privacy#the-three-presets), and
- it hasn't expired.
Anything else is protected. When someone who's allowed in opens a protected file, Markdrop gives their browser a signed link that works only for that file and expires after 15 minutes, or 4 hours for video and audio so playback isn't cut off. Without a valid signed link, the file site refuses to serve a protected file.
When you narrow who can open a file, for example by adding a password, making it private, or locking the workspace, every signed link already handed out stops working at once.
> **Note: A short cache window**
>
> Public files may be cached for up to 5 minutes. If you narrow access to a file that was public, a cached copy can still be served for up to 5 minutes.
Old versions of a file are never public. See [Replace a file and versions](https://docs.markdrop.in/files/replace-and-versions).
## Passwords, edit keys and encryption
- **Read passwords** are stored as bcrypt hashes, never as the password itself. Too many wrong guesses for a document from one place are refused for a while.
- **Edit keys** (they start with `sk_`) are stored as bcrypt hashes. You can rotate one if it leaks. See [Your edit key](https://docs.markdrop.in/documents/edit-key).
- **End-to-end encrypted documents** are encrypted in your browser with AES-256-GCM. The key is in the part of the link after `#k=`, which browsers never send to a server, so Markdrop stores only ciphertext and can't read it. See [End-to-end encryption](https://docs.markdrop.in/sharing/end-to-end-encryption).
## Workspace logos and favicons
Branding images are decoded and re-encoded as fresh PNG files before they're stored. Only the pixels survive, so a file can't smuggle anything else through. See [Brand your workspace](https://docs.markdrop.in/workspaces/branding).
## Custom domains
A workspace's own domain serves documents only after its owner proves control of the domain with a DNS record, and Markdrop re-checks that every day. A domain whose records stop pointing at Markdrop stops serving after three days. Readers who sign in there get a sign-in that works only on that domain, can only read, and lasts 12 hours. See [Sign-in on your domain](https://docs.markdrop.in/workspaces/sign-in-on-your-domain).
---
# Privacy at Markdrop
Source: https://docs.markdrop.in/security/privacy
This page explains, in plain terms, what Markdrop keeps and why. The [privacy policy](https://markdrop.in/privacy) is the formal version.
## What you publish
Documents and files you publish are stored so the link can show them. Anyone with a link set to **Anyone with the link** can open it. Private, password-protected and workspace items are checked before they open. See [Who can open a link](https://docs.markdrop.in/sharing/who-can-open).
[End-to-end encrypted](https://docs.markdrop.in/sharing/end-to-end-encryption) documents are encrypted in your browser. Markdrop stores only ciphertext. The key is in the part of the link after `#`, which browsers never send to a server.
Documents you set to expire are deleted when they expire. You can delete anything you own from your dashboard.
## Your account
Publishing Markdown needs no account. Uploading files, workspaces and the dashboard need a free one. Markdrop keeps your email address, your name and picture if you sign in with Google, and the documents you own.
There's no delete button in Settings yet. To delete your account and everything in it, email Markdrop from the address you signed in with, using the contact on the [privacy policy](https://markdrop.in/privacy).
## What your readers' visits record
When someone opens your link, Markdrop counts the view for your analytics. It stores a salted hash of their IP address, never the address itself, and the approximate location looked up from it. You see totals and countries, never who someone is. Your own views aren't counted.
Reading and watching sessions are deleted after 395 days. [What gets counted](https://docs.markdrop.in/analytics/what-gets-counted) has the full list.
## How Markdrop measures its own site
Markdrop uses three kinds of analytics to understand how the product is used and what to fix:
- **Its own analytics**: page views, time on page, where visits come from, and product events such as "published". No cookies: a random id in your browser's storage. Your IP address is used once to look up your country and city and isn't stored. Kept for about 13 months.
- **PostHog**: product analytics and session recordings. When you sign in, your visits are linked to your account's email.
- **Google Analytics**: aggregate traffic reports.
PostHog and Google Analytics never receive:
- document and file links (a document is reported as an anonymous reference, never its address),
- anything after `#` in a link, including encryption keys and sign-in or invite tokens,
- what you write: editors, previews, document pages and lists of your documents aren't recorded, and every text field is masked,
- the contents or names of files you send peer to peer.
PostHog and Google Analytics run on markdrop.in only, never on a workspace's own domain.
## Cookies and your choices
Markdrop's own analytics set no cookies. PostHog and Google Analytics do.
If you're in the EU, the EEA, the UK or Switzerland, Markdrop asks first. Until you accept, PostHog counts your visit without cookies and records nothing, and Google Analytics doesn't load.
If your browser sends **Global Privacy Control** or **Do Not Track**, Markdrop treats that as a no. When you open someone's document, Global Privacy Control also means no reading or watching data goes to its owner's analytics. Your view still counts, without where you came from.
To change your choice, choose **Privacy choices** in the site's footer.
## Sending files peer to peer
Files go straight from your device to the other person's and are never stored on Markdrop's servers. If a network blocks a direct path, an encrypted relay forwards the bytes and can't read them. Markdrop keeps the name, type and size of the files, how many there were, a hashed IP, your account if you're signed in, and connection details (route, timing, browser). See [Send files peer to peer](https://docs.markdrop.in/share/send-files).
## Who processes data for Markdrop
- Vercel (website hosting) and Amazon Web Services in Mumbai (the API).
- MongoDB Atlas (database) and Cloudflare (file storage, the file viewer and the peer-to-peer relay).
- Resend (sign-in and notification email).
- PostHog and Google (analytics, as above).
Markdrop doesn't sell personal data, and doesn't use it for advertising.
## Email
Markdrop sends sign-in codes, notifications you trigger, and occasional product updates. Every update has an unsubscribe link. The [weekly analytics email](https://docs.markdrop.in/analytics/weekly-email) has its own switch in Settings.
---
# Troubleshooting
Source: https://docs.markdrop.in/help/troubleshooting
Find the message you see, or the thing that isn't working. Each answer says what it means and what to do.
## Signing in
**I didn't get the sign-in code**
Check your spam or junk folder, and that you typed the address correctly. Choose **Resend code** to get a new one. Codes are 6 digits and expire after 15 minutes. You can also choose **Continue with Google** instead.
**Too many tries in a short time**
You've entered too many wrong codes or asked for too many codes in a row. Wait a minute and try again. Each code allows 5 tries.
**That sign-in link has expired**
Sign-in links work once and only for a short time. Enter your email to get a new one.
**Your Google email isn't verified**
Verify the address with Google first, or sign in with your email and a code instead.
## A link won't open
**Nothing at this link**
The document may have expired or been deleted, or the address has a typo. Ask the person who sent it for a fresh link. If you own it, check its expiry in your dashboard.
**This document is private**
It's shared with specific people. Sign in with the address it was shared with and it opens.
**You don't have access**
The document is shared with specific people, and the account you're signed in with isn't one of them. Most often, you're signed in with a personal address and it was shared with your work address, or the other way round. Sign out and sign in with the other address, or ask the owner for access. See [Request access](https://docs.markdrop.in/sharing/request-access).
**This document is password protected**
Enter the password you were given. After too many wrong passwords, Markdrop asks you to wait a few minutes before trying again.
**It opens on markdrop.in but not on our own domain**
On a workspace's own domain you sign in separately. Choose **Sign in** in the domain's header. See [Sign-in on your domain](https://docs.markdrop.in/workspaces/sign-in-on-your-domain).
## Uploading files
**That file is too big**
One file can be up to 25 MB, and video and audio up to 2 GB. For bigger things, send them [peer to peer](https://docs.markdrop.in/share/send-files) instead. See [Limits](https://docs.markdrop.in/account/limits).
**Storage quota exceeded**
Your account holds up to 20 GB, and old versions of files count toward it. Delete files you no longer need, or old versions, then try again. See [Replace a file and versions](https://docs.markdrop.in/files/replace-and-versions).
**My file downloads instead of opening**
Markdrop shows a fixed list of file types in the page. Anything else is offered as a download. See [Supported file types](https://docs.markdrop.in/files/supported-types).
## Workspaces and domains
**The invitation says it was sent to a different address**
Workspace invitations work only for the address they were sent to. Sign in to Markdrop as that address, then open the link again.
**This invitation has expired**
Invitations last 7 days. Ask an admin of the workspace to choose **Resend**.
**My domain won't verify**
See [If it doesn't verify](https://docs.markdrop.in/workspaces/custom-domain#if-it-doesnt-verify) on the custom domain page. The most common cause is a DNS panel that adds your domain to the record name a second time.
**My domain shows Lapsed**
Its DNS records stopped pointing at Markdrop for three days, so it stopped serving. Put both records back, then choose **Check DNS again**. Links on markdrop.in kept working throughout.
**I can't change a setting: it says Admins only**
That setting needs the Admin role. Ask the workspace's owner or an admin. See [Members and roles](https://docs.markdrop.in/workspaces/members-and-roles).
## Analytics
**My own visits are showing up**
Your own views are left out only while you're signed in. Sign in on the browser you use to check your links.
**My numbers look lower than expected**
Views from your workspace's members are hidden by default; turn off **Hide views from … members** on the item's **Audience** tab to include them. Bots and link previews aren't counted as views. See [What gets counted](https://docs.markdrop.in/analytics/what-gets-counted).
**There's no reading or watching data for some visits**
Visitors whose browsers send Global Privacy Control don't send reading or watching data. Very short visits (under 2 seconds of activity, with nothing played) aren't kept as sessions.
## Sending files peer to peer
See [Troubleshooting peer-to-peer transfers](https://docs.markdrop.in/share/troubleshooting) for every message, and what to try when a transfer is slow.
## Too many requests
If the API or an app answers with HTTP status `429`, you've sent too many requests in a short time. Wait a moment and try again. See [Limits](https://docs.markdrop.in/account/limits#request-rates).
## Still stuck?
Check the [FAQ](https://docs.markdrop.in/help/faq). For anything about your account or your data, use the contact on the [privacy policy](https://markdrop.in/privacy).
---
# Frequently asked questions
Source: https://docs.markdrop.in/help/faq
## Accounts
**Do I need an account?**
Not to write and publish a document. You need a free account to upload files, use workspaces, keep what you publish together in a dashboard, and see analytics. Sending files peer to peer needs no account on either side. See [Accounts](https://docs.markdrop.in/get-started/accounts).
**How do I sign in?**
With Google, or with your email: Markdrop emails you a code each time, with no password. Your first sign-in creates the account.
**How do I delete my account?**
There's no delete button in Settings yet. Email Markdrop from the address you signed in with, using the contact on the [privacy policy](https://markdrop.in/privacy).
## Sharing and access
**Can workspace admins open my private items?**
No. Items set to "Only people added" open only for their owner and the people added by name. No workspace role opens them, not even Admin. See [Workspace privacy](https://docs.markdrop.in/workspaces/privacy#what-each-item-setting-means).
**If I share a folder, does it open everything inside?**
No. A folder link never opens anything by itself. Each visitor sees only the items they could already open. See [Share a folder](https://docs.markdrop.in/workspaces/share-a-folder).
**What happens to my files if I leave a workspace, or it's deleted?**
You keep them. When you leave, files you added stay in the workspace and you still own them. When a workspace is deleted, every file goes back to the person who owns it, with the same link and analytics; anything that was open to the team becomes "Only people added".
## Domains
**Will my markdrop.in links break when I add my own domain?**
No. markdrop.in links keep working. Your domain is an extra address for the same documents.
**Can I use my apex domain, like acme.com?**
Yes. An apex domain uses an A record pointing at `76.76.21.21` instead of a CNAME. A subdomain like `docs.acme.com` is the usual choice. See [Put your workspace on your own domain](https://docs.markdrop.in/workspaces/custom-domain).
**How many domains can a workspace have?**
Up to 25. One of them is the default, used for shared links and preview cards; every live domain serves.
## Analytics
**Can I see who opened my document?**
No. You see totals and patterns: views, people, how far they read, countries and devices. Markdrop never shows you who someone is. See [What gets counted](https://docs.markdrop.in/analytics/what-gets-counted).
**Do my own views count?**
Not while you're signed in. If you open your own link signed out, Markdrop can't tell it's you, and the view counts.
**Who can see a document's analytics?**
Only its owner. Workspace admins don't see analytics for documents they don't own.
## Sending files peer to peer
**Are files I send peer to peer stored on Markdrop?**
No. They go straight from your device to the other person's, encrypted. If the networks can't reach each other directly, an encrypted relay forwards the bytes and can't read them. See [Send files peer to peer](https://docs.markdrop.in/share/send-files).
**How big a file can I send peer to peer?**
There's no limit, but the receiving browser holds each file in memory, so very large files are easier to receive with the [Markdrop CLI](https://docs.markdrop.in/cli/send-and-receive), which writes straight to disk.
**Can I close the tab while someone downloads?**
No. The files come from your device, so closing or reloading the tab ends the share.
## Your data
**Does Markdrop store my readers' IP addresses?**
No. It stores a salted hash of the IP address, and the approximate location looked up from it. See [Privacy at Markdrop](https://docs.markdrop.in/security/privacy).
**Can an uploaded HTML page read my Markdrop account?**
No. Uploaded files open from a separate site, inside a sandboxed frame, so their scripts can't reach your sign-in or anything else markdrop.in keeps in your browser. See [How files are served](https://docs.markdrop.in/security/how-files-are-served).
---
# Install the markdrop CLI
Source: https://docs.markdrop.in/cli/install
The markdrop CLI is a single binary for macOS, Linux and Windows. It sends files peer to peer (`send`, `get`), publishes files behind a link you can replace later (`upload`, `replace`), and runs a local MCP server for AI apps on your computer (`mcp`). The current release is 0.5.0.
## Install
**macOS**
With Homebrew:
```bash title="Install with Homebrew"
brew install himanshkukreja/tap/markdrop
```
Without Homebrew, run the install script. It downloads the latest release for your Mac (Apple silicon or Intel) and puts `markdrop` in `/usr/local/bin` when it can write there (or use `sudo` without a password prompt), otherwise in `~/bin`.
```bash title="Install with the script"
curl -fsSL https://github.com/himanshkukreja/markdrop/releases/latest/download/install.sh | sh
```
**Windows**
In PowerShell:
```powershell title="Install with PowerShell"
irm https://github.com/himanshkukreja/markdrop/releases/latest/download/install.ps1 | iex
```
The script installs `markdrop.exe` to `%LOCALAPPDATA%\markdrop` and adds that folder to your user `PATH`. Open a new terminal before you run `markdrop`.
Windows builds are 64-bit x86 (amd64) only. There is no build for Windows on Arm yet.
**Linux**
Run the install script. It picks the build for your machine (x86-64 or arm64) and puts `markdrop` in `/usr/local/bin` when it can write there (or use `sudo` without a password prompt), otherwise in `~/bin`.
```bash title="Install with the script"
curl -fsSL https://github.com/himanshkukreja/markdrop/releases/latest/download/install.sh | sh
```
Prefer a package? Every release has `.deb`, `.rpm` and `.apk` files for `amd64` and `arm64`. Download the one for your system from the [releases page](https://github.com/himanshkukreja/markdrop/releases/latest), then install it:
```bash title="Install a downloaded package"
sudo apt install ./markdrop_0.5.0_linux_amd64.deb # Debian, Ubuntu
sudo dnf install ./markdrop_0.5.0_linux_amd64.rpm # Fedora, RHEL
sudo apk add --allow-untrusted ./markdrop_0.5.0_linux_amd64.apk # Alpine
```
Markdrop doesn't run an apt or dnf repository, so packages don't update themselves. Download the new package to update.
## Check it works
```bash
markdrop --version
```
```text
markdrop version 0.5.0
```
If your shell says `markdrop: command not found`, the folder it was installed to isn't on your `PATH`. The install script prints where it put the binary. When it falls back to `~/bin`, add that folder to your `PATH`:
```bash
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
```
Use `~/.zshrc` instead of `~/.bashrc` if your shell is zsh.
## Update
| You installed with | Update with |
|---|---|
| Homebrew | `brew upgrade markdrop` |
| The install script | Run the same script again. It always fetches the latest release. |
| A `.deb`, `.rpm` or `.apk` package | Download and install the newer package. |
## Download a binary yourself
Each [GitHub release](https://github.com/himanshkukreja/markdrop/releases/latest) has these files, where `0.5.0` is the version:
| File | For |
|---|---|
| `markdrop_0.5.0_darwin_arm64.tar.gz` | macOS on Apple silicon |
| `markdrop_0.5.0_darwin_amd64.tar.gz` | macOS on Intel |
| `markdrop_0.5.0_linux_amd64.tar.gz`, `markdrop_0.5.0_linux_arm64.tar.gz` | Linux |
| `markdrop_0.5.0_windows_amd64.zip` | Windows (x86-64) |
| `checksums.txt` | SHA-256 of every file above |
Check the download against `checksums.txt` before you run it:
```bash title="Linux"
sha256sum --ignore-missing -c checksums.txt
```
```bash title="macOS"
shasum -a 256 markdrop_0.5.0_darwin_arm64.tar.gz
# compare with the matching line in checksums.txt
```
Then extract the archive and move `markdrop` (or `markdrop.exe`) to a folder on your `PATH`.
## Uninstall
1. Run `markdrop logout` to delete the API token saved on this computer, if you saved one. See [Sign in and API tokens](https://docs.markdrop.in/cli/login-and-tokens).
2. Remove the binary: `brew uninstall markdrop`, or delete `/usr/local/bin/markdrop` or `~/bin/markdrop`. On Windows, delete the `%LOCALAPPDATA%\markdrop` folder.
## Next steps
**[Sign in](https://docs.markdrop.in/cli/login-and-tokens)**
Save an API token so `upload` and `replace` can publish as you.
**[Publish a file](https://docs.markdrop.in/cli/upload-and-replace)**
Put a video, PDF or HTML page behind a link and update it later.
**[Send a file](https://docs.markdrop.in/cli/send-and-receive)**
Send a file or folder straight to another computer.
**[Every command](https://docs.markdrop.in/cli/reference)**
Flags, defaults, environment variables and exit codes.
---
# Sign in with an API token
Source: https://docs.markdrop.in/cli/login-and-tokens
The CLI acts as you through an API token, a long string that starts with `mdk_`. Commands that publish or look up your files need one. Peer-to-peer `send` and `get` work without it.
| Needs a token | Works without one |
|---|---|
| `upload`, `replace`, `versions`, `restore`, `info`, `workspaces` | `send`, `get` |
| The file and workspace tools of `markdrop mcp` | The peer-to-peer tools of `markdrop mcp` |
## Create a token
### Open API tokens
Go to [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens) while signed in.
### Create it
Choose **Create token**, name it after the device or script that will use it (for example "My laptop" or "CI"), and choose **Create token**.
### Copy it now
The full token is shown once. Choose **Copy token** and keep it somewhere safe until you've saved it in the CLI.
> **Warning: A token is your whole account**
>
> An API token can do anything you can do on Markdrop: publish, change access, delete. Make one token per device or script, so you can revoke one without breaking the others, and never commit a token to a repository.
## Save it on this computer
```bash
markdrop login
```
Paste the token when asked. Nothing is shown while you paste. The CLI checks the token with Markdrop and prints the account it belongs to:
```text
✓ Logged in as you@example.com. Token saved to /Users/you/Library/Application Support/markdrop/token
```
If Markdrop rejects the token, nothing is saved. If Markdrop can't be reached, the token is saved anyway and checked on first use.
You can also pass the token without the prompt:
```bash
markdrop login --token mdk_••••
printf '%s\n' "$MY_TOKEN" | markdrop login # read from a pipe
```
### Where the token is kept
The token is stored in a file only your user can read (mode `0600`, in a folder with mode `0700`):
| System | File |
|---|---|
| macOS | `~/Library/Application Support/markdrop/token` |
| Linux | `~/.config/markdrop/token` (or `$XDG_CONFIG_HOME/markdrop/token`) |
| Windows | `%AppData%\markdrop\token` |
Set `MARKDROP_CONFIG_DIR` to keep it somewhere else. The file is then `$MARKDROP_CONFIG_DIR/token`.
## Use a token without saving it
Set `MARKDROP_TOKEN` in the environment, or pass `--token` to a single command:
```bash
export MARKDROP_TOKEN=mdk_••••
markdrop upload demo.mp4
markdrop upload demo.mp4 --token mdk_••••
```
When more than one is set, the CLI uses the first it finds in this order:
1. `--token` on the command
2. `MARKDROP_TOKEN`
3. The token saved by `markdrop login`
`markdrop login` warns you when `MARKDROP_TOKEN` is set, because that one wins over the token it saved.
## Use it in CI
Store the token as a secret in your CI system and expose it as `MARKDROP_TOKEN`. For example, in GitHub Actions:
```yaml title=".github/workflows/publish.yml"
- name: Publish the demo video
env:
MARKDROP_TOKEN: ${{ secrets.MARKDROP_TOKEN }}
run: |
curl -fsSL https://github.com/himanshkukreja/markdrop/releases/latest/download/install.sh | sh
markdrop replace demo-clip build/demo.mp4 --json
```
In scripts, `--json` prints one JSON object on standard output and nothing else. See [Upload and replace files](https://docs.markdrop.in/cli/upload-and-replace#use-it-in-scripts).
## Sign out
```bash
markdrop logout
```
This deletes the saved token from this computer. It doesn't revoke the token: anyone who copied it can still use it. To end a token everywhere, open [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens), choose **Revoke** on its row and confirm with **Revoke token**. Anything using it, such as a VS Code window or a CI job, stops working at once.
## If something goes wrong
**“no Markdrop API token: set MARKDROP_TOKEN (or run `markdrop login`)”**
The command needs a token and found none in `--token`, `MARKDROP_TOKEN` or the saved file. Run `markdrop login`, or set `MARKDROP_TOKEN`.
**“the API token was rejected”**
The token was revoked, mistyped or belongs to another server. Create a new one at [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens) and run `markdrop login` again. If `MARKDROP_TOKEN` is set in your shell, it is the one being used: unset it or update it.
**Can I use the same token for the AI connector on claude.ai or ChatGPT?**
No. The hosted MCP server at `https://api.markdrop.in/mcp` only accepts the sign-in each AI app gets through Markdrop's own approval screen, and refuses `mdk_` tokens. API tokens are for the CLI, the VS Code extension, the local MCP server and the REST API. See [Connect an AI app](https://docs.markdrop.in/ai/hosted-mcp).
More about tokens on the web: [API tokens](https://docs.markdrop.in/account/api-tokens).
---
# Upload and replace files from the terminal
Source: https://docs.markdrop.in/cli/upload-and-replace
`markdrop upload` publishes a file from your computer and prints its link. `markdrop replace` puts a new file behind a link you already shared: the link, its access setting, title and view count stay the same, and the previous file is kept as a version you can bring back.
## Publish a file
```bash
markdrop upload demo.mp4
```
```text
✓ Published demo (212.4 MB)
https://markdrop.in/demo
```
Standard output carries only the link, so you can capture it:
```bash
url=$(markdrop upload demo.mp4)
```
By default the address comes from the file name, the title is the file name, and anyone with the link can open it. Change any of that with flags:
```bash
markdrop upload demo.mp4 --slug q3-demo # https://markdrop.in/q3-demo
markdrop upload report.pdf --title "Q3 report" --access private
markdrop upload deck.html --password •••••••• # --access password is implied
markdrop upload spec.pdf --workspace Acme --folder guides/setup
```
| Flag | What it does |
|---|---|
| `--title` | Title shown on the page. Default: the file name. |
| `--access` | Who can open it: `link` (anyone with the link, the default), `private` (only you), `password`, or `workspace` (members of the workspace; the default with `--workspace`). |
| `--password` | The password for `--access password`. Giving a password without `--access` sets `password` for you. |
| `--slug` | A custom address, `markdrop.in/`: 3 to 50 letters, digits, `-` or `_`. If it is taken, the command fails before any data is sent. It is never renamed silently. |
| `--workspace`, `--folder` | File it in one of your workspaces and an existing folder there. See [Workspaces and folders](https://docs.markdrop.in/cli/workspaces). |
| `--json` | Print the result as one JSON object. |
| `--token` | Use this API token for this command only. |
To learn what each access level means for the people you send the link to, see [Who can open a link](https://docs.markdrop.in/sharing/who-can-open).
## What you can upload
The CLI uploads the file types Markdrop renders: PDF, HTML, images, spreadsheets, Word documents, text and JSON, video and audio. The full list is in [Supported file types](https://docs.markdrop.in/files/supported-types).
| Limit | Value |
|---|---|
| Video and audio files | Up to 2 GB each |
| Every other type | Up to 25 MB each |
| Storage per account | 20 GB, earlier versions included |
| Versions kept per file | 5, including the current one |
Zipped websites can't be uploaded from the CLI. Upload them at [markdrop.in/upload](https://markdrop.in/upload). More in [File limits](https://docs.markdrop.in/files/limits).
## How an upload runs
1. **Hashing.** The CLI computes the file's SHA-256 on your computer.
2. **Uploading.** It sends the file straight to storage in parts, four at a time, and retries a part that fails. Upload addresses that expire during a long upload are renewed on their own.
3. **Verifying.** Markdrop joins the parts and checks the size and SHA-256 against what the CLI computed. The link goes live only if they match. If they don't, the command fails and nothing changes on Markdrop.
When standard error is a terminal, a progress line shows each phase, the bytes sent and the speed. In scripts and CI, only the result is printed.
### Resume an interrupted upload
If an upload stops part way (you press Ctrl+C, the network drops, the laptop sleeps), run the same command again:
```bash
markdrop upload demo.mp4
```
```text
Resuming the upload: 4 of 7 parts already on the server.
```
Only the parts Markdrop doesn't have yet are sent. "The same command" means the same file, unchanged, with the same options. An upload can be resumed for 24 hours after it started; after that the CLI says the session has expired and starts over.
The CLI remembers unfinished uploads in your user cache folder. It stores no secrets there.
| System | Folder |
|---|---|
| macOS | `~/Library/Caches/markdrop/uploads` |
| Linux | `~/.cache/markdrop/uploads` |
| Windows | `%LocalAppData%\markdrop\uploads` |
Set `MARKDROP_CACHE_DIR` to use another folder (the CLI adds `uploads` to it).
## Replace the file behind a link
```bash
markdrop replace https://markdrop.in/demo demo-v2.mp4
```
```text
✓ Replaced demo — version 2, 220.1 MB. The previous version is kept (markdrop versions demo).
https://markdrop.in/demo
version 2
```
Everyone who has the link now gets the new file. You can name the file by its link (on markdrop.in or on a workspace's own domain) or by its slug alone: `markdrop replace demo demo-v2.mp4`. Add `--title` to change the title at the same time.
Replacing works like uploading: hashed, sent in parts, verified, and resumable by running the same command again. If the check fails, the link keeps serving the old file.
You can replace a file you own, a file shared with you as an editor, or a file in your workspace that isn't private. The new version counts against the owner's storage. When that storage is full you get an error, and the owner's earlier versions are left alone.
> **Note: Markdown documents and zipped sites**
>
> `replace` is for files. A Markdown document is changed by editing it on markdrop.in, from [VS Code](https://docs.markdrop.in/vscode/sync), or with an AI app's `update_document` tool. A zipped website has no versions: replace it from the Markdrop website.
## See and restore versions
```bash
markdrop versions demo
```
```text
VERSION SIZE TYPE SHA256 CREATED
* 2 220.1 MB video/mp4 4be1a0c9d2f3 2026-10-29 14:02 UTC
1 212.4 MB video/mp4 9f2c1e7a55b0 2026-10-27 09:15 UTC
```
The `*` marks the version the link serves now. Markdrop keeps up to five versions of a file; when a sixth arrives, the oldest is dropped.
To go back:
```bash
markdrop restore demo 1
```
```text
✓ Restored version 1 of demo as version 3.
https://markdrop.in/demo
version 3
```
A restore never deletes anything: the old file comes back as a new version, so you can undo a restore the same way. `v1` works as well as `1`.
## Check a published file
```bash
markdrop info demo
```
```text
https://markdrop.in/demo
slug demo
type video/mp4
size 220.1 MB
version 3
sha256 9f2c1e7a55b0…
updated 2026-10-29 14:20 UTC
created 2026-10-27 09:15 UTC
media 3840×2160 · 42.5s · h264/aac
```
For video and audio, `media` shows the resolution, length and codecs when Markdrop could read them, and `poster` the address of the still frame shown before playback.
## Use it in scripts
With `--json`, `upload`, `replace`, `versions`, `restore` and `info` print exactly one JSON object on standard output and nothing else. Errors go to standard error, and the exit code is `1`.
```bash
markdrop upload demo.mp4 --json
```
```json
{"url":"https://markdrop.in/demo","markdrop_url":"https://markdrop.in/demo","workspace_url":null,"workspace_error":null,"slug":"demo","version":1,"size_bytes":222717952,"content_type":"video/mp4","sha256":"9f2c…"}
```
`url` is the address to share: the workspace's custom domain when the file is in a workspace with a live one, otherwise markdrop.in. `markdrop_url` is always the markdrop.in address.
```bash
slug=$(markdrop upload build/report.pdf --json | jq -r .slug)
markdrop info "$slug" --json | jq .sha256
```
## Related
- [Replace a file and keep its link](https://docs.markdrop.in/files/replace-and-versions) on the web
- [Upload large files](https://docs.markdrop.in/files/large-uploads)
- [Publish files from an AI app on your computer](https://docs.markdrop.in/ai/local-mcp)
- [All CLI flags](https://docs.markdrop.in/cli/reference)
---
# Publish into workspaces and folders
Source: https://docs.markdrop.in/cli/workspaces
A file you upload can go straight into one of your workspaces, and into a folder there. When the workspace has a live custom domain, the link you get is on that domain.
## List your workspaces
```bash
markdrop workspaces
```
```text
ID NAME ROLE DOMAINS
6719f0c2a8e4b3d1c5f60a12 Acme admin docs.acme.com
671a22b9d0c4e8f7a1b3c540 Notes owner -
```
`ROLE` is your role in that workspace: `viewer`, `member`, `admin` or `owner`. `DOMAINS` lists the custom domains that serve documents right now. A domain that is still being verified, or has lapsed, isn't listed.
## List a workspace's folders
Give the workspace's id, its name or one of its domains:
```bash
markdrop workspaces Acme
markdrop workspaces docs.acme.com
```
```text
PATH NAME ITEMS ID
guides Guides 12 671b03e1f2a9c7d4b8e60f31
guides/setup Setup 4 671b03f4a7c2e9d1b0f58a62
releases Release 7 671b0402c9e1f7a3d5b26c94
```
`PATH` is what `--folder` takes. `ITEMS` counts what you can open in the folder and the folders inside it. A name matches without regard to case. If two of your workspaces have the same name, the CLI asks you to use the id.
## Upload into a folder
```bash
markdrop upload spec.pdf --workspace Acme --folder guides/setup
```
```text
✓ Published spec (1.8 MB)
also at https://markdrop.in/spec
https://docs.acme.com/guides/setup/spec
```
- `--workspace` takes an id, a name or a domain, as above.
- `--folder` takes a path such as `guides/setup`, or a folder id. The folder must already exist. Create folders on markdrop.in, see [Folders](https://docs.markdrop.in/workspaces/folders), or ask an AI app to use the `create_folder` tool.
- Without `--folder`, the file goes to the top level of the workspace.
The link printed on standard output is the one to share. It is on the workspace's custom domain when it has a live one. The markdrop.in address is printed on standard error as "also at". Both open the same file with the same access: a document has one access level at every address.
### Who can open it
With `--workspace` and no `--access`, the file is open to the workspace's members. Choose something else with `--access`:
```bash
markdrop upload launch.mp4 --workspace Acme --access link # anyone with the link
markdrop upload draft.pdf --workspace Acme --access private # only you
```
What "members" includes depends on the workspace's privacy settings. See [Workspace privacy](https://docs.markdrop.in/workspaces/privacy) and [Who can open a link](https://docs.markdrop.in/sharing/who-can-open).
### Roles
You need to be a member, an admin or the owner of the workspace to add files to it. Viewers can't add files.
## If filing fails
The CLI looks up the workspace before it sends any data, and Markdrop checks the folder when the upload starts. If filing still fails at the end (for example, the folder was deleted during a long upload, or your role changed), the file is uploaded but stays private to you, and the CLI prints the reason after a `!`:
```text
✓ Published spec (1.8 MB)
! The file was uploaded but couldn't be added to the workspace (…). It is private to you.
https://markdrop.in/spec
```
Fix the cause and move the file into place on markdrop.in. With `--json`, the reason is in `workspace_error`.
## Use it in scripts
```bash
markdrop workspaces --json # {"workspaces":[…]}
markdrop workspaces Acme --json # the folder tree
markdrop upload spec.pdf --workspace Acme --folder guides/setup --json
```
In the upload result, `url` is the address on the custom domain (when it is live) and `markdrop_url` is the markdrop.in address.
## Related
- [Put your workspace on your own domain](https://docs.markdrop.in/workspaces/custom-domain)
- [Members and roles](https://docs.markdrop.in/workspaces/members-and-roles)
- [Upload and replace files](https://docs.markdrop.in/cli/upload-and-replace)
---
# Send and receive files from the terminal
Source: https://docs.markdrop.in/cli/send-and-receive
`markdrop send` shares a file or folder directly from your computer. The recipient opens the link in a browser or runs `markdrop get`, and the bytes travel from your machine to theirs over an encrypted WebRTC connection. Markdrop's server only passes the small handshake messages that connect the two of you; it never receives the file.
## Send a file
```bash
markdrop send report.pdf
```
The CLI prints a share link, a room id and a QR code, then waits:
```text
╭──────────────────────────────────────────────────╮
│ markdrop · P2P File Share │
│ │
│ File : report.pdf (2.3 MB) │
│ Room : a3f7c12e91 │
│ URL : https://markdrop.in/share/a3f7c12e91 │
╰──────────────────────────────────────────────────╯
⠋ Waiting for recipient to open the link…
```
Send the link (or scan the QR code) to the person who should get the file. When they open it, the transfer starts and a progress bar shows how far it has got. `markdrop send` serves one recipient: when the transfer finishes, it exits. Press Ctrl+C to stop sharing at any time.
> **Warning: Anyone with the link can download it**
>
> A share link isn't tied to a person. Send it only to the people you mean to, and stop the share when you're done.
### Send a folder
```bash
markdrop send ./screenshots
```
A folder is compressed into one zip before it is sent. `markdrop get` unpacks it into the original folder structure; a browser saves the zip.
### Link the share to your account
Shares from the CLI are anonymous unless `MARKDROP_TOKEN` is set. With a token, the share is listed under your account, where an AI app connected to Markdrop can see it with `list_shares`. Without one, the CLI tells Markdrop nothing about the file.
```bash
MARKDROP_TOKEN=mdk_•••• markdrop send report.pdf
```
## Receive a file
Pass the full link or the room id:
```bash
markdrop get https://markdrop.in/share/a3f7c12e91
markdrop get a3f7c12e91
```
```text
✓ P2P connection established
File : report.pdf
Size : 2.3 MB
Accept download of "report.pdf" (2.3 MB)? [Y/n]: y
Receiving [████████████████████████████████] 100%
✓ Saved to report.pdf
```
The CLI shows the file's name and size and asks before it downloads. While the file arrives, it is written to a hidden `.markdrop-*.part` file next to the destination and renamed when it is complete.
| Option | What it does |
|---|---|
| `-o`, `--output` | Where to save. A path with a file extension (`~/Downloads/q3.pdf`) is the file name; anything else is a folder, created if missing. Default: the current folder. |
| `-y`, `--yes` | Download without asking. Use it in scripts. |
> **Warning: Existing files are replaced**
>
> If a file with the same name is already in the destination, `markdrop get` replaces it. Save into an empty folder with `-o` when that matters. (The `receive_share` tool of the local MCP server never overwrites; it picks a new name.)
`markdrop get` works with links made by `markdrop send` and links made in the browser at [markdrop.in/share](https://markdrop.in/share).
## When the connection is slow or fails
Most transfers connect directly between the two computers. When the networks in between don't allow that, the CLI falls back to a relay server. The relay passes the encrypted connection along but can't read it. Relayed transfers are usually slower than direct ones.
- **The sender drops off.** If the sender's connection to Markdrop goes away before the transfer starts, `markdrop get` says "The sender is away — waiting for them to reconnect…" and waits up to 10 minutes.
- **A transfer stops moving.** A transfer that makes no progress for 30 seconds ends with an error instead of hanging. Run the command again.
- **"someone else is receiving from this sender right now".** The sender is serving another person. Try again when they are done.
- **"no sender found".** The link is wrong, or the sender has stopped sharing.
### Debugging slow transfers
Two environment variables help when a transfer is slow on your network:
| Variable | Effect |
|---|---|
| `MARKDROP_DEBUG=1` | Print each connection state change on standard error, and the pair of network addresses the connection settled on. |
| `MARKDROP_ICE_EXCLUDE_IFACES=utun,tun` | Leave out network interfaces whose names start with these prefixes (comma-separated). VPN tunnels can carry the handshake but slow the transfer to a crawl. |
More help: [Troubleshoot transfers](https://docs.markdrop.in/share/troubleshooting).
## Related
- [Send files from the browser](https://docs.markdrop.in/share/send-files)
- [Share files from an AI app on your computer](https://docs.markdrop.in/ai/local-mcp)
- [All CLI flags](https://docs.markdrop.in/cli/reference)
---
# CLI reference
Source: https://docs.markdrop.in/cli/reference
This page covers the markdrop CLI 0.5.0. Run `markdrop --help` for the same details in your terminal, and `markdrop --version` to see which version you have.
## Commands
| Command | What it does | Token |
|---|---|---|
| [`send `](#send) | Share a file or folder peer to peer and wait for one recipient | Optional |
| [`get `](#get) | Download a peer-to-peer share | No |
| [`upload `](#upload) | Publish a file and print its link | Yes |
| [`replace `](#replace) | Swap the file behind a link, keeping the link | Yes |
| [`versions `](#versions) | List a file's versions | Yes |
| [`restore `](#restore) | Make an earlier version current again | Yes |
| [`info `](#info) | Show a published file's details | Yes |
| [`workspaces [workspace]`](#workspaces) | List your workspaces, or one workspace's folders | Yes |
| [`login`](#login) | Save an API token on this computer | No |
| [`logout`](#logout) | Delete the saved API token | No |
| [`mcp`](#mcp) | Run the local MCP server for AI apps (stdio) | For file tools |
| `completion ` | Print a shell completion script (bash, zsh, fish, powershell) | No |
| `help [command]` | Show help | No |
"Token" means an API token from `--token`, `MARKDROP_TOKEN` or `markdrop login`. See [Sign in with an API token](https://docs.markdrop.in/cli/login-and-tokens).
## Global flags
These work with every command.
| Flag | Default | What it does |
|---|---|---|
| `--server` | `https://api.markdrop.in` | The Markdrop API the CLI talks to, for both signalling and file uploads. |
| `--origin` | `https://www.markdrop.in` | The `Origin` header sent when opening the signalling connection. |
| `--web` | `https://markdrop.in` for the default server | The web address used to build share links. |
| `-h`, `--help` | | Show help for the command. |
| `-v`, `--version` | | Print the version (root command only). |
You only need `--server`, `--origin` and `--web` when you run your own Markdrop server.
## Peer-to-peer commands
### send
```text
markdrop send
```
Prints a share link, room id and QR code, waits for a recipient, sends the file and exits. A folder is zipped before sending. With `MARKDROP_TOKEN` set, the share is listed under your account. Ctrl+C stops it. Guide: [Send and receive files](https://docs.markdrop.in/cli/send-and-receive).
### get
```text
markdrop get [-o path] [-y]
```
| Flag | Default | What it does |
|---|---|---|
| `-o`, `--output` | Current folder | A folder to save into, or a file path when it has an extension. |
| `-y`, `--yes` | Off | Download without asking for confirmation. |
Accepts `https://markdrop.in/share/` or the bare id (6 to 64 letters, digits, `-` or `_`). Waits up to 10 minutes for a sender who dropped off. Replaces an existing file with the same name.
## File commands
All file commands take these two flags:
| Flag | What it does |
|---|---|
| `--token` | Use this API token instead of `MARKDROP_TOKEN` or the saved one. |
| `--json` | Print the API's result as one compact JSON object on standard output, and nothing else. |
A `` can be a slug (`demo`), a markdrop.in link, or a link on a workspace's own domain. The CLI uses the last part of the path. A peer-to-peer share link is refused.
### upload
```text
markdrop upload [flags]
```
| Flag | Default | What it does |
|---|---|---|
| `--title` | The file name | Title shown on the page. |
| `--access` | `link`; `workspace` with `--workspace`; `password` with `--password` | `link`, `private`, `password` or `workspace`. |
| `--password` | | Password for `--access password`. |
| `--slug` | From the file name | Custom address: 3 to 50 letters, digits, `-` or `_`. Fails if taken. |
| `--workspace` | | A workspace id, name or custom domain to file it in. |
| `--folder` | | An existing folder of that workspace: a path (`guides/setup`) or an id. Needs `--workspace`. |
Prints the link on standard output. Resumes an interrupted upload when run again with the same file and options. Guide: [Upload and replace files](https://docs.markdrop.in/cli/upload-and-replace).
### replace
```text
markdrop replace [--title T]
```
Replaces the file behind the link and prints the link and `version N`. `--title` also changes the title; without it the title is kept.
### versions
```text
markdrop versions
```
Lists versions newest first, with `*` on the one the link serves. Up to five are kept.
### restore
```text
markdrop restore
```
`` is a number from `versions` (`2` or `v2`). The restored file becomes a new version.
### info
```text
markdrop info
```
Prints the link, slug, type, size, version, SHA-256, dates and, for video and audio, resolution, length, codecs and poster.
### workspaces
```text
markdrop workspaces [workspace]
```
Without an argument: your workspaces with id, name, your role and live custom domains. With a workspace id, name or domain: its folders with path, name, item count and id. Guide: [Workspaces and folders](https://docs.markdrop.in/cli/workspaces).
## Account commands
### login
```text
markdrop login [--token T]
```
Prompts for a token without echoing it, or reads one line from standard input when it isn't a terminal, or takes `--token`. Checks the token with the API, then saves it with mode `0600`.
### logout
```text
markdrop logout
```
Deletes the saved token. It doesn't revoke it on markdrop.in.
## mcp
```text
markdrop mcp
```
Runs Markdrop's local MCP server over standard input and output. AI apps start it themselves; you don't run it by hand. Standard output carries only the protocol and logs go to standard error. Guide: [Use Markdrop from an AI app on your computer](https://docs.markdrop.in/ai/local-mcp).
## Environment variables
| Variable | Used by | Effect |
|---|---|---|
| `MARKDROP_TOKEN` | File commands, `send`, `mcp` | API token. Beats the saved token; `--token` beats it. |
| `MARKDROP_CONFIG_DIR` | `login`, `logout`, file commands | Folder for the saved token (`/token`). Default: your user config folder plus `markdrop`. |
| `MARKDROP_CACHE_DIR` | `upload`, `replace`, `mcp` | Folder for unfinished uploads (`/uploads`). Default: your user cache folder plus `markdrop`. |
| `MARKDROP_MCP_ALLOW_ANY_PATH` | `mcp` | `1` turns off the guard that refuses to share or publish secret locations such as `~/.ssh`. |
| `MARKDROP_DEBUG` | `send`, `get`, `mcp` shares | Any value prints connection state changes and the chosen route on standard error. |
| `MARKDROP_ICE_EXCLUDE_IFACES` | `send`, `get`, `mcp` shares | Comma-separated network interface name prefixes to leave out, for example `utun`. |
## Output and exit codes
- Results go to standard output. Progress, notices and errors go to standard error, so `url=$(markdrop upload x.mp4)` captures only the link.
- Progress bars for uploads are drawn only when standard error is a terminal.
- The exit code is `0` on success and `1` on any error. An interrupted `upload` or `replace` exits with `1` and the message "interrupted; run the same command again to resume the upload". Stopping `send` or `get` with Ctrl+C exits with `0`.
---
# Use Markdrop from an AI app
Source: https://docs.markdrop.in/ai/overview
Markdrop speaks the Model Context Protocol (MCP), the standard AI apps use to call outside tools. Once connected, you can say "turn these notes into a Markdrop doc and give me the link" and your assistant publishes it, or "make my Q3 report private" and it changes the access.
There are two Markdrop servers. Which one you add depends on where your AI app runs and what you want it to do.
| | Hosted server | Local server |
|---|---|---|
| Address | `https://api.markdrop.in/mcp` | `markdrop mcp`, part of the CLI |
| Runs | On Markdrop | On your computer, started by the AI app |
| Works with | claude.ai and the Claude apps, ChatGPT, Claude Code, Cursor, VS Code, Windsurf and other apps that add remote servers | Claude Code, Claude Desktop, Cursor, VS Code and other apps that start local servers |
| Signs in with | Markdrop's approval screen (OAuth). No key to paste. | An API token (`MARKDROP_TOKEN`) for publishing |
| Best for | Publishing what the assistant writes, reading and updating your documents, access, analytics, workspaces | Publishing and replacing files from your disk (videos up to 2 GB), sending and receiving files peer to peer |
| Tools | 26 | 11 |
You can add both to the same app: the hosted server for documents, the local one for files on your disk.
## What your assistant can do
- **Publish.** Markdown becomes a rendered document with diagrams and math; an HTML page, CSV, JSON, SVG or text file becomes a link that opens rendered. You can ask for a custom address, a password, an expiry or private access.
- **Read and update.** Read documents you can open, replace a document's content and keep its link, list and search your documents.
- **Control access.** Change who can open a document, delete one, file it in a workspace folder.
- **Work with big files.** Upload a video, PDF or other file and later replace it behind the same link, with the last five versions kept.
- **See how it's doing.** Views, readers, where people drop off in a video, how far they read.
- **Send files peer to peer.** From an app on your computer, share files as a markdrop.in/share link and download links people send you.
Every tool is listed in [MCP tools](https://docs.markdrop.in/ai/tools).
## What it can't do
- It can't read or change end-to-end encrypted documents. Their key never reaches Markdrop.
- It can't get past a password. A password-protected document has to be opened in a browser.
- It only acts as you: it can open what you could open with the link yourself, and writes only to documents you own or can edit.
- Nothing is published unless the assistant calls a publish tool. New links open for anyone with the link, like everything on Markdrop, unless you ask for private access or a password.
## Get started
**[Connect an AI app](https://docs.markdrop.in/ai/hosted-mcp)**
Add the hosted server to Claude, ChatGPT, Cursor or VS Code and approve it on Markdrop.
**[Add the local server](https://docs.markdrop.in/ai/local-mcp)**
Let an AI app on your computer publish files from disk and send them peer to peer.
**[Tool reference](https://docs.markdrop.in/ai/tools)**
Every tool of both servers, grouped by what it does, with the permission it needs.
**[Ask these docs](https://docs.markdrop.in/ai/use-these-docs)**
Give your assistant this documentation as Markdown.
---
# Connect an AI app to Markdrop
Source: https://docs.markdrop.in/ai/hosted-mcp
Markdrop's hosted MCP server lives at one address. Paste it into your AI app, sign in to Markdrop when the app sends you there, and approve. There is no API key, client ID or secret to copy.
```text title="Server URL"
https://api.markdrop.in/mcp
```
You need a Markdrop account. Connecting and using the tools is free.
## Add it to your app
**Claude**
On claude.ai and in Claude Desktop:
1. Open **Settings → Connectors**.
2. Choose **Add custom connector**, name it Markdrop and paste the server URL.
3. Choose **Connect**. Markdrop opens in your browser: sign in and approve.
4. In a chat, ask Claude to publish something to Markdrop.
On Team and Enterprise plans, an owner may need to add the connector for the organisation first.
**Claude Code**
In your terminal:
```bash
claude mcp add --transport http markdrop https://api.markdrop.in/mcp
```
Start Claude Code, run `/mcp`, pick markdrop and sign in. Markdrop opens in your browser to approve. Add `--scope user` to the command to make it available in every project.
**ChatGPT**
On chatgpt.com, with Developer mode:
1. Open **Settings → Apps & Connectors → Advanced settings** and turn on **Developer mode**.
2. Back in **Apps & Connectors**, choose **Create**. Name it Markdrop, paste the server URL and pick **OAuth** for authentication.
3. Choose **Create**, then sign in to Markdrop and approve.
Developer mode needs a Pro, Team, Enterprise or Edu plan.
**Cursor**
Add this to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` in one project:
```json title="mcp.json"
{
"mcpServers": {
"markdrop": {
"url": "https://api.markdrop.in/mcp"
}
}
}
```
Open Cursor's MCP settings and sign in to markdrop when it asks. Markdrop opens in your browser to approve.
**VS Code**
For Copilot Chat in agent mode, add this to `.vscode/mcp.json` in your workspace:
```json title=".vscode/mcp.json"
{
"servers": {
"markdrop": {
"type": "http",
"url": "https://api.markdrop.in/mcp"
}
}
}
```
Choose **Start** above the server in that file, or run **MCP: List Servers**. VS Code opens Markdrop in your browser to sign in and approve. To use it in every workspace, run **MCP: Open User Configuration** and add the same entry.
**Other apps**
Windsurf and any other MCP app that connects to remote servers:
1. Add a remote MCP server with the server URL. The transport is Streamable HTTP.
2. Choose OAuth for authentication. There is no client ID, secret or API key to paste: the app registers itself.
3. Sign in to Markdrop when the app opens it, and approve.
## Approve the app
When the app sends you to Markdrop, you see which app is asking, the account you're signed in as, and what it will be able to do. Choose **Approve** to connect it, or **Cancel** to send it away with nothing shared.
The approval link works once, for 10 minutes. If it has expired, start again from your AI app.
### The two permissions
| Permission | On the approval screen | Lets the app |
|---|---|---|
| `documents:read` | See your documents and files | Read titles, contents, links and view counts; list and search; check files, uploads, shares and workspaces. |
| `documents:write` | Publish and manage documents for you | Publish, update, change access to and delete documents and files; upload and replace files; file documents in workspaces. |
Each tool needs one of them; [MCP tools](https://docs.markdrop.in/ai/tools) lists which. If an app calls a tool it wasn't given permission for, it is asked to reconnect and approve the missing permission.
## Things to ask
Say it the way you would to a colleague:
- "Turn these meeting notes into a Markdrop doc and give me the link."
- "Build an HTML dashboard of this CSV and publish it."
- "Make my Q3 report private."
- "How many people read my launch notes?"
- "Where do people stop watching the demo video?"
- "Put the onboarding guide in Acme's guides/setup folder."
- "Is this share link still working? markdrop.in/share/4f9a2c71e0"
## Manage connected apps
Every app you approve is listed in [Settings → Connected apps](https://markdrop.in/settings/connections). Choose **Disconnect** on an app to end its access: its current sign-in stops working and it can't renew it. To use it again, connect it from the app as before. More in [Connected apps](https://docs.markdrop.in/account/connected-apps).
## Privacy
- The app gets its own token for this server, never your Markdrop sign-in.
- It can't read end-to-end encrypted documents, and it never gets past a password, private access or a workspace's sign-in.
- Markdrop logs which tool ran, when, and whether it worked, for 90 days. It doesn't log what was in the call.
- What the assistant reads from Markdrop becomes part of your conversation, so your AI provider's own data policy applies to it.
## Limits
Each account can make up to 60 calls to writing tools and 300 calls to reading tools per minute, across all its connected apps. Past that, the tool answers "Too many requests in a short time. Wait a minute before trying again."
`get_document` returns at most 200,000 characters of a document and says when it cut the text.
## For developers
Details for people building or debugging an MCP client:
| | |
|---|---|
| Endpoint | `POST https://api.markdrop.in/mcp`, Streamable HTTP. The server is stateless: `GET` and `DELETE` answer `405`. |
| Protocol versions | `2024-11-05`, `2025-03-26`, `2025-06-18`, `2025-11-25` and `2026-07-28` |
| Authorization | OAuth 2.1 authorization code flow with PKCE (`S256`). Clients register with dynamic client registration (`/oauth/register`) or a client ID metadata document. |
| Metadata | `https://api.markdrop.in/.well-known/oauth-protected-resource/mcp` and `https://api.markdrop.in/.well-known/oauth-authorization-server` |
| Redirect URIs | `https`, `http` to a loopback address, or an app's own scheme (for example `cursor://`) |
| Tokens | Access tokens last 1 hour and are valid only for this server. Refresh tokens last 60 days and change on every use; reusing an old one revokes the whole chain. |
| Not accepted | `mdk_` API tokens and markdrop.in session tokens. They are refused rather than downgraded. |
A request without a token gets `401` with a `WWW-Authenticate` header that points to the metadata. A token missing a permission gets `403` with `error="insufficient_scope"` and the scope it needs.
## Related
- [Add the local server](https://docs.markdrop.in/ai/local-mcp) for files on your disk and peer-to-peer sharing
- [MCP tools](https://docs.markdrop.in/ai/tools)
---
# Add the local MCP server
Source: https://docs.markdrop.in/ai/local-mcp
The markdrop CLI includes a local MCP server, `markdrop mcp`. An AI app on your computer starts it and talks to it over standard input and output. Because it runs next to your files, your assistant can publish a 2 GB video or send a folder without passing the file through the conversation.
Use it alongside the [hosted server](https://docs.markdrop.in/ai/hosted-mcp): the hosted one works with documents in your account, the local one with files on your disk. Apps in the cloud, such as claude.ai and ChatGPT, can't use the local server.
## Set it up
### Install the CLI
Install markdrop 0.5.0 or later and check with `markdrop --version`. See [Install the markdrop CLI](https://docs.markdrop.in/cli/install).
Earlier versions have fewer tools: 0.3.0 added the peer-to-peer tools, 0.4.0 the file tools and 0.5.0 the workspace tools.
### Create an API token
Publishing and replacing files need an API token. Create one at [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens). Sharing peer to peer works without it, but with a token your shares are listed under your account, where the hosted server's `list_shares` can see them.
You can give the token to the server in its configuration, as below, or run `markdrop login` once: the server also uses the token that command saves.
### Add the server to your app
Register it once, as `markdrop-p2p`. The app starts and stops it.
**Claude Code**
```bash
claude mcp add markdrop-p2p -e MARKDROP_TOKEN=mdk_•••• -- markdrop mcp
```
Start Claude Code and run `/mcp`: markdrop-p2p shows as connected. There's nothing to sign in to. Add `--scope user` to make it available in every project.
**Claude Desktop**
Open **Settings → Developer → Edit Config** and add this to `claude_desktop_config.json`:
```json title="claude_desktop_config.json"
{
"mcpServers": {
"markdrop-p2p": {
"command": "markdrop",
"args": ["mcp"],
"env": { "MARKDROP_TOKEN": "mdk_••••" }
}
}
}
```
Quit and reopen Claude Desktop. The Markdrop tools appear under the tools menu in a chat.
**Cursor**
Add this to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` in one project:
```json title="mcp.json"
{
"mcpServers": {
"markdrop-p2p": {
"command": "markdrop",
"args": ["mcp"],
"env": { "MARKDROP_TOKEN": "mdk_••••" }
}
}
}
```
Open Cursor's MCP settings and check markdrop-p2p is turned on.
**VS Code**
For Copilot Chat in agent mode, add this to `.vscode/mcp.json` in your workspace:
```json title=".vscode/mcp.json"
{
"servers": {
"markdrop-p2p": {
"type": "stdio",
"command": "markdrop",
"args": ["mcp"],
"env": { "MARKDROP_TOKEN": "mdk_••••" }
}
}
}
```
Choose **Start** above the server in that file, or run **MCP: List Servers**. To use it in every workspace, run **MCP: Open User Configuration** and add the same entry.
> **Tip: “command not found” or the server won't start**
>
> Desktop apps often start with a shorter `PATH` than your terminal. Put the full path from `which markdrop` (on Windows, `where markdrop`) in `command`, for example `/opt/homebrew/bin/markdrop`.
Don't commit a configuration file that contains your token. In a shared project, keep the token in your user configuration or use `markdrop login` instead.
## What it can do
| Tool | What it does | Token |
|---|---|---|
| `upload_file` | Publish a file from disk (up to 2 GB for video and audio) and return its link | Yes |
| `replace_file` | Swap the file behind a link, keeping the link; the old version is kept | Yes |
| `list_file_versions` | A file's versions, newest first | Yes |
| `restore_file_version` | Make an earlier version current again | Yes |
| `get_file_info` | Size, SHA-256, version, and video or audio details | Yes |
| `list_workspaces` | Your workspaces, roles and custom domains | Yes |
| `list_folders` | A workspace's folders, as paths `upload_file` takes | Yes |
| `share_files` | Share files or folders peer to peer and return a markdrop.in/share link | No |
| `share_status` | Progress of every share and download in this session | No |
| `stop_share` | Stop a share, or cancel a download | No |
| `receive_share` | Download a share link into a folder on this computer | No |
Inputs and results for each tool are in [MCP tools](https://docs.markdrop.in/ai/tools#local-server-tools).
### Publishing files
`upload_file` and `replace_file` work like [`markdrop upload` and `markdrop replace`](https://docs.markdrop.in/cli/upload-and-replace): the file is hashed, sent in parts and checked by Markdrop before it goes live, and nothing changes if the check fails. If the app cancels a long upload, calling the tool again with the same arguments resumes it. Apps that show progress get progress updates with the bytes sent.
New uploads are open to anyone with the link unless the assistant sets `access` to `private`, `password` or `workspace`. With `workspace` and `folder`, the file goes into one of your workspaces, and the link returned is on the workspace's custom domain when it has a live one.
### Sharing peer to peer
- `share_files` takes up to 100 paths. One file is sent as it is; several paths or a folder go as one zip, which `markdrop get` and `receive_share` unpack and a browser saves as a zip.
- The link keeps working, for one recipient at a time, until the assistant calls `stop_share` or you close the AI app. Closing the app ends every share it started.
- `receive_share` downloads into `~/Downloads/Markdrop` unless you name another folder, and runs in the background; ask for `share_status` to follow it. It never overwrites a file: a clashing name becomes `name (1).ext`.
## Safety
An AI app can be talked into calling a tool by text it reads: a web page, an issue, a file. The local server refuses well-known secret locations so a request like "share ~/.ssh" doesn't turn into a link to your keys:
- Folders: `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.kube`, `~/.azure`, `~/.password-store`, `~/.config/gcloud`, `~/.config/gh`, macOS Keychains and the desktop keyring
- Files: `.env` files, `.netrc`, `.npmrc`, `.pypirc`, `.git-credentials`, `.pgpass`, `~/.docker/config.json`, SSH private keys such as `id_rsa` and `id_ed25519`, and `.pem`, `.p12`, `.pfx` and keychain files
- Folders that contain any of these, and paths that reach them through a symbolic link
This applies to `share_files`, `upload_file` and `replace_file`. `receive_share` also won't save into those locations or into folders that start programs at login, and it removes leading dots from received names so a sender can't plant a dotfile.
The guard is a safety net, not a sandbox. Read what your assistant is about to publish or share before you approve the call. To turn the guard off, set `MARKDROP_MCP_ALLOW_ANY_PATH=1` in the server's `env`.
## Troubleshooting
- **Logs.** The server writes its log to standard error, which your AI app collects in its MCP log. Standard output carries only the protocol.
- **"no Markdrop API token".** The file tools found no token. Add `MARKDROP_TOKEN` to the server's `env`, or run `markdrop login`, then restart the server from your app.
- **A share link stopped working.** Shares end when the AI app closes. Ask the assistant to share again.
- **Slow transfers.** See [Send and receive files](https://docs.markdrop.in/cli/send-and-receive#when-the-connection-is-slow-or-fails); the same environment variables work in the server's `env`.
---
# MCP tools
Source: https://docs.markdrop.in/ai/tools
Markdrop has two MCP servers. The hosted server at `https://api.markdrop.in/mcp` has 26 tools; the local server, `markdrop mcp` in the CLI, has 11. Your assistant picks the tool from what you ask; you rarely need the names. This page is the reference for when you do.
In the tables, **Permission** is the one the app must have been given when you approved it: `read` is `documents:read` and `write` is `documents:write`. See [Connect an AI app](https://docs.markdrop.in/ai/hosted-mcp#the-two-permissions).
Wherever a tool takes `document`, it accepts a slug (`q3-report`), a markdrop.in link, or the document's link on a workspace's own domain. Wherever it takes `workspace`, it accepts the workspace's id, its name or one of its custom domains.
## Hosted server tools
### Publish and edit
| Tool | Permission | What it does | Main inputs |
|---|---|---|---|
| `publish_document` | write | Publishes Markdown as a document that renders headings, tables, code, Mermaid diagrams and math. Returns the link. | `markdown`, and the publish options below |
| `publish_page` | write | Publishes a complete HTML page, or a `.txt`, `.csv`, `.json` or `.svg` file, as a link that opens rendered. Up to 25 MB. | `filename` (with its extension), `content`, and the publish options |
| `update_document` | write | Replaces the full content of a document or text file, keeping its link. Works on documents you own or can edit. | `document`, `content`, `title` |
| `delete_document` | write | Deletes a document or file you own, permanently. Marked as destructive, so apps that honour that ask you first. | `document` |
The **publish options** are all optional:
| Option | Values |
|---|---|
| `title` | Up to 200 characters |
| `slug` | A custom address, 3 to 50 letters, digits, `-` or `_`. A taken slug is an error, never a silent rename. Random when omitted. |
| `access` | `link` (anyone with the link, the default outside a workspace), `private`, `password` or `workspace` |
| `password` | Required with `access: "password"` |
| `expires` | `never` (default), `1h`, `1d`, `7d` or `30d`, counted from now |
| `workspace`, `folder` | File it in a workspace and an existing folder there. Without `access`, the folder's setting for new items applies, otherwise the workspace's default. |
Results carry `url` (on the workspace's custom domain when it has a live one), `markdrop_url`, `slug`, `title`, `access` and `expires_at`.
### Read and find
| Tool | Permission | What it does | Main inputs |
|---|---|---|---|
| `get_document` | read | Reads a document or text file you can open: content (up to 200,000 characters), title, access, views, workspace and folder. For other files it returns the file's details instead. | `document` |
| `list_documents` | read | Lists documents and files, newest first. | `query`, `kind` (`all`, `document`, `file`), `scope`, `limit` (1–50, default 20), `cursor` |
| `search` | read | Searches titles and slugs (and, for your own documents, text). Returns ids to pass to `fetch`. | `query`, `scope` |
| `fetch` | read | Returns the full text of a search result. | `id` |
`scope` is `mine` (default: your own), `workspaces` (what you can open in your workspaces) or `all` (both, plus documents shared with you).
Password-protected and end-to-end encrypted documents can't be read by any tool.
### Access
| Tool | Permission | What it does | Main inputs |
|---|---|---|---|
| `set_access` | write | Changes who can open a document you own: `link`, `private`, `password` or `workspace`. A document has one access level at every address, so `link` on a workspace document makes it public on markdrop.in and on the workspace's domain alike. | `document`, `access`, `password`, `remove_password`, `expires` |
With `access: "link"`, a password the document already has is kept and applies again unless `remove_password` is `true`.
### Files and uploads
For files up to 2 GB that live on the machine where the assistant can run shell commands. If the assistant runs on your computer, the local server's `upload_file` and `replace_file` are simpler.
| Tool | Permission | What it does | Main inputs |
|---|---|---|---|
| `create_upload` | write | Starts an upload of a new file, or of a replacement for an existing one (`target_slug`). Returns signed part addresses and the exact shell commands (`split` and `curl`) that send the file straight to storage. | `file_name`, `size_bytes`, `sha256`; `target_slug`, `content_type`, `title`; for new files `access`, `password`, `workspace`, `folder`, `slug` |
| `complete_upload` | write | Finishes the upload: Markdrop checks the size and SHA-256, then publishes the file or swaps it in as a new version. Returns the link, or `verifying` for a large file. Safe to call again. | `upload_id`, `parts` (optional) |
| `get_upload_status` | read | State of an upload: `uploading` (with parts received), `verifying`, `done` (with the link and version), `failed`, `expired` or `cancelled`. | `upload_id` |
| `get_file_info` | read | A file's type, size, SHA-256, current version and, for video or audio, duration, dimensions, poster and download links. | `document` |
| `list_file_versions` | read | The kept versions of one of your files, newest first, marking the current one. Up to five are kept. | `document` |
| `restore_file_version` | write | Makes an earlier version current again, as a new version. Nothing is deleted. | `document`, `version` |
Zipped websites can't be uploaded with `create_upload`; use [markdrop.in/upload](https://markdrop.in/upload).
### Analytics
| Tool | Permission | What it does | Main inputs |
|---|---|---|---|
| `get_document_stats` | read | Views, unique readers, read depth, top referrers and the last view of a document you own. | `document` |
| `get_document_analytics` | read | How a document or file you own is doing: views, people, top sources and what the dashboard noticed. For video and audio: how many are still watching at 0:30, 25%, 50%, 75% and the end, where most leave, and playback health. For Markdown: how far readers get section by section and what they copy and click. For PDFs: how many reach each page. Returns a short summary you can quote. | `document`, `range` (`7d`, `30d` (default), `90d`, `all`) |
Your own views are never counted, and views by members of the document's workspace are left out. See [What gets counted](https://docs.markdrop.in/analytics/what-gets-counted).
### Workspaces and folders
| Tool | Permission | What it does | Main inputs |
|---|---|---|---|
| `list_workspaces` | read | Your workspaces: id, name, your role, custom domains (and whether each is live) and the default access for new documents. | none |
| `list_folders` | read | A workspace's folders as paths (`guides/setup`), with how many items you can open in each. | `workspace` |
| `list_workspace_items` | read | Documents and files you can open in a workspace or one folder of it, newest first, with their links. | `workspace`, `folder`, `query`, `limit` (1–100, default 50), `cursor` |
| `add_to_workspace` | write | Files one of your own documents into a workspace (and folder). It keeps its link; its access changes only if you pass `access`. | `document`, `workspace`, `folder`, `access` (`link`, `private`, `workspace`) |
| `remove_from_workspace` | write | Takes a document out of its workspace (its owner or a workspace admin). It keeps its markdrop.in link; a members-only document becomes private. | `document` |
| `create_folder` | write | Creates a folder by path, with any missing parent folders. | `workspace`, `path` |
| `move_to_folder` | write | Moves a workspace document to another folder of the same workspace, or to the top level when `folder` is omitted. | `document`, `folder` |
### Peer-to-peer shares
The hosted server can't send or receive files; that needs the [local server](https://docs.markdrop.in/ai/local-mcp). It can see your shares.
| Tool | Permission | What it does | Main inputs |
|---|---|---|---|
| `list_shares` | read | Peer-to-peer shares you started while signed in (in the browser, or from the CLI with your API token), newest first: whether each is live, recipients connected now and downloads finished in a browser. A share is listed once its first recipient has connected. | `limit` (1–50, default 20), `live_only` |
| `get_share_status` | read | Whether a share link's sender is online and people can join. For your own shares, also what is shared and how many have downloaded. | `link` |
## Local server tools
These run on your computer, inside `markdrop mcp`. They have no permissions to approve; the file and workspace tools act as the owner of the API token the server was given. See [Add the local MCP server](https://docs.markdrop.in/ai/local-mcp).
### Publish files from disk
| Tool | Token | What it does | Main inputs |
|---|---|---|---|
| `upload_file` | Yes | Publishes a file from this computer (video and audio up to 2 GB) and returns its link. Resumes when called again with the same arguments. | `path`, `title`, `access` (`link`, `private`, `password`, `workspace`), `password`, `workspace`, `folder`, `slug` |
| `replace_file` | Yes | Replaces the file behind an existing link with a file from this computer. Link, access, title and views stay; the previous version is kept. | `document`, `path`, `title` |
| `list_file_versions` | Yes | A file's versions, newest first. Up to five are kept. | `document` |
| `restore_file_version` | Yes | Makes an earlier version current again, as a new version. | `document`, `version` |
| `get_file_info` | Yes | Link, type, size, SHA-256, version, dates and video or audio details. | `document` |
Results of `upload_file` and `replace_file` carry `url`, `markdrop_url`, `slug`, `version`, `size_bytes` and `sha256`.
### Workspaces
| Tool | Token | What it does | Main inputs |
|---|---|---|---|
| `list_workspaces` | Yes | Your workspaces: id, name, role, custom domains (verified, live, default) and default access. | none |
| `list_folders` | Yes | A workspace's folders: id, name, path, depth and item count. | `workspace` |
### Share peer to peer
| Tool | Token | What it does | Main inputs |
|---|---|---|---|
| `share_files` | No | Shares files or folders and returns a `markdrop.in/share` link. Several paths or a folder go as one zip. Serves one recipient at a time until stopped or the app closes. | `paths` (1–100) |
| `share_status` | No | State (`waiting`, `connecting`, `transferring`, `done`, `stopped`, `error`), progress, recipients served, saved paths and the last error of each share and download in the session. | `share_id` (a share id or a receive id; omit for everything) |
| `stop_share` | No | Stops a share; anyone downloading is told the sender stopped. Given a receive id, cancels that download. | `share_id` |
| `receive_share` | No | Downloads a share link into `~/Downloads/Markdrop` or a folder you name, in the background. Never overwrites. | `link`, `directory` |
With a token, shares started by `share_files` are also listed by the hosted server's `list_shares`.
## Same name, two servers
`get_file_info`, `list_file_versions`, `restore_file_version`, `list_workspaces` and `list_folders` exist on both servers and do the same thing. If an app has both servers, either may answer.
---
# Use these docs with your AI
Source: https://docs.markdrop.in/ai/use-these-docs
Every page on docs.markdrop.in is also plain Markdown, so you can hand it to an AI assistant without copying text out of a web page. Use it to ask questions about a page, or to give a coding agent the exact steps it needs.
## Copy or open a page
Each page has a **Copy page** button next to its title. Its menu has:
| Item | What it does |
|---|---|
| Copy page as Markdown | Copies the page's Markdown to your clipboard. |
| Copy page with prerequisites | Copies the page together with the pages it lists as prerequisites, so an assistant has everything it needs in one paste. |
| View as Markdown | Opens the page's Markdown in the browser. |
| Open in Claude | Opens claude.ai with a prompt to read this page. |
| Open in ChatGPT | Opens ChatGPT with the same prompt. |
| Open in Perplexity | Opens Perplexity with the same prompt. |
| Add docs to Cursor | Installs the [docs MCP server](#connect-the-docs-to-your-ai-app) in Cursor. |
| Add docs to VS Code | Installs the docs MCP server in VS Code, for Copilot Chat. |
The prompt is "Read {page}.md and answer my questions about it." When you open an assistant, the page's Markdown is also copied to your clipboard, in case the assistant can't open the link: paste it into the chat.
To copy one section, hover over its heading and choose **Copy section**.
## Get any page as Markdown
Add `.md` to a page's address:
```text
https://docs.markdrop.in/cli/upload-and-replace.md
```
Tools that ask for Markdown get it at the normal address too. Send `Accept: text/markdown`:
```bash
curl -H "Accept: text/markdown" https://docs.markdrop.in/cli/upload-and-replace
```
## Give an agent all the docs
| File | What's in it |
|---|---|
| [`/llms.txt`](https://docs.markdrop.in/llms.txt) | An index of every page, with a one-line description and a link to its Markdown. Small enough to keep in an agent's context and follow links from. |
| [`/llms-full.txt`](https://docs.markdrop.in/llms-full.txt) | Every page in one Markdown file. Use it when the agent should have everything at once. |
| [`/changelog.md`](https://docs.markdrop.in/changelog.md) | The changelog as Markdown, newest first. |
For example, in Claude Code:
```text
Read https://docs.markdrop.in/llms.txt, then use the markdrop CLI to publish build/demo.mp4 into my Acme workspace's releases folder.
```
## Ask the docs
**Ask AI** answers questions from these docs, with a numbered link to the exact section each part of the answer came from. Choose **Ask AI** in the header, press ⌘I, use **Ask about this** next to any heading, or press ⌘K and then Tab. Selecting a source scrolls to that section and highlights it.
- It only answers from these docs. When they don't cover something, it says so instead of guessing.
- It can't change anything in your account; it explains how to do things.
- What you type is sent to Ask AI only when you ask. Searching with ⌘K never leaves your browser.
- There is a limit of 20 questions an hour per person. If Ask AI is busy or off, you get links to ask Claude or ChatGPT with the page attached instead.
## Connect the docs to your AI app
These docs have their own MCP server at `https://docs.markdrop.in/mcp`. Connect it, and your AI app can search and read the docs as tools whenever it needs them, without you pasting anything. It is public and read-only: no account and no sign-in.
**Cursor**
[Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=markdrop-docs&config=eyJ1cmwiOiJodHRwczovL2RvY3MubWFya2Ryb3AuaW4vbWNwIn0=) opens Cursor and asks you to install it. Or add this to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` in one project:
```json title="mcp.json"
{
"mcpServers": {
"markdrop-docs": {
"url": "https://docs.markdrop.in/mcp"
}
}
}
```
**VS Code**
[Add to VS Code](https://vscode.dev/redirect/mcp/install?name=markdrop-docs&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fdocs.markdrop.in%2Fmcp%22%7D) opens VS Code and asks you to install it. Or, for Copilot Chat in agent mode, add this to `.vscode/mcp.json` in your workspace:
```json title=".vscode/mcp.json"
{
"servers": {
"markdrop-docs": {
"type": "http",
"url": "https://docs.markdrop.in/mcp"
}
}
}
```
**Claude Code**
In your terminal:
```bash
claude mcp add --transport http markdrop-docs https://docs.markdrop.in/mcp
```
Add `--scope user` to make it available in every project.
**Claude**
On claude.ai and in Claude Desktop:
1. Open **Settings → Connectors**.
2. Choose **Add custom connector**, name it Markdrop docs and paste `https://docs.markdrop.in/mcp`.
3. Save it. There is nothing to sign in to.
**Other apps**
Any app that connects to remote MCP servers over Streamable HTTP works. Add `https://docs.markdrop.in/mcp` with no authentication.
**Add docs to Cursor** and **Add docs to VS Code** are also in every page's **Copy page** menu.
### Tools
| Tool | What it does |
|---|---|
| `search_docs` | Takes `query` and an optional `limit` (1 to 20, 8 by default). Returns the best-matching sections and pages, each with its title, the page it is on, a link to the exact section and a short snippet. |
| `read_page` | Takes a page's `path`, like `/workspaces/custom-domain`, or its full address. Returns the page as Markdown. With `include_prerequisites`, the pages it builds on come first. |
| `whats_changed` | Takes `since_date` (`YYYY-MM-DD`) or `since_cli_version` (like `0.4.0`). Returns the changelog entries since then, newest first, with the pages each one changed. With neither, returns the latest 10. |
| `report_problem` | Takes a `page` and the `problem`. Tells the docs team a page is wrong, out of date or missing something. |
The [API reference](https://docs.markdrop.in/api/overview) is included: `read_page` on `/api/documents/publish-a-document` returns that endpoint's parameters, request body, responses and code samples.
For example, in Cursor:
```text
Using the markdrop-docs tools, find out how to replace a published PDF without changing its link, then do it for build/report.pdf.
```
This is separate from Markdrop's own MCP server, which works with your documents and files. To connect that, see [Connect an AI app](https://docs.markdrop.in/ai/hosted-mcp).
---
# API overview
Source: https://docs.markdrop.in/api/overview
Everything you can do on markdrop.in goes through a JSON API at `https://api.markdrop.in`. Use it to publish from your own scripts and services. For publishing files from a terminal, the [CLI](https://docs.markdrop.in/cli/install) is usually simpler; for AI apps, use the [MCP server](https://docs.markdrop.in/ai/overview).
Every endpoint has its own page in this section, grouped in the sidebar: what it does, the token it needs, its rate limit, parameters, request body, responses, and samples in curl, JavaScript and Python. The pages are generated from the API's own description. The machine-readable OpenAPI description is at [`https://api.markdrop.in/openapi.json`](https://api.markdrop.in/openapi.json).
Endpoints that only markdrop.in's own pages use, such as sign-in, Google Docs export and the VS Code extension's sync, are left out.
## Base URL
```text
https://api.markdrop.in/api/v1
```
All requests and responses use JSON (`Content-Type: application/json`) unless an endpoint says otherwise. Times are ISO 8601 in UTC.
## Your first request
Publish a Markdown document. No account is needed:
```bash
curl -X POST https://api.markdrop.in/api/v1/documents \
-H "Content-Type: application/json" \
-d '{"title": "Hello", "content": "# Hello\n\nPublished from curl."}'
```
```json
{
"slug": "k3x9q2m",
"url": "https://markdrop.in/k3x9q2m",
"title": "Hello",
"edit_secret": "sk_••••",
"expires_at": null,
"views": 0,
"kind": "markdown"
}
```
The response is longer than shown. Keep `edit_secret`: it is returned only once, and it is the only way to change or delete a document published without an account. See [Edit key](https://docs.markdrop.in/documents/edit-key).
Read it back:
```bash
curl https://api.markdrop.in/api/v1/documents/k3x9q2m
```
## Authentication
Requests can be anonymous, or carry one or more of these:
| Credential | How to send it | What it proves |
|---|---|---|
| API token | `Authorization: Bearer mdk_••••` | You are the account that created the token. Documents you publish are owned by that account. |
| Edit secret | `X-Edit-Secret: sk_••••` | You may change or delete this one document. |
| Read password | `X-Read-Password: ••••••••` | You know the password of a password-protected document. |
### API tokens
Create a token at [markdrop.in/settings/tokens](https://markdrop.in/settings/tokens). It starts with `mdk_`, is shown once, and works until you revoke it. A token acts as your whole account, so treat it like a password and make one per script or device. More in [API tokens](https://docs.markdrop.in/account/api-tokens).
```bash
curl https://api.markdrop.in/api/v1/me/documents \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
A revoked or mistyped API token gets `401` with `"detail": "Invalid API token"`. It is never treated as anonymous.
The sign-in token markdrop.in uses in your browser is also accepted as a bearer token, but it expires. Use an API token for anything that runs unattended.
The MCP endpoint, `https://api.markdrop.in/mcp`, is the exception: it accepts only the tokens AI apps get through Markdrop's approval screen, and refuses `mdk_` tokens. See [Connect an AI app](https://docs.markdrop.in/ai/hosted-mcp#for-developers).
### Edit secrets
Every new document comes with an edit secret (`sk_…`) in the create response. Send it as `X-Edit-Secret` to update (`PUT /documents/{slug}`), delete (`DELETE /documents/{slug}`), or change the password and expiry (`PATCH /documents/{slug}/settings`) without signing in. If an edit secret leaks, the document's owner can replace it with `POST /documents/{slug}/edit-secret/rotate`; the old one stops working at once.
To attach an anonymous document to your account, call `POST /documents/{slug}/claim` with both your API token and its `X-Edit-Secret`.
### Password-protected documents
Send the password as `X-Read-Password` when you read:
```bash
curl https://api.markdrop.in/api/v1/documents/q3-report \
-H "X-Read-Password: ••••••••"
```
Without it, the API answers `401` with the header `X-Markdrop-Gate: password`. A wrong password gets `403` `"Incorrect password"`. After repeated wrong passwords from one address, the API answers `429` for a few minutes.
## Errors
Errors use HTTP status codes and a JSON body with a `detail` message you can show to a person:
```json
{ "detail": "Document not found" }
```
| Status | Meaning |
|---|---|
| `400` | The request can't be carried out as sent. |
| `401` | Sign-in or a password is needed, or the token is invalid. Check `X-Markdrop-Gate`: `password` or `signin`. |
| `403` | You're signed in but not allowed to do this, or the password is wrong. |
| `404` | Nothing there, or nothing you're allowed to know about. A private document answers `404` to people who can't open it. |
| `409` | A conflict, such as a custom address that is already taken. |
| `413` | The content or file is over its size limit. The `detail` says the limit. |
| `422` | The body failed validation. `detail` is a list of problems, each with a `loc` (where) and `msg` (what). Long values are replaced by their length. |
| `429` | Too many requests. See [Rate limits](#rate-limits). |
| `5xx` | A problem on Markdrop's side. Retry after a short wait. |
A `422` looks like this:
```json
{
"detail": [
{ "type": "string_too_short", "loc": ["body", "custom_slug"], "msg": "String should have at least 3 characters", "input": "ab" }
]
}
```
### Renamed documents
When a document's address was renamed, reading the old slug answers `200` with `{"moved": true, "slug": "", "url": "…"}` and the header `X-Markdrop-Moved: `, but only to someone allowed to read the document. Follow it to the new slug.
## Rate limits
Limits are counted per client IP address, per minute unless shown otherwise. Some of the most used:
| Requests | Limit |
|---|---|
| Create a document (`POST /documents`), or update, delete, rename, claim, copy or change the settings of one | 10 per minute |
| Read a document (`GET /documents/{slug}`) | 60 per minute |
| Start a file upload (`POST /files/uploads`) | 30 per minute |
| Create an API token | 20 per hour |
Over the limit, the API answers `429` with a `Retry-After` header giving the seconds to wait, and this body:
```json
{ "error": "Rate limit exceeded: 10 per 1 minute" }
```
Note that this one error uses `error` rather than `detail`. Browsers can read `Retry-After` from cross-origin requests.
## Where to go next
**[Publish files from the terminal](https://docs.markdrop.in/cli/upload-and-replace)**
Uploads of large files in parts, with verification and resume, already built.
**[Connect an AI app](https://docs.markdrop.in/ai/hosted-mcp)**
Let Claude or ChatGPT call Markdrop for you.
**[Passwords and expiry](https://docs.markdrop.in/documents/passwords-and-expiry)**
What each protection does for the people you send a link to.
---
# Publish a document
Source: https://docs.markdrop.in/api/documents/publish-a-document
`POST https://api.markdrop.in/api/v1/documents`
Creates a Markdown document and returns its link. Works without an account: send a token to own it. Keep the `edit_secret` from a call without a token: it is the only way to change or delete that document later.
- **Authentication:** Token optional: send `Authorization: Bearer mdk_…` to act as your account.
- **Rate limit:** 10 per minute per IP address.
- **MCP tool:** `publish_document` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [Publish your first document](https://docs.markdrop.in/get-started/first-document)
## Request body
`application/json`, required.
- `title` (string): Shown on the page and in link previews. Up to 200 characters.
- `content` (string, required): The document's Markdown. 1–500,000 characters.
- `custom_slug` (string): Your own address: `markdrop.in/`. Letters, digits, `-` and `_`. Taken or reserved addresses answer `409`. 3–50 characters. Matches `^[a-zA-Z0-9_-]+$`.
- `expires_in` ("never" | "1d" | "7d" | "30d" | "custom"): When the document is deleted. `custom` needs `custom_expires_at`. Default `"never"`.
- `custom_expires_at` (string (date-time)): The deletion time, when `expires_in` is `custom`. Must be in the future. ISO 8601, for example `2026-11-30T12:00:00Z`.
- `read_password` (string): Readers must enter this password before they see the content. 1–100 characters.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `access_level` ("private" | "link" | "workspace"): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace` (string): File it in this workspace: the workspace's id. 1–64 characters.
- `folder` (string): An existing folder of `workspace`: its id or its path, like `guides/setup`. 1–400 characters.
## Responses
- `201`: Created. Returns the document with its `slug`, `url` and `edit_secret`.
- `slug` (string)
- `url` (string)
- `folder_path` (array of string)
- `title` (string | null): Shown on the page and in link previews.
- `content` (string): The document's Markdown.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owned` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `id` (string | null)
- `ref` (string | null)
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `artifact_url` (string | null)
- `download_url` (string | null)
- `poster_url` (string | null)
- `media` (object | null)
- `file_version` (integer | null)
- `workspace_url` (string | null)
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace_id` (string | null)
- `can` (object | null)
- `read_ticket` (string | null)
- `access_epoch` (integer): Default `0`.
- `edit_secret` (string): Returned once. Send it as `X-Edit-Secret` to change or delete a document published without a token.
- `workspace_error` (string | null)
- `409`: The custom address is taken, or reserved.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Release notes",
"content": "# Release notes\n\nPublished from the API.",
"expires_in": "7d"
}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Release notes",
"content": "# Release notes\n\nPublished from the API.",
"expires_in": "7d"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={
"title": "Release notes",
"content": "# Release notes\n\nPublished from the API.",
"expires_in": "7d",
},
)
r.raise_for_status()
print(r.json())
```
---
# Read a document
Source: https://docs.markdrop.in/api/documents/read-a-document
`GET https://api.markdrop.in/api/v1/documents/{slug}`
Returns a document's content and details, if you may read it. For a private or workspace document, send a token; for a password-protected one, send the password in `X-Read-Password`.
A document whose address changed answers at its old address too, but only to someone who can read it: `200` with `"moved": true` and the current `slug`.
- **Authentication:** Token optional: send `Authorization: Bearer mdk_…` to act as your account, or the document's edit secret in `X-Edit-Secret`; `X-Read-Password` for a password-protected document.
- **Rate limit:** 60 per minute per IP address.
- **MCP tool:** `get_document` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Headers
- `X-Read-Password` (string): The password of a password-protected document.
- `X-Edit-Secret` (string): The document's edit secret (`sk_…`), for a document published without an account. See [Edit key](https://docs.markdrop.in/documents/edit-key).
- `X-Markdrop-Workspace` (string)
- `X-Markdrop-Path` (string)
## Responses
- `200`: OK.
- `slug` (string): The address: `markdrop.in/`.
- `url` (string): The link to share.
- `folder_path` (array of string)
- `title` (string | null): Shown on the page and in link previews.
- `content` (string): The document's Markdown.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owned` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `id` (string | null)
- `ref` (string | null)
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): `markdown` for a document, `artifact` for a file. Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `artifact_url` (string | null)
- `download_url` (string | null)
- `poster_url` (string | null)
- `media` (object | null)
- `file_version` (integer | null)
- `workspace_url` (string | null): The address on its workspace's own domain, when it has a live one.
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace_id` (string | null)
- `can` (object | null): What you may do with it: `edit`, `replace`, `settings`, `access`, `rename`, `delete`, `share`, `move`, `unshare`.
- `read_ticket` (string | null)
- `access_epoch` (integer): Default `0`.
- `401`: It needs a token, or the right password.
- `404`: No such document, or you can't open it.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/documents/k3x9q2m \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/documents/k3x9q2m",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Update a document
Source: https://docs.markdrop.in/api/documents/update-a-document
`PUT https://api.markdrop.in/api/v1/documents/{slug}`
Replaces a document's content, and optionally its title, password and expiry. Send a token as the owner or an editor, or the document's edit secret in `X-Edit-Secret`.
- **Authentication:** Token optional: send `Authorization: Bearer mdk_…` to act as your account, or the document's edit secret in `X-Edit-Secret`.
- **Rate limit:** 10 per minute per IP address.
- **MCP tool:** `update_document` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [Edit key](https://docs.markdrop.in/documents/edit-key)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Headers
- `X-Edit-Secret` (string): The document's edit secret (`sk_…`), for a document published without an account. See [Edit key](https://docs.markdrop.in/documents/edit-key).
## Request body
`application/json`, required.
- `title` (string): Shown on the page and in link previews. Up to 200 characters.
- `content` (string, required): The document's Markdown. 1–500,000 characters.
- `read_password` (string): Readers must enter this password before they see the content. Up to 100 characters.
- `remove_password` (boolean): `true` removes the password. Default `false`.
- `expires_in` ("never" | "1h" | "1d" | "7d" | "30d" | "custom"): When the document is deleted. `custom` needs `custom_expires_at`.
- `custom_expires_at` (string (date-time)): The deletion time, when `expires_in` is `custom`. Must be in the future. ISO 8601, for example `2026-11-30T12:00:00Z`.
## Responses
- `200`: OK.
- `slug` (string): The address: `markdrop.in/`.
- `url` (string): The link to share.
- `folder_path` (array of string)
- `title` (string | null): Shown on the page and in link previews.
- `content` (string): The document's Markdown.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owned` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `id` (string | null)
- `ref` (string | null)
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): `markdown` for a document, `artifact` for a file. Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `artifact_url` (string | null)
- `download_url` (string | null)
- `poster_url` (string | null)
- `media` (object | null)
- `file_version` (integer | null)
- `workspace_url` (string | null): The address on its workspace's own domain, when it has a live one.
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace_id` (string | null)
- `can` (object | null): What you may do with it: `edit`, `replace`, `settings`, `access`, `rename`, `delete`, `share`, `move`, `unshare`.
- `read_ticket` (string | null)
- `access_epoch` (integer): Default `0`.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/documents/k3x9q2m \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Release notes",
"content": "# Release notes\n\nUpdated from the API."
}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Release notes",
"content": "# Release notes\n\nUpdated from the API."
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/documents/k3x9q2m",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={
"title": "Release notes",
"content": "# Release notes\n\nUpdated from the API.",
},
)
r.raise_for_status()
print(r.json())
```
---
# Delete a document
Source: https://docs.markdrop.in/api/documents/delete-a-document
`DELETE https://api.markdrop.in/api/v1/documents/{slug}`
Deletes a document for good. Send a token as the owner, or the document's edit secret in `X-Edit-Secret`.
- **Authentication:** Token optional: send `Authorization: Bearer mdk_…` to act as your account, or the document's edit secret in `X-Edit-Secret`.
- **Rate limit:** 10 per minute per IP address.
- **MCP tool:** `delete_document` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Headers
- `X-Edit-Secret` (string): The document's edit secret (`sk_…`), for a document published without an account. See [Edit key](https://docs.markdrop.in/documents/edit-key).
## Responses
- `204`: No content.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/documents/k3x9q2m \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/documents/k3x9q2m",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Rotate the edit secret
Source: https://docs.markdrop.in/api/documents/rotate-the-edit-secret
`POST https://api.markdrop.in/api/v1/documents/{slug}/edit-secret/rotate`
Replaces a document's edit secret, for when the old one leaked. The old secret stops working at once; the new one is in the response, once. Owner only.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 10 per minute per IP address.
- **Guide:** [If the secret leaks](https://docs.markdrop.in/documents/edit-key#if-the-secret-leaks)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents/k3x9q2m/edit-secret/rotate \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/edit-secret/rotate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/edit-secret/rotate",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Claim a document
Source: https://docs.markdrop.in/api/documents/claim-a-document
`POST https://api.markdrop.in/api/v1/documents/{slug}/claim`
Adds a document you published without an account to your account. Send its edit secret in `X-Edit-Secret`.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 10 per minute per IP address.
- **Guide:** [Save it to your account](https://docs.markdrop.in/documents/edit-key#save-it-to-your-account)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Headers
- `X-Edit-Secret` (string, required): The document's edit secret (`sk_…`), for a document published without an account. See [Edit key](https://docs.markdrop.in/documents/edit-key).
## Responses
- `200`: OK.
- `slug` (string): The address: `markdrop.in/`.
- `url` (string): The link to share.
- `folder_path` (array of string)
- `title` (string | null): Shown on the page and in link previews.
- `content` (string): The document's Markdown.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owned` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `id` (string | null)
- `ref` (string | null)
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): `markdown` for a document, `artifact` for a file. Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `artifact_url` (string | null)
- `download_url` (string | null)
- `poster_url` (string | null)
- `media` (object | null)
- `file_version` (integer | null)
- `workspace_url` (string | null): The address on its workspace's own domain, when it has a live one.
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace_id` (string | null)
- `can` (object | null): What you may do with it: `edit`, `replace`, `settings`, `access`, `rename`, `delete`, `share`, `move`, `unshare`.
- `read_ticket` (string | null)
- `access_epoch` (integer): Default `0`.
- `401`: No token, or the token is revoked or mistyped.
- `409`: Another account already owns it.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents/k3x9q2m/claim \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/claim", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/claim",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Copy a document
Source: https://docs.markdrop.in/api/documents/copy-a-document
`POST https://api.markdrop.in/api/v1/documents/{slug}/copy`
Makes a copy of a document you can read, owned by you, with a new address, no password and no expiry. The copy is private unless the original is open to anyone with the link.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 10 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Headers
- `X-Markdrop-Workspace` (string)
## Request body
`application/json`, required.
- `read_password` (string): Readers must enter this password before they see the content. Up to 100 characters.
## Responses
- `201`: Created.
- `slug` (string)
- `url` (string)
- `folder_path` (array of string)
- `title` (string | null): Shown on the page and in link previews.
- `content` (string): The document's Markdown.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owned` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `id` (string | null)
- `ref` (string | null)
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `artifact_url` (string | null)
- `download_url` (string | null)
- `poster_url` (string | null)
- `media` (object | null)
- `file_version` (integer | null)
- `workspace_url` (string | null)
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace_id` (string | null)
- `can` (object | null)
- `read_ticket` (string | null)
- `access_epoch` (integer): Default `0`.
- `edit_secret` (string): Returned once. Send it as `X-Edit-Secret` to change or delete a document published without a token.
- `workspace_error` (string | null)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents/k3x9q2m/copy \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/copy", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/copy",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={},
)
r.raise_for_status()
print(r.json())
```
---
# Change the address
Source: https://docs.markdrop.in/api/documents/change-the-address
`PATCH https://api.markdrop.in/api/v1/documents/{slug}/slug`
Gives a document a new address. Its old address keeps working for people who can read it.
- **Authentication:** Token optional: send `Authorization: Bearer mdk_…` to act as your account, or the document's edit secret in `X-Edit-Secret`.
- **Rate limit:** 10 per minute per IP address.
- **Guide:** [Addresses](https://docs.markdrop.in/documents/addresses)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Headers
- `X-Edit-Secret` (string): The document's edit secret (`sk_…`), for a document published without an account. See [Edit key](https://docs.markdrop.in/documents/edit-key).
## Request body
`application/json`, required.
- `new_slug` (string, required): The new address. The old one keeps working as a redirect for people who can read the document. 3–50 characters. Matches `^[a-zA-Z0-9_-]+$`.
## Responses
- `200`: OK.
- `slug` (string): The address: `markdrop.in/`.
- `url` (string): The link to share.
- `folder_path` (array of string)
- `title` (string | null): Shown on the page and in link previews.
- `content` (string): The document's Markdown.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owned` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `id` (string | null)
- `ref` (string | null)
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): `markdown` for a document, `artifact` for a file. Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `artifact_url` (string | null)
- `download_url` (string | null)
- `poster_url` (string | null)
- `media` (object | null)
- `file_version` (integer | null)
- `workspace_url` (string | null): The address on its workspace's own domain, when it has a live one.
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace_id` (string | null)
- `can` (object | null): What you may do with it: `edit`, `replace`, `settings`, `access`, `rename`, `delete`, `share`, `move`, `unshare`.
- `read_ticket` (string | null)
- `access_epoch` (integer): Default `0`.
- `409`: That address is taken, or reserved.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PATCH https://api.markdrop.in/api/v1/documents/k3x9q2m/slug \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"new_slug":"release-notes"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/slug", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"new_slug": "release-notes"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.patch(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/slug",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"new_slug": "release-notes"},
)
r.raise_for_status()
print(r.json())
```
---
# Set password and expiry
Source: https://docs.markdrop.in/api/documents/set-password-and-expiry
`PATCH https://api.markdrop.in/api/v1/documents/{slug}/settings`
Changes a document's password or expiry without sending its content. Every field is optional: leave one out to keep it. `"read_password": ""` removes the password. Send a token as the owner, or the edit secret.
- **Authentication:** Token optional: send `Authorization: Bearer mdk_…` to act as your account, or the document's edit secret in `X-Edit-Secret`.
- **Rate limit:** 10 per minute per IP address.
- **Guide:** [Passwords and expiry](https://docs.markdrop.in/documents/passwords-and-expiry)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Headers
- `X-Edit-Secret` (string): The document's edit secret (`sk_…`), for a document published without an account. See [Edit key](https://docs.markdrop.in/documents/edit-key).
## Request body
`application/json`, required.
- `read_password` (string): Readers must enter this password before they see the content. Up to 100 characters.
- `remove_password` (boolean): `true` removes the password. Default `false`.
- `expires_in` ("never" | "1h" | "1d" | "7d" | "30d" | "custom"): When the document is deleted. `custom` needs `custom_expires_at`.
- `custom_expires_at` (string (date-time)): The deletion time, when `expires_in` is `custom`. Must be in the future. ISO 8601, for example `2026-11-30T12:00:00Z`.
## Responses
- `200`: OK.
- `slug` (string): The address: `markdrop.in/`.
- `url` (string): The link to share.
- `folder_path` (array of string)
- `title` (string | null): Shown on the page and in link previews.
- `content` (string): The document's Markdown.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owned` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `id` (string | null)
- `ref` (string | null)
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): `markdown` for a document, `artifact` for a file. Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `artifact_url` (string | null)
- `download_url` (string | null)
- `poster_url` (string | null)
- `media` (object | null)
- `file_version` (integer | null)
- `workspace_url` (string | null): The address on its workspace's own domain, when it has a live one.
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace_id` (string | null)
- `can` (object | null): What you may do with it: `edit`, `replace`, `settings`, `access`, `rename`, `delete`, `share`, `move`, `unshare`.
- `read_ticket` (string | null)
- `access_epoch` (integer): Default `0`.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PATCH https://api.markdrop.in/api/v1/documents/k3x9q2m/settings \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"expires_in":"7d"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/settings", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"expires_in": "7d"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.patch(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/settings",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"expires_in": "7d"},
)
r.raise_for_status()
print(r.json())
```
---
# Report a document
Source: https://docs.markdrop.in/api/documents/report-a-document
`POST https://api.markdrop.in/api/v1/documents/{slug}/report`
Flags a document for abuse. Always answers `202`, whether or not the address exists.
- **Authentication:** No token needed.
- **Rate limit:** 5 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Request body
`application/json`, required.
- `reason` (string): Up to 500 characters.
## Responses
- `202`: Accepted.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents/k3x9q2m/report \
-H "Content-Type: application/json" \
-d '{"reason":"Phishing page"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/report", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"reason": "Phishing page"
}),
});
const data = await res.json();
```
**Python**
```python
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/report",
json={"reason": "Phishing page"},
)
r.raise_for_status()
print(r.json())
```
---
# List your documents
Source: https://docs.markdrop.in/api/documents/list-your-documents
`GET https://api.markdrop.in/api/v1/me/documents`
The documents and files you own, a page at a time, with counts of each kind.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **MCP tool:** `list_documents` ([tools](https://docs.markdrop.in/ai/tools))
## Query parameters
- `page` (integer): Page number, from 1. At least 1. Default `1`.
- `limit` (integer): How many to return. From 1 to 100. Default `20`.
- `per_page` (integer): Same as `limit`, up to 50; wins when both are sent. From 1 to 50.
- `q` (string): Only titles or addresses containing this text.
- `kind` (string): `document` (or `markdown`) for documents, `artifact` for files. One of `markdown`, `document`, `artifact`. One of `markdown`, `document`, `artifact`.
## Responses
- `200`: OK.
- `documents` (array of object)
- `id` (string | null)
- `slug` (string)
- `url` (string)
- `title` (string | null): Shown on the page and in link previews.
- `content_preview` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `export_pdf_count` (integer): Default `0`.
- `copy_url_count` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `workspace_id` (string | null)
- `folder_id` (string | null)
- `folder_path` (array of string)
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `shared_with_count` (integer): Default `0`.
- `shared_by_name` (string | null)
- `shared_by_email` (string | null)
- `is_mine` (boolean): Default `true`.
- `total` (integer)
- `page` (integer)
- `pages` (integer)
- `per_page` (integer): Default `20`.
- `count_all` (integer): Default `0`.
- `count_markdown` (integer): Default `0`.
- `count_artifact` (integer): Default `0`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Get the preview image
Source: https://docs.markdrop.in/api/documents/get-the-preview-image
`GET https://api.markdrop.in/api/v1/og/{slug}.png`
The 1200×630 image link previews show for a document, as PNG.
- **Authentication:** No token needed.
- **Rate limit:** 120 per minute per IP address.
- **Guide:** [Link previews](https://docs.markdrop.in/documents/link-previews)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `host` (string): Draw the card for this address (a workspace's own domain) instead of markdrop.in. Up to 253 characters.
## Responses
- `200`: OK.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/og/k3x9q2m.png
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/og/k3x9q2m.png");
const data = await res.blob();
```
**Python**
```python
import requests
r = requests.get("https://api.markdrop.in/api/v1/og/k3x9q2m.png")
r.raise_for_status()
open("out.png", "wb").write(r.content)
```
---
# Start a file upload
Source: https://docs.markdrop.in/api/files/start-a-file-upload
`POST https://api.markdrop.in/api/v1/files/uploads`
Starts publishing a file of any size: send its name, size and SHA-256, and get back presigned URLs to `PUT` each part to. Then [finish the upload](https://docs.markdrop.in/api/files/finish-a-file-upload). With `target_slug`, the file replaces the one behind an existing link instead, keeping the link, its access and its views.
The CLI does all of this, with resume, in one command.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
- **CLI:** `markdrop upload deck.pdf --title "Q3 deck"`
- **MCP tool:** `create_upload` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [Large uploads](https://docs.markdrop.in/files/large-uploads)
## Request body
`application/json`, required.
- `file_name` (string, required): The file's name, with its extension. 1–255 characters.
- `size_bytes` (integer, required): The file's exact size in bytes. At least 1.
- `content_type` (string): Up to 255 characters. Default `""`.
- `sha256` (string, required): The SHA-256 of the whole file, in hex. The server checks it before the file goes live. Matches `^[0-9a-fA-F]{64}$`.
- `target_slug` (string): Replace the file behind this existing address instead of publishing a new one. The link, access and views stay. Matches `^[A-Za-z0-9_-]{1,64}$`.
- `title` (string): Shown on the page and in link previews. Up to 200 characters.
- `access` ("link" | "private" | "password" | "workspace"): Who can open it: `link`, `private`, `password` (needs `password`) or `workspace` (needs `workspace`).
- `password` (string): 1–100 characters.
- `workspace` (string): File it in this workspace: the workspace's id. 1–64 characters.
- `folder` (string): An existing folder of `workspace`: its id or its path, like `guides/setup`. 1–400 characters.
- `slug` (string): Your own address: `markdrop.in/`. 3–50 characters. Matches `^[a-zA-Z0-9_-]+$`.
## Responses
- `200`: OK.
- `upload_id` (string)
- `part_size` (integer)
- `parts` (array of object)
- `part_number` (integer)
- `url` (string)
- `expires_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `401`: No token, or the token is revoked or mistyped.
- `409`: The address you asked for is taken, or reserved.
- `413`: The file is over the size limit for its type.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
- `507`: Your storage is full.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/files/uploads \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"file_name": "deck.pdf",
"size_bytes": 1048576,
"sha256": "SHA256_OF_THE_FILE",
"title": "Q3 deck"
}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/uploads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"file_name": "deck.pdf",
"size_bytes": 1048576,
"sha256": "SHA256_OF_THE_FILE",
"title": "Q3 deck"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/files/uploads",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={
"file_name": "deck.pdf",
"size_bytes": 1048576,
"sha256": "SHA256_OF_THE_FILE",
"title": "Q3 deck",
},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop upload deck.pdf --title "Q3 deck"
```
---
# Get fresh part URLs
Source: https://docs.markdrop.in/api/files/get-fresh-part-urls
`POST https://api.markdrop.in/api/v1/files/uploads/{upload_id}/urls`
New presigned URLs for parts of an upload in progress, for when the first ones expired before you used them.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `upload_id` (string, required): The `upload_id` that [Start a file upload](https://docs.markdrop.in/api/files/start-a-file-upload) returned.
## Request body
`application/json`.
- `part_numbers` (array of integer): The parts that need a fresh URL. Leave it out for every part not yet uploaded. Up to 10,000 items.
## Responses
- `200`: OK.
- `upload_id` (string)
- `part_size` (integer)
- `parts` (array of object)
- `part_number` (integer)
- `url` (string)
- `expires_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID/urls \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"part_numbers":[1,2]}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID/urls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"part_numbers": [
1,
2
]
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID/urls",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"part_numbers": [1, 2]},
)
r.raise_for_status()
print(r.json())
```
---
# Check an upload
Source: https://docs.markdrop.in/api/files/check-an-upload
`GET https://api.markdrop.in/api/v1/files/uploads/{upload_id}`
Where an upload stands: how many parts arrived and, once finished, whether the check passed (`state` is `done`, with `result`) or failed (`failed`, with `error`).
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 240 per minute per IP address.
- **MCP tool:** `get_upload_status` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `upload_id` (string, required): The `upload_id` that [Start a file upload](https://docs.markdrop.in/api/files/start-a-file-upload) returned.
## Responses
- `200`: OK.
- `upload_id` (string)
- `state` (string)
- `file_name` (string)
- `content_type` (string)
- `size_bytes` (integer)
- `sha256` (string)
- `part_size` (integer)
- `part_count` (integer)
- `parts_uploaded` (array of object)
- `part_number` (integer)
- `etag` (string)
- `size` (integer)
- `target_slug` (string | null)
- `expires_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `verified_bytes` (integer | null)
- `result` (object | null)
- `url` (string)
- `markdrop_url` (string | null)
- `workspace_url` (string | null)
- `workspace_error` (string | null)
- `slug` (string)
- `version` (integer)
- `size_bytes` (integer)
- `content_type` (string)
- `sha256` (string)
- `error` (string | null)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Cancel an upload
Source: https://docs.markdrop.in/api/files/cancel-an-upload
`DELETE https://api.markdrop.in/api/v1/files/uploads/{upload_id}`
Stops an upload in progress.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `upload_id` (string, required): The `upload_id` that [Start a file upload](https://docs.markdrop.in/api/files/start-a-file-upload) returned.
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Finish a file upload
Source: https://docs.markdrop.in/api/files/finish-a-file-upload
`POST https://api.markdrop.in/api/v1/files/uploads/{upload_id}/complete`
Publishes the file once every part is uploaded. The server checks the SHA-256 first. `200` returns the link. `202` (`{"state": "verifying"}`) means the check is still running: [check the upload](https://docs.markdrop.in/api/files/check-an-upload) until `state` is `done`. Calling it again on a finished upload returns the same result.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
- **CLI:** `markdrop upload deck.pdf`
- **MCP tool:** `complete_upload` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `upload_id` (string, required): The `upload_id` that [Start a file upload](https://docs.markdrop.in/api/files/start-a-file-upload) returned.
## Request body
`application/json`.
- `parts` (array of object): Each part's number and the `ETag` storage returned when you uploaded it. Up to 10,000 items.
- `part_number` (integer, required): From 1 to 10,000.
- `etag` (string, required): 1–200 characters.
## Responses
- `200`: Published. Returns the link.
- `202`: Still checking the file. Poll the upload's status.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID/complete \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"parts":[{"part_number":1,"etag":"ETAG_FROM_THE_PUT"}]}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID/complete", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"parts": [
{
"part_number": 1,
"etag": "ETAG_FROM_THE_PUT"
}
]
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/files/uploads/UPLOAD_ID/complete",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"parts": [{"part_number": 1, "etag": "ETAG_FROM_THE_PUT"}]},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop upload deck.pdf
```
---
# Get file details
Source: https://docs.markdrop.in/api/files/get-file-details
`GET https://api.markdrop.in/api/v1/files/{slug}`
A published file's size, type, current version, SHA-256 and, for video and audio, its duration and dimensions.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **CLI:** `markdrop info k3x9q2m`
- **MCP tool:** `get_file_info` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Responses
- `200`: OK.
- `slug` (string)
- `url` (string)
- `markdrop_url` (string | null)
- `workspace_url` (string | null)
- `title` (string | null): Shown on the page and in link previews.
- `content_type` (string)
- `size_bytes` (integer)
- `sha256` (string | null)
- `version` (integer)
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `media` (object | null)
- `duration_s` (number | null)
- `width` (integer | null)
- `height` (integer | null)
- `video_codec` (string | null)
- `audio_codec` (string | null)
- `poster_url` (string | null)
- `download_url` (string | null)
- `access` (string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/files/k3x9q2m \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/k3x9q2m", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/files/k3x9q2m",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop info k3x9q2m
```
---
# List file versions
Source: https://docs.markdrop.in/api/files/list-file-versions
`GET https://api.markdrop.in/api/v1/files/{slug}/versions`
Every version of a file you own. Replacing a file keeps the previous version.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **CLI:** `markdrop versions k3x9q2m`
- **MCP tool:** `list_file_versions` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [Replace and versions](https://docs.markdrop.in/files/replace-and-versions)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Responses
- `200`: OK.
- `versions` (array of object)
- `version` (integer)
- `size_bytes` (integer)
- `sha256` (string | null)
- `content_type` (string)
- `created_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `current` (boolean)
- `media` (object | null)
- `filename` (string | null)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/files/k3x9q2m/versions \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/k3x9q2m/versions", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/files/k3x9q2m/versions",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop versions k3x9q2m
```
---
# Restore a file version
Source: https://docs.markdrop.in/api/files/restore-a-file-version
`POST https://api.markdrop.in/api/v1/files/{slug}/versions/{version}/restore`
Makes an earlier version current again, at the same link. Versions start at 1.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
- **CLI:** `markdrop restore k3x9q2m 1`
- **MCP tool:** `restore_file_version` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [Replace and versions](https://docs.markdrop.in/files/replace-and-versions)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
- `version` (integer, required)
## Responses
- `200`: OK.
- `slug` (string)
- `url` (string)
- `markdrop_url` (string | null)
- `workspace_url` (string | null)
- `title` (string | null): Shown on the page and in link previews.
- `content_type` (string)
- `size_bytes` (integer)
- `sha256` (string | null)
- `version` (integer)
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `media` (object | null)
- `duration_s` (number | null)
- `width` (integer | null)
- `height` (integer | null)
- `video_codec` (string | null)
- `audio_codec` (string | null)
- `poster_url` (string | null)
- `download_url` (string | null)
- `access` (string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/files/k3x9q2m/versions/1/restore \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/files/k3x9q2m/versions/1/restore", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/files/k3x9q2m/versions/1/restore",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop restore k3x9q2m 1
```
---
# Publish an HTML page
Source: https://docs.markdrop.in/api/files/publish-an-html-page
`POST https://api.markdrop.in/api/v1/artifacts/paste`
Publishes HTML you send in the request as a page that renders. Up to 10 MB; anything larger goes through [Start a file upload](https://docs.markdrop.in/api/files/start-a-file-upload).
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 10 per minute per IP address.
- **MCP tool:** `publish_page` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [HTML pages](https://docs.markdrop.in/files/html-pages)
## Request body
`application/json`, required.
- `content` (string, required): The document's Markdown.
- `title` (string): Shown on the page and in link previews. Up to 200 characters.
- `custom_slug` (string): Your own address: `markdrop.in/`. Letters, digits, `-` and `_`. Taken or reserved addresses answer `409`. 3–50 characters. Matches `^[a-zA-Z0-9_-]+$`.
- `expires_in` ("never" | "1d" | "7d" | "30d" | "custom"): When the document is deleted. `custom` needs `custom_expires_at`. Default `"never"`.
- `custom_expires_at` (string (date-time)): The deletion time, when `expires_in` is `custom`. Must be in the future. ISO 8601, for example `2026-11-30T12:00:00Z`.
- `read_password` (string): Readers must enter this password before they see the content. 1–100 characters.
- `access_level` ("private" | "link"): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
## Responses
- `201`: Created.
- `slug` (string)
- `url` (string)
- `title` (string | null): Shown on the page and in link previews.
- `kind` (string): Default `"artifact"`.
- `mime` (string)
- `renderer` (string)
- `type_label` (string)
- `size_bytes` (integer)
- `original_filename` (string | null)
- `artifact_url` (string)
- `workspace_url` (string | null)
- `download_url` (string)
- `poster_url` (string | null)
- `media` (object | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `edit_secret` (string)
- `workspace_error` (string | null)
- `401`: No token, or the token is revoked or mistyped.
- `413`: The HTML is over 10 MB.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
- `507`: Your storage is full.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/artifacts/paste \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Launch page",
"content": "Launch
"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/artifacts/paste",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={
"title": "Launch page",
"content": "Launch
Hello
",
},
)
r.raise_for_status()
print(r.json())
```
---
# Get a single upload URL
Source: https://docs.markdrop.in/api/files/get-a-single-upload-url
`POST https://api.markdrop.in/api/v1/artifacts/upload-url`
The one-request upload markdrop.in uses for smaller files: returns a presigned URL to `PUT` the whole file to, then [publish it](https://docs.markdrop.in/api/files/publish-an-uploaded-file). For anything large, or to resume, use [Start a file upload](https://docs.markdrop.in/api/files/start-a-file-upload).
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
## Request body
`application/json`, required.
- `filename` (string, required): 1–255 characters.
- `content_type` (string, required): Up to 255 characters.
- `size_bytes` (integer, required): At least 1.
- `target_slug` (string): Matches `^[A-Za-z0-9_-]{1,64}$`.
## Responses
- `200`: OK.
- `upload_url` (string)
- `blob_key` (string)
- `mime` (string)
- `required_content_type` (string)
- `expires_in` (integer): When the document is deleted. `custom` needs `custom_expires_at`.
- `401`: No token, or the token is revoked or mistyped.
- `413`: The file is over the size limit for its type.
- `415`: That file type isn't supported.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/artifacts/upload-url \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filename": "deck.pdf",
"content_type": "application/pdf",
"size_bytes": 1048576
}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/artifacts/upload-url", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"filename": "deck.pdf",
"content_type": "application/pdf",
"size_bytes": 1048576
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/artifacts/upload-url",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={
"filename": "deck.pdf",
"content_type": "application/pdf",
"size_bytes": 1048576,
},
)
r.raise_for_status()
print(r.json())
```
---
# Publish an uploaded file
Source: https://docs.markdrop.in/api/files/publish-an-uploaded-file
`POST https://api.markdrop.in/api/v1/artifacts`
Publishes a file uploaded with [Get a single upload URL](https://docs.markdrop.in/api/files/get-a-single-upload-url), after checking its real size and type.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
## Request body
`application/json`, required.
- `blob_key` (string, required): 1–300 characters.
- `title` (string): Shown on the page and in link previews. Up to 200 characters.
- `filename` (string): Up to 255 characters.
- `custom_slug` (string): Your own address: `markdrop.in/`. Letters, digits, `-` and `_`. Taken or reserved addresses answer `409`. 3–50 characters. Matches `^[a-zA-Z0-9_-]+$`.
- `expires_in` ("never" | "1d" | "7d" | "30d" | "custom"): When the document is deleted. `custom` needs `custom_expires_at`. Default `"never"`.
- `custom_expires_at` (string (date-time)): The deletion time, when `expires_in` is `custom`. Must be in the future. ISO 8601, for example `2026-11-30T12:00:00Z`.
- `read_password` (string): Readers must enter this password before they see the content. 1–100 characters.
- `access_level` ("private" | "link" | "workspace"): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `workspace` (string): File it in this workspace: the workspace's id. 1–64 characters.
- `folder` (string): An existing folder of `workspace`: its id or its path, like `guides/setup`. 1–400 characters.
## Responses
- `201`: Created.
- `slug` (string)
- `url` (string)
- `title` (string | null): Shown on the page and in link previews.
- `kind` (string): Default `"artifact"`.
- `mime` (string)
- `renderer` (string)
- `type_label` (string)
- `size_bytes` (integer)
- `original_filename` (string | null)
- `artifact_url` (string)
- `workspace_url` (string | null)
- `download_url` (string)
- `poster_url` (string | null)
- `media` (object | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `edit_secret` (string)
- `workspace_error` (string | null)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/artifacts \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"blob_key":"BLOB_KEY","title":"Q3 deck"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/artifacts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"blob_key": "BLOB_KEY",
"title": "Q3 deck"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/artifacts",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"blob_key": "BLOB_KEY", "title": "Q3 deck"},
)
r.raise_for_status()
print(r.json())
```
---
# Update a file's settings
Source: https://docs.markdrop.in/api/files/update-a-file-s-settings
`PATCH https://api.markdrop.in/api/v1/artifacts/{slug}`
Changes a file's title, password or expiry. Every field is optional. The owner may change all of them; editors may change the title.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Request body
`application/json`, required.
- `title` (string): Shown on the page and in link previews. Up to 200 characters.
- `read_password` (string): Readers must enter this password before they see the content. Up to 100 characters.
- `remove_password` (boolean): `true` removes the password. Default `false`.
- `expires_in` ("never" | "1h" | "1d" | "7d" | "30d" | "custom"): When the document is deleted. `custom` needs `custom_expires_at`.
- `custom_expires_at` (string (date-time)): The deletion time, when `expires_in` is `custom`. Must be in the future. ISO 8601, for example `2026-11-30T12:00:00Z`.
## Responses
- `200`: OK.
- `slug` (string)
- `url` (string)
- `title` (string | null): Shown on the page and in link previews.
- `kind` (string): Default `"artifact"`.
- `mime` (string)
- `renderer` (string)
- `type_label` (string)
- `size_bytes` (integer)
- `original_filename` (string | null)
- `artifact_url` (string)
- `workspace_url` (string | null)
- `download_url` (string)
- `poster_url` (string | null)
- `media` (object | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PATCH https://api.markdrop.in/api/v1/artifacts/k3x9q2m \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Q3 deck (final)"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/artifacts/k3x9q2m", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Q3 deck (final)"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.patch(
"https://api.markdrop.in/api/v1/artifacts/k3x9q2m",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"title": "Q3 deck (final)"},
)
r.raise_for_status()
print(r.json())
```
---
# Replace a file
Source: https://docs.markdrop.in/api/files/replace-a-file
`PUT https://api.markdrop.in/api/v1/artifacts/{slug}/file`
Swaps the file behind a link for one uploaded with [Get a single upload URL](https://docs.markdrop.in/api/files/get-a-single-upload-url), keeping the link and its settings. The previous file is kept as a version.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
- **CLI:** `markdrop replace k3x9q2m deck-v2.pdf`
- **Guide:** [Replace and versions](https://docs.markdrop.in/files/replace-and-versions)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Request body
`application/json`, required.
- `blob_key` (string, required): 1–300 characters.
- `filename` (string): Up to 255 characters.
## Responses
- `200`: OK.
- `slug` (string)
- `url` (string)
- `title` (string | null): Shown on the page and in link previews.
- `kind` (string): Default `"artifact"`.
- `mime` (string)
- `renderer` (string)
- `type_label` (string)
- `size_bytes` (integer)
- `original_filename` (string | null)
- `artifact_url` (string)
- `workspace_url` (string | null)
- `download_url` (string)
- `poster_url` (string | null)
- `media` (object | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `is_owner` (boolean): Default `false`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
- `507`: The owner's storage is full.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/artifacts/k3x9q2m/file \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"blob_key":"BLOB_KEY"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/artifacts/k3x9q2m/file", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"blob_key": "BLOB_KEY"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/artifacts/k3x9q2m/file",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"blob_key": "BLOB_KEY"},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop replace k3x9q2m deck-v2.pdf
```
---
# Get sharing settings
Source: https://docs.markdrop.in/api/access/get-sharing-settings
`GET https://api.markdrop.in/api/v1/documents/{slug}/access`
Who can open a document and the people it is shared with. People with a role on the document can call it; the list of people is for the owner and editors.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **Guide:** [Who can open a document](https://docs.markdrop.in/sharing/who-can-open)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Responses
- `200`: OK.
- `level` ("private" | "link" | "workspace")
- `allow_resharing` (boolean)
- `is_password_protected` (boolean)
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key.
- `in_workspace` (boolean)
- `workspace_id` (string | null)
- `workspace_name` (string | null)
- `folder_id` (string | null)
- `can` (object | null)
- `your_role` ("owner" | "editor" | "viewer")
- `owner_name` (string | null)
- `owner_email` (string | null)
- `your_email` (string | null)
- `can_manage` (boolean)
- `can_share` (boolean)
- `grants` (array of object)
- `email` (string)
- `role` ("viewer" | "editor")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `granted_by_email` (string | null)
- `notified` (boolean): Default `false`.
- `active` (boolean): Default `true`.
- `effective_role` ("viewer" | "editor" | null)
- `url` (string | null)
- `workspace_url` (string | null)
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/documents/k3x9q2m/access \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/access", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/access",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Set who can open it
Source: https://docs.markdrop.in/api/access/set-who-can-open-it
`PUT https://api.markdrop.in/api/v1/documents/{slug}/access/level`
Sets a document's access level. Narrowing it takes effect at once, for every address the document has.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
- **MCP tool:** `set_access` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [Who can open a document](https://docs.markdrop.in/sharing/who-can-open)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Request body
`application/json`, required.
- `level` ("private" | "link" | "workspace", required): `link` (anyone with the link), `private` (only the people you share it with) or `workspace` (members of its workspace).
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/documents/k3x9q2m/access/level \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"level":"private"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/access/level", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"level": "private"
}),
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/access/level",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"level": "private"},
)
r.raise_for_status()
```
---
# Set access for several documents
Source: https://docs.markdrop.in/api/access/set-access-for-several-documents
`POST https://api.markdrop.in/api/v1/documents/access/level`
Sets the access level of many documents you own at once. Each one is checked on its own: the response has a result per address.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
## Request body
`application/json`, required.
- `slugs` (array of string, required): 1–200 items.
- `level` ("private" | "link" | "workspace", required)
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents/access/level \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slugs":["k3x9q2m","q3-report"],"level":"private"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/access/level", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"slugs": [
"k3x9q2m",
"q3-report"
],
"level": "private"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents/access/level",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"slugs": ["k3x9q2m", "q3-report"], "level": "private"},
)
r.raise_for_status()
print(r.json())
```
---
# Let editors share
Source: https://docs.markdrop.in/api/access/let-editors-share
`PUT https://api.markdrop.in/api/v1/documents/{slug}/access/resharing`
Whether editors may share the document with more people.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Request body
`application/json`, required.
- `allow` (boolean, required)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/documents/k3x9q2m/access/resharing \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allow":false}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/access/resharing", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"allow": false
}),
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/access/resharing",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"allow": False},
)
r.raise_for_status()
```
---
# Share with a person
Source: https://docs.markdrop.in/api/access/share-with-a-person
`POST https://api.markdrop.in/api/v1/documents/{slug}/access/people`
Gives one person access by email, and optionally emails them. If the email fails, the access stays: the response says whether it was sent.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per hour per IP address.
- **Guide:** [Share with people](https://docs.markdrop.in/sharing/share-with-people)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Request body
`application/json`, required.
- `email` (string (email), required): The person's email address. They don't need an account yet.
- `role` ("viewer" | "editor"): `viewer` can open it; `editor` can also change it. Default `"viewer"`.
- `notify` (boolean): Email them a link. Default `true`.
- `message` (string): A note included in the email. Up to 500 characters.
## Responses
- `201`: Created.
- `email` (string)
- `role` ("viewer" | "editor")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `granted_by_email` (string | null)
- `notified` (boolean): Default `false`.
- `active` (boolean): Default `true`.
- `effective_role` ("viewer" | "editor" | null)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents/k3x9q2m/access/people \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"sam@example.com","role":"viewer","notify":true}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/access/people", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "sam@example.com",
"role": "viewer",
"notify": true
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/access/people",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={
"email": "sam@example.com",
"role": "viewer",
"notify": True,
},
)
r.raise_for_status()
print(r.json())
```
---
# Remove a person
Source: https://docs.markdrop.in/api/access/remove-a-person
`DELETE https://api.markdrop.in/api/v1/documents/{slug}/access/people/{email}`
Takes away one person's access.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
- `email` (string, required)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/documents/k3x9q2m/access/people/sam%40example.com \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/access/people/sam%40example.com", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/access/people/sam%40example.com",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Request access
Source: https://docs.markdrop.in/api/access/request-access
`POST https://api.markdrop.in/api/v1/documents/{slug}/access-requests`
Asks the owner of a document you can't open for access. The owner can approve or decline it.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per hour per IP address.
- **Guide:** [Request access](https://docs.markdrop.in/sharing/request-access)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Request body
`application/json`, required.
- `message` (string): Up to 500 characters.
## Responses
- `201`: Created.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/documents/k3x9q2m/access-requests \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"Could I see the Q3 numbers?"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/access-requests", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"message": "Could I see the Q3 numbers?"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/access-requests",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"message": "Could I see the Q3 numbers?"},
)
r.raise_for_status()
print(r.json())
```
---
# Check your request
Source: https://docs.markdrop.in/api/access/check-your-request
`GET https://api.markdrop.in/api/v1/documents/{slug}/access-requests/mine`
Whether your request for access to a document is pending, approved or declined.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/documents/k3x9q2m/access-requests/mine \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/documents/k3x9q2m/access-requests/mine", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/documents/k3x9q2m/access-requests/mine",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# List requests for you
Source: https://docs.markdrop.in/api/access/list-requests-for-you
`GET https://api.markdrop.in/api/v1/me/access-requests`
Requests for access to documents you own.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Query parameters
- `status` ("pending" | "approved" | "declined" | "all"): Default `"pending"`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/access-requests \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/access-requests", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/access-requests",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Get an access request
Source: https://docs.markdrop.in/api/access/get-an-access-request
`GET https://api.markdrop.in/api/v1/access-requests/{request_id}`
One request for access to a document you own.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `request_id` (string, required)
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/access-requests/REQUEST_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/access-requests/REQUEST_ID", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/access-requests/REQUEST_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Answer an access request
Source: https://docs.markdrop.in/api/access/answer-an-access-request
`POST https://api.markdrop.in/api/v1/access-requests/{request_id}/decision`
Approves a request as a viewer or editor, or declines it.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per hour per IP address.
## Path parameters
- `request_id` (string, required)
## Request body
`application/json`, required.
- `decision` ("view" | "edit" | "decline", required): `view` or `edit` approves with that role; `decline` refuses.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/access-requests/REQUEST_ID/decision \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision":"view"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/access-requests/REQUEST_ID/decision", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"decision": "view"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/access-requests/REQUEST_ID/decision",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"decision": "view"},
)
r.raise_for_status()
print(r.json())
```
---
# List workspaces
Source: https://docs.markdrop.in/api/workspaces/list-workspaces
`GET https://api.markdrop.in/api/v1/workspaces`
The workspaces you belong to, with your role in each and their live domains.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
- **CLI:** `markdrop workspaces`
- **MCP tool:** `list_workspaces` ([tools](https://docs.markdrop.in/ai/tools))
## Responses
- `200`: OK.
- `workspaces` (array of object)
- `id` (string)
- `name` (string)
- `owner_id` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `branding` (object): Every field optional — an unset workspace looks exactly like Markdrop.
- `settings` (object)
- `role` ("owner" | "admin" | "member" | "viewer")
- `primary_host` (string | null)
- `domains` (array of object)
- `default_access` (string): Default `"workspace"`.
- `privacy` (string): Default `"custom"`.
- `member_count` (integer): Default `0`.
- `pending_invite_count` (integer): Default `0`.
- `domain_count` (integer): Default `0`.
- `401`: No token, or the token is revoked or mistyped.
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop workspaces
```
---
# Create a workspace
Source: https://docs.markdrop.in/api/workspaces/create-a-workspace
`POST https://api.markdrop.in/api/v1/workspaces`
Creates a workspace with you as its owner.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 10 per minute per IP address.
- **Guide:** [Create a workspace](https://docs.markdrop.in/workspaces/create)
## Request body
`application/json`, required.
- `name` (string, required): 1–80 characters.
## Responses
- `201`: Created.
- `id` (string)
- `name` (string)
- `owner_id` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `branding` (object): Every field optional — an unset workspace looks exactly like Markdrop.
- `site_name` (string | null): Up to 60 characters.
- `favicon_url` (string | null): Up to 500 characters.
- `logo_url` (string | null): Up to 500 characters.
- `accent_color` (string | null): Matches `^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`.
- `hide_markdrop_branding` (boolean): Default `false`.
- `logo_well` ("light" | "dark" | "checker" | null)
- `settings` (object)
- `viewer_chrome` ("full" | "minimal" | "none"): Default `"full"`.
- `require_auth_to_view` (boolean): Default `false`.
- `members_can_share_folders` (boolean): Default `true`.
- `default_access` ("link" | "workspace" | "private"): Default `"workspace"`.
- `role` ("owner" | "admin" | "member" | "viewer")
- `primary_host` (string | null)
- `domains` (array of object)
- `host` (string)
- `kind` (string | null)
- `status` (string): Default `"pending"`.
- `verified` (boolean)
- `primary` (boolean)
- `live` (boolean)
- `txt_ok` (boolean | null)
- `routing_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `default_access` (string): Default `"workspace"`.
- `privacy` (string): Default `"custom"`.
- `member_count` (integer): Default `0`.
- `pending_invite_count` (integer): Default `0`.
- `domain_count` (integer): Default `0`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Acme"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Acme"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"name": "Acme"},
)
r.raise_for_status()
print(r.json())
```
---
# Get a workspace
Source: https://docs.markdrop.in/api/workspaces/get-a-workspace
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}`
A workspace's name, branding, settings and your role in it.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Responses
- `200`: OK.
- `id` (string)
- `name` (string)
- `owner_id` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `branding` (object): Every field optional — an unset workspace looks exactly like Markdrop.
- `site_name` (string | null): Up to 60 characters.
- `favicon_url` (string | null): Up to 500 characters.
- `logo_url` (string | null): Up to 500 characters.
- `accent_color` (string | null): Matches `^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`.
- `hide_markdrop_branding` (boolean): Default `false`.
- `logo_well` ("light" | "dark" | "checker" | null)
- `settings` (object)
- `viewer_chrome` ("full" | "minimal" | "none"): Default `"full"`.
- `require_auth_to_view` (boolean): Default `false`.
- `members_can_share_folders` (boolean): Default `true`.
- `default_access` ("link" | "workspace" | "private"): Default `"workspace"`.
- `role` ("owner" | "admin" | "member" | "viewer")
- `primary_host` (string | null)
- `domains` (array of object)
- `host` (string)
- `kind` (string | null)
- `status` (string): Default `"pending"`.
- `verified` (boolean)
- `primary` (boolean)
- `live` (boolean)
- `txt_ok` (boolean | null)
- `routing_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `default_access` (string): Default `"workspace"`.
- `privacy` (string): Default `"custom"`.
- `member_count` (integer): Default `0`.
- `pending_invite_count` (integer): Default `0`.
- `domain_count` (integer): Default `0`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Update a workspace
Source: https://docs.markdrop.in/api/workspaces/update-a-workspace
`PUT https://api.markdrop.in/api/v1/workspaces/{workspace_id}`
Changes a workspace's name, branding or settings. Every field is optional.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
- **Guide:** [Branding](https://docs.markdrop.in/workspaces/branding)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `name` (string): 1–80 characters.
- `branding` (object): Every field optional — an unset workspace looks exactly like Markdrop.
- `site_name` (string): Up to 60 characters.
- `favicon_url` (string): Up to 500 characters.
- `logo_url` (string): Up to 500 characters.
- `accent_color` (string): Matches `^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`.
- `hide_markdrop_branding` (boolean): Default `false`.
- `logo_well` ("light" | "dark" | "checker")
- `settings` (object)
- `viewer_chrome` ("full" | "minimal" | "none"): Default `"full"`.
- `require_auth_to_view` (boolean): Default `false`.
- `members_can_share_folders` (boolean): Default `true`.
- `default_access` ("link" | "workspace" | "private"): Default `"workspace"`.
## Responses
- `200`: OK.
- `id` (string)
- `name` (string)
- `owner_id` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `branding` (object): Every field optional — an unset workspace looks exactly like Markdrop.
- `site_name` (string | null): Up to 60 characters.
- `favicon_url` (string | null): Up to 500 characters.
- `logo_url` (string | null): Up to 500 characters.
- `accent_color` (string | null): Matches `^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`.
- `hide_markdrop_branding` (boolean): Default `false`.
- `logo_well` ("light" | "dark" | "checker" | null)
- `settings` (object)
- `viewer_chrome` ("full" | "minimal" | "none"): Default `"full"`.
- `require_auth_to_view` (boolean): Default `false`.
- `members_can_share_folders` (boolean): Default `true`.
- `default_access` ("link" | "workspace" | "private"): Default `"workspace"`.
- `role` ("owner" | "admin" | "member" | "viewer")
- `primary_host` (string | null)
- `domains` (array of object)
- `host` (string)
- `kind` (string | null)
- `status` (string): Default `"pending"`.
- `verified` (boolean)
- `primary` (boolean)
- `live` (boolean)
- `txt_ok` (boolean | null)
- `routing_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `default_access` (string): Default `"workspace"`.
- `privacy` (string): Default `"custom"`.
- `member_count` (integer): Default `0`.
- `pending_invite_count` (integer): Default `0`.
- `domain_count` (integer): Default `0`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Acme Inc."}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Acme Inc."
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"name": "Acme Inc."},
)
r.raise_for_status()
print(r.json())
```
---
# Delete a workspace
Source: https://docs.markdrop.in/api/workspaces/delete-a-workspace
`DELETE https://api.markdrop.in/api/v1/workspaces/{workspace_id}`
Deletes a workspace, its folders and its domains. Documents filed in it are not deleted: they go back to their owners. Send the workspace's name to confirm.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 10 per hour per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `confirm_name` (string, required): The workspace's name, typed back, to confirm. 1–80 characters.
## Responses
- `200`: OK.
- `documents_released` (integer)
- `domains_removed` (integer)
- `members_removed` (integer)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"confirm_name":"Acme"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"confirm_name": "Acme"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"confirm_name": "Acme"},
)
r.raise_for_status()
print(r.json())
```
---
# List members
Source: https://docs.markdrop.in/api/workspaces/list-members
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/members`
Everyone in a workspace, with their roles.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Responses
- `200`: OK.
- `members` (array of object)
- `user_id` (string)
- `role` ("owner" | "admin" | "member" | "viewer")
- `email` (string | null)
- `name` (string | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Change a member's role
Source: https://docs.markdrop.in/api/workspaces/change-a-member-s-role
`PUT https://api.markdrop.in/api/v1/workspaces/{workspace_id}/members/{member_user_id}`
Makes a member an admin, a member or a viewer.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
- **Guide:** [Members and roles](https://docs.markdrop.in/workspaces/members-and-roles)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `member_user_id` (string, required)
## Request body
`application/json`, required.
- `role` ("admin" | "member" | "viewer", required): `admin`, `member` or `viewer`.
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members/USER_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"admin"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members/USER_ID", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"role": "admin"
}),
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members/USER_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"role": "admin"},
)
r.raise_for_status()
```
---
# Remove a member
Source: https://docs.markdrop.in/api/workspaces/remove-a-member
`DELETE https://api.markdrop.in/api/v1/workspaces/{workspace_id}/members/{member_user_id}`
Removes someone from a workspace.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `member_user_id` (string, required)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members/USER_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members/USER_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/members/USER_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# List invitations
Source: https://docs.markdrop.in/api/workspaces/list-invitations
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/invitations`
Invitations that haven't been answered yet.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Responses
- `200`: OK.
- `invitations` (array of object)
- `id` (string)
- `email` (string)
- `role` ("owner" | "admin" | "member" | "viewer")
- `status` ("pending" | "accepted" | "declined" | "revoked" | "expired")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `invited_by_name` (string | null)
- `responded_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Invite someone
Source: https://docs.markdrop.in/api/workspaces/invite-someone
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/invitations`
Emails someone an invitation to join the workspace.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per hour per IP address.
- **Guide:** [Members and roles](https://docs.markdrop.in/workspaces/members-and-roles)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `email` (string (email), required)
- `role` ("admin" | "member" | "viewer"): `admin`, `member` or `viewer`. See [Members and roles](https://docs.markdrop.in/workspaces/members-and-roles). Default `"member"`.
## Responses
- `201`: Created.
- `id` (string)
- `email` (string)
- `role` ("owner" | "admin" | "member" | "viewer")
- `status` ("pending" | "accepted" | "declined" | "revoked" | "expired")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `invited_by_name` (string | null)
- `responded_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"sam@example.com","role":"member"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "sam@example.com",
"role": "member"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"email": "sam@example.com", "role": "member"},
)
r.raise_for_status()
print(r.json())
```
---
# Revoke an invitation
Source: https://docs.markdrop.in/api/workspaces/revoke-an-invitation
`DELETE https://api.markdrop.in/api/v1/workspaces/{workspace_id}/invitations/{invite_id}`
Withdraws an invitation that hasn't been answered yet.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `invite_id` (string, required)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations/INVITE_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations/INVITE_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/invitations/INVITE_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Upload a logo or favicon
Source: https://docs.markdrop.in/api/workspaces/upload-a-logo-or-favicon
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/branding/{kind}`
Uploads an image and returns its URL. It isn't used until you save that URL in the workspace's branding with [Update a workspace](https://docs.markdrop.in/api/workspaces/update-a-workspace).
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per hour per IP address.
- **Guide:** [Branding](https://docs.markdrop.in/workspaces/branding)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `kind` (string, required): `logo` or `favicon`.
## Request body
`multipart/form-data`, required.
- `file` (file, required)
## Responses
- `200`: OK.
- `url` (string)
- `kind` ("favicon" | "logo")
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/branding/logo \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-F 'file=@logo.png'
```
**JavaScript**
```javascript
import { readFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("logo.png")]), "logo.png");
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/branding/logo", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
body: form,
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/branding/logo",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
files={"file": open("logo.png", "rb")},
)
r.raise_for_status()
print(r.json())
```
---
# Preview an invitation
Source: https://docs.markdrop.in/api/workspaces/preview-an-invitation
`GET https://api.markdrop.in/api/v1/invites/{token}`
What an invitation link is for: the workspace, the role and whether it is still open. Answers `200` for invitations already accepted or withdrawn too, saying so.
- **Authentication:** Token optional: send `Authorization: Bearer mdk_…` to act as your account.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `token` (string, required): The token from the invitation link.
## Responses
- `200`: OK.
- `workspace_name` (string)
- `role` ("owner" | "admin" | "member" | "viewer")
- `email` (string)
- `invited_by_name` (string | null)
- `status` ("pending" | "accepted" | "declined" | "revoked" | "expired")
- `expires_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `signed_in_as` (string | null)
- `email_matches` (boolean): Default `false`.
- `already_member` (boolean): Default `false`.
- `logo_url` (string | null)
- `accent_color` (string | null)
- `logo_well` ("light" | "dark" | "checker" | null)
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/invites/INVITE_TOKEN \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/invites/INVITE_TOKEN", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/invites/INVITE_TOKEN",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Accept an invitation
Source: https://docs.markdrop.in/api/workspaces/accept-an-invitation
`POST https://api.markdrop.in/api/v1/invites/{token}/accept`
Joins the workspace with the role the invitation offers, as the account the token belongs to.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
## Path parameters
- `token` (string, required): The token from the invitation link.
## Responses
- `200`: OK.
- `workspace_id` (string)
- `workspace_name` (string)
- `role` ("owner" | "admin" | "member" | "viewer")
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/invites/INVITE_TOKEN/accept \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/invites/INVITE_TOKEN/accept", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/invites/INVITE_TOKEN/accept",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Decline an invitation
Source: https://docs.markdrop.in/api/workspaces/decline-an-invitation
`POST https://api.markdrop.in/api/v1/invites/{token}/decline`
Turns an invitation down. No token needed: the invitation link is enough.
- **Authentication:** No token needed.
- **Rate limit:** 20 per minute per IP address.
## Path parameters
- `token` (string, required): The token from the invitation link.
## Responses
- `204`: No content.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/invites/INVITE_TOKEN/decline
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/invites/INVITE_TOKEN/decline", {
method: "POST",
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import requests
r = requests.post("https://api.markdrop.in/api/v1/invites/INVITE_TOKEN/decline")
r.raise_for_status()
```
---
# Transfer ownership
Source: https://docs.markdrop.in/api/folders/transfer-ownership
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/transfer`
Makes another member the owner. You become an admin. Documents, sharing and domains don't change. Owner only.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 10 per hour per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `user_id` (string, required): 1–64 characters.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/transfer \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id":"USER_ID"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/transfer", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"user_id": "USER_ID"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/transfer",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"user_id": "USER_ID"},
)
r.raise_for_status()
print(r.json())
```
---
# List folders
Source: https://docs.markdrop.in/api/folders/list-folders
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders`
Every folder in a workspace.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **MCP tool:** `list_folders` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Responses
- `200`: OK.
- `folders` (array of object)
- `id` (string)
- `workspace_id` (string)
- `name` (string)
- `parent_id` (string | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `slug` (string): Default `""`.
- `path` (array of string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Create a folder
Source: https://docs.markdrop.in/api/folders/create-a-folder
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders`
Creates a folder at the top level, or inside another folder with `parent_id`.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
- **Guide:** [Folders](https://docs.markdrop.in/workspaces/folders)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `name` (string, required): 1–80 characters.
- `parent_id` (string): Create it inside this folder. Leave it out for the top level.
## Responses
- `201`: Created.
- `id` (string)
- `workspace_id` (string)
- `name` (string)
- `parent_id` (string | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `slug` (string): Default `""`.
- `path` (array of string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Guides"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Guides"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"name": "Guides"},
)
r.raise_for_status()
print(r.json())
```
---
# Update a folder
Source: https://docs.markdrop.in/api/folders/update-a-folder
`PUT https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders/{folder_id}`
Renames a folder, or moves it with `parent_id` and `reparent: true`.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `folder_id` (string, required): The folder's id, from [List folders](https://docs.markdrop.in/api/folders/list-folders).
## Request body
`application/json`, required.
- `name` (string): 1–80 characters.
- `parent_id` (string)
- `reparent` (boolean): Default `false`.
## Responses
- `200`: OK.
- `id` (string)
- `workspace_id` (string)
- `name` (string)
- `parent_id` (string | null)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `slug` (string): Default `""`.
- `path` (array of string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Handbook"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Handbook"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"name": "Handbook"},
)
r.raise_for_status()
print(r.json())
```
---
# Rename or move a folder
Source: https://docs.markdrop.in/api/folders/rename-or-move-a-folder
`PATCH https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders/{folder_id}`
Renames a folder, changes its address, moves it (`parent_id`; `null` is the top level) or sets the access new documents in it start with. Admins and the owner. Every document below it moves to its new address.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `folder_id` (string, required): The folder's id, from [List folders](https://docs.markdrop.in/api/folders/list-folders).
## Request body
`application/json`, required.
- `name` (string): 1–80 characters.
- `slug` (string): 1–40 characters.
- `parent_id` (string)
- `default_access` ("link" | "private" | "workspace")
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PATCH https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Handbook"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Handbook"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.patch(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"name": "Handbook"},
)
r.raise_for_status()
print(r.json())
```
---
# Delete a folder
Source: https://docs.markdrop.in/api/folders/delete-a-folder
`DELETE https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders/{folder_id}`
Deletes a folder. The documents in it aren't deleted: `unfiled_documents` says how many are no longer in a folder.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `folder_id` (string, required): The folder's id, from [List folders](https://docs.markdrop.in/api/folders/list-folders).
## Query parameters
- `mode` (string): One of `unfile`, `move_up`. One of `unfile`, `move_up`. Default `"unfile"`.
## Responses
- `200`: OK.
- `unfiled_documents` (integer)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Get the folder tree
Source: https://docs.markdrop.in/api/folders/get-the-folder-tree
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/tree`
The workspace's folders as a tree, with their paths.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **CLI:** `markdrop workspaces Acme`
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/tree \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/tree", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/tree",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
**CLI**
```bash
markdrop workspaces Acme
```
---
# List workspace items
Source: https://docs.markdrop.in/api/folders/list-workspace-items
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/items`
The documents and folders in a workspace that you can see.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Query parameters
- `folder` (string): Up to 64 characters. Default `"all"`.
- `q` (string): Up to 200 characters.
- `kind` (string): One of `markdown`, `document`, `artifact`. One of `markdown`, `document`, `artifact`.
- `cursor` (string): Up to 200 characters.
- `limit` (integer): How many to return. From 1 to 100. Default `50`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Preview as someone else
Source: https://docs.markdrop.in/api/folders/preview-as-someone-else
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/preview`
What another person would see in a folder, and what is hidden from them and why. Members can preview; previewing a named person (`as` set to an email) is for admins.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Query parameters
- `folder` (string): Up to 64 characters. Default `"root"`.
- `as` ("anyone" | "member" | "email"): Default `"anyone"`.
- `email` (string): Up to 320 characters.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/preview \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/preview", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/preview",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# File documents
Source: https://docs.markdrop.in/api/folders/file-documents
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/items/add`
Files documents you own into the workspace. Their access stays the same unless you send `access`.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
- **MCP tool:** `add_to_workspace` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `slugs` (array of string, required): Addresses of documents you own. 1–200 items.
- `folder_id` (string): File them in this folder. Leave it out for the top level.
- `access` ("link" | "private" | "workspace"): Also change who can open them. Left out, their access doesn't change.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/add \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slugs":["k3x9q2m"],"folder_id":"FOLDER_ID"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/add", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"slugs": [
"k3x9q2m"
],
"folder_id": "FOLDER_ID"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/add",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"slugs": ["k3x9q2m"], "folder_id": "FOLDER_ID"},
)
r.raise_for_status()
print(r.json())
```
---
# Move documents
Source: https://docs.markdrop.in/api/folders/move-documents
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/items/move`
Moves documents to another folder. Every document is checked first: if you can't move one, none move.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `slugs` (array of string, required): 1–200 items.
- `folder_id` (string): The destination folder, or `null` for the top level.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/move \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slugs":["k3x9q2m"],"folder_id":"FOLDER_ID"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/move", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"slugs": [
"k3x9q2m"
],
"folder_id": "FOLDER_ID"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/move",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"slugs": ["k3x9q2m"], "folder_id": "FOLDER_ID"},
)
r.raise_for_status()
print(r.json())
```
---
# Take documents out
Source: https://docs.markdrop.in/api/folders/take-documents-out
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/items/remove`
Takes documents out of the workspace. They go back to their owners. A document with `workspace` access becomes `private`.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `slugs` (array of string): Up to 200 items.
- `handles` (array of string): Up to 200 items.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/remove \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slugs":["k3x9q2m"]}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/remove", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"slugs": [
"k3x9q2m"
]
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/items/remove",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"slugs": ["k3x9q2m"]},
)
r.raise_for_status()
print(r.json())
```
---
# Move a document
Source: https://docs.markdrop.in/api/folders/move-a-document
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/documents/move`
Moves one document to a folder (`folder_id`), or to the top level (`null`).
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `slug` (string, required): 1–50 characters.
- `folder_id` (string)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/move \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug":"k3x9q2m","folder_id":"FOLDER_ID"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/move", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"slug": "k3x9q2m",
"folder_id": "FOLDER_ID"
}),
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/move",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"slug": "k3x9q2m", "folder_id": "FOLDER_ID"},
)
r.raise_for_status()
```
---
# Share a folder
Source: https://docs.markdrop.in/api/folders/share-a-folder
`PUT https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders/{folder_id}/share`
Creates a link to a folder. Each visitor sees only the documents they could already open: sharing a folder never changes any document's access. `hidden` lists what the link's audience can't see.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
- **Guide:** [Share a folder](https://docs.markdrop.in/workspaces/share-a-folder)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `folder_id` (string, required): The folder's id, from [List folders](https://docs.markdrop.in/api/folders/list-folders).
## Request body
`application/json`, required.
- `level` ("link" | "workspace", required): `link`: anyone with the folder link; `workspace`: members only. Each visitor still sees only the documents they could already open.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"level":"link"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"level": "link"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"level": "link"},
)
r.raise_for_status()
print(r.json())
```
---
# Get a folder's link
Source: https://docs.markdrop.in/api/folders/get-a-folder-s-link
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders/{folder_id}/share`
The folder's share, or `404` when it isn't shared.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `folder_id` (string, required): The folder's id, from [List folders](https://docs.markdrop.in/api/folders/list-folders).
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Stop sharing a folder
Source: https://docs.markdrop.in/api/folders/stop-sharing-a-folder
`DELETE https://api.markdrop.in/api/v1/workspaces/{workspace_id}/folders/{folder_id}/share`
Turns off the folder's link.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `folder_id` (string, required): The folder's id, from [List folders](https://docs.markdrop.in/api/folders/list-folders).
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/folders/FOLDER_ID/share",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Open a shared folder
Source: https://docs.markdrop.in/api/folders/open-a-shared-folder
`GET https://api.markdrop.in/api/v1/folders/shared/{share_id}`
What a shared folder link shows: the folder, its subfolders and the documents in it that you can open. A folder shared with `workspace` access needs a member's token.
- **Authentication:** No token needed.
- **Rate limit:** 300 per minute per IP address.
## Path parameters
- `share_id` (string, required)
## Query parameters
- `path` (string): Up to 1,000 characters. Default `""`.
## Responses
- `200`: OK.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/folders/shared/SHARE_ID
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/folders/shared/SHARE_ID");
const data = await res.json();
```
**Python**
```python
import requests
r = requests.get("https://api.markdrop.in/api/v1/folders/shared/SHARE_ID")
r.raise_for_status()
print(r.json())
```
---
# List workspace documents
Source: https://docs.markdrop.in/api/folders/list-workspace-documents
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/documents`
The documents filed in a workspace.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Query parameters
- `page` (integer): Page number, from 1. At least 1. Default `1`.
- `limit` (integer): How many to return. From 1 to 100. Default `20`.
- `per_page` (integer): From 1 to 50.
- `q` (string)
- `kind` (string): One of `markdown`, `document`, `artifact`. One of `markdown`, `document`, `artifact`.
- `folder_id` (string): The folder's id, from [List folders](https://docs.markdrop.in/api/folders/list-folders).
- `unfiled` (boolean): Default `false`.
## Responses
- `200`: OK.
- `documents` (array of object)
- `id` (string | null)
- `slug` (string)
- `url` (string)
- `title` (string | null): Shown on the page and in link previews.
- `content_preview` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `export_pdf_count` (integer): Default `0`.
- `copy_url_count` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `workspace_id` (string | null)
- `folder_id` (string | null)
- `folder_path` (array of string)
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `shared_with_count` (integer): Default `0`.
- `shared_by_name` (string | null)
- `shared_by_email` (string | null)
- `is_mine` (boolean): Default `true`.
- `total` (integer)
- `page` (integer)
- `pages` (integer)
- `per_page` (integer): Default `20`.
- `count_all` (integer): Default `0`.
- `count_markdown` (integer): Default `0`.
- `count_artifact` (integer): Default `0`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Add a document
Source: https://docs.markdrop.in/api/folders/add-a-document
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/documents`
Files one of your documents in the workspace, by its id.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `document_id` (string, required)
- `folder_id` (string)
## Responses
- `201`: Created.
- `id` (string | null)
- `slug` (string)
- `url` (string)
- `title` (string | null): Shown on the page and in link previews.
- `content_preview` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `updated_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `views` (integer): Default `0`.
- `export_pdf_count` (integer): Default `0`.
- `copy_url_count` (integer): Default `0`.
- `is_password_protected` (boolean): Default `false`.
- `google_doc_url` (string | null)
- `google_doc_stale` (boolean): Default `false`.
- `vscode_synced` (boolean): Default `false`.
- `encrypted` (boolean): Set by clients that encrypt in the browser: `content` and `title` are already ciphertext. The server never sees the key. Default `false`.
- `kind` (string): Default `"markdown"`.
- `mime` (string | null)
- `renderer` (string | null)
- `type_label` (string | null)
- `size_bytes` (integer | null)
- `original_filename` (string | null)
- `workspace_id` (string | null)
- `folder_id` (string | null)
- `folder_path` (array of string)
- `access_level` (string): Who can open it: `link` (anyone with the link), `private` (only you and the people you share it with) or `workspace` (members of its workspace). Default `"link"`.
- `shared_with_count` (integer): Default `0`.
- `shared_by_name` (string | null)
- `shared_by_email` (string | null)
- `is_mine` (boolean): Default `true`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"document_id":"DOCUMENT_ID"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"document_id": "DOCUMENT_ID"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"document_id": "DOCUMENT_ID"},
)
r.raise_for_status()
print(r.json())
```
---
# Count workspace documents
Source: https://docs.markdrop.in/api/folders/count-workspace-documents
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/documents/counts`
How many documents a workspace has in each folder.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/counts \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/counts", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/counts",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Remove a document
Source: https://docs.markdrop.in/api/folders/remove-a-document
`DELETE https://api.markdrop.in/api/v1/workspaces/{workspace_id}/documents/{document_id}`
Takes a document out of the workspace. It isn't deleted: it goes back to its owner unchanged.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `document_id` (string, required)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/DOCUMENT_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/DOCUMENT_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/DOCUMENT_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# File in a folder
Source: https://docs.markdrop.in/api/folders/file-in-a-folder
`PUT https://api.markdrop.in/api/v1/workspaces/{workspace_id}/documents/{document_id}/folder`
Puts a workspace document in a folder, or at the top level with `null`.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `document_id` (string, required)
## Request body
`application/json`, required.
- `folder_id` (string)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/DOCUMENT_ID/folder \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"folder_id":"FOLDER_ID"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/DOCUMENT_ID/folder", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"folder_id": "FOLDER_ID"
}),
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/documents/DOCUMENT_ID/folder",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"folder_id": "FOLDER_ID"},
)
r.raise_for_status()
```
---
# List domains
Source: https://docs.markdrop.in/api/domains/list-domains
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains`
A workspace's custom domains and where each one stands.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **Guide:** [Custom domain](https://docs.markdrop.in/workspaces/custom-domain)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Responses
- `200`: OK.
- `domains` (array of object)
- `id` (string)
- `host` (string)
- `kind` ("app" | "cdn")
- `status` ("pending" | "verified" | "failed" | "lapsed")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `verified_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_checked_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_error` (string | null)
- `attached` (boolean): Default `false`.
- `is_primary` (boolean): Default `false`.
- `dns_record_name` (string | null)
- `dns_record_type` (string | null): Default `"TXT"`.
- `dns_record_value` (string | null)
- `dns_target_name` (string | null)
- `dns_target_type` (string | null)
- `dns_target_value` (string | null)
- `warning` (string | null)
- `live` (boolean): Default `false`.
- `routing_ok` (boolean | null)
- `txt_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `first_failed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `lapsed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `apex_certain` (boolean | null): Default `true`.
- `dns_routing_options` (array of object)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Add a domain
Source: https://docs.markdrop.in/api/domains/add-a-domain
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains`
Adds a domain to a workspace and returns the DNS records to create at your DNS provider.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
- **Guide:** [Custom domain](https://docs.markdrop.in/workspaces/custom-domain)
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
## Request body
`application/json`, required.
- `host` (string, required): The full hostname, like `docs.acme.com`. 3–253 characters.
- `kind` ("app" | "cdn", required): `app` serves documents and files; `cdn` serves files only.
## Responses
- `201`: Created.
- `id` (string)
- `host` (string)
- `kind` ("app" | "cdn")
- `status` ("pending" | "verified" | "failed" | "lapsed")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `verified_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_checked_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_error` (string | null)
- `attached` (boolean): Default `false`.
- `is_primary` (boolean): Default `false`.
- `dns_record_name` (string | null)
- `dns_record_type` (string | null): Default `"TXT"`.
- `dns_record_value` (string | null)
- `dns_target_name` (string | null)
- `dns_target_type` (string | null)
- `dns_target_value` (string | null)
- `warning` (string | null)
- `live` (boolean): Default `false`.
- `routing_ok` (boolean | null)
- `txt_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `first_failed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `lapsed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `apex_certain` (boolean | null): Default `true`.
- `dns_routing_options` (array of object)
- `type` (string)
- `name` (string)
- `value` (string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"host":"docs.acme.com","kind":"app"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"host": "docs.acme.com",
"kind": "app"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"host": "docs.acme.com", "kind": "app"},
)
r.raise_for_status()
print(r.json())
```
---
# Verify a domain
Source: https://docs.markdrop.in/api/domains/verify-a-domain
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains/{domain_id}/verify`
Checks the domain's DNS records now, instead of waiting for the next automatic check.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `domain_id` (string, required): The domain's id, from [List domains](https://docs.markdrop.in/api/domains/list-domains).
## Responses
- `200`: OK.
- `id` (string)
- `host` (string)
- `kind` ("app" | "cdn")
- `status` ("pending" | "verified" | "failed" | "lapsed")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `verified_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_checked_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_error` (string | null)
- `attached` (boolean): Default `false`.
- `is_primary` (boolean): Default `false`.
- `dns_record_name` (string | null)
- `dns_record_type` (string | null): Default `"TXT"`.
- `dns_record_value` (string | null)
- `dns_target_name` (string | null)
- `dns_target_type` (string | null)
- `dns_target_value` (string | null)
- `warning` (string | null)
- `live` (boolean): Default `false`.
- `routing_ok` (boolean | null)
- `txt_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `first_failed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `lapsed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `apex_certain` (boolean | null): Default `true`.
- `dns_routing_options` (array of object)
- `type` (string)
- `name` (string)
- `value` (string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/verify \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/verify",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Turn on a domain
Source: https://docs.markdrop.in/api/domains/turn-on-a-domain
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains/{domain_id}/attach`
Starts serving a verified domain, with HTTPS.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `domain_id` (string, required): The domain's id, from [List domains](https://docs.markdrop.in/api/domains/list-domains).
## Responses
- `200`: OK.
- `id` (string)
- `host` (string)
- `kind` ("app" | "cdn")
- `status` ("pending" | "verified" | "failed" | "lapsed")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `verified_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_checked_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_error` (string | null)
- `attached` (boolean): Default `false`.
- `is_primary` (boolean): Default `false`.
- `dns_record_name` (string | null)
- `dns_record_type` (string | null): Default `"TXT"`.
- `dns_record_value` (string | null)
- `dns_target_name` (string | null)
- `dns_target_type` (string | null)
- `dns_target_value` (string | null)
- `warning` (string | null)
- `live` (boolean): Default `false`.
- `routing_ok` (boolean | null)
- `txt_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `first_failed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `lapsed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `apex_certain` (boolean | null): Default `true`.
- `dns_routing_options` (array of object)
- `type` (string)
- `name` (string)
- `value` (string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
- `501`: Domains can't be turned on automatically here. Finish the step by hand.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/attach \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/attach", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/attach",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Take a domain offline
Source: https://docs.markdrop.in/api/domains/take-a-domain-offline
`POST https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains/{domain_id}/detach`
Stops serving a domain without losing its verification, so you can turn it back on later.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `domain_id` (string, required): The domain's id, from [List domains](https://docs.markdrop.in/api/domains/list-domains).
## Responses
- `200`: OK.
- `id` (string)
- `host` (string)
- `kind` ("app" | "cdn")
- `status` ("pending" | "verified" | "failed" | "lapsed")
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `verified_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_checked_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_error` (string | null)
- `attached` (boolean): Default `false`.
- `is_primary` (boolean): Default `false`.
- `dns_record_name` (string | null)
- `dns_record_type` (string | null): Default `"TXT"`.
- `dns_record_value` (string | null)
- `dns_target_name` (string | null)
- `dns_target_type` (string | null)
- `dns_target_value` (string | null)
- `warning` (string | null)
- `live` (boolean): Default `false`.
- `routing_ok` (boolean | null)
- `txt_ok` (boolean | null)
- `failures` (integer): Default `0`.
- `first_failed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `lapsed_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `expires_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `apex_certain` (boolean | null): Default `true`.
- `dns_routing_options` (array of object)
- `type` (string)
- `name` (string)
- `value` (string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/detach \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/detach", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/detach",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Detect the DNS provider
Source: https://docs.markdrop.in/api/domains/detect-the-dns-provider
`GET https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains/{domain_id}/provider`
Which DNS provider the domain uses, so setup instructions can name the right screens. Makes a live DNS lookup.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 30 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `domain_id` (string, required): The domain's id, from [List domains](https://docs.markdrop.in/api/domains/list-domains).
## Responses
- `200`: OK.
- `detected` (boolean): Default `false`.
- `provider_id` (string | null)
- `provider_name` (string | null)
- `panel_url` (string | null)
- `host_field` (string | null)
- `record_host` (string | null)
- `target_host` (string | null)
- `note` (string | null)
- `nameservers` (array of string)
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/provider \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/provider", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/provider",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Set the primary domain
Source: https://docs.markdrop.in/api/domains/set-the-primary-domain
`PUT https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains/{domain_id}/primary`
Chooses the domain the workspace's links and previews use. Admins and the owner.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `domain_id` (string, required): The domain's id, from [List domains](https://docs.markdrop.in/api/domains/list-domains).
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X PUT https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/primary \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/primary", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.put(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID/primary",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Remove a domain
Source: https://docs.markdrop.in/api/domains/remove-a-domain
`DELETE https://api.markdrop.in/api/v1/workspaces/{workspace_id}/domains/{domain_id}`
Removes a domain from the workspace. Links on it stop working; the documents keep their markdrop.in addresses.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per minute per IP address.
## Path parameters
- `workspace_id` (string, required): The workspace's id, from the address of its settings page (`markdrop.in/settings/workspaces/`) or from [List workspaces](https://docs.markdrop.in/api/workspaces/list-workspaces).
- `domain_id` (string, required): The domain's id, from [List domains](https://docs.markdrop.in/api/domains/list-domains).
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/workspaces/WORKSPACE_ID/domains/DOMAIN_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Analytics for everything
Source: https://docs.markdrop.in/api/analytics/analytics-for-everything
`GET https://api.markdrop.in/api/v1/me/analytics`
Totals across every document and file you own.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **Guide:** [Analytics](https://docs.markdrop.in/analytics/overview)
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/analytics \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/analytics", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/analytics",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Who is reading now
Source: https://docs.markdrop.in/api/analytics/who-is-reading-now
`GET https://api.markdrop.in/api/v1/me/analytics/live`
People reading or watching your documents right now.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 240 per minute per IP address.
- **Guide:** [Live now](https://docs.markdrop.in/analytics/live)
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/analytics/live \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/analytics/live", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/analytics/live",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Export all analytics
Source: https://docs.markdrop.in/api/analytics/export-all-analytics
`GET https://api.markdrop.in/api/v1/me/analytics.csv`
Your analytics across every document, as CSV.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/analytics.csv \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/analytics.csv", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.text();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/analytics.csv",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.text)
```
---
# Document view totals
Source: https://docs.markdrop.in/api/analytics/document-view-totals
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics`
View counts, unique readers, read depth and top referrers for a document. Owner only: anyone else who can read it gets `403`.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **MCP tool:** `get_document_stats` ([tools](https://docs.markdrop.in/ai/tools))
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `all`. One of `7d`, `30d`, `all`. Default `"30d"`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Document overview
Source: https://docs.markdrop.in/api/analytics/document-overview
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics/overview`
The headline numbers for one document or file you own.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **MCP tool:** `get_document_analytics` ([tools](https://docs.markdrop.in/ai/tools))
- **Guide:** [Analytics](https://docs.markdrop.in/analytics/overview)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/overview \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/overview", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/overview",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Video and audio retention
Source: https://docs.markdrop.in/api/analytics/video-and-audio-retention
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics/media`
How many people are still watching or listening through a video or audio file.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **Guide:** [Video and audio](https://docs.markdrop.in/analytics/video-and-audio)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `ver` (integer): From 1 to 100,000.
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/media \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/media", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/media",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Video frame
Source: https://docs.markdrop.in/api/analytics/video-frame
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics/frame`
A frame of your video at `t` seconds, as JPEG. `404` when the file isn't a video.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `t` (number, required): From 0 to 86,400.
- `ver` (integer): From 1 to 100,000.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl 'https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/frame?t=30' \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/frame?t=30", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.blob();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/frame?t=30",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
open("out.jpeg", "wb").write(r.content)
```
---
# Reading depth
Source: https://docs.markdrop.in/api/analytics/reading-depth
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics/content`
How far readers get through a Markdown document, section by section.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **Guide:** [Documents and PDFs](https://docs.markdrop.in/analytics/documents-and-pdfs)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/content \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/content", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/content",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# PDF pages reached
Source: https://docs.markdrop.in/api/analytics/pdf-pages-reached
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics/pages`
How many readers reach each page of a PDF.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
- **Guide:** [Documents and PDFs](https://docs.markdrop.in/analytics/documents-and-pdfs)
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/pages \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/pages", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/pages",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Audience
Source: https://docs.markdrop.in/api/analytics/audience
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics/audience`
Who a document's readers are: new or returning, their devices and countries.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/audience \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/audience", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/audience",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Numbers by version
Source: https://docs.markdrop.in/api/analytics/numbers-by-version
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics/versions`
A file's numbers split by the version people saw.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/versions \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/versions", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics/versions",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Export document analytics
Source: https://docs.markdrop.in/api/analytics/export-document-analytics
`GET https://api.markdrop.in/api/v1/me/documents/{slug}/analytics.csv`
One document's analytics, as CSV.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Path parameters
- `slug` (string, required): The document's address: the part after `markdrop.in/`.
## Query parameters
- `range` (string): The period to count: `7d`, `30d`, `90d` or `all`. One of `7d`, `30d`, `90d`, `all`. One of `7d`, `30d`, `90d`, `all`. Default `"30d"`.
- `internal` (integer): From 0 to 1. Default `0`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics.csv \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics.csv", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.text();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/documents/k3x9q2m/analytics.csv",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.text)
```
---
# Set the weekly email
Source: https://docs.markdrop.in/api/analytics/set-the-weekly-email
`PATCH https://api.markdrop.in/api/v1/me/preferences`
Turns the weekly analytics email on or off.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Guide:** [Weekly email](https://docs.markdrop.in/analytics/weekly-email)
## Request body
`application/json`, required.
- `analytics_digest` ("weekly" | "off", required): `weekly` sends the weekly analytics email; `off` stops it.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
## Examples
**curl**
```bash
curl -X PATCH https://api.markdrop.in/api/v1/me/preferences \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"analytics_digest":"off"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/preferences", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"analytics_digest": "off"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.patch(
"https://api.markdrop.in/api/v1/me/preferences",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"analytics_digest": "off"},
)
r.raise_for_status()
print(r.json())
```
---
# Get your account
Source: https://docs.markdrop.in/api/account/get-your-account
`GET https://api.markdrop.in/api/v1/auth/me`
The account a token belongs to: id, email, name and sign-in methods. A quick way to check a token works.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
## Responses
- `200`: OK.
- `id` (string)
- `email` (string)
- `name` (string | null)
- `picture` (string | null)
- `providers` (array of string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `analytics_digest` ("weekly" | "off"): Default `"weekly"`.
- `401`: No token, or the token is revoked or mistyped.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/auth/me \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/auth/me", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/auth/me",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Set your name
Source: https://docs.markdrop.in/api/account/set-your-name
`PATCH https://api.markdrop.in/api/v1/auth/me`
Set the display name (used for passwordless email accounts with no name).
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
## Request body
`application/json`, required.
- `name` (string, required): 1–80 characters.
## Responses
- `200`: OK.
- `id` (string)
- `email` (string)
- `name` (string | null)
- `picture` (string | null)
- `providers` (array of string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `analytics_digest` ("weekly" | "off"): Default `"weekly"`.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
## Examples
**curl**
```bash
curl -X PATCH https://api.markdrop.in/api/v1/auth/me \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Sam Lee"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/auth/me", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Sam Lee"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.patch(
"https://api.markdrop.in/api/v1/auth/me",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"name": "Sam Lee"},
)
r.raise_for_status()
print(r.json())
```
---
# List API tokens
Source: https://docs.markdrop.in/api/account/list-api-tokens
`GET https://api.markdrop.in/api/v1/me/tokens`
Your API tokens: name, first characters, when each was created and last used. Never the tokens themselves.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Guide:** [API tokens](https://docs.markdrop.in/account/api-tokens)
## Responses
- `200`: OK.
- `tokens` (array of object)
- `id` (string)
- `name` (string)
- `prefix` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_used_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `401`: No token, or the token is revoked or mistyped.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/tokens \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/tokens", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/tokens",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Create an API token
Source: https://docs.markdrop.in/api/account/create-an-api-token
`POST https://api.markdrop.in/api/v1/me/tokens`
Creates an API token (`mdk_…`). It is shown only in this response.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 20 per hour per IP address.
- **Guide:** [API tokens](https://docs.markdrop.in/account/api-tokens)
## Request body
`application/json`, required.
- `name` (string): A name to recognise it by, like the script or device that uses it. Up to 80 characters. Default `"API token"`.
## Responses
- `200`: OK.
- `id` (string)
- `name` (string)
- `prefix` (string)
- `created_at` (string (date-time)): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `last_used_at` (string (date-time) | null): ISO 8601, for example `2026-11-30T12:00:00Z`.
- `token` (string): The token. Shown only in this response: store it now.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl -X POST https://api.markdrop.in/api/v1/me/tokens \
-H "Authorization: Bearer $MARKDROP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Build server"}'
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/tokens", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Build server"
}),
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.post(
"https://api.markdrop.in/api/v1/me/tokens",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
json={"name": "Build server"},
)
r.raise_for_status()
print(r.json())
```
---
# Revoke an API token
Source: https://docs.markdrop.in/api/account/revoke-an-api-token
`DELETE https://api.markdrop.in/api/v1/me/tokens/{token_id}`
Revokes a token at once. Requests with it get `401` from then on.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
## Path parameters
- `token_id` (string, required)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/me/tokens/TOKEN_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/tokens/TOKEN_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/me/tokens/TOKEN_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```
---
# Suggest people
Source: https://docs.markdrop.in/api/account/suggest-people
`GET https://api.markdrop.in/api/v1/me/contacts`
Up to 50 people you can share with: people you shared documents with before, and members of your workspaces. Never a search of every account.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Query parameters
- `q` (string): Only people whose email or name contains this text. Up to 200 characters.
## Responses
- `200`: OK.
- `contacts` (array of object)
- `email` (string)
- `name` (string | null)
- `source` ("shared" | "workspace")
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/contacts \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/contacts", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/contacts",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Shared with you
Source: https://docs.markdrop.in/api/account/shared-with-you
`GET https://api.markdrop.in/api/v1/me/shared`
Documents other people shared with you, a page at a time.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Query parameters
- `cursor` (string): The `next_cursor` of the previous page. Up to 300 characters.
- `limit` (integer): How many to return. From 1 to 100. Default `30`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/shared \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/shared", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/shared",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Recently opened
Source: https://docs.markdrop.in/api/account/recently-opened
`GET https://api.markdrop.in/api/v1/me/recent`
Documents you opened recently.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 120 per minute per IP address.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/recent \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/recent", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/recent",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Quick search
Source: https://docs.markdrop.in/api/account/quick-search
`GET https://api.markdrop.in/api/v1/search/quick`
Finds files, folders and workspaces you can open by title, address or file name. Never searches content.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Rate limit:** 60 per minute per IP address.
## Query parameters
- `q` (string): Up to 200 characters. Default `""`.
- `limit` (integer): How many to return. From 1 to 20. Default `8`.
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
- `429`: Too many requests from your IP address. Wait the seconds in `Retry-After`.
## Examples
**curl**
```bash
curl 'https://api.markdrop.in/api/v1/search/quick?q=release' \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/search/quick?q=release", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/search/quick?q=release",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# List connected apps
Source: https://docs.markdrop.in/api/account/list-connected-apps
`GET https://api.markdrop.in/api/v1/me/connections`
AI apps you connected to Markdrop through its approval screen.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
- **Guide:** [Connected apps](https://docs.markdrop.in/account/connected-apps)
## Responses
- `200`: OK.
- `401`: No token, or the token is revoked or mistyped.
## Examples
**curl**
```bash
curl https://api.markdrop.in/api/v1/me/connections \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/connections", {
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
const data = await res.json();
```
**Python**
```python
import os
import requests
r = requests.get(
"https://api.markdrop.in/api/v1/me/connections",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
print(r.json())
```
---
# Disconnect an app
Source: https://docs.markdrop.in/api/account/disconnect-an-app
`DELETE https://api.markdrop.in/api/v1/me/connections/{client_id}`
Revokes an AI app's access at once.
- **Authentication:** API token required: `Authorization: Bearer mdk_…`.
## Path parameters
- `client_id` (string, required)
## Responses
- `204`: No content.
- `401`: No token, or the token is revoked or mistyped.
- `422`: The request didn't validate. `detail` lists each problem, with where it is (`loc`) and why (`msg`).
## Examples
**curl**
```bash
curl -X DELETE https://api.markdrop.in/api/v1/me/connections/CLIENT_ID \
-H "Authorization: Bearer $MARKDROP_TOKEN"
```
**JavaScript**
```javascript
const res = await fetch("https://api.markdrop.in/api/v1/me/connections/CLIENT_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MARKDROP_TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status}`);
```
**Python**
```python
import os
import requests
r = requests.delete(
"https://api.markdrop.in/api/v1/me/connections/CLIENT_ID",
headers={"Authorization": f"Bearer {os.environ['MARKDROP_TOKEN']}"},
)
r.raise_for_status()
```