Endpoint reference

POST /api/watermark

Stamp text across the pages of a PDF, such as DRAFT or CONFIDENTIAL. Choose opacity, size, colour, angle and position. Returns the stamped PDF as application/pdf.

Request

Method: POST. URL: https://pdfops.dev/api/watermark. Body: multipart/form-data.

FieldTypeRequiredDescription
pdfFile (PDF binary)YesExactly one PDF, up to 4 MB and 2,000 pages. The whole request must stay under 4.5 MB, Vercel's platform request limit.
textStringYesThe watermark text, up to 200 characters. Standard Helvetica only (Latin-1 range); other characters fail with unsupported_characters.
opacityNumberNo (default 0.15)0.05 to 1.
sizeNumberNo (default 48)Font size in points, 6 to 200.
colorStringNo (default 808080)Hex colour with 3 or 6 digits, such as cc0000. A leading hash is optional.
rotationNumberNo (default 45)Degrees counter-clockwise as the viewer sees it, -360 to 360.
positionStringNo (default center)center, top or bottom. Top and bottom sit at 90% and 10% of the page height.
pagesStringNoPage list such as 1-3,5 (see Page lists). Default: all pages.
passwordStringNoOpens a PDF that needs a user password. Not needed for owner-locked files with an empty user password.

The optional X-API-Key header works as on /api/merge: no header is anonymous, 100 requests per IP per month; a valid key uses your tier's quota; an invalid or revoked key returns 401 invalid_api_key. A successful (2xx) call counts 1 request. Check consumption with GET /api/usage.

Page lists

The pages value is a comma list of items: N (one page), A-B (an inclusive range) or A- (page A through the last page). Pages are 1-based. Spaces around items and around the dash are ignored. last and negative numbers are not supported. A page past the end of the PDF, a reversed range, an empty item or an empty value fails with invalid_pages.

Response

On success: 200 OK, Content-Type: application/pdf, body is the watermarked PDF.

On any failure: 4xx or 500, Content-Type: application/json, body is { "error": "<code>", "details": "<explanation>" }.

Error codes

Error codeStatusTrigger
invalid_multipart400Request body could not be parsed as multipart/form-data.
missing_pdf400The request does not have exactly one pdf file field.
pdf_too_large400The pdf file is over 4 MB.
invalid_pdf400The PDF could not be parsed (corrupt, or not a PDF).
encrypted_pdf400The PDF needs a password we were not given. Owner-locked files with an empty user password (most government blanks) open on their own; for a real user password send it as the password form field.
decompressed_too_large400Object streams inside the PDF decompress to over 32 MB.
too_many_pages400The PDF has over 2,000 pages.
missing_text400text is missing or empty.
text_too_long400text is over 200 characters.
unsupported_characters400text has a character that standard Helvetica cannot draw. details names the first one.
invalid_parameter400opacity, size, rotation, color or position is invalid or out of range. details names it.
invalid_pages400pages is empty, malformed or out of range. details says which item failed.
save_failed500PDF serialization failed. Retryable.
request_too_large413The whole request body is over 4.5 MB (Vercel's platform request limit). Rejected before anything is parsed.
invalid_api_key401An X-API-Key header was sent but the key is unknown or revoked. No fallback to the anonymous IP cap. Get a fresh key at /pricing.
rate_limited (anonymous)429No X-API-Key header and the per-IP limit (100/month) is exhausted. Retry-After gives seconds until next calendar month.
rate_limited (keyed)429Your key's tier quota (Free 250 / Indie 4,000 / Pro 25,000 per month) is exhausted. Retry-After gives seconds until next calendar month.

Example

curl -X POST https://pdfops.dev/api/watermark \
  -F "pdf=@contract.pdf" \
  -F "text=DRAFT" \
  -F "opacity=0.2" \
  -F "color=cc0000" \
  -o draft.pdf \
  -H "X-API-Key: pdfops_live_..."  # optional, raises your quota to your tier

Keyless calls work: 100 requests per IP per month with no key.

Using this for real? A free API key raises the keyless 100 calls per IP to 250 requests a month, with no card.

Get a free API key

Code examples

Have an API key? Add an X-API-Key header to any request. It is optional, and it raises your quota from 100/IP/month to your tier's.

JavaScript (Node.js 22+) — built-in fetch + FormData
import { readFile, writeFile } from 'node:fs/promises';

const fd = new FormData();
fd.append('pdf', new Blob([await readFile('contract.pdf')], { type: 'application/pdf' }), 'contract.pdf');
fd.append('text', 'DRAFT');
fd.append('opacity', '0.2');
fd.append('color', 'cc0000');

const res = await fetch('https://pdfops.dev/api/watermark', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.PDFOPS_API_KEY }, // optional
  body: fd,
});
if (!res.ok) {
  const err = await res.json();
  throw new Error(`PDFops watermark failed: ${err.error}: ${err.details}`);
}
await writeFile('draft.pdf', Buffer.from(await res.arrayBuffer()));

Behavioral notes

FAQ

Can I use an image as a watermark?

No. This endpoint stamps text only.

Why was my text rejected with unsupported_characters?

The text is drawn in standard Helvetica, which covers the Latin-1 range. A character outside it, such as a CJK character or an emoji, is refused. details names the first bad character.

Related: Stamp page numbers · Password-protect a PDF · Set title and author.

Get a free API key. Keyless calls get 100 requests per IP per month; a free key raises that to 250, with no card. Enter your email and the key arrives in your inbox.

Get a free API key