Quickstart
Three steps: set your key, create a PDF, take a screenshot. Every example on this page is run against the live API before it is published.
1. Set your API key
The examples read the key from the SAHIFA_API_KEY environment variable, so it never ends up in your source code.
# macOS, Linux
export SAHIFA_API_KEY="sk_live_..."
# Windows PowerShell
$env:SAHIFA_API_KEY = "sk_live_..."
Don't have a key yet? See getting an API key.
2. Create a PDF from HTML
This request sends a short Arabic invoice and adds a page footer. It saves invoice.pdf.
# HTML to PDF with an Arabic footer. Requires: SAHIFA_API_KEY in the environment.
curl https://api.sahifa.dev/v3/convert/pdf \
--user "api:$SAHIFA_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"source": "<html dir=\"rtl\" lang=\"ar\"><body><h1>فاتورة ضريبية</h1><p>الإجمالي: 1,150.00 ر.س</p></body></html>",
"format": "A4",
"margin": "20mm",
"footer": { "source": "<div style=\"width:100%;text-align:center\">صفحة {{page}} من {{total}}</div>" }
}' \
--fail-with-body --output invoice.pdf// HTML to PDF with an Arabic footer. Node.js 18+ (built-in fetch).
// Run: SAHIFA_API_KEY=... node pdf.mjs
import { writeFile } from 'node:fs/promises';
const html = `<html dir="rtl" lang="ar"><body>
<h1>فاتورة ضريبية</h1>
<p>الإجمالي: 1,150.00 ر.س</p>
</body></html>`;
const res = await fetch('https://api.sahifa.dev/v3/convert/pdf', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`api:${process.env.SAHIFA_API_KEY}`).toString('base64'),
'Content-Type': 'application/json',
},
body: JSON.stringify({
source: html,
format: 'A4',
margin: '20mm',
footer: { source: '<div style="width:100%;text-align:center">صفحة {{page}} من {{total}}</div>' },
}),
});
if (!res.ok) throw new Error(`Sahifa error ${res.status}: ${(await res.json()).error}`);
await writeFile('invoice.pdf', Buffer.from(await res.arrayBuffer()));
console.log(`invoice.pdf created in ${res.headers.get('x-response-duration')} ms`);# HTML to PDF with an Arabic footer. Python 3.8+, requires: pip install requests
# Run: SAHIFA_API_KEY=... python pdf.py
import os
import requests
html = """<html dir="rtl" lang="ar"><body>
<h1>فاتورة ضريبية</h1>
<p>الإجمالي: 1,150.00 ر.س</p>
</body></html>"""
res = requests.post(
"https://api.sahifa.dev/v3/convert/pdf",
auth=("api", os.environ["SAHIFA_API_KEY"]),
json={
"source": html,
"format": "A4",
"margin": "20mm",
"footer": {"source": '<div style="width:100%;text-align:center">صفحة {{page}} من {{total}}</div>'},
},
timeout=60,
)
if not res.ok:
raise RuntimeError(f"Sahifa error {res.status_code}: {res.json()['error']}")
with open("invoice.pdf", "wb") as f:
f.write(res.content)
print(f"invoice.pdf created in {res.headers['X-Response-Duration']} ms")// HTML to PDF with an Arabic footer. .NET 8+.
// .NET 10: SAHIFA_API_KEY=... dotnet run pdf.cs (or paste into Program.cs of a console app)
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
var key = Environment.GetEnvironmentVariable("SAHIFA_API_KEY");
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"api:{key}")));
var html = """
<html dir="rtl" lang="ar"><body>
<h1>فاتورة ضريبية</h1>
<p>الإجمالي: 1,150.00 ر.س</p>
</body></html>
""";
var body = new JsonObject
{
["source"] = html,
["format"] = "A4",
["margin"] = "20mm",
["footer"] = new JsonObject { ["source"] = "<div style=\"width:100%;text-align:center\">صفحة {{page}} من {{total}}</div>" },
};
var res = await http.PostAsync("https://api.sahifa.dev/v3/convert/pdf",
new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
throw new Exception($"Sahifa error {(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("invoice.pdf", await res.Content.ReadAsByteArrayAsync());
Console.WriteLine("invoice.pdf saved");// HTML to PDF with an Arabic footer. Java 18+ (UTF-8 source by default).
// Run: SAHIFA_API_KEY=... java Pdf.java
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.util.Base64;
public class Pdf {
public static void main(String[] args) throws Exception {
String key = System.getenv("SAHIFA_API_KEY");
String auth = Base64.getEncoder().encodeToString(("api:" + key).getBytes(StandardCharsets.UTF_8));
// JSON written by hand to keep the example dependency-free; use Jackson or Gson in real code.
String body = """
{
"source": "<html dir='rtl' lang='ar'><body><h1>فاتورة ضريبية</h1><p>الإجمالي: 1,150.00 ر.س</p></body></html>",
"format": "A4",
"margin": "20mm",
"footer": { "source": "<div style='width:100%;text-align:center'>صفحة {{page}} من {{total}}</div>" }
}
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.sahifa.dev/v3/convert/pdf"))
.header("Authorization", "Basic " + auth)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpResponse<byte[]> res = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofByteArray());
if (res.statusCode() != 200) {
throw new RuntimeException("Sahifa error " + res.statusCode() + ": " + new String(res.body(), StandardCharsets.UTF_8));
}
Files.write(Path.of("invoice.pdf"), res.body());
System.out.println("invoice.pdf saved");
}
}The footer uses the {{page}} and {{total}} variables. A header or footer needs a margin large enough to hold it. See the PDF reference for every option.
3. Take a screenshot
This request captures a whole web page as PNG and hides cookie consent pop-ups. It saves page.png.
# Full-page screenshot of a URL, without cookie banners. Requires: SAHIFA_API_KEY.
curl --get https://api.sahifa.dev/take \
--data-urlencode "access_key=$SAHIFA_API_KEY" \
--data-urlencode "url=https://example.com" \
--data "format=png" \
--data "full_page=true" \
--data "block_cookie_banners=true" \
--fail-with-body --output page.png// Full-page screenshot of a URL, without cookie banners. Node.js 18+.
// Run: SAHIFA_API_KEY=... node screenshot.mjs
import { writeFile } from 'node:fs/promises';
const params = new URLSearchParams({
access_key: process.env.SAHIFA_API_KEY,
url: 'https://example.com',
format: 'png',
full_page: 'true',
block_cookie_banners: 'true',
});
const res = await fetch(`https://api.sahifa.dev/take?${params}`);
if (!res.ok) throw new Error(`Sahifa error ${res.status}: ${(await res.json()).error}`);
await writeFile('page.png', Buffer.from(await res.arrayBuffer()));
console.log('page.png saved');# Full-page screenshot of a URL, without cookie banners. Requires: pip install requests
# Run: SAHIFA_API_KEY=... python screenshot.py
import os
import requests
res = requests.get(
"https://api.sahifa.dev/take",
params={
"access_key": os.environ["SAHIFA_API_KEY"],
"url": "https://example.com",
"format": "png",
"full_page": "true",
"block_cookie_banners": "true",
},
timeout=60,
)
if not res.ok:
raise RuntimeError(f"Sahifa error {res.status_code}: {res.json()['error']}")
with open("page.png", "wb") as f:
f.write(res.content)
print("page.png saved")// Full-page screenshot of a URL, without cookie banners. .NET 8+.
// .NET 10: SAHIFA_API_KEY=... dotnet run screenshot.cs
var key = Environment.GetEnvironmentVariable("SAHIFA_API_KEY");
var query = string.Join("&", new Dictionary<string, string>
{
["access_key"] = key!,
["url"] = "https://example.com",
["format"] = "png",
["full_page"] = "true",
["block_cookie_banners"] = "true",
}.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
var res = await http.GetAsync($"https://api.sahifa.dev/take?{query}");
if (!res.IsSuccessStatusCode)
throw new Exception($"Sahifa error {(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("page.png", await res.Content.ReadAsByteArrayAsync());
Console.WriteLine("page.png saved");// Full-page screenshot of a URL, without cookie banners. Java 11+.
// Run: SAHIFA_API_KEY=... java Screenshot.java
import java.net.URI;
import java.net.URLEncoder;
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.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class Screenshot {
public static void main(String[] args) throws Exception {
Map<String, String> params = new LinkedHashMap<>();
params.put("access_key", System.getenv("SAHIFA_API_KEY"));
params.put("url", "https://example.com");
params.put("format", "png");
params.put("full_page", "true");
params.put("block_cookie_banners", "true");
String query = params.entrySet().stream()
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.sahifa.dev/take?" + query)).GET().build();
HttpResponse<byte[]> res = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofByteArray());
if (res.statusCode() != 200) {
throw new RuntimeException("Sahifa error " + res.statusCode() + ": " + new String(res.body(), StandardCharsets.UTF_8));
}
Files.write(Path.of("page.png"), res.body());
System.out.println("page.png saved");
}
}See the screenshot reference for viewports, element capture, dark mode and blocking options.
4. Handle busy moments
When the render queue is full the API answers 429. Retrying after a short wait is safe, because a failed request has no side effects.
# curl retries 408, 429, 500, 502, 503 and 504 responses on its own, honouring Retry-After.
curl https://api.sahifa.dev/v3/convert/pdf \
--user "api:$SAHIFA_API_KEY" \
--header "Content-Type: application/json" \
--data '{ "source": "https://example.com" }' \
--retry 4 --retry-delay 2 --retry-max-time 60 \
--fail-with-body --output example.pdf// Retry on 429 (queue full) and 503 (temporarily unavailable), honouring Retry-After.
// Node.js 18+. Run: SAHIFA_API_KEY=... node retry.mjs
import { writeFile } from 'node:fs/promises';
async function render(body, attempts = 4) {
for (let attempt = 1; ; attempt++) {
const res = await fetch('https://api.sahifa.dev/v3/convert/pdf', {
method: 'POST',
headers: { 'X-API-Key': process.env.SAHIFA_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (res.ok) return Buffer.from(await res.arrayBuffer());
const retryable = res.status === 429 || res.status === 503;
if (!retryable || attempt === attempts) throw new Error(`Sahifa error ${res.status}: ${(await res.json()).error}`);
const wait = Number(res.headers.get('retry-after')) || 2 ** attempt;
await new Promise((r) => setTimeout(r, wait * 1000));
}
}
await writeFile('example.pdf', await render({ source: 'https://example.com' }));
console.log('example.pdf saved');# Retry on 429 (queue full) and 503 (temporarily unavailable), honouring Retry-After.
# Requires: pip install requests. Run: SAHIFA_API_KEY=... python retry.py
import os
import time
import requests
def render(body, attempts=4):
for attempt in range(1, attempts + 1):
res = requests.post(
"https://api.sahifa.dev/v3/convert/pdf",
headers={"X-API-Key": os.environ["SAHIFA_API_KEY"]},
json=body,
timeout=60,
)
if res.ok:
return res.content
if res.status_code not in (429, 503) or attempt == attempts:
raise RuntimeError(f"Sahifa error {res.status_code}: {res.json()['error']}")
time.sleep(int(res.headers.get("Retry-After", 2 ** attempt)))
with open("example.pdf", "wb") as f:
f.write(render({"source": "https://example.com"}))
print("example.pdf saved")// Retry on 429 (queue full) and 503 (temporarily unavailable), honouring Retry-After. .NET 8+.
// .NET 10: SAHIFA_API_KEY=... dotnet run retry.cs
using System.Net;
using System.Text;
using System.Text.Json.Nodes;
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
http.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("SAHIFA_API_KEY"));
async Task<byte[]> Render(JsonObject body, int attempts = 4)
{
for (var attempt = 1; ; attempt++)
{
var res = await http.PostAsync("https://api.sahifa.dev/v3/convert/pdf",
new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"));
if (res.IsSuccessStatusCode) return await res.Content.ReadAsByteArrayAsync();
var retryable = res.StatusCode is HttpStatusCode.TooManyRequests or HttpStatusCode.ServiceUnavailable;
if (!retryable || attempt == attempts)
throw new Exception($"Sahifa error {(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
var wait = res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt));
await Task.Delay(wait);
}
}
await File.WriteAllBytesAsync("example.pdf", await Render(new JsonObject { ["source"] = "https://example.com" }));
Console.WriteLine("example.pdf saved");// Retry on 429 (queue full) and 503 (temporarily unavailable), honouring Retry-After. Java 11+.
// Run: SAHIFA_API_KEY=... java Retry.java
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;
public class Retry {
static final HttpClient HTTP = HttpClient.newHttpClient();
static byte[] render(String json, int attempts) throws Exception {
for (int attempt = 1; ; attempt++) {
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.sahifa.dev/v3/convert/pdf"))
.header("X-API-Key", System.getenv("SAHIFA_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8))
.build();
HttpResponse<byte[]> res = HTTP.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (res.statusCode() == 200) return res.body();
boolean retryable = res.statusCode() == 429 || res.statusCode() == 503;
if (!retryable || attempt == attempts) {
throw new RuntimeException("Sahifa error " + res.statusCode() + ": " + new String(res.body(), StandardCharsets.UTF_8));
}
long wait = res.headers().firstValue("Retry-After").map(Long::parseLong).orElse((long) Math.pow(2, attempt));
Thread.sleep(wait * 1000);
}
}
public static void main(String[] args) throws Exception {
Files.write(Path.of("example.pdf"), render("{\"source\": \"https://example.com\"}", 4));
System.out.println("example.pdf saved");
}
}What next
- Arabic and RTL documents: fonts, digits and tables that repeat their header on every page.
- Tips and pitfalls: pages built with JavaScript, lazy images, page breaks.
- Errors: what each status code means and how to fix it.