Getting started
Make your first API request.
The Papermark API is HTTP, JSON, and bearer-token-authenticated. There
is no SDK to install; every endpoint is one fetch away.
Base URL
https://api.papermark.comAll endpoints sit under /v1/. The v1 prefix is in the path, not a
header, so the absolute path of the documents list is
/v1/documents.
Authentication
Every request carries an Authorization: Bearer … header. Mint a
token in the dashboard at Settings → API Tokens (pm_live_…)
and treat it like a password. See
Authentication for the spec, including
the OAuth 2.1 device flow.
Your first request
curl https://api.papermark.com/v1/documents \
-H "Authorization: Bearer pm_live_…"Response shape:
{
"data": [
{
"id": "doc_…",
"object": "document",
"name": "Pitch Deck.pdf",
"created": "2026-04-22T12:34:56.000Z",
"updated_at": "2026-04-22T12:34:56.000Z"
}
],
"next_cursor": null
}Empty? You don't have any documents yet. Upload one:
# Step 1: get a presigned upload URL
UPLOAD=$(curl -sX POST https://api.papermark.com/v1/documents/upload-url \
-H "Authorization: Bearer pm_live_…" \
-H "Content-Type: application/json" \
-d '{"file_name": "pitch.pdf", "content_type": "application/pdf"}')
UPLOAD_URL=$(echo "$UPLOAD" | jq -r '.upload_url')
UPLOAD_ID=$(echo "$UPLOAD" | jq -r '.upload_id')
# Step 2: PUT the file to `upload_url`, forwarding every key in
# `required_headers` exactly as returned (omitting one yields 403)
HEADERS=()
while IFS=$'\t' read -r k v; do HEADERS+=(-H "$k: $v"); done \
< <(echo "$UPLOAD" | jq -r '.required_headers | to_entries[] | "\(.key)\t\(.value)"')
curl -X PUT "$UPLOAD_URL" --upload-file ./pitch.pdf "${HEADERS[@]}"
# Step 3: register the document with the opaque upload_id
curl -X POST https://api.papermark.com/v1/documents \
-H "Authorization: Bearer pm_live_…" \
-H "Content-Type: application/json" \
-d "{\"name\": \"Pitch Deck\", \"upload_id\": \"$UPLOAD_ID\"}"upload_id is an opaque one-time handle bound to the presigned URL.
The S3 path is built server-side; clients never see or supply it.
The CLI does all three steps in one call (papermark documents upload ./pitch.pdf), but doing it directly is good practice for
understanding the surface.
Response objects
Every resource the API returns carries a read-only object field: a
stable string discriminator. It lets you tell resources apart in
polymorphic contexts (a link points at either a document or a
dataroom) and lets SDKs deserialize to the right type.
object value | Resource |
|---|---|
document | A document |
document_version | A version of a document |
folder | A team-library folder |
link | A share link |
dataroom | A dataroom |
dataroom_folder | A folder inside a dataroom |
dataroom_document | A document attached to a dataroom |
view | A view event |
visitor | A persistent visitor |
visitor_view | A view event in a visitor's history |
A link additionally carries target_type (document or
dataroom) telling you which of document_id / dataroom_id is
populated; the other is null.
Timestamps
Timestamps are ISO 8601 UTC strings. The creation time is created
(no _at suffix); every other timestamp keeps the _at suffix:
updated_at, expires_at, viewed_at, downloaded_at.
Pagination
List endpoints return up to 25 records by default and accept:
?limit=N: between 1 and 100?cursor=<id>: pass back thenext_cursorfrom the previous response
Every list response is the same envelope: a data array plus a
next_cursor. next_cursor is the cursor to pass into the next
request; when it is null, you have reached the last page (there is
no separate has_more flag; a null cursor is the end-of-list
signal). Pagination is cursor-based, not offset-based: there are no
page / offset parameters.
curl "https://api.papermark.com/v1/documents?limit=50&cursor=doc_KlmN456" \
-H "Authorization: Bearer pm_live_…"Deleting resources
Every DELETE endpoint returns 200 with the deleted object:
{
"id": "doc_aBc123",
"object": "document",
"deleted": true
}deleted is always true. Whether the delete is internally a hard
delete or a soft delete (links, for instance, are soft-deleted to
preserve view history) is invisible to the caller. DELETE is
reserved for final, immediate deletion; it never returns a bare
204.
Deleting a non-empty folder is refused with 409 conflict unless
you pass ?cascade=true. What cascade does depends on the folder type:
- Team-library folder: deletes the folder and permanently deletes every nested folder and document inside it.
- Dataroom folder: deletes the folder and detaches every nested document from the dataroom; those documents stay in the team library.
Either way it is a single, atomic operation.
Errors
Failures come back with HTTP 4xx/5xx and a structured envelope:
{
"error": {
"code": "unauthorized",
"message": "Bearer token missing or invalid.",
"doc_url": "https://www.papermark.com/docs/api/errors#unauthorized"
}
}Every error.code has a matching anchor on the
Errors page so the doc_url resolves.
Rate limits
The default budget is 60 requests per minute per token. Every
response carries X-RateLimit-Limit, X-RateLimit-Remaining, and
X-RateLimit-Reset headers so you can pace yourself. See
Rate limits.