PDF Loft API
Run every server PDF tool, manage the document library and receive signed webhooks from your own software.
Choose how to connect
- REST APICall https://api.pdfloft.com/v1 from your own code. Upload a file, run a tool, follow the run and download the result. Start with the quick start on this page.
- WebhooksBe told when a run finishes or a signature request changes, in a signed message, instead of polling.
- MCPLet an AI assistant run PDF tools for you by adding one web address, with your approval. No code.
No code is needed for Zapier and Make either: they use an API key and the same webhooks.
What the API does
The PDF Loft API runs every tool the web app and the apps offer, manages the document library, and tells your software when something finishes. It is a JSON API over HTTPS at https://api.pdfloft.com, versioned in the path (/v1), and described by an OpenAPI 3.1 document at /v1/openapi.json.
- Upload files, start a run of a tool on them, follow the run, and download its results.
- List, save, download and delete documents in the library.
- Send documents for electronic signature, follow each signer and download the signed document and its certificate.
- Receive signed webhook events instead of polling.
The API is included in the Team plan and above. Calls use the same task pool as the web apps: there is no separate allowance, and a run that fails is not counted. Every tool can be run through the API, including merge, split and the others that the web app processes in your browser. A run through the API uploads the files and runs on the server, and the files are deleted two hours after the run; the web app does not change, and its browser tools still keep the file on your device. The Tools and options page lists the files and options of every tool.
Quick start
Make an API key in Settings, then Developers, and keep it in an environment variable named PDFLOFT_API_KEY. The key is shown once. The steps below compress a PDF and download the result.
1. Upload the file
Send the bytes as the request body with the content type of the file. The answer holds the file id.
curl -X POST https://api.pdfloft.com/v1/files \
-H "Authorization: Bearer $PDFLOFT_API_KEY" \
-H "Content-Type: application/pdf" \
--data-binary @contract.pdfimport { readFile } from 'node:fs/promises';
const apiKey = '<your API key>'; // read it from your secret store, never from source
const headers = { Authorization: `Bearer ${apiKey}` };
const body = await readFile('contract.pdf');
const response = await fetch('https://api.pdfloft.com/v1/files', {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/pdf' },
body,
});
const file = await response.json(); // { id: 'file_...', sizeBytes, contentType, expiresAt }import os
import requests
headers = {"Authorization": f"Bearer {os.environ['PDFLOFT_API_KEY']}"}
with open("contract.pdf", "rb") as handle:
response = requests.post(
"https://api.pdfloft.com/v1/files",
headers={**headers, "Content-Type": "application/pdf"},
data=handle,
)
file = response.json() # {"id": "file_...", "sizeBytes": ..., ...}2. Start a run
Name the tool in the path and the uploaded files in the body. The Idempotency-Key header makes the call safe to send again after a network failure. The answer is 202 with the run.
curl -X POST https://api.pdfloft.com/v1/tools/compress/runs \
-H "Authorization: Bearer $PDFLOFT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"files":["file_Qm3nT9xvB2kLw8RzYc1pAe5H"],"options":{"quality":"small"}}'const run = await fetch('https://api.pdfloft.com/v1/tools/compress/runs', {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify({ files: [file.id], options: { quality: 'small' } }),
}).then((r) => r.json());import uuid
run = requests.post(
"https://api.pdfloft.com/v1/tools/compress/runs",
headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
json={"files": [file["id"]], "options": {"quality": "small"}},
).json()3. Follow the run
A run is queued, running, then succeeded, failed or cancelled. Poll no faster than once a second, or subscribe to the job.completed and job.failed events instead.
curl https://api.pdfloft.com/v1/jobs/cm3x8k2f10001 \
-H "Authorization: Bearer $PDFLOFT_API_KEY"let job = run;
while (job.status === 'queued' || job.status === 'running') {
await new Promise((resolve) => setTimeout(resolve, 1000));
job = await fetch(`https://api.pdfloft.com/v1/jobs/${run.id}`, { headers }).then((r) => r.json());
}import time
job = run
while job["status"] in ("queued", "running"):
time.sleep(1)
job = requests.get(f"https://api.pdfloft.com/v1/jobs/{run['id']}", headers=headers).json()4. Download the result
Results are kept for two hours. The answer holds a signed link that is valid for five minutes. To keep a result, save it as a document.
curl https://api.pdfloft.com/v1/jobs/cm3x8k2f10001/results/0 \
-H "Authorization: Bearer $PDFLOFT_API_KEY"
# The answer holds a signed link. Fetch it without the key:
curl -o compressed.pdf "<url from the answer>"const link = await fetch(`https://api.pdfloft.com/v1/jobs/${job.id}/results/0`, { headers }).then((r) => r.json());
const bytes = await fetch(link.url).then((r) => r.arrayBuffer()); // the signed link needs no keylink = requests.get(f"https://api.pdfloft.com/v1/jobs/{job['id']}/results/0", headers=headers).json()
open("compressed.pdf", "wb").write(requests.get(link["url"]).content) # the signed link needs no keyGuides for common jobs
- Convert a file: a Word document to PDF.
- Merge files: several PDFs into one.
- OCR a scanned PDF: make it searchable.
- Send a document for signature: signers, events, the signed document and its certificate.
- React to webhooks: verify each signed event.
- Connect an AI assistant and use Zapier or Make.
Reference and rules
- Authentication explains keys and scopes.
- The API reference lists every endpoint with its parameters, responses and examples.
- Errors, rate limits, idempotency and pagination apply to every call.
- The changelog says how the API is versioned and what changed.