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:
| Part | Required | Description |
|---|---|---|
options | No | A JSON object of conversion options, up to 16 KB. Must come before the file. |
file | Yes | The 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.
| State | Meaning |
|---|---|
queued | Waiting for a worker |
validating | Opening the PDF and counting the pages |
processing | Converting pages |
packaging | Creating the ZIP |
cancel_requested | Stopping after you asked to cancel |
succeeded | Finished, with at least one page converted. Check pages_failed: if some pages failed, error.code is partial. |
failed | Finished with nothing to download. See error. |
cancelled | Cancelled by you |
expired | The 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
/resultagain for fresh ones. zip_urlisnullunless you asked for"zip": true.- Before the job ends you get
409 job_not_finished. After the results are deleted you get410 result_expired.
How long files are kept
| Data | Kept |
|---|---|
| Your uploaded PDF | Until the job finishes |
| JPGs and ZIP | One hour after the job finishes (results_expire_at) |
| Download links | 15 minutes each (links_expire_at) |
| PDF password | Encrypted 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.