Locked PDFs
A locked PDF is refused rather than silently unlocked. The refusal names the lock, so your code decides rather than having it decided.
Why it refuses#
A locked PDF is somebody else's decision about their own document, and an
API that silently strips it the moment one arrives has made that decision
for them without being asked. So an ordinary post of a locked file comes
back with the lock named, and unlock=1 is the caller saying
yes.
Two kinds of lock#
| Status | Body | What it is | |
|---|---|---|---|
| Restrictions | 409 | "locked": "restrictions" | Opens for everyone and forbids copying and editing. Most PDFs people call locked are this, and it comes off with no password because it is not one. |
| Password | 422 | "locked": "password" | Will not open at all. You need the password. There is nothing here that guesses one. |
{
"ok": false,
"detail": "that PDF is locked against copying and editing. Post it again with unlock=1 to have the lock removed first.",
"locked": "restrictions",
"opens": true,
"allows": { "print": true, "copy": false, "modify": false },
"unlock": "post again with ?unlock=1"
}
The three modes#
| What it does | What it costs | |
|---|---|---|
unlock=1 | Remove the lock. Keeps the document intact where it can, draws a new PDF from it where it cannot. | Nothing, usually. If it had to redraw, the response says so in warnings. |
unlock=decrypt | Insist on keeping the document intact. | Fails rather than redrawing. |
unlock=rebuild | Always draw a brand new PDF from it. | Text stays text. Form fields and annotations become part of the page. |
The password#
The X-Doc-Password header, or password in the
JSON body. Never in the query string — a URL is written into every
access log it passes through, and this endpoint refuses a request that
carries one there rather than quietly honouring it. A caller who puts a
password in a URL has already written it into their own logs, and finding
that out from an error is better than finding it out later.
# refused first, on purpose — the body names the lock
curl -s https://docubend.com/api/v1/pdfs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/pdf" \
--data-binary @locked.pdf
# yes, take it off
curl -s "https://docubend.com/api/v1/pdfs?unlock=1&title=Report" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/pdf" \
--data-binary @locked.pdf
const r = await fetch(`${BASE}/api/v1/pdfs?unlock=1&title=Report`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/pdf" },
body: await readFile("locked.pdf"),
});
if (r.status === 409) {
const { locked, allows } = await r.json(); // "restrictions"
}
r = requests.post(f"{BASE}/api/v1/pdfs",
params={"unlock": 1, "title": "Report"},
headers={"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/pdf"},
data=open("locked.pdf", "rb").read(), timeout=120)
if r.status_code == 409:
print(r.json()["locked"]) # "restrictions"