# DocPin documentation (all pages)
> Every DocPin doc in one file. Index: https://docpin.app/llms.txt
---
Source: https://docpin.app/docs/index.md
---
# DocPin developer docs
DocPin publishes HTML pages made by people and AI apps (Claude Artifacts, ChatGPT pages, exported reports, prototypes) to a link that looks like yours: `https://acme.docpin.it/q4-report`, or `https://docs.acme.com/q4-report` on your own domain. Pages can be public, password-protected or limited to certain people. Every update keeps the same link and the earlier versions.
## Ways to publish
| From | How | Docs |
| --- | --- | --- |
| Claude, ChatGPT, any MCP client | Connect DocPin's MCP server, then ask: "publish this to DocPin" | [MCP](/docs/mcp.md) |
| Scripts, CI, your own app | `POST https://api.docpin.app/v1/pages` with an API key | [REST API](/docs/api.md) |
| A person | Drag a file, folder or `.zip` into the DocPin app | [docpin.app](https://docpin.app) |
## Building interactive and live pages
- **[How pages run](/docs/page-runtime.md):** the sandbox, links, URL state (`?query` and `#hash` that sync with the address bar), and `localStorage`.
- **[Page data](/docs/page-data.md):** a JSON store per page. The page reads it with `docpin.data()`, and you update it from the API, MCP or the app, without republishing.
- **[Recipe: live dashboard](/docs/recipes/live-dashboard.md):** a complete page plus update scripts in curl, Node.js and Python.
## Links and addresses
| Address | Example | Notes |
| --- | --- | --- |
| Workspace link | `https://acme.docpin.it/sales` | `{workspace}.docpin.it/{slug}`, or `/{board}/{slug}` in a board. |
| Custom domain | `https://docs.acme.com/sales` | Pro. Replaces the workspace link. |
| Short link | `https://docpin.it/Ab3dE7` | Never changes. The only link for unlisted pages. |
| API | `https://api.docpin.app` | REST API. |
| MCP | `https://mcp.docpin.app/mcp` | Remote MCP server with OAuth. |
## Plans
Free includes 3 pages, workspace links, public pages and secret links, and page data. Pro ($8 a month) adds unlimited pages, custom domains, password and people-only pages, and a smaller badge.
## For AI models
These docs are written to be read by models as well as people. Every page is plain Markdown at its `.md` address. [`/llms.txt`](/llms.txt) is the index, and [`/llms-full.txt`](/llms-full.txt) has every page in one file.
---
Source: https://docpin.app/docs/page-data.md
---
# Page data
Every DocPin page has a **data store**: one JSON object, up to 1 MB, kept separately from the page's HTML. The page reads it in the browser. You change it from the API, from an AI app over MCP, or in the DocPin web app, and the page shows the new values without being republished.
Use it for anything that changes more often than the page itself: dashboards, status boards, price lists, scores, schedules, "last updated" banners.
## How it fits together
```
your script / cron / CI ──PATCH /v1/pages/{id}/data──┐
Claude or ChatGPT ───────set_page_data (MCP)─────────┤
DocPin web app ──────────Data tab────────────────────┤
▼
page data (JSON, ≤ 1 MB)
│
docpin.data() / docpin.onData() ◀┘ (in the published page)
```
- Updating data **doesn't create a version**, and the page's version history isn't touched.
- Data belongs to the page, not to a version: publishing a new version, or restoring an old one, keeps the current data.
- Deleting the page deletes its data.
- Available on Free and Pro.
## Read it in the page
DocPin adds a small script to every published page, which provides a global `docpin` object. You don't include anything yourself.
### `docpin.data()`
Returns a promise for the page's data object (`{}` if nothing has been set).
```html
```
It uses `textContent`, not `innerHTML`, for data values, so text in the data can't inject markup.
## 2. Publish it
**From the API:**
```bash
curl https://api.docpin.app/v1/pages \
-H "Authorization: Bearer $DOCPIN_API_KEY" \
-F file=@dashboard.html -F slug=sales
```
The response includes `url` (the link to share) and `doc.id`. Save `doc.id` as `PAGE_ID`.
**From Claude or ChatGPT:** "Publish this to DocPin as sales."
## 3. Push numbers
**From the API**, from cron, CI or any scheduler:
```bash
curl -X PATCH https://api.docpin.app/v1/pages/$PAGE_ID/data \
-H "Authorization: Bearer $DOCPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"revenue": 128400,
"customers": 37,
"regions": [{"name": "New Zealand", "revenue": 51200}, {"name": "Australia", "revenue": 77200}],
"asOf": "2026-11-06T09:00:00Z"
}'
```
**From Node.js:**
```js
const res = await fetch(`https://api.docpin.app/v1/pages/${process.env.PAGE_ID}/data`, {
method: "PATCH",
headers: { Authorization: `Bearer ${process.env.DOCPIN_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ revenue: 128400, customers: 37, asOf: new Date().toISOString() }),
});
if (!res.ok) throw new Error((await res.json()).error);
```
**From Python:**
```python
import os, datetime, requests
r = requests.patch(
f"https://api.docpin.app/v1/pages/{os.environ['PAGE_ID']}/data",
headers={"Authorization": f"Bearer {os.environ['DOCPIN_API_KEY']}"},
json={"revenue": 128400, "customers": 37, "asOf": datetime.datetime.now(datetime.timezone.utc).isoformat()},
)
r.raise_for_status()
```
**From Claude or ChatGPT:** "Update the data on the sales dashboard: revenue 128,400, 37 customers." The app calls `set_page_data`.
## 4. Check it
- Open the page's link: the numbers show, and anyone with it open sees updates within a minute.
- In the DocPin app, the page's **Data** tab shows the current JSON.
- `GET /v1/pages/$PAGE_ID/data` returns it too.
## Variations
- **Private dashboard:** make the page password-protected or people-only (Pro). The data is then just as private.
- **Several sources:** have each job `PATCH` only its own keys (`sales`, `support`, `uptime`), so they never overwrite each other.
- **History or charts:** keep a short array in the data, e.g. the last 30 daily values, and replace it each run. Store long history elsewhere: the limit is 1 MB per page.
---
Source: https://docpin.app/docs/api.md
---
# REST API
Publish and manage pages from scripts, CI and your own apps.
- **Base URL:** `https://api.docpin.app`
- **Auth:** `Authorization: Bearer dp_…`. API keys belong to one workspace and act with the rights of the owner who created them; they stop working if that person leaves the workspace. Workspace owners create keys in the DocPin app under **Connections → API keys**. The full key is shown once.
- **Format:** JSON in and out, except uploads, which can be `multipart/form-data`.
- **Errors:** a non-2xx status with `{ "error": "Plain-English message" }`.
- **Rate limits:** about 30 publishes a minute per workspace, and separately about 30 data writes a minute. Over that: `429`.
Pages are identified by **id** (a UUID) in paths. `GET /v1/pages` lists them, and publishing returns `doc.id`.
## Publish a page
`POST /v1/pages`
Upload one HTML file, several files, or a `.zip` (up to 25 MB) as `multipart/form-data`, or send HTML as JSON.
| Field | Type | Notes |
| --- | --- | --- |
| `file` | file(s) | `index.html`, other files, or one `.zip`. Multipart only. |
| `html` | string | The whole HTML document. JSON only. |
| `title` | string | Defaults to the document's ``. |
| `slug` | string | The page's address, e.g. `sales` → `/sales`. **Reusing a slug publishes a new version of that page**: same link, earlier versions kept. |
| `board` | string | Board slug to publish into. |
| `unlisted` | boolean | Only the short link opens it, and it stays off listings. |
| `access` | object | Who can open it: `{"kind":"public"}`, `{"kind":"password","password":"…"}`, or `{"kind":"people","people":["sam@acme.com","@acme.com"]}`. Private kinds need Pro. In multipart, send it as a JSON string. |
```bash
curl https://api.docpin.app/v1/pages -H "Authorization: Bearer $DOCPIN_API_KEY" \
-F file=@index.html -F slug=q4-report
curl https://api.docpin.app/v1/pages -H "Authorization: Bearer $DOCPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "Hello
Hello
", "slug": "hello"}'
```
Response (`201` for a new page, `200` for a new version):
```json
{
"created": true,
"url": "https://acme.docpin.it/hello",
"prettyUrl": "https://acme.docpin.it/hello",
"shortUrl": "https://docpin.it/Ab3dE7",
"doc": { "id": "61fb0253-…", "title": "Hello", "slug": "hello", "board": null, "unlisted": false, "access": { "kind": "public" } },
"version": { "id": "…", "files": 1, "bytes": 52 }
}
```
`url` is the link to share.
## Pages
| Method | Path | Body | Description |
| --- | --- | --- | --- |
| `GET` | `/v1/pages` | | Lists pages, newest first: `id`, `title`, `slug`, `board`, `prettyUrl`, `shortUrl`, `access`, `views`, `updated`, `archived`. |
| `PATCH` | `/v1/pages/{id}` | `title`, `slug`, `board`, `unlisted`, `listed`, `archived` | Changes a page. A new slug changes the link; the old link redirects. |
| `DELETE` | `/v1/pages/{id}` | | Deletes the page, its files, versions and data. |
| `PUT` | `/v1/pages/{id}/access` | an `access` object (above), or `{"kind":"inherit"}` | Changes who can open it, without changing the link. |
| `GET` | `/v1/pages/{id}/views` | | View counts by day, and named viewers for people-only pages. |
## Page data
A JSON object per page that the published page reads with `docpin.data()`. See [Page data](/docs/page-data.md).
| Method | Path | Body | Description |
| --- | --- | --- | --- |
| `GET` | `/v1/pages/{id}/data` | | `{ "data": {…}, "updatedAt": "…" \| null }` |
| `PATCH` | `/v1/pages/{id}/data` | JSON object | Merges top-level keys; `null` removes a key. Returns the full data. |
| `PUT` | `/v1/pages/{id}/data` | JSON object | Replaces everything. Returns the full data. |
Up to 1 MB per page, measured as JSON text.
## Versions
Every publish to the same slug is kept.
| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/pages/{id}/versions` | Newest first, with `number`, `publishedAt`, `via`, `by`, and `current`. |
| `POST` | `/v1/pages/{id}/versions/{versionId}/preview` | `{ "url": "…" }`: a private preview link that works for an hour. |
| `POST` | `/v1/pages/{id}/versions/{versionId}/restore` | Makes that version live again behind the same links. Newer versions are kept. |
## Boards
Boards group pages under one landing page, like `/team-updates`.
| Method | Path | Body | Description |
| --- | --- | --- | --- |
| `GET` | `/v1/boards` | | Lists boards. |
| `POST` | `/v1/boards` | `title`, optional `slug`, `description` | Creates a board. |
| `PATCH` | `/v1/boards/{slug}` | `title`, `slug`, `description` | Changes a board. |
| `PUT` | `/v1/boards/{slug}/access` | an `access` object | Who can open the board and its pages. |
| `DELETE` | `/v1/boards/{slug}` | | Deletes the board. Its pages are kept unless you add `?pages=delete`. |
## Status codes
| Status | Meaning |
| --- | --- |
| `200` / `201` | OK / created. |
| `400` | Something in the request is wrong; the message says what. |
| `401` | Missing or invalid key, or the key's creator left the workspace. |
| `403` | Not allowed: a Pro feature on Free, an owner-only action, or a taken-down page. |
| `404` | Not found in this workspace. |
| `409` | Conflict, e.g. a slug already in use. |
| `413` | Too large. |
| `415` | Unsupported content type for publishing. |
| `429` | Rate limited. Wait a minute. |
---
Source: https://docpin.app/docs/mcp.md
---
# MCP: DocPin for AI apps
DocPin is a remote [Model Context Protocol](https://modelcontextprotocol.io) server. Once it's connected, an AI app like Claude or ChatGPT can publish pages, update them, manage who can open them, and push data to them.
- **Server URL:** `https://mcp.docpin.app/mcp` (streamable HTTP)
- **Auth:** OAuth. The person signs in to DocPin and picks one workspace; the app then acts in that workspace with their rights.
## Connect
**Claude:** Customize → Connectors → Add → Add custom connector. Name it DocPin, paste the server URL, then Connect and sign in.
**ChatGPT:** chatgpt.com/plugins → Add → Add custom MCP server. Name it DocPin, keep OAuth, paste the server URL, create it, then Connect DocPin. In a chat, type `@DocPin`.
**Other MCP clients:** add a remote server with the URL above. The client discovers OAuth through the server's `/.well-known` documents and registers itself.
## Tools
Pages are referred to by `slug` (plus `board` if the page is in one) or by `id` from `list_pages`.
| Tool | What it does |
| --- | --- |
| `publish_page` | Publishes HTML (`html`) or several files (`files`: `[{path, content}]` with an `index.html`) and returns the links. **Reusing a `slug` publishes a new version behind the same link.** Optional `title`, `board`, `unlisted`, `access`. |
| `list_pages` | Pages with their links and slugs, newest first. Optional `board`. |
| `rename_page` | New title and/or slug. Old links redirect, and the short link never changes. |
| `move_page` | Moves a page into or out of a board. |
| `archive_page` | Hides a page from the page list without deleting it (`archived: false` brings it back). |
| `delete_page` | Deletes a page. Only when the person explicitly asks. |
| `set_access` | Who can open a page or board: `public`, `password`, `people` (emails and `@domains`), or `inherit`. Private pages need Pro. |
| `list_versions` / `restore_version` | Version history, and putting an earlier version live again. |
| `get_page_views` | How often a page was opened, and by whom on people-only pages. |
| `get_page_data` | A page's [data](/docs/page-data.md): the JSON object it reads with `docpin.data()`. |
| `set_page_data` | Updates a page's data without republishing. Merges by default (`null` removes a key); `replace: true` replaces everything. |
| `list_boards` / `create_board` / `update_board` / `delete_board` | Boards: landing pages that group related pages. |
The server also returns instructions on connect that summarise how published pages run. They're the same rules as [How pages run](/docs/page-runtime.md).
## Building pages with an AI app
For a page that's interactive or updated over time, a model should:
1. **Write the page to read from page data.** Lay out the HTML, then fill values from `docpin.data()`, or `docpin.onData()` for pages left open. Always handle empty data.
2. **Publish it** with `publish_page` and a clear `slug`.
3. **Fill in the data** with `set_page_data`. Use `get_page_data` first when changing an existing page's data, so no keys are lost.
4. **Update later** with `set_page_data` alone. Republish only when the layout changes, with `publish_page` and the same slug.
5. **Keep shareable state in the URL** (`history.replaceState`) and personal state in `localStorage`.
**Example conversation:**
> **Person:** Make me a dashboard of this week's support tickets and publish it to DocPin.
> **Model:** *(writes HTML that renders from `docpin.onData`, calls `publish_page` with slug `support`, then `set_page_data` with `{ "open": 42, "closed": 118, "asOf": "…" }`)* It's live at acme.docpin.it/support.
> **Person** *(the next day):* Update it: 37 open, 131 closed.
> **Model:** *(calls `set_page_data` with `{ "open": 37, "closed": 131, "asOf": "…" }`)* Updated. Anyone with it open will see the new numbers within a minute.
## Notes for app builders
- Tool results include structured content (`structuredContent`) alongside the text, for example links and ids from `publish_page`.
- Errors come back as tool results with `isError: true` and a plain-English message to show the person, such as an upgrade prompt for Pro features.
- `set_page_data` and publishing are each rate limited to about 30 a minute per workspace.