API

Upload, list, delete, and debug PDFs programmatically on API-enabled plans.

Authentication

Send your API key with the X-API-Key header. API access requires a verified email account.

X-API-Key: YOUR_API_KEY

API key scopes

Limit what an API key can do from the API dashboard. Existing keys start with all scopes enabled.

upload     Create PDFs and signed upload URLs
read       List and read PDF metadata
delete     Delete hosted PDFs
analytics  Read analytics data
webhooks   Manage webhook integrations

Multiple API keys

Create separate keys for production, staging, Zapier, Make, or internal tools. Each key has its own name, prefix, scopes, last-used timestamp, and revoke button. New keys are shown once when created.

Production key: upload, read, delete
Staging key:    upload, read
Zapier key:     upload
Reporting key:  read, analytics

API request logs

The API dashboard shows recent requests with method, endpoint, status code, timestamp, IP address, key name, and error message. Use this to debug failed uploads, missing scopes, expired signed upload URLs, and rate limits.

POST /api/v1/upload-url  201
GET  /api/v1/pdfs        403  API key is missing required scope: read
POST /api/v1/upload/{token} 410  Upload token expired

Upload PDF

curl -X POST https://pdfhost.se/api/v1/upload \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "[email protected]"

The response includes the PDF slug and hosted URL.

Signed upload URLs

Use signed upload URLs when your app wants a browser, mobile app, or no-code workflow to upload directly to PDFHost without exposing your API key. Signed URLs are one-use and expire after 15 minutes by default.

curl -X POST https://pdfhost.se/api/v1/upload-url \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "proposal.pdf",
    "title": "Client proposal",
    "folder": "Sales",
    "tags": "proposal, client",
    "max_bytes": 10485760
  }'

Then upload the PDF to the returned upload_url using multipart form data.

curl -X POST "RETURNED_UPLOAD_URL" \
  -F "[email protected]"

List PDFs

curl https://pdfhost.se/api/v1/pdfs \
  -H "X-API-Key: YOUR_API_KEY"

Search and filter PDFs

Use query parameters on the list endpoint to search by title, filename, slug, folder, or tags. This endpoint requires the read scope.

curl "https://pdfhost.se/api/v1/pdfs?q=proposal&folder=Sales&tag=client&limit=25" \
  -H "X-API-Key: YOUR_API_KEY"

Folder and tag summaries

Fetch folder and tag counts for building your own document browser.

curl https://pdfhost.se/api/v1/folders \
  -H "X-API-Key: YOUR_API_KEY"

curl https://pdfhost.se/api/v1/tags \
  -H "X-API-Key: YOUR_API_KEY"

Update folder, tags, or title

Update document organization without changing the hosted URL. This endpoint requires the upload scope.

curl -X POST https://pdfhost.se/api/v1/pdfs/pdf_xxxxxxxxxxxx/metadata \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q4 Client Proposal",
    "folder": "Sales",
    "tags": ["proposal", "client", "q4"]
  }'

Analytics API

Fetch the same analytics shown in the dashboard: views, downloads, unique visitors, read-time trend, referrers, devices, browsers, and countries. This endpoint requires the analytics scope and an analytics-enabled plan.

curl "https://pdfhost.se/api/v1/pdfs/pdf_xxxxxxxxxxxx/analytics?range=30" \
  -H "X-API-Key: YOUR_API_KEY"

Use range=7, range=30, range=90, or a custom date range.

curl "https://pdfhost.se/api/v1/pdfs/pdf_xxxxxxxxxxxx/analytics?range=custom&start=2026-05-01&end=2026-05-28" \
  -H "X-API-Key: YOUR_API_KEY"

Analytics CSV export

Download daily views, downloads, and average read time as CSV.

curl "https://pdfhost.se/api/v1/pdfs/pdf_xxxxxxxxxxxx/analytics.csv?range=30" \
  -H "X-API-Key: YOUR_API_KEY" \
  -o analytics.csv

Replace PDF without changing the link

Use the replace endpoint when a proposal, menu, report, or catalog changes but the public URL, QR code, embed code, and analytics history should stay the same. This endpoint requires the upload scope.

curl -X POST https://pdfhost.se/api/v1/pdfs/pdf_xxxxxxxxxxxx/replace \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "[email protected]" \
  -F "title=Updated client proposal"

The response returns the same hosted URL.

{
  "replaced": true,
  "slug": "pdf_xxxxxxxxxxxx",
  "url": "https://pdfhost.se/d/pdf_xxxxxxxxxxxx",
  "download_url": "https://pdfhost.se/d/pdf_xxxxxxxxxxxx/download",
  "size": 845120
}

Delete PDF

curl -X DELETE https://pdfhost.se/api/v1/pdfs/pdf_xxxxxxxxxxxx \
  -H "X-API-Key: YOUR_API_KEY"

Language examples

Switch language to copy the same core API workflow in the stack you use.

curl -X POST https://pdfhost.se/api/v1/upload \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "[email protected]" \
  -F "folder=Sales" \
  -F "tags=proposal,client"

curl https://pdfhost.se/api/v1/pdfs \
  -H "X-API-Key: YOUR_API_KEY"
const apiKey = "YOUR_API_KEY";

async function listPdfs() {
  const res = await fetch("https://pdfhost.se/api/v1/pdfs", {
    headers: { "X-API-Key": apiKey }
  });
  return await res.json();
}

async function deletePdf(slug) {
  const res = await fetch(`https://pdfhost.se/api/v1/pdfs/${slug}`, {
    method: "DELETE",
    headers: { "X-API-Key": apiKey }
  });
  return await res.json();
}
import fs from "fs";
import fetch from "node-fetch";
import FormData from "form-data";

const form = new FormData();
form.append("pdf", fs.createReadStream("./proposal.pdf"));
form.append("folder", "Sales");
form.append("tags", "proposal,client");

const res = await fetch("https://pdfhost.se/api/v1/upload", {
  method: "POST",
  headers: { "X-API-Key": "YOUR_API_KEY", ...form.getHeaders() },
  body: form
});

console.log(await res.json());
import requests

api_key = "YOUR_API_KEY"

with open("proposal.pdf", "rb") as pdf:
    response = requests.post(
        "https://pdfhost.se/api/v1/upload",
        headers={"X-API-Key": api_key},
        files={"pdf": pdf},
        data={"folder": "Sales", "tags": "proposal,client"},
        timeout=60,
    )

print(response.status_code)
print(response.json())
<?php

$ch = curl_init("https://pdfhost.se/api/v1/upload");
$payload = [
    "pdf" => new CURLFile(__DIR__ . "/proposal.pdf", "application/pdf", "proposal.pdf"),
    "folder" => "Sales",
    "tags" => "proposal,client",
];

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => ["X-API-Key: YOUR_API_KEY"],
    CURLOPT_RETURNTRANSFER => true,
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

echo $status . PHP_EOL;
echo $body . PHP_EOL;

Signed upload with fetch

Ask your server for a signed upload URL, then send the PDF to that temporary URL.

const createUrl = await fetch("https://pdfhost.se/api/v1/upload-url", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    filename: "proposal.pdf",
    title: "Client proposal",
    max_bytes: 10485760
  })
});

const signed = await createUrl.json();
const form = new FormData();
form.append("pdf", fileInput.files[0]);

const uploaded = await fetch(signed.upload_url, {
  method: "POST",
  body: form
});

console.log(await uploaded.json());

Zapier setup

Use Webhooks by Zapier with a custom request.

Action: POST
URL: https://pdfhost.se/api/v1/upload-url
Headers:
  X-API-Key: YOUR_API_KEY
  Content-Type: application/json
Data:
  {
    "filename": "zapier-upload.pdf",
    "title": "Zapier upload",
    "max_bytes": 10485760
  }

Next action:
  POST the PDF file to the returned upload_url as multipart field "pdf".

Make setup

Use the HTTP module. First create the upload URL, then upload the file to the returned URL.

Module 1: HTTP / Make a request
Method: POST
URL: https://pdfhost.se/api/v1/upload-url
Headers:
  X-API-Key: YOUR_API_KEY
  Content-Type: application/json
Body type: Raw JSON

Module 2: HTTP / Upload a file
Method: POST
URL: upload_url from Module 1
Field name: pdf

Error responses

Errors use JSON and standard HTTP status codes. Check the API logs in your dashboard for the full message and request context.

{
  "error": "API key is missing required scope: upload"
}

Common statuses:
401 Unauthorized
402 Payment required
403 Missing verification, plan, or scope
410 Signed upload URL expired
429 API rate limit exceeded

Webhook events

Create webhook endpoints from the API dashboard to receive document events in your app.

pdf.uploaded
pdf.deleted
pdf.viewed
pdf.downloaded
pdf.expired

Webhook signature verification

Each delivery includes X-PDFHost-Timestamp and X-PDFHost-Signature. Verify the signature with your webhook secret.

const crypto = require("crypto");

const signedPayload = timestamp + "." + rawBody;
const expected = "sha256=" + crypto
  .createHmac("sha256", webhookSecret)
  .update(signedPayload)
  .digest("hex");

if (signature !== expected) {
  throw new Error("Invalid PDFHost webhook signature");
}

Example JSON response

{
  "id": "42",
  "slug": "pdf_xxxxxxxxxxxx",
  "url": "https://pdfhost.se/d/pdf_xxxxxxxxxxxx",
  "size": 532100
}