Convert PDF documents to HTML while preserving their page layout with a direct HTTP request from any language or platform. For language-specific client libraries, see the SDK guides.
| Request | Details |
|---|---|
| Method and endpoint | POST https://api.pdfcrowd.com/convert/24.04/ |
| Authentication | HTTP Basic: your PDFCrowd username and API key |
| Request body | Form fields; use multipart form data for file uploads |
| Successful response | 200 OK with the HTML or ZIP bytes in the response body |
Choose an input below. You can run the examples below with the displayed API credentials. Update the input URLs and filenames as needed.
Each example saves the returned HTML to a local file. If a conversion fails, inspect the response for error details.
Use a Bash-compatible shell.
Basic options
-u — Supply your username and API key.-F — Send a form field.-o — Save the response to a file.-f — Report HTTP errors as command failures.-s -S — Hide the progress meter while keeping error messages.File input
file=@document.pdf — Upload a local file.Diagnostics
--fail-with-body
— Use instead of -f to retain the error response body.
-D — Save response headers to a file.-w — Print response information;
the diagnostic example
prints the HTTP status.
Install Requests with
python3 -m pip install requests. Save an example as
convert.py and run it with python3 convert.py.
Request options
auth — Supply your username and API key using HTTP Basic authentication.data — Send form fields; Requests encodes their values.files — Upload files opened in binary mode. Requests sets the multipart boundary.allow_redirects=False — Return the endpoint's response without following redirects.Response handling
raise_for_status() — Raise an exception for HTTP errors before opening the output file.response.content — Save the response bytes to a file opened with "wb".with — Close opened files even if the request or output write fails.
Connection errors raise exceptions. The examples do not set a client timeout or
retry requests. If you add a timeout, allow for conversion time;
Requests applies connection and read timeouts, not an overall conversion deadline.
The diagnostic example
saves response headers and body and prints the HTTP status before calling
raise_for_status(), so an HTTP error still leaves diagnostic files.
Use Node.js with its built-in Fetch API; no HTTP package is needed.
Save an example as convert.mjs and run it with
node convert.mjs. The .mjs extension enables imports
and top-level await.
Request options
Authorization — Send your username and API key using HTTP Basic authentication.FormData — Send form fields and files. Fetch sets the multipart boundary.readFile and Blob — Read uploads as bytes into memory and include their filenames.redirect: 'manual' — Return the endpoint's response without following redirects.Response handling
response.arrayBuffer() — Read the response bytes into memory.response.status — Check for HTTP errors before opening the output file; Fetch does not throw for HTTP errors itself.writeFile — Save the bytes to the output file. File and connection errors propagate as exceptions.
The examples use Fetch's native connection, response-header, and body inactivity
limits, which depend on the Node.js runtime. They do not set an overall conversion
deadline or retry requests. Allow for conversion time if you add a timeout using
an AbortSignal.
The diagnostic example saves response headers and body and prints the HTTP status before throwing on an HTTP error, so diagnostic files remain available.
Install Axios with npm install axios. Save an example as
convert.mjs and run it with node convert.mjs.
The .mjs extension enables imports and top-level await.
Request options
auth — Send your username and API key using HTTP Basic authentication.FormData — Send form fields and files. Axios sets the multipart boundary.readFile and Blob — Read uploads as bytes into memory and include their filenames.maxRedirects: 0 — Return the endpoint's response without following redirects.Response handling
responseType: 'arraybuffer' — Read the response as bytes.validateStatus — Reject HTTP errors before writing the output file.writeFile — Save the response bytes to the output file.Axios does not set an overall timeout by default. Allow for conversion time if you add one. Failed requests and file writes produce an error message and a nonzero exit status; the error handler prints only the message so request credentials are not included.
The diagnostic example saves response headers and body and prints the HTTP status before reporting an HTTP error, so diagnostic files remain available.
Install Guzzle with composer require guzzlehttp/guzzle.
Save an example as convert.php beside your vendor
directory and run it with php convert.php.
Request options
auth — Send your username and API key using HTTP Basic authentication.multipart — Send form fields and files. Guzzle sets the multipart boundary.Utils::tryFopen — Open input files as binary streams for upload.'allow_redirects' => false — Return the endpoint's response without following redirects.Response handling
'http_errors' => false — Inspect the HTTP status before writing the output file.getBody — Read the response as a binary string.file_put_contents — Save the response bytes and check that the entire result was written.Guzzle does not set an overall timeout by default. Allow for conversion time if you add one. Failed requests and file writes produce an error message and a nonzero exit status.
The diagnostic example saves response headers and body and prints the HTTP status before reporting an HTTP error, so diagnostic files remain available.
Send the PDF's address in the url field. This example converts
its pages to HTML and saves the result in invoice.html:
curl -f -s -S \ -u 'demo:demo' \ -o invoice.html \ -F url=https://your-server.com/invoice.pdf \ -F input_format=pdf \ -F output_format=html \ https://api.pdfcrowd.com/convert/24.04/
import requests response = requests.post( 'https://api.pdfcrowd.com/convert/24.04/', auth=('demo', 'demo'), data={ 'input_format': 'pdf', 'output_format': 'html', 'url': 'https://your-server.com/invoice.pdf', }, allow_redirects=False, ) response.raise_for_status() with open('invoice.html', 'wb') as output_file: output_file.write(response.content)
import { writeFile } from 'node:fs/promises'; const username = 'demo'; const apiKey = 'demo'; const form = new FormData(); form.append("input_format", "pdf"); form.append("output_format", "html"); form.append("url", "https://your-server.com/invoice.pdf"); const response = await fetch("https://api.pdfcrowd.com/convert/24.04/", { method: 'POST', headers: { Authorization: 'Basic ' + Buffer.from( username + ':' + apiKey).toString('base64'), }, body: form, redirect: 'manual', }); const body = Buffer.from(await response.arrayBuffer()); if (response.status >= 400) { throw new Error('HTTP ' + response.status + ' ' + response.statusText); } await writeFile("invoice.html", body);
import axios from 'axios'; import { writeFile } from 'node:fs/promises'; const username = 'demo'; const apiKey = 'demo'; const form = new FormData(); form.append("input_format", "pdf"); form.append("output_format", "html"); form.append("url", "https://your-server.com/invoice.pdf"); try { const response = await axios.post("https://api.pdfcrowd.com/convert/24.04/", form, { auth: { username, password: apiKey }, responseType: 'arraybuffer', maxRedirects: 0, validateStatus: status => status < 400, }); await writeFile("invoice.html", response.data); } catch (error) { console.error(error.message); process.exitCode = 1; }
<?php require 'vendor/autoload.php'; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Utils; try { $client = new Client(); $response = $client->post('https://api.pdfcrowd.com/convert/24.04/', [ 'auth' => ['demo', 'demo'], 'multipart' => [ ['name' => 'input_format', 'contents' => 'pdf'], ['name' => 'output_format', 'contents' => 'html'], ['name' => 'url', 'contents' => 'https://your-server.com/invoice.pdf'], ], 'allow_redirects' => false, 'http_errors' => false, ]); $status = $response->getStatusCode(); if ($status >= 400) { throw new \RuntimeException("HTTP $status " . $response->getReasonPhrase()); } $body = (string) $response->getBody(); if (file_put_contents('invoice.html', $body) !== strlen($body)) { throw new \RuntimeException('Failed to write output file'); } } catch (\Throwable $error) { fwrite(STDERR, $error->getMessage() . "\n"); exit(1); }
PDFCrowd downloads the PDF and returns the result in the same request. By default, images, stylesheets and fonts are embedded in the HTML file.
Upload an existing PDF in the file field. Replace the path with
your local file; this example saves the HTML in logo.html:
curl -f -s -S \ -u 'demo:demo' \ -o logo.html \ -F file=@logo.pdf \ -F input_format=pdf \ -F output_format=html \ https://api.pdfcrowd.com/convert/24.04/
import requests with open('logo.pdf', 'rb') as input_1: response = requests.post( 'https://api.pdfcrowd.com/convert/24.04/', auth=('demo', 'demo'), data={ 'input_format': 'pdf', 'output_format': 'html', }, files={ 'file': input_1, }, allow_redirects=False, ) response.raise_for_status() with open('logo.html', 'wb') as output_file: output_file.write(response.content)
import { readFile, writeFile } from 'node:fs/promises'; import { basename } from 'node:path'; const username = 'demo'; const apiKey = 'demo'; const form = new FormData(); form.append("input_format", "pdf"); form.append("output_format", "html"); form.append("file", new Blob([await readFile("logo.pdf")]), basename("logo.pdf")); const response = await fetch("https://api.pdfcrowd.com/convert/24.04/", { method: 'POST', headers: { Authorization: 'Basic ' + Buffer.from( username + ':' + apiKey).toString('base64'), }, body: form, redirect: 'manual', }); const body = Buffer.from(await response.arrayBuffer()); if (response.status >= 400) { throw new Error('HTTP ' + response.status + ' ' + response.statusText); } await writeFile("logo.html", body);
import axios from 'axios'; import { readFile, writeFile } from 'node:fs/promises'; import { basename } from 'node:path'; const username = 'demo'; const apiKey = 'demo'; const form = new FormData(); form.append("input_format", "pdf"); form.append("output_format", "html"); form.append("file", new Blob([await readFile("logo.pdf")]), basename("logo.pdf")); try { const response = await axios.post("https://api.pdfcrowd.com/convert/24.04/", form, { auth: { username, password: apiKey }, responseType: 'arraybuffer', maxRedirects: 0, validateStatus: status => status < 400, }); await writeFile("logo.html", response.data); } catch (error) { console.error(error.message); process.exitCode = 1; }
<?php require 'vendor/autoload.php'; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Utils; try { $client = new Client(); $response = $client->post('https://api.pdfcrowd.com/convert/24.04/', [ 'auth' => ['demo', 'demo'], 'multipart' => [ ['name' => 'input_format', 'contents' => 'pdf'], ['name' => 'output_format', 'contents' => 'html'], ['name' => 'file', 'contents' => Utils::tryFopen('logo.pdf', 'rb'), 'filename' => basename('logo.pdf')], ], 'allow_redirects' => false, 'http_errors' => false, ]); $status = $response->getStatusCode(); if ($status >= 400) { throw new \RuntimeException("HTTP $status " . $response->getReasonPhrase()); } $body = (string) $response->getBody(); if (file_put_contents('logo.html', $body) !== strlen($body)) { throw new \RuntimeException('Failed to write output file'); } } catch (\Throwable $error) { fwrite(STDERR, $error->getMessage() . "\n"); exit(1); }
Open the saved HTML in a browser. If you choose separate resource files, the result is a ZIP archive; see Successful Conversion for output handling.
Use your PDFCrowd username as the HTTP Basic username and your API key as the password. Configure these credentials using your HTTP client's Basic authentication option.
Find and manage your credentials on the API Keys page.
Send the input and conversion settings as form fields in a POST request. JSON request bodies are not supported. Your HTTP client handles form encoding and the appropriate headers.
Use the versioned endpoint: https://api.pdfcrowd.com/convert/24.04/.
Keep the version explicit in your integration and review
API versioning before changing it.
Errors are returned as plain text by default; add ?errfmt=json
to the endpoint to receive JSON errors. This changes only the error format;
a successful response still contains the converted HTML or ZIP archive.
See Handle the Response for response handling.
Set input_format=pdf and output_format=html.
Supply one of the following inputs:
| Field | What to Send |
|---|---|
url |
An http:// or https:// address that returns a PDF and is reachable from PDFCrowd's servers. |
file |
The PDF's contents uploaded as a multipart file part. |
Upload the file's contents, not its filename or local path as a text field.
If the PDF is already in memory, use your HTTP client's file-upload option
to send those bytes in the file part.
PDFCrowd cannot fetch a PDF from your computer's localhost;
upload its contents instead.
Send conversion settings as additional form fields alongside your input. Common options include:
| Purpose | Options |
|---|---|
| Page selection | print_page_range |
| Embedded or separate resources |
image_mode,
css_mode,
font_mode
|
| Consistent ZIP output | force_zip |
| Page scale and image quality |
scale_factor,
dpi,
image_format
|
| Style the generated HTML | custom_css |
| Prefix generated IDs and classes | html_namespace |
| Password-protected input | pdf_password |
The parameter reference lists all settings, accepted values, defaults, and constraints. For complete conversion requests, see the HTTP examples.
A successful conversion returns 200 OK with the HTML or ZIP
in the response body. Save the returned file or use it in your application.
image_mode,
css_mode, or
font_mode
to separate returns a ZIP containing the HTML and its resource files.
force_zip=true returns a ZIP even when all resources are embedded.The Quick Start requests return HTML, for example:
HTTP/1.1 200 OK Content-Type: text/html x-pdfcrowd-job-id: example-job-id [HTML document]
Check the HTTP status before processing the body. Use Content-Type
to distinguish HTML (text/html) from ZIP output
(application/zip). Save a ZIP as binary data and extract it before
opening the HTML; keep its resource files at their relative paths.
The generated HTML represents the PDF's text, images and positioned content. It preserves a fixed page layout rather than recovering the original source document's structure.
An unsuccessful request returns an error HTTP status. PDFCrowd error responses also include a reason code that identifies the specific problem.
By default, the error body is plain text in this format:
<status_code>.<reason_code> - <message>
For structured errors, append ?errfmt=json to the endpoint:
https://api.pdfcrowd.com/convert/24.04/?errfmt=json
An example JSON error body for missing conversion input is:
{
"status_code": 400,
"reason_code": 325,
"message": "There is no input specified to be converted."
}
PDFCrowd JSON errors use Content-Type: application/json. A
successful request still returns HTML or a ZIP archive
when errfmt=json is set. If a failed response has a different
content type, retain its body and status for diagnosis instead of assuming
it is JSON.
| Status | What it means | What to do |
|---|---|---|
400 |
Invalid request or conversion failure | Read the reason code and correct the input or settings. |
401 |
Missing credentials or an inactive license | Check your username, API key, and license status. |
403 |
Suspended service or no credits remaining | Check your account and available credits. |
413 |
Upload exceeds the 300 MB limit | Reduce the upload size. |
429 |
Request rate limit reached | Wait and reduce the rate of new requests. |
430 |
Concurrent request limit reached | Allow active requests to finish before starting more. |
503 |
Temporary network issue | Retry after a delay. |
See all status and reason codes for specific error explanations. For request limits and retry guidance, see Limits and Retries.
Use these headers, when present, to record conversion results and diagnose problems:
| Header | Use |
|---|---|
x-pdfcrowd-job-id |
Identify the conversion in logs and support requests. |
x-pdfcrowd-reason-code |
Read the error reason code; 0 indicates success. |
x-pdfcrowd-debug-log |
Open the debug log when debug logging is enabled. |
x-pdfcrowd-consumed-credits |
Record credits consumed by this conversion. |
x-pdfcrowd-remaining-credits |
Monitor the remaining account balance. |
x-pdfcrowd-pages |
Read the number of rendered pages. |
x-pdfcrowd-output-size |
Read the output size in bytes. |
Request rate and concurrency limits depend on your license. Control how
quickly you submit conversions and how many you run at once. A 429
response concerns request rate; a 430 response concerns requests
already in progress.
The maximum upload size is 300 MB.
For temporary failures, use a bounded number of retries with increasing delays. Correct invalid input, authentication, or account problems before retrying those requests.
If a conversion exceeds 60 seconds of processing time, PDFCrowd stops it and returns an error response.
Capture the HTTP status, response headers, and response body when diagnosing
a request. Set debug_log to true to enable a
conversion debug log, and add errfmt=json to the endpoint's
query string for structured error details. Use the input and conversion
settings from the request you're investigating.
curl --fail-with-body \ -u 'demo:demo' \ -D response.headers \ -o response.body \ -w 'HTTP %{http_code}\n' \ -F url=https://your-server.com/invoice.pdf \ -F input_format=pdf \ -F output_format=html \ -F debug_log=true \ 'https://api.pdfcrowd.com/convert/24.04/?errfmt=json'
import requests response = requests.post( 'https://api.pdfcrowd.com/convert/24.04/?errfmt=json', auth=('demo', 'demo'), data={ 'input_format': 'pdf', 'output_format': 'html', 'debug_log': 'true', 'url': 'https://your-server.com/invoice.pdf', }, allow_redirects=False, ) with open('response.headers', 'w', encoding='utf-8') as headers_file: for name, value in response.headers.items(): headers_file.write(f'{name}: {value}\n') with open('response.body', 'wb') as output_file: output_file.write(response.content) print(f'HTTP {response.status_code}') response.raise_for_status()
import { writeFile } from 'node:fs/promises'; const username = 'demo'; const apiKey = 'demo'; const form = new FormData(); form.append("input_format", "pdf"); form.append("output_format", "html"); form.append("debug_log", "true"); form.append("url", "https://your-server.com/invoice.pdf"); const response = await fetch("https://api.pdfcrowd.com/convert/24.04/?errfmt=json", { method: 'POST', headers: { Authorization: 'Basic ' + Buffer.from( username + ':' + apiKey).toString('base64'), }, body: form, redirect: 'manual', }); const body = Buffer.from(await response.arrayBuffer()); await writeFile("response.headers", Array.from(response.headers, ([name, value]) => name + ': ' + value ).join('\n') + '\n'); await writeFile("response.body", body); console.log('HTTP ' + response.status); if (response.status >= 400) { throw new Error('HTTP ' + response.status + ' ' + response.statusText); }
import axios from 'axios'; import { writeFile } from 'node:fs/promises'; const username = 'demo'; const apiKey = 'demo'; const form = new FormData(); form.append("input_format", "pdf"); form.append("output_format", "html"); form.append("debug_log", "true"); form.append("url", "https://your-server.com/invoice.pdf"); try { const response = await axios.post("https://api.pdfcrowd.com/convert/24.04/?errfmt=json", form, { auth: { username, password: apiKey }, responseType: 'arraybuffer', maxRedirects: 0, validateStatus: () => true, }); await writeFile("response.headers", response.headers.toString() + '\n'); await writeFile("response.body", response.data); console.log('HTTP ' + response.status); if (response.status >= 400) { throw new Error('HTTP ' + response.status + ' ' + response.statusText); } } catch (error) { console.error(error.message); process.exitCode = 1; }
<?php require 'vendor/autoload.php'; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Utils; try { $client = new Client(); $response = $client->post('https://api.pdfcrowd.com/convert/24.04/?errfmt=json', [ 'auth' => ['demo', 'demo'], 'multipart' => [ ['name' => 'input_format', 'contents' => 'pdf'], ['name' => 'output_format', 'contents' => 'html'], ['name' => 'debug_log', 'contents' => 'true'], ['name' => 'url', 'contents' => 'https://your-server.com/invoice.pdf'], ], 'allow_redirects' => false, 'http_errors' => false, ]); $status = $response->getStatusCode(); $headers = ''; foreach ($response->getHeaders() as $name => $values) { $headers .= $name . ": " . implode(", ", $values) . "\n"; } if (file_put_contents('response.headers', $headers) !== strlen($headers)) { throw new \RuntimeException('Failed to write response headers'); } $body = (string) $response->getBody(); if (file_put_contents('response.body', $body) !== strlen($body)) { throw new \RuntimeException('Failed to write output file'); } echo "HTTP $status\n"; if ($status >= 400) { throw new \RuntimeException("HTTP $status " . $response->getReasonPhrase()); } } catch (\Throwable $error) { fwrite(STDERR, $error->getMessage() . "\n"); exit(1); }
Check the HTTP status before interpreting the body. A successful response contains HTML or ZIP bytes; an unsuccessful response should be inspected for error details. If no HTTP response arrives, check the error reported by your HTTP client.
The x-pdfcrowd-debug-log header links to diagnostic information
about the conversion. You can also find logs in your
conversion history.
| Problem | Check |
|---|---|
| No input or no request data | Send form fields rather than JSON. Supply url or upload the PDF's contents in a multipart file part named file. |
| The PDF cannot be loaded | Check that the URL returns the PDF rather than a login page and is reachable from PDFCrowd's servers. For a local file, check that your client uploads its contents. |
| The PDF cannot be opened |
Check that the input is a valid PDF. If it is password-protected, supply
pdf_password.
This is separate from your PDFCrowd API key.
|
| A ZIP is returned instead of an HTML file |
Check image_mode,
css_mode and
font_mode.
Use embed for all three and omit
force_zip=true
to receive a self-contained HTML file.
|
| Images, styles or fonts are missing |
When using separate resources, extract the entire ZIP and keep the files at
their relative paths. Check that the browser can load them. Also check
image_mode;
none omits images.
|
| The HTML has the wrong scale or blurry images |
Adjust scale_factor
for page scale and dpi
for image resolution.
|
| Generated IDs or classes conflict with the surrounding page |
Use html_namespace
to prefix the generated IDs and classes. Use
custom_css
to adjust the output's styling.
|
| The HTML does not reflow like an ordinary web page | The output preserves the PDF's fixed page layout. It does not reconstruct the source document's semantic structure or responsive layout. |
| The HTTP client times out before receiving the response | Check its timeout settings and allow enough time for conversion, uploading the input, and downloading the result. |
For help, contact support and include any available diagnostics and enough detail for us to reproduce the problem.