PDFDispatch Docs

Getting started

Three steps, about a minute. You need a PDFDispatch account and a terminal with curl; the same request in nine other languages is further down.

1. Make a key

Sign in at pdfdispatch.com/app/keys and make a key. Give it a name that says where it will be used (for example “billing server”), so you can recognise it later. The key starts with pdd_ and is shown only once: copy it now.

Keep it in an environment variable rather than in your code:

export PDD_KEY=pdd_your_key_here

The Free plan includes 100 PDFs a month, so you can try everything below without paying.

2. Write a page

Any HTML works. Save this as index.html:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      body { font-family: sans-serif; margin: 0; }
      h1 { color: #dc2626; }
    </style>
  </head>
  <body>
    <h1>Hello from PDFDispatch</h1>
    <p>This page became a PDF.</p>
  </body>
</html>

3. Send it

curl --fail-with-body \
  --header "Authorization: Bearer $PDD_KEY" \
  --form files=@index.html \
  --output hello.pdf \
  https://api.pdfdispatch.com/v1/convert/html

Open hello.pdf: that is the whole round trip. The answer had status 200 and the PDF as its body. Had something gone wrong, --fail-with-body makes curl stop with an error, and hello.pdf holds the explanation instead (for example {"code": 40101, ...} for a key that is not right; see Limits and errors).

The same in your language

The example below adds a stylesheet, A4 paper, background colours and a file name. Pick a language; your choice is remembered on every page.

Bash (curl)

# index.html and style.css are in the current folder; the key is in PDD_KEY.
# On an error curl exits with a non-zero status, and the output file holds the error message instead.
curl --fail-with-body --silent --show-error \
  --header "Authorization: Bearer $PDD_KEY" \
  --header "Output-Filename: invoice-1042" \
  --form files=@index.html \
  --form files=@style.css \
  --form paperWidth=210mm \
  --form paperHeight=297mm \
  --form printBackground=true \
  --output invoice-1042.pdf \
  https://api.pdfdispatch.com/v1/convert/html

Node.js

// node html-to-pdf.mjs  (index.html and style.css next to it; the key in PDD_KEY)
import { openAsBlob } from "node:fs";
import { writeFile } from "node:fs/promises";

const form = new FormData();
form.append("files", await openAsBlob("index.html"), "index.html");
form.append("files", await openAsBlob("style.css"), "style.css");
form.append("paperWidth", "210mm");
form.append("paperHeight", "297mm");
form.append("printBackground", "true");

const res = await fetch("https://api.pdfdispatch.com/v1/convert/html", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PDD_KEY}`,
    "Output-Filename": "invoice-1042",
  },
  body: form,
});

if (!res.ok) {
  // Errors from the gateway are JSON with a "code"; errors of the conversion are plain text.
  console.error(res.status, res.headers.get("x-request-id"), await res.text());
  process.exit(1);
}

await writeFile("invoice-1042.pdf", Buffer.from(await res.arrayBuffer()));
Node.js 20 or newer (built-in fetch, FormData and fs.openAsBlob). No packages needed.

Bun

// bun html-to-pdf.ts  (index.html and style.css next to it; the key in PDD_KEY)
const form = new FormData();
form.append("files", Bun.file("index.html"), "index.html");
form.append("files", Bun.file("style.css"), "style.css");
form.append("paperWidth", "210mm");
form.append("paperHeight", "297mm");
form.append("printBackground", "true");

const res = await fetch("https://api.pdfdispatch.com/v1/convert/html", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${Bun.env.PDD_KEY}`,
    "Output-Filename": "invoice-1042",
  },
  body: form,
});

if (!res.ok) {
  console.error(res.status, res.headers.get("x-request-id"), await res.text());
  process.exit(1);
}

await Bun.write("invoice-1042.pdf", res);
Bun 1.1 or newer. No packages needed.

Python

# python html_to_pdf.py  (index.html and style.css next to it; the key in PDD_KEY)
import os
import sys

import requests

with open("index.html", "rb") as html, open("style.css", "rb") as css:
    res = requests.post(
        "https://api.pdfdispatch.com/v1/convert/html",
        headers={
            "Authorization": f"Bearer {os.environ['PDD_KEY']}",
            "Output-Filename": "invoice-1042",
        },
        files=[
            ("files", ("index.html", html, "text/html")),
            ("files", ("style.css", css, "text/css")),
        ],
        data={"paperWidth": "210mm", "paperHeight": "297mm", "printBackground": "true"},
        timeout=70,
    )

if not res.ok:
    print(res.status_code, res.headers.get("X-Request-Id"), res.text, file=sys.stderr)
    sys.exit(1)

with open("invoice-1042.pdf", "wb") as out:
    out.write(res.content)
Python 3.9 or newer with Requests (pip install requests).

PHP

<?php
// php html-to-pdf.php  (index.html and style.css next to it; the key in PDD_KEY)

$curl = curl_init('https://api.pdfdispatch.com/v1/convert/html');
$out = fopen('invoice-1042.pdf', 'wb');

curl_setopt_array($curl, [
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PDD_KEY'),
        'Output-Filename: invoice-1042',
    ],
    // PHP needs a different key per file; the API does not mind what the file parts are called.
    CURLOPT_POSTFIELDS => [
        'files[0]' => new CURLFile('index.html', 'text/html', 'index.html'),
        'files[1]' => new CURLFile('style.css', 'text/css', 'style.css'),
        'paperWidth' => '210mm',
        'paperHeight' => '297mm',
        'printBackground' => 'true',
    ],
    CURLOPT_FILE => $out,
    CURLOPT_TIMEOUT => 70,
]);

$ok = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
fclose($out);

if (! $ok || $status !== 200) {
    // The error body was written to the file; show it and remove the file.
    fwrite(STDERR, "PDFDispatch $status: " . curl_error($curl) . file_get_contents('invoice-1042.pdf') . "\n");
    unlink('invoice-1042.pdf');
    exit(1);
}
PHP 8.1 or newer with the curl extension (on by default in most builds).

Go

// go run .  (index.html and style.css next to it; the key in PDD_KEY)
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"time"
)

func main() {
	var body bytes.Buffer
	form := multipart.NewWriter(&body)
	for _, name := range []string{"index.html", "style.css"} {
		if err := addFile(form, "files", name); err != nil {
			log.Fatal(err)
		}
	}
	form.WriteField("paperWidth", "210mm")
	form.WriteField("paperHeight", "297mm")
	form.WriteField("printBackground", "true")
	form.Close()

	req, err := http.NewRequest(http.MethodPost, "https://api.pdfdispatch.com/v1/convert/html", &body)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PDD_KEY"))
	req.Header.Set("Content-Type", form.FormDataContentType())
	req.Header.Set("Output-Filename", "invoice-1042")

	client := &http.Client{Timeout: 70 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		msg, _ := io.ReadAll(res.Body)
		log.Fatalf("PDFDispatch %d (request %s): %s", res.StatusCode, res.Header.Get("X-Request-Id"), msg)
	}

	out, err := os.Create("invoice-1042.pdf")
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()
	if _, err := io.Copy(out, res.Body); err != nil {
		log.Fatal(err)
	}
	fmt.Println("wrote invoice-1042.pdf")
}

func addFile(form *multipart.Writer, field, path string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()
	part, err := form.CreateFormFile(field, path)
	if err != nil {
		return err
	}
	_, err = io.Copy(part, f)
	return err
}
Go 1.22 or newer; standard library only.

Java

// java Main.java  (index.html and style.css next to it; the key in PDD_KEY)
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        String boundary = UUID.randomUUID().toString();
        Multipart form = new Multipart(boundary);
        form.file("files", Path.of("index.html"), "text/html");
        form.file("files", Path.of("style.css"), "text/css");
        for (Map.Entry<String, String> field : Map.of(
                "paperWidth", "210mm",
                "paperHeight", "297mm",
                "printBackground", "true").entrySet()) {
            form.field(field.getKey(), field.getValue());
        }

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.pdfdispatch.com/v1/convert/html"))
                .timeout(Duration.ofSeconds(70))
                .header("Authorization", "Bearer " + System.getenv("PDD_KEY"))
                .header("Output-Filename", "invoice-1042")
                .header("Content-Type", "multipart/form-data; boundary=" + boundary)
                .POST(HttpRequest.BodyPublishers.ofByteArray(form.finish()))
                .build();

        HttpResponse<byte[]> response = HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofByteArray());

        if (response.statusCode() != 200) {
            System.err.printf("PDFDispatch %d (request %s): %s%n", response.statusCode(),
                    response.headers().firstValue("X-Request-Id").orElse("-"),
                    new String(response.body(), StandardCharsets.UTF_8));
            System.exit(1);
        }
        Files.write(Path.of("invoice-1042.pdf"), response.body());
    }

    /** A small multipart/form-data body builder: java.net.http has none. */
    static final class Multipart {
        private final ByteArrayOutputStream body = new ByteArrayOutputStream();
        private final String boundary;

        Multipart(String boundary) {
            this.boundary = boundary;
        }

        void field(String name, String value) {
            write("--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + name + "\"\r\n\r\n" + value + "\r\n");
        }

        void file(String name, Path path, String contentType) throws IOException {
            write("--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + name + "\"; filename=\""
                    + path.getFileName() + "\"\r\nContent-Type: " + contentType + "\r\n\r\n");
            body.write(Files.readAllBytes(path));
            write("\r\n");
        }

        byte[] finish() {
            write("--" + boundary + "--\r\n");
            return body.toByteArray();
        }

        private void write(String text) {
            body.writeBytes(text.getBytes(StandardCharsets.UTF_8));
        }
    }
}
Java 11 or newer; java.net.http only. Run with java Main.java.

C#

// dotnet run  (index.html and style.css next to it; the key in PDD_KEY)
using System.Net.Http.Headers;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(70) };

using var form = new MultipartFormDataContent();
foreach (var (name, type) in new[] { ("index.html", "text/html"), ("style.css", "text/css") })
{
    var file = new ByteArrayContent(await File.ReadAllBytesAsync(name));
    file.Headers.ContentType = new MediaTypeHeaderValue(type);
    form.Add(file, "files", name);
}
form.Add(new StringContent("210mm"), "paperWidth");
form.Add(new StringContent("297mm"), "paperHeight");
form.Add(new StringContent("true"), "printBackground");

using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.pdfdispatch.com/v1/convert/html")
{
    Content = form,
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("PDD_KEY"));
request.Headers.Add("Output-Filename", "invoice-1042");

using var response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
    var requestId = response.Headers.TryGetValues("X-Request-Id", out var ids) ? ids.First() : "-";
    Console.Error.WriteLine($"PDFDispatch {(int)response.StatusCode} (request {requestId}): {await response.Content.ReadAsStringAsync()}");
    return 1;
}

await using var output = File.Create("invoice-1042.pdf");
await response.Content.CopyToAsync(output);
return 0;
.NET 8 or newer, as the Program.cs of a console app (dotnet new console).

Swift

// swift main.swift  (index.html and style.css next to it; the key in PDD_KEY)
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking // Linux
#endif

let boundary = UUID().uuidString
var body = Data()

func field(_ name: String, _ value: String) {
    body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"\(name)\"\r\n\r\n\(value)\r\n".data(using: .utf8)!)
}

func file(_ name: String, path: String, type: String) throws {
    let header = "--\(boundary)\r\nContent-Disposition: form-data; name=\"\(name)\"; filename=\"\(path)\"\r\n"
        + "Content-Type: \(type)\r\n\r\n"
    body.append(header.data(using: .utf8)!)
    body.append(try Data(contentsOf: URL(fileURLWithPath: path)))
    body.append("\r\n".data(using: .utf8)!)
}

try file("files", path: "index.html", type: "text/html")
try file("files", path: "style.css", type: "text/css")
field("paperWidth", "210mm")
field("paperHeight", "297mm")
field("printBackground", "true")
body.append("--\(boundary)--\r\n".data(using: .utf8)!)

var request = URLRequest(url: URL(string: "https://api.pdfdispatch.com/v1/convert/html")!)
request.httpMethod = "POST"
request.timeoutInterval = 70
request.setValue("Bearer \(ProcessInfo.processInfo.environment["PDD_KEY"] ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("invoice-1042", forHTTPHeaderField: "Output-Filename")
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

let (data, response) = try await URLSession.shared.upload(for: request, from: body)
let http = response as! HTTPURLResponse
guard http.statusCode == 200 else {
    let requestId = http.value(forHTTPHeaderField: "X-Request-Id") ?? "-"
    FileHandle.standardError.write("PDFDispatch \(http.statusCode) (request \(requestId)): \(String(decoding: data, as: UTF8.self))\n".data(using: .utf8)!)
    exit(1)
}
try data.write(to: URL(fileURLWithPath: "invoice-1042.pdf"))
Swift 5.7 or newer, Foundation only (macOS, iOS, Linux). In an app you ship, keep the key on your server, not in the app.

Dart (Flutter)

// dart run main.dart  (index.html and style.css next to it; the key in PDD_KEY)
import 'dart:io';

import 'package:http/http.dart' as http;

Future<void> main() async {
  final request = http.MultipartRequest(
    'POST',
    Uri.parse('https://api.pdfdispatch.com/v1/convert/html'),
  )
    ..headers['Authorization'] = 'Bearer ${Platform.environment['PDD_KEY']}'
    ..headers['Output-Filename'] = 'invoice-1042'
    ..fields['paperWidth'] = '210mm'
    ..fields['paperHeight'] = '297mm'
    ..fields['printBackground'] = 'true'
    ..files.add(await http.MultipartFile.fromPath('files', 'index.html'))
    ..files.add(await http.MultipartFile.fromPath('files', 'style.css'));

  final response = await http.Response.fromStream(
    await request.send().timeout(const Duration(seconds: 70)),
  );

  if (response.statusCode != 200) {
    stderr.writeln('PDFDispatch ${response.statusCode} '
        '(request ${response.headers['x-request-id']}): ${response.body}');
    exit(1);
  }
  await File('invoice-1042.pdf').writeAsBytes(response.bodyBytes);
}
Dart 3 with the http package (dart pub add http, or flutter pub add http). In a Flutter app you ship, anyone can take the key out of the app: call your own server from the app, and make the PDF request there.

Next