PapermarkDocs

Upload a new version of a document

Replace the file behind an existing document so every live link instantly serves the update.

Versions sit inside a document. The document id and every link pointing at it stay the same; only the file bytes change. Uploading a new version makes it primary in the same transaction, so the next viewer to open any existing link sees the new file. Older versions remain queryable and can be promoted back at any time.

Use this when you need to push a corrected deck, a redlined contract, or an updated pricing sheet without re-sharing URLs.

# 1. Find the document id (skip if you already have it)
papermark documents list | grep "Acme pitch"

# 2. Upload the replacement file as a new version
papermark documents versions add doc_abc123 ./pitch-v2.pdf
# → Version 2 added: dv_xyz789

# 3. Confirm the primary moved
papermark documents versions list doc_abc123

The newly uploaded version becomes primary automatically. All existing share links now serve pitch-v2.pdf on the next view.

To roll back to an earlier cut:

papermark documents versions promote doc_abc123 dv_oldver1

The previous primary is demoted in the same transaction.

Same three-step upload as a fresh document — presign, PUT, register — but the register step targets /v1/documents/{id}/versions instead of /v1/documents.

TOKEN=pm_live_…
DOC_ID=doc_abc123

# 1. Get a presigned upload URL
UPLOAD=$(curl -sX POST https://api.papermark.com/v1/documents/upload-url \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"file_name":"pitch-v2.pdf","content_type":"application/pdf","content_length":2048576}')

UPLOAD_URL=$(echo "$UPLOAD" | jq -r '.upload_url')
UPLOAD_ID=$(echo "$UPLOAD" | jq -r '.upload_id')

# 2. PUT the file bytes to S3. Forward every key in
#    required_headers exactly as returned — omitting one yields 403.
#    Today the presign returns Content-Type + Content-Disposition;
#    if more keys appear, forward those too.
HEADER_ARGS=()
while IFS=$'\t' read -r k v; do
  HEADER_ARGS+=(-H "$k: $v")
done < <(echo "$UPLOAD" | jq -r '.required_headers | to_entries[] | "\(.key)\t\(.value)"')

curl -X PUT "$UPLOAD_URL" \
  --upload-file ./pitch-v2.pdf \
  "${HEADER_ARGS[@]}"

# 3. Register as a new version
curl -X POST "https://api.papermark.com/v1/documents/$DOC_ID/versions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"upload_id\":\"$UPLOAD_ID\"}"

Alternative: skip steps 1–2 and have Papermark fetch the file from a public URL.

curl -X POST "https://api.papermark.com/v1/documents/$DOC_ID/versions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source_url":"https://cdn.acme.com/pitch-v2.pdf"}'

The response includes version_number and is_primary: true. Conversion / pre-processing kicks off in the background — viewers hitting the link in the next few seconds may briefly see the prior rendering while the new one is built.

Required scope: documents.write. To list or fetch existing versions: documents.read.

To roll back:

curl -X PATCH "https://api.papermark.com/v1/documents/$DOC_ID/versions/dv_oldver1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_primary":true}'

The pitch deck at document id doc_abc123 needs to be replaced with the new file at https://cdn.acme.com/pitch-v2.pdf. Upload it as a new version and tell me the version number.

The agent calls add_document_version with the document id and source_url, then reports back the version_number so you can roll back later if needed.

No public URL? Use the presigned flow. add_document_version accepts an upload_id — the same handle upload_document takes — so a private file never has to be exposed at a public link:

Here's the updated deck (attached). Push it as a new version of document doc_abc123 — don't create a new document.

Over the HTTP transport the agent chains request_document_upload → PUTs the bytes straight to storage from its code-execution sandbox → add_document_version with document_id + upload_id. Over the stdio transport it can skip the presign and pass file_path directly. Either way the bytes never pass through the model context and nothing is published to a public URL.

So add_document_version takes exactly one of upload_id (a completed presigned upload), file_path (stdio only — the local alternative to upload_id), source_url (public HTTPS URL the API fetches), or external_url (repoint a link/notion document — that one only, and only for those document types).

To roll back:

List the versions on document doc_abc123 and promote version 1 back to primary.

The agent chains list_document_versions → confirms the version id with you → promote_document_version.

What viewers see

Anyone opening an existing share link after the new version is promoted lands on the new file. No re-share, no broken bookmarks. View analytics continue accumulating on the same link; per-version view counts are tracked separately if you need to know who saw which.

Versioning model in one paragraph

Each document has one primary version (is_primary: true) and zero or more historical versions. Adding a new version always makes it primary and demotes the previous primary in a single transaction — there is never a window where two versions are primary. Promoting an older version does the same swap in reverse. Older versions are retained until the document itself is deleted.

Common pitfalls

  • 403 on the S3 PUT: you dropped one of required_headers from the presign response. Pass them all back verbatim.
  • The link still serves the old file: the rendered preview is cached for a few seconds while the new version is converted. Force a refresh, or wait 5–10 seconds.
  • You meant to upload a different document: there's no in-place edit. Add another new version with the correct file — the wrong one stays in history but is no longer primary.

On this page