API Documentation

Convert HTML to PDF through our RESTful API

Overview

The pdfy API allows you to convert HTML content into high-quality PDF documents. All API endpoints are RESTful and return JSON responses.

Base URL: https://pdfy.app/api/v1

Authentication

All API requests require authentication using a Bearer token in the Authorization header.

Authorization: Bearer YOUR_API_TOKEN

PHP SDK

We provide an official PHP SDK that makes it easy to integrate PDF generation into your PHP applications. The SDK handles authentication, error handling, and provides a clean, object-oriented interface.

Official PHP SDK

Complete documentation, installation instructions, and examples available on GitHub.

✅ Type-safe PHP 8.4+ • 🔄 Async & Sync • 🎯 Laravel Ready • 🚨 Rich Error Handling

Quick Start

composer require pdfy/php-sdk

Rate Limiting

To ensure fair usage and maintain service quality, the pdfy API implements rate limiting on all endpoints. Rate limits are applied per API token and reset automatically.

Current Rate Limits

PDF Generation 10/minute

POST /api/v1/pdfs

Status & Downloads 60/minute

GET endpoints

Rate Limit Headers

Every API response includes headers that help you track your rate limit status:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1640995200
Retry-After: 45
X-RateLimit-Limit Maximum requests allowed in the time window
X-RateLimit-Remaining Requests remaining in current window
X-RateLimit-Reset Unix timestamp when the limit resets
Retry-After Seconds to wait before retrying (when rate limited)

Rate Limit Exceeded

When you exceed the rate limit, the API returns a 429 Too Many Requests status:

{
  "error": true,
  "message": "Too many requests. Please wait before trying again.",
  "error_code": "RATE_LIMIT_EXCEEDED"
}

Best Practices

Monitor Headers: Always check rate limit headers in responses to avoid hitting limits
Implement Backoff: Use exponential backoff when receiving 429 responses
Cache Results: Store generated PDFs to avoid regenerating identical content

Handling Rate Limits in Code

async function generatePdfWithRetry(htmlContent, filename, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('/api/v1/pdfs', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer YOUR_API_TOKEN',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ html: htmlContent, filename })
      });

      // Check rate limit headers
      const remaining = response.headers.get('X-RateLimit-Remaining');
      const resetTime = response.headers.get('X-RateLimit-Reset');

      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);

        // Wait before retrying
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }

      if (response.ok) {
        console.log(`Rate limit remaining: ${remaining}`);
        return await response.json();
      }

      throw new Error(`HTTP ${response.status}: ${response.statusText}`);

    } catch (error) {
      if (attempt === maxRetries) throw error;

      // Exponential backoff for other errors
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Generate PDF

Create a new PDF from HTML content. This endpoint returns immediately with a job ID for async processing.

POST /api/v1/pdfs

Request Body

{
  "html": "<h1>Hello World</h1><p>This is a test PDF.</p>",
  "filename": "my-document.pdf",
  "options": {
    "format": "A4",
    "orientation": "portrait",
    "margin_top": 1.0,
    "margin_right": 1.0,
    "margin_bottom": 1.0,
    "margin_left": 1.0,
    "margin_unit": "cm"
  }
}

Response

{
  "success": true,
  "data": {
    "job_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "message": "PDF generation started"
  }
}

Options Parameters

Parameter Type Default Description
format string A4 Page format (A4, A3, A5, Letter, Legal, Tabloid)
orientation string portrait Page orientation (portrait, landscape)
margin_top number 0 Top margin value (0-100)
margin_right number 0 Right margin value (0-100)
margin_bottom number 0 Bottom margin value (0-100)
margin_left number 0 Left margin value (0-100)
margin_unit string cm Unit for all margins (cm, mm, in, px)
print_background boolean true Include background graphics

Check Status

Check the status of a PDF generation job.

GET /api/v1/pdfs/{job_id}

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "filename": "my-document.pdf",
    "status": "completed",
    "file_size": 245760,
    "file_size_human": "240 KB",
    "created_at": "2025-08-14T10:30:00Z",
    "completed_at": "2025-08-14T10:30:15Z",
    "download_url": "/api/v1/pdfs/550e8400-e29b-41d4-a716-446655440000/download"
  }
}

Download PDF

Download the generated PDF file. Only available when status is "completed".

GET /api/v1/pdfs/{job_id}/download

Returns the PDF file as a binary download with appropriate headers.

List PDFs

Get a list of your PDF generation jobs with optional filtering.

GET /api/v1/pdfs

Query Parameters

  • status - Filter by status (pending, processing, completed, failed)
  • limit - Number of results (max 100, default 20)

Response

{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "filename": "my-document.pdf",
      "status": "completed",
      "file_size": 245760,
      "file_size_human": "240 KB",
      "created_at": "2025-08-14T10:30:00Z",
      "completed_at": "2025-08-14T10:30:15Z",
      "download_url": "/api/v1/pdfs/550e8400-e29b-41d4-a716-446655440000/download"
    }
  ],
  "meta": {
    "total": 1,
    "user_id": 123
  }
}

Error Handling

The API uses standard HTTP status codes and returns detailed error information.

Error Response Format

{
  "error": true,
  "message": "Validation failed",
  "error_code": "VALIDATION_ERROR",
  "errors": {
    "html": ["The html field is required."]
  }
}

Common Error Codes

VALIDATION_ERROR - Invalid request data
RATE_LIMIT_EXCEEDED - Too many requests
DAILY_LIMIT_EXCEEDED - Daily PDF limit reached
JOB_NOT_FOUND - PDF job not found
PDF_NOT_READY - PDF not ready for download

Code Examples

cURL

# Generate PDF
curl -X POST https://pdfy.app/api/v1/pdfs \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Hello World</h1>",
    "filename": "hello.pdf"
  }'

# Check status
curl -X GET https://pdfy.app/api/v1/pdfs/JOB_ID \
  -H "Authorization: Bearer YOUR_API_TOKEN"

# Download PDF (use -L: the API redirects to a short-lived signed URL)
curl -L -X GET https://pdfy.app/api/v1/pdfs/JOB_ID/download \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -o hello.pdf

JavaScript (fetch)

const API_TOKEN = 'YOUR_API_TOKEN';
const BASE_URL = 'https://pdfy.app/api/v1';

async function generatePDF(html, filename) {
  const response = await fetch(`${BASE_URL}/pdfs`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html: html,
      filename: filename
    })
  });

  const result = await response.json();
  return result.data.job_id;
}

async function checkStatus(jobId) {
  const response = await fetch(`${BASE_URL}/pdfs/${jobId}`, {
    headers: {
      'Authorization': `Bearer ${API_TOKEN}`,
    }
  });

  return await response.json();
}

async function downloadPDF(jobId) {
  const response = await fetch(`${BASE_URL}/pdfs/${jobId}/download`, {
    headers: {
      'Authorization': `Bearer ${API_TOKEN}`,
    }
  });

  const blob = await response.blob();
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'document.pdf';
  a.click();
}

PHP

<?php

$apiToken = 'YOUR_API_TOKEN';
$baseUrl = 'https://pdfy.app/api/v1';

function generatePDF($html, $filename) {
    global $apiToken, $baseUrl;

    $data = [
        'html' => $html,
        'filename' => $filename
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "$baseUrl/pdfs");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $apiToken,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    $result = json_decode($response, true);
    return $result['data']['job_id'];
}

function checkStatus($jobId) {
    global $apiToken, $baseUrl;

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "$baseUrl/pdfs/$jobId");
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $apiToken
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    return json_decode($response, true);
}