Entries
Entries are what viewers save from a page: votes, RSVPs, sign-ups, answers, comments. Page data is written by you; entries are written by the people viewing the page.
At a glance
| Off by default | Turn on per page: Data → Let viewers save entries in the app, PATCH /v1/pages/{id} with {"entries": "viewers"}, or the MCP tool set_page_entries |
| Two kinds | Shared collections everyone can read (docpin.shared), and each person's private object (docpin.mine) |
| Who can save | Viewers signed in to DocPin who can open the page. One email is one person |
| Who can read | Anyone who can open the page reads shared entries, with authors' names (the part of the email before the @) |
| Emails | Only your workspace sees them, in the app, the API and MCP |
| Removing | Viewers delete their own entries, or everything they saved with Remove my entries in the DocPin panel. Your workspace can delete any entry |
Who can save
| Page access | Read shared entries | Save entries |
|---|---|---|
team | Members | Members |
people | Members and listed people | Members and listed people |
password | Anyone with the password | People signed in to DocPin on that site. The password screen offers your team a sign-in |
public | Anyone | Only people already signed in to DocPin on that site |
A secret-link (unlisted) public or password page, at its docpin.it short link | Anyone | Nobody: viewers stay anonymous there |
- Public pages don't offer a sign-in yet, and password pages offer it only to your team, so in practice the people saving are your team. Anyone with a DocPin account can sign in on the site, though, so don't rely on this to keep a public page's entries to your team.
- Drafts, archived pages and version previews don't take entries.
- When entries are on, the page's DocPin panel says "This page saves what you enter. People in its workspace can see it."
Use them in the page
Read first, then save when the person does something, such as pressing a button. Saving can fail (they may not be signed in), so catch it and say why.
<button id="going">I'm going</button>
<p id="status"></p>
<ul id="rsvps"></ul>
<script type="module">
const status = document.querySelector("#status");
const show = async () => {
const { entries } = await docpin.shared.list("rsvps");
const list = document.querySelector("#rsvps");
list.replaceChildren();
for (const e of entries) {
const li = document.createElement("li");
li.textContent = e.author.name;
list.append(li);
}
};
document.querySelector("#going").onclick = async () => {
try {
await docpin.shared.put("rsvps", { going: true });
await show();
} catch (e) {
status.textContent = e.message;
}
};
show().catch((e) => (status.textContent = e.message));
</script>Always show entries as text, never as HTML. Use
textContentorcreateTextNode, notinnerHTML. An entry is whatever a viewer typed; inserting it as HTML would let one viewer run code in other viewers' browsers.
Page API
docpin.viewer(): Promise<{ signedIn: true; name: string } | { signedIn: false }>
docpin.mine.get(): Promise<object> // {} until something's saved
docpin.mine.set(changes: object): Promise<object> // merges; a null value removes that key
docpin.shared.list(collection: string, options?: { cursor?: string }):
Promise<{ entries: Entry[]; count: number; cursor: string | null }>
docpin.shared.put(collection: string, value: unknown): Promise<Entry>
docpin.shared.add(collection: string, value: unknown): Promise<Entry>
docpin.shared.remove(id: string): Promise<{ removed: true; id: string }>
type Entry = {
id: string;
collection: string;
author: { name: string; you: boolean }; // name: the part of the email before the @
value: unknown;
createdAt: string;
updatedAt: string;
};| Call | What it does |
|---|---|
viewer() | Whether the person is signed in, and their name. Pages never get a viewer's full email |
mine.get() / mine.set(changes) | This person's private object on this page. Only they read it through the page. Up to 64 KB |
shared.list(collection, { cursor }) | Up to 500 entries, oldest first. When cursor isn't null, pass it back for the next 500 |
shared.put(collection, value) | This person's one entry in the collection, created or replaced. For votes, RSVPs and scores: nobody can vote twice |
shared.add(collection, value) | A new entry every time. For comments and logs |
shared.remove(id) | Deletes one of this person's own entries |
Rules:
- Collection names are 1 to 40 of
a-z 0-9 - _. - Use a collection with either
putoradd, not both. A collection keeps the kind of its first entry, so a one-each poll can't be padded with extra entries. - A value is any JSON except
null, up to 16 KB. - To count votes, count entries. There's deliberately no shared counter.
- To show other people's new entries, call
listagain, after saving or on a timer of 15 seconds or more.
Errors
A failed call rejects with an Error whose code is:
code | Meaning |
|---|---|
off | Entries aren't on, the page is a draft or archived, or it isn't open at its DocPin link (a version preview, or a file opened on its own) |
signin | The person needs to be signed in to DocPin to save |
forbidden | They can't open the page, or it isn't their entry |
not_found | That entry doesn't exist any more |
limit | A limit was reached |
rate | Too many calls. Wait a moment |
invalid | Something about the call is wrong; the message says what |
busy | DocPin was busy. Try again |
timeout | No answer within 15 seconds. The save may still have worked: read before saving again |
network | The browser couldn't reach DocPin |
error | Something went wrong on DocPin's side |
Limits
| Free | Pro | |
|---|---|---|
| All entries on a page | 1 MB, 2,000 entries | 10 MB, 50,000 entries |
| Shared entries one person can have on a page | 100, 100 KB | 1,000, 1 MB |
| One entry | 16 KB | 16 KB |
| Private object per person per page | 64 KB | 64 KB |
| Saving | 30 a minute per person, 300 per page | Same |
Reading (viewer, mine.get, list) | 300 a minute per person on a page (per network address if not signed in), and 3,000 a minute per page in total | Same |
Making an entry smaller always works, even over a limit.
For your workspace
Everyone in the workspace can see entries with each author's email: in the app on the page's Data tab (with CSV and JSON export), or with the REST API and MCP.
PATCH/v1/pages/{id}
Body { "entries": "viewers" } turns entries on; { "entries": "off" } closes the page to new ones, keeping what's there. Returns { "entries": "viewers" | "off" }. You can also pass entries to POST /v1/pages; it's ignored for drafts.
GET/v1/pages/{id}/entries
Query: collection (a name, or empty for everyone's private objects; leave it out for all), offset. Returns the newest 500 entries, newest first:
{
"setting": "viewers",
"usage": { "bytes": 2140, "entries": 12, "maxBytes": 1000000, "maxEntries": 2000 },
"collections": [{ "name": "rsvps", "mine": false, "count": 9 }, { "name": "", "mine": true, "count": 3 }],
"entries": [{ "id": "…", "kind": "put", "collection": "rsvps", "author": "sam@acme.com", "value": { "going": true }, "createdAt": "…", "updatedAt": "…" }],
"count": 12,
"next": null
}kind is put, add, or mine for a private object (whose collection is null). When next isn't null, pass it as offset.
DELETE/v1/pages/{id}/entries
Deletes a collection (?collection=rsvps), everyone's private objects (?collection= with nothing after it), or every entry (no collection). Returns { "removed": 9 }.
DELETE/v1/pages/{id}/entries/{entryId}
Deletes one entry. Returns { "removed": 1 }, or 404.
MCP
| Tool | What it does |
|---|---|
set_page_entries | on: true or false for a page |
get_page_entries | Totals, collections and the newest entries with emails. Optional collection |
Deleting the page, or deleting its board along with the board's pages, deletes its entries. Renaming or moving it, or publishing a new version, keeps them. When someone deletes their DocPin account, their entries are deleted.