Convert a file

Convert a Word document to PDF through the API: upload, run, wait and download, in curl, JavaScript and Python.

Before you start

  • An API key with the tools:run scope to upload and run, and jobs:read to follow the run and download the result. Authentication shows how to make one.
  • A Team, Business or Enterprise account. The API is included in those plans. The largest file and the number of files in one run follow the plan: for the Team plan, 250 MB and 50. The rate limits page lists every plan.
  • The tool id is word-to-pdf. GET https://api.pdfloft.com/v1/tools/word-to-pdf returns the files and options it takes, and the Tools and options page lists them all.

The whole script

The script uploads a Word document, starts a run of word-to-pdf, waits for it and saves the first result. Upload and run are separate calls: POST /v1/files returns a file id, and POST /v1/tools/word-to-pdf/runs takes the ids. The Idempotency-Key header lets you send the run call again after a network failure without starting a second run. A run is queued, running, then succeeded, failed or cancelled.

curl
FILE0=$(curl -s -X POST https://api.pdfloft.com/v1/files \
  -H "Authorization: Bearer $PDFLOFT_API_KEY" \
  -H "Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document" \
  --data-binary @report.docx | jq -r .id)

JOB=$(curl -s -X POST https://api.pdfloft.com/v1/tools/word-to-pdf/runs \
  -H "Authorization: Bearer $PDFLOFT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"files":["'"$FILE0"'"],"options":{}}' | jq -r .id)

until [ "$(curl -s https://api.pdfloft.com/v1/jobs/$JOB -H "Authorization: Bearer $PDFLOFT_API_KEY" | jq -r .status)" = "succeeded" ]; do sleep 1; done

URL=$(curl -s https://api.pdfloft.com/v1/jobs/$JOB/results/0 -H "Authorization: Bearer $PDFLOFT_API_KEY" | jq -r .url)
curl -s -o report.pdf "$URL"
JavaScript
import { readFile, writeFile } from 'node:fs/promises';

const apiKey = '<your API key>'; // read it from your secret store, never from source
const headers = { Authorization: `Bearer ${apiKey}` };

async function upload(path, contentType) {
  const response = await fetch('https://api.pdfloft.com/v1/files', {
    method: 'POST',
    headers: { ...headers, 'Content-Type': contentType },
    body: await readFile(path),
  });
  return response.json();
}

const file0 = await upload('report.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');

let job = await fetch('https://api.pdfloft.com/v1/tools/word-to-pdf/runs', {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
  body: JSON.stringify({ files: [file0.id], options: {} }),
}).then((r) => r.json());

while (job.status === 'queued' || job.status === 'running') {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  job = await fetch(`https://api.pdfloft.com/v1/jobs/${job.id}`, { headers }).then((r) => r.json());
}
if (job.status !== 'succeeded') throw new Error(`The run ended as ${job.status}: ${job.error}`);

const link = await fetch(`https://api.pdfloft.com/v1/jobs/${job.id}/results/0`, { headers }).then((r) => r.json());
await writeFile('report.pdf', Buffer.from(await fetch(link.url).then((r) => r.arrayBuffer())));
Python
import os
import time
import uuid

import requests

headers = {"Authorization": f"Bearer {os.environ['PDFLOFT_API_KEY']}"}


def upload(path, content_type):
    with open(path, "rb") as handle:
        return requests.post("https://api.pdfloft.com/v1/files", headers={**headers, "Content-Type": content_type}, data=handle).json()


file0 = upload("report.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")

job = requests.post(
    "https://api.pdfloft.com/v1/tools/word-to-pdf/runs",
    headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
    json={"files": [file0["id"]], "options": {}},
).json()

while job["status"] in ("queued", "running"):
    time.sleep(1)
    job = requests.get(f"https://api.pdfloft.com/v1/jobs/{job['id']}", headers=headers).json()
if job["status"] != "succeeded":
    raise RuntimeError(f"The run ended as {job['status']}: {job['error']}")

link = requests.get(f"https://api.pdfloft.com/v1/jobs/{job['id']}/results/0", headers=headers).json()
open("report.pdf", "wb").write(requests.get(link["url"]).content)

Other conversions

Change the tool id and the file. excel-to-pdf, ppt-to-pdf, html-to-pdf, jpg-to-pdf and zpl-to-pdf make a PDF. pdf-to-word, pdf-to-excel, pdf-to-ppt, pdf-to-jpg and pdf-to-png read a PDF. When a tool is given several files it makes one result for each, so download /v1/jobs/{jobId}/results/1 and the indexes after it. The results field of the run says how many there are. A conversion of a PDF to pictures returns one picture for a one page document and otherwise one ZIP.

If a call fails

  • file.type_refused: the file is not a type the tool reads. Send a .docx, .doc, .odt or .rtf file to word-to-pdf, and set the Content-Type header to match.
  • file.too_large: the file is larger than the plan allows. The details give maxBytes.
  • tasks.exhausted: the account has used its tasks for the period. The details give when it resets.
  • Every error code is listed on the errors page, and a run that failed carries its code in the error field of the run.