# Protecting and unlocking documents

How the five security actions fit together, and the order to call them in when protecting or unlocking a document. Passwords control opening, flags control use.

PDF security is two separate things, and getting the order right depends on
knowing which is which. A **password** encrypts the file and controls who can
*open* it. **Restrictions** are permission flags that control what a reader can
*do* once it is open: print, copy, edit. This guide sequences the five security
actions into a protect-then-distribute flow and an unlock flow.

## The security actions

| Action | Route | What it does |
| --- | --- | --- |
| [Add password](/docs/api/add-password-to-pdf) | `add_password` | Encrypts the PDF and sets the password needed to open it. |
| [Add restrictions](/docs/api/add-restrictions-to-pdf) | `add_restrictions` | Sets permission flags (print, copy, edit) behind an owner password; can also set an open password. |
| [Remove password](/docs/api/remove-password-from-pdf) | `remove_password` | Decrypts a PDF when you supply its current password. |
| [Remove restrictions](/docs/api/remove-restrictions-from-pdf) | `remove_restrictions` | Clears all permission flags. |
| [Remove signatures](/docs/api/remove-signatures-from-pdf) | `remove_signatures` | Strips cryptographic signatures and timestamps. |

<Info>
  **Passwords vs. restrictions.** `add_password` takes a single `password`: the
  password a reader types to open the file. `add_restrictions` takes an
  `owner_password` that guards the permission flags, plus an optional
  `user_password` that acts as the open password. Set `user_password` and the
  document needs a password to open; leave it out and anyone can open it but is
  still bound by the permission flags.
</Info>

## The one rule that governs the order

<Warning>
  **Encrypt last.** An encrypted PDF can't be opened by any other action, because
  there is no field to supply its password. So run every content and permission
  step first (merge, watermark, restrictions) and add the open password as the
  final step. To modify a document that is already password-protected, [remove
  the password](/docs/api/remove-password-from-pdf) first, process it, then
  re-protect.
</Warning>

Because of that rule, don't stack `add_password` and then `add_restrictions`:
the restrictions call can't open the just-encrypted file. When you need **both**
an open password and permission flags, set them in a single `add_restrictions`
call using `owner_password` and `user_password` together.

## Protect a document for distribution

Lock down a report so it requires a password to open, and can't be printed,
copied, or edited, all in one call.

<Steps>

<Step title="Prepare the content first">
  Do any merging, watermarking, or page work now, while the document is still
  unencrypted. See [Chaining actions](/docs/api/chaining-actions).
</Step>

<Step title="Apply restrictions and an open password">
  `POST /v1/add_restrictions` with an `owner_password` (guards the permissions),
  a `user_password` (the open password), and the permission flags set to `false`
  for what you want to forbid.
</Step>

</Steps>

<CodeGroup>

```bash title="cURL"
curl https://api.pdfblocks.com/v1/add_restrictions \
  -H 'X-API-Key: your_api_key' \
  -F file=@input.pdf \
  -F owner_password='owner-secret' \
  -F user_password='open-secret' \
  -F allow_print=false \
  -F allow_copy_content=false \
  -F allow_change_content=false \
  -o protected.pdf
```

```python title="Python"
# pip install requests
import requests

with open('input.pdf', 'rb') as file:
    response = requests.post(
        'https://api.pdfblocks.com/v1/add_restrictions',
        headers={'X-API-Key': 'your_api_key'},
        files={'file': file},
        data={
            'owner_password': 'owner-secret',
            'user_password': 'open-secret',
            'allow_print': 'false',
            'allow_copy_content': 'false',
            'allow_change_content': 'false',
        },
    )

response.raise_for_status()
with open('protected.pdf', 'wb') as output:
    output.write(response.content)
```

```javascript title="Node.js"
// Node.js 18+
import { readFile, writeFile } from 'node:fs/promises';

const body = new FormData();
body.set('file', new Blob([await readFile('input.pdf')]), 'input.pdf');
body.set('owner_password', 'owner-secret');
body.set('user_password', 'open-secret');
body.set('allow_print', 'false');
body.set('allow_copy_content', 'false');
body.set('allow_change_content', 'false');

const response = await fetch('https://api.pdfblocks.com/v1/add_restrictions', {
  method: 'POST',
  headers: { 'X-API-Key': 'your_api_key' },
  body,
});

if (!response.ok) throw new Error(`Request failed: ${response.status}`);
await writeFile('protected.pdf', Buffer.from(await response.arrayBuffer()));
```

```php title="PHP"
<?php
$ch = curl_init('https://api.pdfblocks.com/v1/add_restrictions');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['X-API-Key: your_api_key'],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile('input.pdf', 'application/pdf'),
        'owner_password' => 'owner-secret',
        'user_password' => 'open-secret',
        'allow_print' => 'false',
        'allow_copy_content' => 'false',
        'allow_change_content' => 'false',
    ],
]);

$pdf = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) {
    file_put_contents('protected.pdf', $pdf);
}
```

```ruby title="Ruby"
# gem install http
require 'http'

response = HTTP
  .headers('X-API-Key' => 'your_api_key')
  .post('https://api.pdfblocks.com/v1/add_restrictions', form: {
    file: HTTP::FormData::File.new('input.pdf'),
    owner_password: 'owner-secret',
    user_password: 'open-secret',
    allow_print: 'false',
    allow_copy_content: 'false',
    allow_change_content: 'false',
  })

File.write('protected.pdf', response.body) if response.status.success?
```

```go title="Go"
package main

import (
	"bytes"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func main() {
	var buf bytes.Buffer
	form := multipart.NewWriter(&buf)

	file, _ := os.Open("input.pdf")
	defer file.Close()
	part, _ := form.CreateFormFile("file", "input.pdf")
	io.Copy(part, file)

	form.WriteField("owner_password", "owner-secret")
	form.WriteField("user_password", "open-secret")
	form.WriteField("allow_print", "false")
	form.WriteField("allow_copy_content", "false")
	form.WriteField("allow_change_content", "false")
	form.Close()

	req, _ := http.NewRequest("POST",
		"https://api.pdfblocks.com/v1/add_restrictions", &buf)
	req.Header.Set("Content-Type", form.FormDataContentType())
	req.Header.Set("X-API-Key", "your_api_key")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()

	out, _ := os.Create("protected.pdf")
	defer out.Close()
	io.Copy(out, res.Body)
}
```

```csharp title="C#"
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");

using var form = new MultipartFormDataContent
{
    { new ByteArrayContent(File.ReadAllBytes("input.pdf")), "file", "input.pdf" },
    { new StringContent("owner-secret"), "owner_password" },
    { new StringContent("open-secret"), "user_password" },
    { new StringContent("false"), "allow_print" },
    { new StringContent("false"), "allow_copy_content" },
    { new StringContent("false"), "allow_change_content" },
};

var response = await client.PostAsync(
    "https://api.pdfblocks.com/v1/add_restrictions", form);
response.EnsureSuccessStatusCode();
await File.WriteAllBytesAsync(
    "protected.pdf", await response.Content.ReadAsByteArrayAsync());
```

</CodeGroup>

If you only need an open password and no permission flags, `add_password` is the
simpler call:

```bash title="cURL"
curl https://api.pdfblocks.com/v1/add_password \
  -H 'X-API-Key: your_api_key' \
  -F file=@input.pdf \
  -F password='pa$$word' \
  -o encrypted.pdf
```

Both `add_password` and `add_restrictions` accept an `encryption_algorithm` of
`AES-128` (the default) or `AES-256`. See [Add
password](/docs/api/add-password-to-pdf) and [Add
restrictions](/docs/api/add-restrictions-to-pdf) for every field.

## Unlock a document

To lift protection you hold the credentials for, decrypt first, then clear the
permission flags.

<Steps>

<Step title="Remove the open password">
  `POST /v1/remove_password` with the document's current `password`. The result
  no longer requires a password to open, so later steps can read it.
</Step>

<Step title="Clear the restrictions">
  `POST /v1/remove_restrictions` strips the permission flags, leaving an
  unrestricted PDF.
</Step>

</Steps>

<CodeGroup>

```bash title="cURL"
# 1. Decrypt with the known password.
curl -sS https://api.pdfblocks.com/v1/remove_password \
  -H 'X-API-Key: your_api_key' \
  -F file=@protected.pdf \
  -F password='open-secret' |
# 2. Clear the permission flags from the decrypted bytes.
curl -sS https://api.pdfblocks.com/v1/remove_restrictions \
  -H 'X-API-Key: your_api_key' \
  -F 'file=@-;filename=unlocked.pdf;type=application/pdf' \
  -o unrestricted.pdf
```

```python title="Python"
# pip install requests
import requests

BASE = 'https://api.pdfblocks.com'
HEADERS = {'X-API-Key': 'your_api_key'}

# 1. Decrypt with the known password.
with open('protected.pdf', 'rb') as file:
    unlocked = requests.post(
        f'{BASE}/v1/remove_password',
        headers=HEADERS,
        files={'file': file},
        data={'password': 'open-secret'},
    )
unlocked.raise_for_status()

# 2. Clear the permission flags from the decrypted bytes.
unrestricted = requests.post(
    f'{BASE}/v1/remove_restrictions',
    headers=HEADERS,
    files={'file': ('unlocked.pdf', unlocked.content, 'application/pdf')},
)
unrestricted.raise_for_status()

with open('unrestricted.pdf', 'wb') as output:
    output.write(unrestricted.content)
```

</CodeGroup>

<Info>
  `remove_restrictions` has no password field, so it works on a document that is
  restricted but not password-protected. If a document has an open password, run
  `remove_password` first, as the flow above does, so the restrictions call can
  read it.
</Info>

## Remove signatures

A cryptographic signature locks a PDF: any edit invalidates it. If you must
re-process a signed document, strip the signatures and timestamps first with
`remove_signatures`. This does invalidate the signatures, which is unavoidable
once the content changes.

```bash title="cURL"
curl https://api.pdfblocks.com/v1/remove_signatures \
  -H 'X-API-Key: your_api_key' \
  -F file=@signed.pdf \
  -o unsigned.pdf
```

## Related

<CardGroup cols={2}>

<Card title="Add a password" href="/docs/api/add-password-to-pdf">
  Encrypt a PDF with an open password.
</Card>

<Card title="Add restrictions" href="/docs/api/add-restrictions-to-pdf">
  Set the full permission-flag matrix.
</Card>

<Card title="Remove a password" href="/docs/api/remove-password-from-pdf">
  Decrypt a PDF you have the password for.
</Card>

<Card title="Chaining actions" href="/docs/api/chaining-actions">
  Watermark or merge before you encrypt.
</Card>

</CardGroup>
