Using the API

Converting PDFs

A conversion is a job: you upload a PDF, the job runs, and you download the results.

<?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"

Uploading

POST /v1/conversions with the scope conversions:write. Send multipart/form-data with these parts, in this order:

PartRequiredDescription
optionsNoA JSON object of conversion options, up to 16 KB. Must come before the file.
fileYesThe PDF

The Idempotency-Key header is required: a value you choose, unique for each conversion, such as a UUID or your own document ID. See Retries and idempotency.

The answer is 202 Accepted with the job. The upload is checked immediately; the rest of the PDF is checked when the job runs, so problems such as a wrong password appear on the job, not on this response.

{
  "id": "01a19b62-dd01-7c80-811b-74793f0a32b6",
  "state": "queued",
  "channel": "api",
  "created_at": "2026-11-02T08:31:50Z",
  "started_at": null,
  "finished_at": null,
  "input_filename": "report.pdf",
  "input_bytes": 3585,
  "options": {
    "pages": "1-2", "dpi": 300, "quality": 90, "subsampling": "auto", "background": "#FFFFFF",
    "name_template": "{original}_page_{page_number:0001}", "zip": true
  },
  "page_count": null,
  "pages_selected": null,
  "pages_succeeded": 0,
  "pages_failed": 0,
  "credits_reserved": 0,
  "credits_consumed": 0,
  "error": null,
  "cancel_requested": false,
  "results_expire_at": null
}

Following the job

GET /v1/conversions/{id} with the scope conversions:read returns the job in the same form. Check it at most once a second, or use a webhook instead.

StateMeaning
queuedWaiting for a worker
validatingOpening the PDF and counting the pages
processingConverting pages
packagingCreating the ZIP
cancel_requestedStopping after you asked to cancel
succeededFinished, with at least one page converted. Check pages_failed: if some pages failed, error.code is partial.
failedFinished with nothing to download. See error.
cancelledCancelled by you
expiredThe results have been deleted

Downloading the results

GET /v1/conversions/{id}/result with the scope conversions:read:

{
  "conversion": { "id": "01a19b62-dd01-7c80-811b-74793f0a32b6", "state": "succeeded", "pages_succeeded": 2, "credits_consumed": 6 },
  "pages": [
    { "page": 1, "filename": "report_page_0001.jpg", "width": 2550, "height": 3300, "bytes": 266957,
      "url": "https://rasternivo-api.swiftinfocorp.com/v1/files/MDFhMTli…" },
    { "page": 2, "filename": "report_page_0002.jpg", "width": 2550, "height": 3300, "bytes": 269464,
      "url": "https://rasternivo-api.swiftinfocorp.com/v1/files/MDFhMTli…" }
  ],
  "zip_url": "https://rasternivo-api.swiftinfocorp.com/v1/files/MDFhMTli…",
  "links_expire_at": "2026-11-02T08:46:51Z"
}
  • The links need no API key, so treat them as private. They expire after 15 minutes; call /result again for fresh ones.
  • zip_url is null unless you asked for "zip": true.
  • Before the job ends you get 409 job_not_finished. After the results are deleted you get 410 result_expired.

How long files are kept

DataKept
Your uploaded PDFUntil the job finishes
JPGs and ZIPOne hour after the job finishes (results_expire_at)
Download links15 minutes each (links_expire_at)
PDF passwordEncrypted until the job ends, then erased. Never logged or returned.

Download your results promptly and store them yourself if you need them longer.

Listing and cancelling

# The newest conversions (scope conversions:read)
curl "https://rasternivo-api.swiftinfocorp.com/v1/conversions?limit=50" \
  -H "Authorization: Bearer $RASTERNIVO_API_KEY"

# Cancel a job (scope conversions:write). Safe to repeat.
curl -X POST "https://rasternivo-api.swiftinfocorp.com/v1/conversions/JOB_ID/cancel" \
  -H "Authorization: Bearer $RASTERNIVO_API_KEY"

The list returns {"conversions": [...], "next_cursor": "..."}. To get the next page, pass ?cursor= with that value; next_cursor is null on the last page. Pages not converted before a cancel are not charged.