Examples

Complete code examples

These are complete programs you can copy and run, and each one was tested against the real API. They read your details from environment variables, so your secrets stay out of your code.

Setup

LanguageYou need
PHPPHP 8 with the curl extension and allow_url_fopen
PythonPython 3 and pip install requests
Node.jsNode.js 20 or newer. No packages. Save files as .mjs.
Environment variableUsed for
RASTERNIVO_API_KEYYour API key
RASTERNIVO_EMAIL, RASTERNIVO_PASSWORDCreating an account or an API key
RASTERNIVO_TOKENThe token from the verification email
RASTERNIVO_WEBHOOK_SECRETYour whsec_… webhook secret

Convert a PDF to JPG

Uploads report.pdf, waits for the job, and saves every page.

<?php
$api = getenv('RASTERNIVO_API_URL') ?: 'https://rasternivo-api.swiftinfocorp.com/v1';
$key = getenv('RASTERNIVO_API_KEY');

function get_json(string $url, string $key): array
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ["Authorization: Bearer $key"],
    ]);
    return json_decode(curl_exec($ch), true);
}

// 1. Upload. The options field must come before the file.
$ch = curl_init("$api/conversions");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer $key",
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
    CURLOPT_POSTFIELDS => [
        'options' => json_encode(['pages' => 'all', 'dpi' => 150, 'quality' => 90, 'zip' => true]),
        'file' => new CURLFile('report.pdf', 'application/pdf'),
    ],
]);
$job = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 202) {
    exit('Upload failed: ' . $job['error']['message'] . "\n");
}

// 2. Wait until the job ends.
while (!in_array($job['state'], ['succeeded', 'failed', 'cancelled', 'expired'])) {
    sleep(2);
    $job = get_json("$api/conversions/{$job['id']}", $key);
}
if ($job['state'] !== 'succeeded') {
    exit('Conversion ' . $job['state'] . ': ' . ($job['error']['message'] ?? '') . "\n");
}

// 3. Download every page. The links need no API key and expire after 15 minutes.
$result = get_json("$api/conversions/{$job['id']}/result", $key);
foreach ($result['pages'] as $page) {
    if (!empty($page['url'])) {
        file_put_contents(basename($page['filename']), file_get_contents($page['url']));
        echo 'Saved ', $page['filename'], "\n";
    }
}
echo 'Credits used: ', $job['credits_consumed'], "\n";
import json
import os
import time
import uuid

import requests

API = os.environ.get("RASTERNIVO_API_URL", "https://rasternivo-api.swiftinfocorp.com/v1")
headers = {"Authorization": "Bearer " + os.environ["RASTERNIVO_API_KEY"]}

# 1. Upload. requests sends the options field before the file, as the API needs.
options = {"pages": "all", "dpi": 150, "quality": 90, "zip": True}
with open("report.pdf", "rb") as pdf:
    r = requests.post(
        f"{API}/conversions",
        headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
        data={"options": json.dumps(options)},
        files={"file": ("report.pdf", pdf, "application/pdf")},
    )
if r.status_code != 202:
    raise SystemExit("Upload failed: " + r.json()["error"]["message"])
job = r.json()

# 2. Wait until the job ends.
while job["state"] not in ("succeeded", "failed", "cancelled", "expired"):
    time.sleep(2)
    job = requests.get(f"{API}/conversions/{job['id']}", headers=headers).json()
if job["state"] != "succeeded":
    raise SystemExit(f"Conversion {job['state']}: {job['error']}")

# 3. Download every page. The links need no API key and expire after 15 minutes.
result = requests.get(f"{API}/conversions/{job['id']}/result", headers=headers).json()
for page in result["pages"]:
    if page.get("url"):
        with open(os.path.basename(page["filename"]), "wb") as out:
            out.write(requests.get(page["url"]).content)
        print("Saved", page["filename"])
print("Credits used:", job["credits_consumed"])
// Node.js 20 or newer: no packages needed.
import { randomUUID } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { basename } from "node:path";
import { setTimeout as sleep } from "node:timers/promises";

const API = process.env.RASTERNIVO_API_URL ?? "https://rasternivo-api.swiftinfocorp.com/v1";
const headers = { Authorization: `Bearer ${process.env.RASTERNIVO_API_KEY}` };
const getJson = async (path) => (await fetch(API + path, { headers })).json();

// 1. Upload. The options field must come before the file.
const form = new FormData();
form.append("options", JSON.stringify({ pages: "all", dpi: 150, quality: 90, zip: true }));
form.append("file", new Blob([await readFile("report.pdf")], { type: "application/pdf" }), "report.pdf");

const res = await fetch(`${API}/conversions`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": randomUUID() },
  body: form,
});
let job = await res.json();
if (res.status !== 202) throw new Error(`Upload failed: ${job.error.message}`);

// 2. Wait until the job ends.
while (!["succeeded", "failed", "cancelled", "expired"].includes(job.state)) {
  await sleep(2000);
  job = await getJson(`/conversions/${job.id}`);
}
if (job.state !== "succeeded") throw new Error(`Conversion ${job.state}: ${job.error?.message}`);

// 3. Download every page. The links need no API key and expire after 15 minutes.
const result = await getJson(`/conversions/${job.id}/result`);
for (const page of result.pages) {
  if (page.url) {
    const file = await fetch(page.url);
    await writeFile(basename(page.filename), Buffer.from(await file.arrayBuffer()));
    console.log("Saved", page.filename);
  }
}
console.log("Credits used:", job.credits_consumed);
API="https://rasternivo-api.swiftinfocorp.com/v1"

# 1. Upload. The options part must come before the file part.
curl -X POST "$API/conversions" \
  -H "Authorization: Bearer $RASTERNIVO_API_KEY" \
  -H "Idempotency-Key: report-2026-11-02-001" \
  -F 'options={"pages": "all", "dpi": 150, "quality": 90, "zip": true}' \
  -F "file=@report.pdf;type=application/pdf"

# 2. Check the job until "state" is succeeded, failed or cancelled.
curl "$API/conversions/JOB_ID" -H "Authorization: Bearer $RASTERNIVO_API_KEY"

# 3. Get the download links, then download.
curl "$API/conversions/JOB_ID/result" -H "Authorization: Bearer $RASTERNIVO_API_KEY"
curl -o report.zip "ZIP_URL_FROM_THE_RESULT"

Create an API key

<?php
$api = getenv('RASTERNIVO_API_URL') ?: 'https://rasternivo-api.swiftinfocorp.com/v1';
$email = getenv('RASTERNIVO_EMAIL');
$password = getenv('RASTERNIVO_PASSWORD');

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEFILE => '', // keep the session cookie between the two requests
]);

// 1. Sign in
curl_setopt_array($ch, [
    CURLOPT_URL => "$api/auth/login",
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode(['email' => $email, 'password' => $password]),
]);
$login = json_decode(curl_exec($ch), true);

// 2. Create the key
curl_setopt_array($ch, [
    CURLOPT_URL => "$api/api-keys",
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-CSRF-Token: ' . $login['csrf_token']],
    CURLOPT_POSTFIELDS => json_encode([
        'label' => 'my-server',
        'scopes' => ['conversions:read', 'conversions:write', 'usage:read'],
        'password' => $password, // asked again because a key is a long-lived credential
    ]),
]);
$key = json_decode(curl_exec($ch), true);

echo $key['secret'], "\n"; // shown only once: store it safely
import os
import requests

API = os.environ.get("RASTERNIVO_API_URL", "https://rasternivo-api.swiftinfocorp.com/v1")
email = os.environ["RASTERNIVO_EMAIL"]
password = os.environ["RASTERNIVO_PASSWORD"]

session = requests.Session()  # keeps the session cookie between requests

# 1. Sign in
login = session.post(f"{API}/auth/login", json={"email": email, "password": password})
login.raise_for_status()

# 2. Create the key
r = session.post(
    f"{API}/api-keys",
    headers={"X-CSRF-Token": login.json()["csrf_token"]},
    json={
        "label": "my-server",
        "scopes": ["conversions:read", "conversions:write", "usage:read"],
        "password": password,  # asked again because a key is a long-lived credential
    },
)
r.raise_for_status()

print(r.json()["secret"])  # shown only once: store it safely
const API = process.env.RASTERNIVO_API_URL ?? "https://rasternivo-api.swiftinfocorp.com/v1";
const email = process.env.RASTERNIVO_EMAIL;
const password = process.env.RASTERNIVO_PASSWORD;

// 1. Sign in
const login = await fetch(`${API}/auth/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email, password }),
});
if (!login.ok) throw new Error(await login.text());
const { csrf_token } = await login.json();
const cookie = login.headers.getSetCookie().map((c) => c.split(";")[0]).join("; ");

// 2. Create the key
const res = await fetch(`${API}/api-keys`, {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-CSRF-Token": csrf_token, Cookie: cookie },
  body: JSON.stringify({
    label: "my-server",
    scopes: ["conversions:read", "conversions:write", "usage:read"],
    password, // asked again because a key is a long-lived credential
  }),
});
if (!res.ok) throw new Error(await res.text());

console.log((await res.json()).secret); // shown only once: store it safely
API="https://rasternivo-api.swiftinfocorp.com/v1"

# 1. Sign in. The cookie is saved to session.txt; copy csrf_token from the response.
curl -c session.txt -X POST "$API/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password"}'

# 2. Create the key, sending the cookie, the CSRF token and your password again.
curl -b session.txt -X POST "$API/api-keys" \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: CSRF_TOKEN_FROM_STEP_1" \
  -d '{"label": "my-server",
       "scopes": ["conversions:read", "conversions:write", "usage:read"],
       "password": "your-password"}'

Check your plan and credits

<?php
$api = getenv('RASTERNIVO_API_URL') ?: 'https://rasternivo-api.swiftinfocorp.com/v1';

$ch = curl_init("$api/usage");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('RASTERNIVO_API_KEY')],
]);
$usage = json_decode(curl_exec($ch), true);

echo $usage['plan']['name'], ' (', $usage['plan']['source'], '): ',
     $usage['credits']['available'], " credits available\n";
import os
import requests

API = os.environ.get("RASTERNIVO_API_URL", "https://rasternivo-api.swiftinfocorp.com/v1")

usage = requests.get(
    f"{API}/usage",
    headers={"Authorization": "Bearer " + os.environ["RASTERNIVO_API_KEY"]},
).json()

print(f"{usage['plan']['name']} ({usage['plan']['source']}): "
      f"{usage['credits']['available']} credits available")
const API = process.env.RASTERNIVO_API_URL ?? "https://rasternivo-api.swiftinfocorp.com/v1";

const res = await fetch(`${API}/usage`, {
  headers: { Authorization: `Bearer ${process.env.RASTERNIVO_API_KEY}` },
});
const usage = await res.json();

console.log(`${usage.plan.name} (${usage.plan.source}): ${usage.credits.available} credits available`);
curl "https://rasternivo-api.swiftinfocorp.com/v1/usage" \
  -H "Authorization: Bearer $RASTERNIVO_API_KEY"

Receive webhooks

A small receiver that checks the signature of every delivery. See Webhooks.

<?php
// webhook.php: point your webhook endpoint at this file.
$secret = getenv('RASTERNIVO_WEBHOOK_SECRET'); // whsec_...
$body = file_get_contents('php://input');      // the raw body, before any JSON parsing

$timestamp = 0;
$signatures = [];
foreach (explode(',', $_SERVER['HTTP_RASTERNIVO_SIGNATURE'] ?? '') as $part) {
    [$name, $value] = array_pad(explode('=', $part, 2), 2, '');
    if ($name === 't') $timestamp = (int) $value;
    if ($name === 'v1') $signatures[] = $value;
}

$expected = hash_hmac('sha256', "$timestamp.$body", $secret);
$valid = false;
foreach ($signatures as $signature) {
    $valid = $valid || hash_equals($expected, $signature);
}
if (!$valid || abs(time() - $timestamp) > 300) { // also rejects replays older than 5 minutes
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// De-duplicate with $_SERVER['HTTP_RASTERNIVO_EVENT_ID'], then do your work.
error_log($event['type'] . ' ' . $event['data']['conversion']['id']);
http_response_code(200);
# webhook.py: a small receiver using only the standard library.
import hashlib
import hmac
import json
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer

SECRET = os.environ["RASTERNIVO_WEBHOOK_SECRET"]  # whsec_...


def is_valid(body: bytes, header: str) -> bool:
    parts = [p.split("=", 1) for p in header.split(",") if "=" in p]
    timestamp = next((v for k, v in parts if k == "t"), "0")
    if abs(time.time() - int(timestamp)) > 300:  # reject replays older than 5 minutes
        return False
    expected = hmac.new(SECRET.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
    return any(k == "v1" and hmac.compare_digest(expected, v) for k, v in parts)


class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("Content-Length", 0)))  # raw body
        if not is_valid(body, self.headers.get("RasterNivo-Signature", "")):
            self.send_response(400)
            self.end_headers()
            return
        event = json.loads(body)
        # De-duplicate with the RasterNivo-Event-Id header, then do your work.
        print(event["type"], event["data"]["conversion"]["id"])
        self.send_response(200)
        self.end_headers()


HTTPServer(("", int(os.environ.get("PORT", 3000))), Handler).serve_forever()
// webhook.mjs: a small receiver using only built-in modules.
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const secret = process.env.RASTERNIVO_WEBHOOK_SECRET; // whsec_...

function isValid(body, header = "") {
  const parts = header.split(",").map((p) => p.split("="));
  const timestamp = parts.find(([k]) => k === "t")?.[1] ?? "0";
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // reject old replays
  const expected = Buffer.from(createHmac("sha256", secret).update(`${timestamp}.`).update(body).digest("hex"));
  return parts.some(([k, v]) => k === "v1" && v.length === expected.length && timingSafeEqual(Buffer.from(v), expected));
}

createServer((req, res) => {
  const chunks = [];
  req.on("data", (c) => chunks.push(c));
  req.on("end", () => {
    const body = Buffer.concat(chunks); // the raw body, before any JSON parsing
    if (!isValid(body, req.headers["rasternivo-signature"])) {
      res.writeHead(400).end();
      return;
    }
    const event = JSON.parse(body);
    // De-duplicate with the rasternivo-event-id header, then do your work.
    console.log(event.type, event.data.conversion.id);
    res.writeHead(200).end();
  });
}).listen(process.env.PORT ?? 3000);

Create an account

<?php
$api = getenv('RASTERNIVO_API_URL') ?: 'https://rasternivo-api.swiftinfocorp.com/v1';

$ch = curl_init("$api/auth/register");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'email' => getenv('RASTERNIVO_EMAIL'),
        'password' => getenv('RASTERNIVO_PASSWORD'),
        'accept_terms' => true,
    ]),
]);
curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), " Check your inbox for the verification email.\n"; // 202
import os
import requests

API = os.environ.get("RASTERNIVO_API_URL", "https://rasternivo-api.swiftinfocorp.com/v1")

r = requests.post(f"{API}/auth/register", json={
    "email": os.environ["RASTERNIVO_EMAIL"],
    "password": os.environ["RASTERNIVO_PASSWORD"],
    "accept_terms": True,
})
print(r.status_code, "Check your inbox for the verification email.")  # 202
const API = process.env.RASTERNIVO_API_URL ?? "https://rasternivo-api.swiftinfocorp.com/v1";

const res = await fetch(`${API}/auth/register`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    email: process.env.RASTERNIVO_EMAIL,
    password: process.env.RASTERNIVO_PASSWORD,
    accept_terms: true,
  }),
});
console.log(res.status, "Check your inbox for the verification email."); // 202
curl -X POST "https://rasternivo-api.swiftinfocorp.com/v1/auth/register" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "choose-a-long-password", "accept_terms": true}'

Verify your email

<?php
$api = getenv('RASTERNIVO_API_URL') ?: 'https://rasternivo-api.swiftinfocorp.com/v1';

$ch = curl_init("$api/auth/verify-email");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode(['token' => getenv('RASTERNIVO_TOKEN')]),
]);
curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), " Email verified. Your 14-day trial has started.\n";
import os
import requests

API = os.environ.get("RASTERNIVO_API_URL", "https://rasternivo-api.swiftinfocorp.com/v1")

r = requests.post(f"{API}/auth/verify-email", json={"token": os.environ["RASTERNIVO_TOKEN"]})
r.raise_for_status()
print("Email verified. Your 14-day trial has started.")
const API = process.env.RASTERNIVO_API_URL ?? "https://rasternivo-api.swiftinfocorp.com/v1";

const res = await fetch(`${API}/auth/verify-email`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ token: process.env.RASTERNIVO_TOKEN }),
});
if (!res.ok) throw new Error(await res.text());
console.log("Email verified. Your 14-day trial has started.");
curl -X POST "https://rasternivo-api.swiftinfocorp.com/v1/auth/verify-email" \
  -H "Content-Type: application/json" \
  -d '{"token": "THE_TOKEN_FROM_THE_EMAIL"}'