Code examples
Complete programs in five languages. Each one reads the key from SAHIFA_API_KEY, calls the live API and saves the result. They use only the standard library, except requests for Python. All of them are run against the API before every change to this page.
Requirements
| Language | Version | Run with |
|---|---|---|
| curl | 7.76+ (for --fail-with-body) | bash pdf.sh |
| JavaScript | Node.js 18+ (built-in fetch) | node pdf.mjs |
| Python | 3.8+ and pip install requests | python pdf.py |
| .NET | .NET 8+ | dotnet run pdf.cs on .NET 10, or paste into Program.cs of a console app |
| Java | Java 18+ for the PDF example (UTF-8 source), 11+ for the others | java Pdf.java |
HTML to PDF
An Arabic invoice with a page footer, saved as 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");
}
}Screenshot of a web page
The whole page as PNG, with cookie consent pop-ups hidden, saved as 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");
}
}Retrying
Retries on 429 and 503 with an increasing wait, and honours Retry-After. Other errors are raised immediately.
# 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");
}
}Browsers and front-end code
Do not call the API from a browser: the key would be visible to every visitor. Call it from your server and send the file to the browser. In Express, for example:
app.get('/invoices/:id.pdf', async (req, res) => {
const html = await renderInvoiceHtml(req.params.id); // your template
const pdf = 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({ source: html, format: 'A4', margin: '15mm' }),
});
if (!pdf.ok) return res.status(502).send('PDF could not be created');
res.type('application/pdf').send(Buffer.from(await pdf.arrayBuffer()));
});
Using an existing PDFShift or ScreenshotOne library
If your code already uses a client library for either service, point its base URL to https://api.sahifa.dev and use your Sahifa key. See migrating.