# PDF Shield

> PDF Shield stamps personalised watermarks onto PDF documents through a REST API. You design a
> watermark template once (text and images placed on the page, with `{{variables}}` such as the
> reader's name), then call the API for each copy you hand out, so every copy can be traced to
> the person who received it.

- Website: https://pdfshield.app
- API base URL: https://api.pdfshield.app
- OpenAPI 3.1 description (authoritative): https://api.pdfshield.app/v1/openapi.yaml
- Interactive API reference: https://api.pdfshield.app/docs
- API catalog (RFC 9727): https://pdfshield.app/.well-known/api-catalog

## Watermark a PDF

Templates are designed in the dashboard at https://pdfshield.app (each has an ID such as
`tpl_01JA2Y8Q3V8K2M7N5P4R6S9T0W` and lists the variable names it uses). Integrations call:

```
POST https://api.pdfshield.app/v1/watermarks
X-API-Key: psk_...
Content-Type: application/json

{
  "templateId": "tpl_01JA2Y8Q3V8K2M7N5P4R6S9T0W",
  "source": { "url": "https://example.com/handbook.pdf" },
  "variables": { "name": "Ada Lovelace", "date": "2026-10-03" },
  "cacheKey": "handbook-v3-ada"
}
```

Response `200`:

```
{
  "id": "wmk_…",
  "url": "https://…",          // opens the PDF in a browser
  "downloadUrl": "https://…",  // saves the file
  "expiresAt": "…",            // both links expire after one hour
  "cached": false,
  "pageCount": 42,
  "templateId": "tpl_…"
}
```

- `source` is either `{ "url": … }` (public http/https URL, at most 25 MB, must respond within
  20 s) or `{ "uploadId": … }` from the upload flow below. Set exactly one.
- `variables` fill `{{placeholders}}` in the template's text. A placeholder without a value is
  left as written.
- `cacheKey` (optional, `[A-Za-z0-9._-]{1,128}`): a repeat request with the same key returns
  the stored output for 7 days without using quota. Use keys that change when the source,
  template or variables change.
- Pass returned URLs on unchanged; any added query parameter invalidates their signature.

## Upload a PDF that isn't public

1. `POST /v1/uploads` with `{ "contentType": "application/pdf", "size": <bytes> }` → `{ "id", "uploadUrl", "method": "PUT", "headers" }`.
2. `PUT` the bytes to `uploadUrl` with the returned `headers` (no API key on this request).
3. `POST /v1/watermarks` with `"source": { "uploadId": "<id>" }`. Uploads expire after 24 hours.

## Authentication

- Server-to-server: `X-API-Key: psk_…`, created in the dashboard. Accepted by
  `POST /v1/watermarks`, `GET /v1/watermarks` (recent jobs) and `POST /v1/uploads`.
- Other `/v1` endpoints (templates, API keys, billing) are for the dashboard and use a
  short-lived bearer token from sign-in; integrations don't need them.
- `GET /v1/plans` is public.

## Errors

Every error has the body `{ "error_code": "<status>", "message": "<readable explanation>" }`.

| Status | Meaning |
| --- | --- |
| 400 | Invalid request (`error_code` is `BAD_REQUEST` for schema validation failures) |
| 401 | Missing, invalid or revoked API key |
| 403 | Paid subscription unpaid or incomplete |
| 404 | Template or upload not found in this account |
| 422 | Source could not be downloaded, or is not a readable PDF (including password-protected PDFs) |
| 429 | Monthly allowance used up; the message says when it resets |

## Plans

Free: 10 watermarked PDFs a month. Personal: 1,000 a month, $20. Enterprise: 5,000 a month, $40.
Cache hits and failed requests are not counted. Current plans: `GET https://api.pdfshield.app/v1/plans`.

## Pages

- [API guide](https://pdfshield.app/developers): quickstart, code samples in curl, Node and Python, uploads, variables, caching, errors.
- [Pricing](https://pdfshield.app/pricing)

## Optional

- `POST /watermark` is the deprecated previous API, kept for existing integrations: flat JSON body
  `{ "pdfUrl", "templateId", "cacheKey", …variables }` with `X-API-Key`. Use `/v1/watermarks` instead.
- Text uses the 14 standard PDF fonts, so templates render Latin-script text only.
