Skip to content
Porter

Quickstart

Upload a PDF. Get a useful result. Start with a free validation.

1. Create a key

Open API setup, choose a name and copy your key. An organization owner or administrator can create keys when the organization’s purchased plan includes API access.

Keep the key on your server or in your local environment. Never embed it in a browser application.

export PORTER_API_KEY="your-key"

2. Run an example

Python needs requests; JavaScript needs Node.js 22 or later. The shell example needs Bash, cURL, jq and uuidgen. Pass a PDF path when running the example. Use python porter.py document.pdf for Python.

The example prepares an upload, sends the PDF directly to storage, creates a validation job, and waits for its report. Validation-only uses zero page credits. The same API key works across your organization’s API-created files.

# pip install requests
# Set PORTER_API_KEY in your environment. Save as porter.py.
import os, sys, time, uuid
from pathlib import Path
import requests

base = "https://api.autotune.porter.fi/v1"
path = Path(sys.argv[1])
headers = {"Authorization": "Bearer " + os.environ["PORTER_API_KEY"]}

def api(method, route, body=None):
    request_headers = dict(headers)
    if method == "POST":
        request_headers["Idempotency-Key"] = str(uuid.uuid4())
    for attempt in range(6):
        try:
            response = requests.request(method, base + route, json=body,
                headers=request_headers, timeout=30)
        except requests.RequestException:
            if attempt == 5: raise
            time.sleep(min(2 ** attempt, 30))
            continue
        data = response.json()
        retry = response.status_code == 429 or response.status_code >= 500 or (
            response.status_code == 409 and data.get("error", {}).get("code") == "request_in_progress")
        if retry and attempt < 5:
            time.sleep(min(int(response.headers.get("Retry-After", 2 ** attempt)), 60))
            continue
        response.raise_for_status()
        return data

upload = api("POST", "/uploads", {"filename": path.name, "size_bytes": path.stat().st_size})
with path.open("rb") as pdf:
    sent = requests.post(upload["upload_url"], data=upload["fields"],
        files={"file": (path.name, pdf, "application/pdf")}, timeout=300)
    sent.raise_for_status()
job = api("POST", "/jobs", {"upload_id": upload["upload_id"], "mode": "validate_only"})
for _ in range(360):
    job = api("GET", "/jobs/" + job["id"])
    if job["status"] in ("completed", "failed"): break
    time.sleep(5)
else:
    raise TimeoutError("Keep job id " + job["id"] + " and check it later")
print(job)
if job["status"] == "failed":
    raise RuntimeError(job["error"])
if job["validation"]["status"] != "skipped":
    print(api("GET", "/jobs/" + job["id"] + "/report"))
if job["has_output"]:
    download = api("GET", "/jobs/" + job["id"] + "/download")
    pdf = requests.get(download["download_url"], timeout=300)
    pdf.raise_for_status()
    Path("repaired.pdf").write_bytes(pdf.content)

3. Read the result

completed means the requested work finished. Check the report for accessibility findings; completion alone does not establish conformance. Your job also appears in API setup.

For automatic notifications, follow the webhook guide. To repair PDFs, select a repair mode.