Skip to content

Docs · updated 31 October 2026

Quickstart and reference

Base URL https://api.pdftrusted.com. JSON in, JSON out. Every tool uses the same four calls.

Quickstart

  1. Sign in with Google or email, then create a key in the console. The secret is shown once.
  2. Create a task, upload the file, process it and download the result.

cURL

API=https://api.pdftrusted.com
ID=$(curl -sS -X POST $API/v1/tasks -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" -d '{"tool":"compress"}' | jq -r .task_id)
curl -sS -X PUT $API/v1/tasks/$ID/upload -H "X-API-Key: $KEY" -F file=@in.pdf
curl -sS -X POST $API/v1/tasks/$ID/process -H "X-API-Key: $KEY"   # waits; 200 when done
curl -sS -L "$API/v1/tasks/$ID/download?redirect=1" -H "X-API-Key: $KEY" -o out.pdf

Python

import os, requests
API, H = "https://api.pdftrusted.com", {"X-API-Key": os.environ["KEY"]}
t = requests.post(f"{API}/v1/tasks", headers=H, json={"tool": "compress"}).json()
requests.put(f"{API}/v1/tasks/{t['task_id']}/upload", headers=H, files={"file": open("in.pdf", "rb")}).raise_for_status()
r = requests.post(f"{API}/v1/tasks/{t['task_id']}/process?async=1", headers=H).json()
while r["status"] == "processing":
    r = requests.get(f"{API}/v1/tasks/{t['task_id']}?wait=15", headers=H).json()
if r["status"] == "done":
    url = requests.get(f"{API}/v1/tasks/{t['task_id']}/download", headers=H).json()["download_url"]
    open("out.pdf", "wb").write(requests.get(url).content)
else:
    print(r["error"], r.get("error_message"))

Node.js

const API = "https://api.pdftrusted.com";
const H = { "X-API-Key": process.env.KEY };
const task = await (await fetch(`${API}/v1/tasks`, {
  method: "POST", headers: { ...H, "Content-Type": "application/json" },
  body: JSON.stringify({ tool: "compress" }),
})).json();
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("in.pdf")]), "in.pdf");
await fetch(`${API}/v1/tasks/${task.task_id}/upload`, { method: "PUT", headers: H, body: form });
await fetch(`${API}/v1/tasks/${task.task_id}/process`, { method: "POST", headers: H });
const { download_url } = await (await fetch(`${API}/v1/tasks/${task.task_id}/download`, { headers: H })).json();

Authentication and keys

Send your key as X-API-Key: sk_... (or Authorization: Bearer sk_...). Never put a key in browser or mobile code; call the API from your server.

  • Main key: your account's plan and quota.
  • Extra live keys (sk_live_): up to 10, one per app or environment, same plan and quota. Delete one without touching the others.
  • Test keys (sk_test_): a free sandbox, 100 jobs a day, 5 MB and 10 pages a file, every tool except AI ones. Never touches your quota or credits.

Keys are managed in the console or over the API with your sign-in token (a key can't manage keys): GET /v1/keys, POST /v1/keys, POST /v1/keys/rotate (new secret, same quota), DELETE /v1/keys, POST /v1/keys/extra with {"name","mode"} and DELETE /v1/keys/extra/{id}.

Tasks

GET/v1/tools

Every tool id. GET /v1/tools/{tool} gives its upload field (file, repeated files or none), a required option and the size, page and file caps per plan. Browse them on the tools page.

POST/v1/tasks

Body tool, optional options. Answers 201 with task_id and upload_url. For big files send filename, size and content_type (or files for several) to get a signed URL, so the file goes straight to storage. A file over your cap is refused here, before any upload (413).

PUT/v1/tasks/{id}/upload

Multipart field file (files, repeated, for merge and image to PDF). Or PUT the bytes to the signed URL with the headers from the create answer.

POST/v1/tasks/{id}/process

Waits and answers 200 with download_url. With ?async=1 (or Prefer: respond-async) it answers 202 at once; a job still running after 90 seconds also turns into 202. Calling it again never starts a second job.

GET/v1/tasks/{id}

status: created, uploaded, processing, done, failed or expired. Add ?wait=15 to hold the answer up to 15 s until the job finishes.

GET/v1/tasks/{id}/download

{"download_url": "..."}, a signed link valid for 15 minutes. ?redirect=1 gives a 302 instead (handy with curl -L).

DELETE/v1/tasks/{id}

Deletes input and output now: 204, 404 not_found, or 409 task_busy while processing. Otherwise files are removed within an hour of the job finishing, and an upload that is never processed expires after an hour (410 upload_expired).

Webhooks

Instead of polling, POST /v1/tasks/{id}/process with JSON {"webhook_url": "https://you.example/hook"}. It answers 202, and we POST task.completed or task.failed with the task as JSON. HTTPS only, public addresses only, port 443 or 8443. We try 3 times (now, after 5 s, after 30 s) and stop on a 4xx other than 408, 409, 425 or 429. GET /v1/tasks/{id} shows the delivery status.

Verify PDFTrusted-Signature: t=...,v1=...: HMAC-SHA256 with your webhook secret (console → Settings) over t + "." + raw body. Reject a t more than 5 minutes old.

import hmac, hashlib, time
def verify(secret, header, body: bytes):
    p = dict(x.split("=", 1) for x in header.split(","))
    mac = hmac.new(secret.encode(), p["t"].encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, p["v1"]) and abs(time.time() - int(p["t"])) < 300

Credits and usage

Free accounts get 50 jobs a month (10 a minute, one at a time, 25 MB and 50 pages a file). On an API plan each successful job spends credits from the per-tool table: taken when the job starts, given back if it fails. Per-page tools count the pages of the file you send.

GET/v1/me/usage

plan, used, remaining, caps, and billing.meters with used and cap for api_credits, ocr_pages, translate_pages and engine_s. Add ?refresh=1 right after a purchase.

GET/v1/me/jobs?days=30

Sign-in token only. Your last 100 API jobs (tool, result, credits, pages, duration, key name) plus totals per day and per tool. Powers the console's Usage page; totals match the credit meter exactly.

Errors and limits

Every error is {"error": {"code", "message"}}. Branch on code, show message. Limit errors also carry upgrade_url or credits_url, and docs_url. Every 429 and 503 has Retry-After; task creation and usage send X-RateLimit-Limit, -Remaining and -Reset. Every answer has X-Request-Id; quote it to support.

StatusCodeMeaningWhat to do
429api_credits_exceededNot enough API credits left this month for this job (needed and left are in the error).Upgrade in the console, or wait for the monthly reset.
429quota_exceededThe plan's job quota for this day or month is used.Wait for Retry-After, or upgrade (upgrade_url).
429ocr_pages_exceededThis month's OCR pages are used.Upgrade your API plan (credits_url).
429translate_pages_exceededThis month's translated pages are used.Upgrade your API plan (credits_url).
429engine_time_exceededThis month's processing time is used.Wait for the next month, or pick a larger plan.
429too_many_concurrentToo many jobs at once for the plan.Wait for a running job to finish.
429rate_limitedToo many jobs this minute.Retry after Retry-After seconds.
413file_too_largeThe file is over the plan's size cap.Split or compress it, or upgrade (upgrade_url).
413too_many_pagesThe file has more pages than the plan allows.Split it, or upgrade (upgrade_url).
401invalid_api_keyThe key is wrong, deleted or turned off.Check the key, or rotate it in the console.
422pdf_passwordThe PDF is encrypted.Send the password in options, or unlock it first.
503engine_unavailableThe engine is busy or restarting.Retry after Retry-After seconds.

Other codes you may see: 400 unknown_tool, bad_request, bad_webhook_url; 413 too_many_files; 422 not_pdf, pdf_unreadable; 403 test_key_tool (AI tool with a test key).

OpenAPI

The machine-readable spec is at api.pdftrusted.com/openapi.json, with an interactive reference at api.pdftrusted.com/docs. Import it into Postman or generate a client.