Integrating media processing into backend systems often means running ImageMagick, headless Chromium for documents, or a pile of single-purpose libraries. Imazeo provides a project-scoped REST API (and official SDKs) for image transforms, PDF workflows, and QR/barcode generation—without putting processing binaries on every application host.
Before you transform media programmatically, your service must authenticate with a project API key. This guide walks through creating a project-scoped key in the Customer portal, storing it safely, uploading an original, and running your first live transform.
Understanding project-scoped keys
Imazeo separates assets, processing, and usage by project. Keys are issued per project, not as a single root account secret:
Boundary isolation: A key for staging cannot read or change media owned by production.
Targeted revocation: If one environment leaks a secret, revoke and replace that key without touching other services.
Project-scoped usage: Uploads, transforms, and tool usage are attributed to the project that owns the key.
Security rule: Treat project API keys as backend secrets. Never put them in browsers, mobile apps, public repos, or frontend env files. Send media requests from your server (or other trusted backend runtime) only.
Step 1: Create a project API key
Sign in at app.imazeo.com and complete any required email verification.
Create a project for this integration (or select an existing one).
Open API keys, select that project, and click Create key.
Give the key a descriptive name (for example
production-worker-backend), then copy the raw key immediately. It is shown only once; the backend stores a hash.Record the project’s UUID from the portal. Use that UUID in request paths—not the project name or slug.
Current keys look like imz_<uuid>.<secret>. Do not invent imz_live_ / imz_test_ prefixes.
Step 2: Store credentials securely
Keep secrets in environment variables or a vault—not in source. For local work:
export IMAZEO_API_ORIGIN="https://api.imazeo.com"
export IMAZEO_PROJECT_ID="00000000-0000-4000-8000-000000000001"
export IMAZEO_API_KEY="imz_00000000-0000-4000-8000-000000000001.REPLACE_WITH_YOUR_SECRET"
export IMAZEO_INPUT="./field-notebook.png"
export IMAZEO_OUTPUT="./output.webp"Use an origin only (no /v1 suffix). Add .env to .gitignore. Prefer HTTPS outside localhost.
Media routes authenticate with the X-API-Key header. Customer portal management uses Authorization: Bearer. Never send both on one request—the API rejects that combination.
Step 3: Upload, then transform
Successful transform responses are image bytes (not JSON). Failures still return a JSON error envelope—always check HTTP status before writing a file.
Owned originals use imageId (the upload response’s data._id). You can also pass a public url instead of imageId (exactly one is required). Storage modes:
CACHE(default) — retained for a configured TTLPERSIST— longer-lived derivative metadata/storage rulesEPHEMERAL— bytes in the response only
Response headers such as X-Transformation-ID and X-Cache (HIT / MISS) identify the derivative for later downloads.
Using cURL
# 1) Upload an original you own
curl --fail-with-body --silent --show-error \
--request POST "$IMAZEO_API_ORIGIN/v1/projects/$IMAZEO_PROJECT_ID/images" \
--header "X-API-Key: $IMAZEO_API_KEY" \
--header "X-Upload-Length: $(wc -c < "$IMAZEO_INPUT" | tr -d '[:space:]')" \
--form "file=@$IMAZEO_INPUT" \
--output upload.json
IMAZEO_IMAGE_ID=$(node -e 'const fs=require("node:fs"); const r=JSON.parse(fs.readFileSync("upload.json","utf8")); if(!r.success || !r.data?._id) process.exit(1); process.stdout.write(r.data._id);')
export IMAZEO_IMAGE_ID
# 2) Transform to a 600×600 cover WebP
curl --fail-with-body --silent --show-error \
--request POST "$IMAZEO_API_ORIGIN/v1/projects/$IMAZEO_PROJECT_ID/transforms" \
--header "X-API-Key: $IMAZEO_API_KEY" \
--header "Content-Type: application/json" \
--data "{\"imageId\":\"$IMAZEO_IMAGE_ID\",\"mode\":\"CACHE\",\"transform\":{\"width\":600,\"height\":600,\"fit\":\"cover\",\"format\":\"webp\",\"quality\":80}}" \
--dump-header response-headers.txt \
--output "$IMAZEO_OUTPUT"Using Python
import json
import os
from pathlib import Path
import requests
origin = os.environ["IMAZEO_API_ORIGIN"].rstrip("/")
project_id = os.environ["IMAZEO_PROJECT_ID"]
api_key = os.environ["IMAZEO_API_KEY"]
input_path = Path(os.environ["IMAZEO_INPUT"])
output_path = Path(os.environ["IMAZEO_OUTPUT"])
headers = {"X-API-Key": api_key}
base = f"{origin}/v1/projects/{project_id}"
with input_path.open("rb") as handle:
raw = handle.read()
upload = requests.post(
f"{base}/images",
headers={**headers, "X-Upload-Length": str(len(raw))},
files={"file": (input_path.name, raw)},
timeout=60,
allow_redirects=False,
)
upload.raise_for_status()
envelope = upload.json()
image_id = envelope["data"]["_id"]
transform = requests.post(
f"{base}/transforms",
headers={**headers, "Content-Type": "application/json"},
json={
"imageId": image_id,
"mode": "CACHE",
"transform": {
"width": 600,
"height": 600,
"fit": "cover",
"format": "webp",
"quality": 80,
},
},
timeout=60,
allow_redirects=False,
)
if transform.status_code != 200:
raise SystemExit(
f"Transform failed ({transform.status_code}): {transform.text}"
)
output_path.write_bytes(transform.content)
print(
"Saved",
output_path,
"transformation:",
transform.headers.get("X-Transformation-ID"),
"cache:",
transform.headers.get("X-Cache"),
)Using Node.js / TypeScript
import { readFile, writeFile } from "node:fs/promises";
import { basename } from "node:path";
const origin = process.env.IMAZEO_API_ORIGIN;
const projectId = process.env.IMAZEO_PROJECT_ID;
const apiKey = process.env.IMAZEO_API_KEY;
const input = process.env.IMAZEO_INPUT;
const output = process.env.IMAZEO_OUTPUT;
if (!origin || !projectId || !apiKey || !input || !output) {
throw new Error("Set IMAZEO_API_ORIGIN, IMAZEO_PROJECT_ID, IMAZEO_API_KEY, IMAZEO_INPUT, and IMAZEO_OUTPUT.");
}
const endpoint = `${origin.replace(/\/$/, "")}/v1/projects/${encodeURIComponent(projectId)}`;
const headers = { "X-API-Key": apiKey };
async function checked(response: Response) {
if (!response.ok) {
const body = await response.json().catch(() => null);
const code = body?.error?.code ?? "REQUEST_FAILED";
const requestId =
body?.meta?.requestId ?? response.headers.get("X-Request-ID") ?? "unavailable";
throw new Error(`HTTP ${response.status}: ${code}; request ID: ${requestId}`);
}
return response;
}
const bytes = await readFile(input);
const form = new FormData();
form.append("file", new Blob([bytes]), basename(input));
const upload = await checked(
await fetch(`${endpoint}/images`, {
method: "POST",
headers: { ...headers, "X-Upload-Length": String(bytes.byteLength) },
body: form,
redirect: "error",
}),
);
const envelope = await upload.json();
if (envelope.success !== true || typeof envelope.data?._id !== "string") {
throw new Error("Unexpected upload response; expected data._id.");
}
const transformed = await checked(
await fetch(`${endpoint}/transforms`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
imageId: envelope.data._id,
mode: "CACHE",
transform: {
width: 600,
height: 600,
fit: "cover",
format: "webp",
quality: 80,
},
}),
redirect: "error",
}),
);
if (!(transformed.headers.get("Content-Type") ?? "").startsWith("image/")) {
throw new Error("Expected an image response; output was not written.");
}
await writeFile(output, Buffer.from(await transformed.arrayBuffer()));
console.log("Transformation completed.", {
imageId: envelope.data._id,
transformationId: transformed.headers.get("X-Transformation-ID"),
cache: transformed.headers.get("X-Cache"),
});For a packaged client, the official TypeScript package is @imazeo/sdk; it also sends X-API-Key and never pairs it with a Bearer token.
Troubleshooting common HTTP status codes
Status | Probable cause | Recommended fix |
|---|---|---|
| Missing/invalid key ( | Use |
| Key/project mismatch, inactive entitlements, or plan/feature limits ( | Confirm the path |
| Unknown | Upload to this project first, then pass |
| Invalid body (for example both | Send exactly one of |
Next steps
Authentication and API keys — credential rules and revocation.
Transforms — fit, crop, format, quality, and storage modes.
PDF tools — info, render, merge, and extract.
Quickstart — upload + transform end to end.