HTML to PDF REST API. The one you forget about.
Two parameters. One endpoint. A perfect PDF. SelectPdf converts any URL
or raw HTML to PDF over REST — full HTML5/CSS3/JavaScript, custom headers, automatic
bookmarks, no installation. From C# or any .NET app, install the official client with
dotnet add package SelectPdf.Api, or POST to the endpoint with HttpClient; the clients for
Java, PHP, Python, Node.js, Ruby and Perl work the same way.
Already on the older endpoint? See the v1 legacy reference →
One request, one PDF.
Send your API key and the HTML (or a url) to the endpoint; the response body is the PDF. The request needs an API key — get a free one, 200 conversions with no credit card.
curl https://selectpdf.com/api2/convert/ \ -d "key=YOUR_KEY" \ --data-urlencode "html=<h1>Hello World</h1>" \ -o document.pdf
Get a free API key · API pricing · Parameter reference · The full REST request
HTML to PDF API in seven languages.
Open-source client libraries on GitHub. Same API surface across runtimes — learn once, deploy anywhere. Version 1.6.0 adds tagged PDF and PDF standards, ZUGFeRD / Factur-X electronic invoices and a keyless demo mode.
using System;
using SelectPdf.Api;
HtmlToPdfClient client = new HtmlToPdfClient(apiKey);
client.setPageSize(PageSize.A4); // A0–A8, Letter, Legal
client.setPageOrientation(PageOrientation.Portrait);
client.setMargins(0);
client.setRenderingEngine(RenderingEngine.WebKit); // or Chromium
client.setShowPageNumbers(false);
client.setPdfBookmarksSelectors("H1, H2"); // outline from H1/H2
// --- Convert a live URL to a local PDF ---
client.convertUrlToFile(url, localFile);
Console.WriteLine("Pages: " + client.getNumberOfPages());
// --- Convert a raw HTML string to bytes (in-memory) ---
byte[] pdf = client.convertHtmlString("<h1>Hi</h1>");
Console.WriteLine("Credits left: " + client.CreditsRemaining);
package com.selectpdf;
HtmlToPdfClient client = new HtmlToPdfClient(apiKey);
client.setPageSize(ApiEnums.PageSize.A4);
client.setPageOrientation(ApiEnums.PageOrientation.Portrait);
client.setMargins(0);
client.setRenderingEngine(ApiEnums.RenderingEngine.WebKit);
client.setShowPageNumbers(false);
client.setPdfBookmarksSelectors("H1, H2"); // outline from H1/H2
// --- Convert a live URL to a local PDF ---
client.convertUrlToFile(url, localFile);
System.out.printf("Pages: %d%n", client.getNumberOfPages());
// --- Convert a raw HTML string to bytes (in-memory) ---
byte[] pdf = client.convertHtmlString("<h1>Hi</h1>");
UsageClient u = new UsageClient(apiKey);
System.out.println("Usage: " + u.getUsage(false));
require("SelectPdf.Api.php");
$client = new SelectPdf\Api\HtmlToPdfClient($apiKey);
$client->setPageSize(SelectPdf\Api\PageSize::A4);
$client->setPageOrientation(SelectPdf\Api\PageOrientation::Portrait);
$client->setMargins(0);
$client->setRenderingEngine(SelectPdf\Api\RenderingEngine::WebKit);
$client->setShowPageNumbers(false);
$client->setPdfBookmarksSelectors("H1, H2"); // outline from H1/H2
// --- Convert a live URL to a local PDF ---
$client->convertUrlToFile($url, $localFile);
echo "Pages: " . $client->getNumberOfPages() . "\n";
// --- Convert a raw HTML string to bytes ---
$pdf = $client->convertHtmlString("<h1>Hi</h1>");
$u = new \SelectPdf\Api\UsageClient($apiKey);
echo "Credits: " . $u->getUsage(false)["available"] . "\n";
import selectpdf
client = selectpdf.HtmlToPdfClient(apiKey)
client.setPageSize(selectpdf.PageSize.A4)
client.setPageOrientation(selectpdf.PageOrientation.Portrait)
client.setMargins(0)
client.setRenderingEngine(selectpdf.RenderingEngine.WebKit)
client.setShowPageNumbers(False)
client.setPdfBookmarksSelectors("H1, H2") # outline from H1/H2
# --- Convert a live URL to a local PDF ---
client.convertUrlToFile(url, localFile)
print("Pages:", client.getNumberOfPages())
# --- Convert a raw HTML string to bytes (in-memory) ---
pdf = client.convertHtmlString("<h1>Hi</h1>")
usage = selectpdf.UsageClient(apiKey).getUsage()
print("Credits:", usage["available"])
var selectpdf = require('selectpdf');
var client = new selectpdf.HtmlToPdfClient(apiKey);
client.setPageSize('A4');
client.setPageOrientation('Portrait');
client.setMargins(0);
client.setRenderingEngine('WebKit'); // or 'Chromium'
client.setShowPageNumbers(false);
client.setPdfBookmarksSelectors('H1, H2'); // outline from H1/H2
// --- Convert a live URL to a local PDF ---
client.convertUrlToFile(url, localFile, function(err, file) {
if (err) return console.error('Convert error:', err);
console.log('Pages:', client.getNumberOfPages());
var usage = new selectpdf.UsageClient(apiKey);
usage.getUsage(false, function(e, d) {
if (!e) console.log('Credits:', d['available']);
});
});
require 'selectpdf'
client = SelectPdf::HtmlToPdfClient.new(api_key)
client.page_size = SelectPdf::PageSize::A4
client.page_orientation = SelectPdf::PageOrientation::PORTRAIT
client.margins = 0
client.rendering_engine = SelectPdf::RenderingEngine::WEBKIT
client.page_numbers = false
client.pdf_bookmarks_selectors = 'H1, H2' # outline from H1/H2
# --- Convert a live URL to a local PDF ---
client.convert_url_to_file(url, local_file)
print "Pages: #{client.number_of_pages}\n"
# --- Convert a raw HTML string to bytes (in-memory) ---
pdf = client.convert_html_string('<h1>Hi</h1>')
usage = SelectPdf::UsageClient.new(api_key).get_usage(false)
print "Credits: #{usage['available']}\n"
use SelectPdf;
use JSON;
my $client = SelectPdf::HtmlToPdfClient->new($apiKey);
$client->setPageSize("A4");
$client->setPageOrientation("Portrait");
$client->setMargins(0);
$client->setRenderingEngine("WebKit"); # or "Chromium"
$client->setShowPageNumbers("False");
$client->setPdfBookmarksSelectors("H1, H2"); # outline
# --- Convert a live URL to a local PDF ---
$client->convertUrlToFile($url, $local_file);
print "Pages: " . $client->getNumberOfPages() . "\n";
# --- Convert a raw HTML string to bytes (in-memory) ---
my $pdf = $client->convertHtmlString("<h1>Hi</h1>");
my $u = SelectPdf::UsageClient->new($apiKey);
print "Credits: " . $u->getUsage(0)->{"available"} . "\n";
Render any web page. Pixel-perfect.
Full HTML5, CSS3 & JavaScript
Same engine as the .NET PDF library — modern web, modern PDFs. WebKit, Blink and Chromium rendering engines available.
Every page-setup knob you need
Page sizes from A0 to Legal, custom dimensions, portrait/landscape, individual margins, custom headers and footers.
PDF metadata & viewer behavior
Title, author, keywords, passwords, viewer page mode, hide toolbar, fit window. The output behaves the way you set it.
Hide elements, scope to a section
CSS selectors to hide unwanted elements, or convert only one element. Render the receipt, not the wrapper page.
Automatic bookmarks from selectors
Generate PDF bookmarks from H1/H2/CSS classes. Long documents get a real navigation tree, generated for free.
REST means any language, anywhere
GET or POST. JSON or form-encoded. .NET, Java, PHP, Python, Ruby, Node.js, Perl — or hand-roll it with cURL.
Convert HTML to PDF — one endpoint, three required things.
Send GET or POST. Pass parameters via querystring (GET) or request body (POST). The body can be application/x-www-form-urlencoded or application/json — your choice.
https://selectpdf.com/api2/convert/?key=YOUR_KEY&url=https://example.com
https://selectpdf.com/api2/convert/
{
"key": "YOUR_LICENSE_KEY",
"url": "https://example.com"
}
using System.Net.Http.Json;
using var http = new HttpClient();
var response = await http.PostAsJsonAsync(
"https://selectpdf.com/api2/convert/",
new { key = "YOUR_API_KEY", url = "https://example.com" });
response.EnsureSuccessStatusCode();
byte[] pdf = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("example.pdf", pdf);
url
URL · OR · HTML
The web page to convert. URL-encode the value.
html
URL · OR · HTML
Or pass raw HTML instead. Pair with base_url to resolve relative paths.
url, html and base_url.
Both HTTP and HTTPS work. If a parameter is missing, the documented default is used.
See the full reference on the 98-parameter Parameters page.
Six HTML to PDF API endpoints. One key.
All endpoints under https://selectpdf.com/api2/. The same license key authenticates every call.
/api2/convert/
Convert URL or HTML to PDF. The main endpoint — synchronous unless async=True. Both verbs accepted: GET parameters as querystring, POST as JSON or form-urlencoded. More details →
/api2/asyncjob/
Poll an async conversion. Returns the PDF once ready, or HTTP 202 with progress while still rendering. More details →
/api2/webelements/
Retrieve PDF coordinates of matched HTML elements, in tandem with pdf_web_elements_selectors. More details →
/api2/invoice/
Convert an HTML invoice into a ZUGFeRD / Factur-X hybrid electronic invoice — one PDF/A-3 file with the invoice XML embedded. POST multipart only, because the XML is uploaded as a file. More details →
/api2/usage/
Read your subscription: current plan, monthly limit, used credits and remaining credits. More details →
/api2/convert/demo/
No-key watermarked demo — same engine, with conversion limits and demo-only safeguards. More details →
The same key reaches two more REST APIs on the same account and the same monthly
allowance: the PDF Merge API
(/api2/pdfmerge/) combines uploaded files and URLs into a single PDF, and the
PDF to Text API
(/api2/pdftotext/) extracts text from a PDF or searches it. Both meter by
page count — every 50 pages is one conversion.
For long jobs that outlast your HTTP connection.
Recommended for long pages, multi-megabyte HTML inputs, or callers that cannot keep an HTTP connection open for the full conversion. Submit once, poll the result when it is ready.
- POST
/api2/convert/withasync=True(and the rest of your parameters). - The server replies
202 Acceptedand returns the job ID in theX-SelectPdf-Job-Idresponse header. - Poll
GET /api2/asyncjob/?key=YOUR_KEY&job_id=…at a reasonable cadence. - Each poll:
202= still processing ·200= PDF returned in the response body ·499= conversion failed (plain-text reason in the body).
https://selectpdf.com/api2/asyncjob/?key=YOUR_KEY&job_id=JOB_ID
PDF coordinates for HTML elements you select.
When you pass pdf_web_elements_selectors, the API records the final PDF position of every HTML element matching your CSS selectors. Use it to overlay annotations, links or interactive layers on the produced PDF.
- Synchronous calls — the data is returned in the response: base64-encoded JSON in the
X-SelectPdf-Web-Elementsheader. Large payloads are split across numberedX-SelectPdf-Web-Elements-Header-Nheaders, withX-SelectPdf-Web-Elements-Headers-Countgiving the total chunks. - Asynchronous calls — once the PDF is ready, fetch the data from
GET /api2/webelements/?key=YOUR_KEY&job_id=…instead of inspecting headers.
https://selectpdf.com/api2/webelements/?key=YOUR_KEY&job_id=JOB_ID
The result is a JSON array — one entry per matched element — with the element's HTML id, tag name, CSS class, and PDF rectangle coordinates. See pdf_web_elements_selectors in the parameters reference.
Accessible and archivable PDF.
Set tagged=true and the API emits a tagged PDF: a logical structure tree covering headings, paragraphs, lists, tables, figures with alternate text, links and reading order — what a screen reader needs to make sense of the document, and what accessibility procurement asks for by name.
pdf_standard targets a conformance level: PDF/A for long-term archiving, PDF/X for graphics exchange, PDF/SiqQ for documents that will be digitally signed. PDF/A-3a is the accessible archival level, and produces a tagged document on its own.
Tagged output needs the Blink or Chromium engine. If you don’t specify one, the API picks Chromium for you and tells you so in the X-SelectPdf-Engine response header.
curl https://selectpdf.com/api2/convert/ \ -d "key=YOUR_KEY" \ -d "url=https://example.com" \ -d "tagged=true" \ -d "pdf_standard=pdfa3a" \ -d "doc_language=en-US" \ -o accessible.pdf
See the accessibility & standards parameters for the full list of conformance targets.
Electronic invoicing, in one call.
POST /api2/invoice/ turns an HTML invoice into a ZUGFeRD / Factur-X hybrid electronic invoice. You get one PDF/A-3 file: the human-readable invoice your customer opens, with the machine-readable XML embedded inside it for their accounting system.
Six profiles are supported — MINIMUM, BASIC WL, BASIC, EN 16931, EXTENDED and XRECHNUNG, the German public-sector profile. The API picks the correct attachment relationship for the profile you choose and names the embedded file the way the standard requires, so you don’t have to read the specification to get it right.
The invoice is produced as PDF/A-3a by default — the accessible level, so the visible invoice is readable by assistive technology as well as archivable.
curl https://selectpdf.com/api2/invoice/ \ -F "key=YOUR_KEY" \ -F "url=https://your-app.example/invoices/INV-2026-001" \ -F "zugferd_profile=en16931" \ -F "zugferd_xml=@factur-x.xml" \ -o invoice.pdf
Every conversion parameter applies here too — page size, margins, headers, footers, engine. The invoice endpoint adds zugferd_xml, zugferd_profile, zugferd_relationship and zugferd_schema.
Try the HTML to PDF API — no key required.
Same rendering engine as the paid endpoint, with conversion limits and a watermark. No signup. For one-off conversions in the browser, use the free online HTML to PDF converter, which has no watermark.
HTTP status, plain English.
Standard HTTP semantics. The body of every non-200 contains a plain-text explanation — no error shapes to parse, no enum to memorize.
Content-Type: application/pdf.X-SelectPdf-Job-Id header; poll /api2/asyncjob/ until the PDF is ready.url or html string not specified, parameter validation failure, or — on the demo endpoint — an SSRF-flagged URL. The response body explains in plain text.Retry-After header giving the back-off in seconds.Retry-After header gives a suggested back-off.v2).pdf_web_elements_selectors.tagged or pdf_standard=pdfa3a request that did not name an engine is promoted off the default to Chromium. More details →production or demo.worker (every conversion is dispatched to a worker process; the legacy in-process branch was retired).-1 means unlimited.Read your subscription. Programmatically.
Two ways to know where your quota stands. Every successful conversion already returns X-SelectPdf-Credits-Total and X-SelectPdf-Credits-Remaining response headers — read those for a zero-extra-cost live view of remaining credits. Use the dedicated /api2/usage/ endpoint below when you also need the subscription tier or a month-by-month history.
https://selectpdf.com/api2/usage/?key=YOUR_KEY&get_history=True
https://selectpdf.com/api2/usage/
{
"key": "YOUR_LICENSE_KEY",
"get_history": "True"
}
{
"status": "License key active.",
"subscription_type": "Entry Level",
"limit": 2000,
"used": 340,
"available": 1660,
"history": [
{ "year": 2026, "month": 4,
"conversions": 340, "credits": 340 },
{ "year": 2026, "month": 3,
"conversions": 1876, "credits": 1923 }
]
}
Concurrency scales with your plan.
Each plan allows a fixed number of simultaneous requests. Excess requests are queued or rejected with a 429 Too Many Requests. One conversion credit covers up to 50 PDF pages — a 1-page and a 49-page PDF cost the same.
Each 50 pages of generated output counts as one conversion credit. A 12-page PDF, a 1-page PDF and a 49-page PDF all cost the same. See API pricing for plan details.
HTML to PDF API, answered.
Nine quick answers about the REST API. For the full surface, see the 98-parameter reference.
https://selectpdf.com/api2/convert/ and returns the PDF in the response body. From C#, use the official open-source .NET client (dotnet add package SelectPdf.Api, MIT license) or call the endpoint with HttpClient. A free 7-day trial includes 200 conversions, with no credit card.min_load_time to wait a known minimum before snapshotting the page, and max_load_time as the upper bound — up to 120 seconds. For pages that signal completion from JavaScript, set startup_mode to Manual and call SelectPdf.startConversion() from within the page.engine parameter — WebKit (the default), WebKit Restricted, Blink and Chromium. Chromium is the modern CEF-based engine recommended for new integrations and is the engine the free demo endpoint uses internally. Set engine to the value you need; if you do not pass the parameter, the conversion runs on WebKit.429 Too Many Requests (wait a moment and retry; on the demo endpoint the Retry-After header tells you how long), and 503 Service Unavailable on the demo endpoint when the global concurrency cap is reached. Permanent: 400 Bad Request (the parameters are wrong) and 401 Authorization Required (the license key is missing or invalid). 499 means the conversion itself failed — the body explains why.async=True returns 202 Accepted and a job ID in the X-SelectPdf-Job-Id header; poll GET /api2/asyncjob/?key=…&job_id=… until the PDF is returned. Useful when a single conversion can exceed your client's HTTP timeout.