PapermarkDocs

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.com

All 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 valueResource
documentA document
document_versionA version of a document
folderA team-library folder
linkA share link
dataroomA dataroom
dataroom_folderA folder inside a dataroom
dataroom_documentA document attached to a dataroom
viewA view event
visitorA persistent visitor
visitor_viewA 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 the next_cursor from 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.

Where to go next

  • Reference: every endpoint, generated from openapi.json
  • Scopes: what each scope unlocks
  • CLI: the same surface from the command line

On this page