API & integration

Imazeo API Key Setup: Your First Authenticated Request

Create your first project-scoped API key in Imazeo, configure environment variables safely, and make your first authenticated image transformation request.

Minimalist editorial illustration showing a floating API card with an abstract key icon connecting to an image transformation frame, a PDF document outline, and a QR grid block in teal, sage, and deep ink.

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

  1. Sign in at app.imazeo.com and complete any required email verification.

  2. Create a project for this integration (or select an existing one).

  3. Open API keys, select that project, and click Create key.

  4. 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.

  5. 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:

bash
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 TTL

  • PERSIST — longer-lived derivative metadata/storage rules

  • EPHEMERAL — 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

bash
# 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

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

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

401

Missing/invalid key (API_KEY_INVALID, API_KEY_REVOKED), wrong header, or sending X-API-Key and Bearer together.

Use X-API-Key: <project-key> only. Trim env whitespace. Create a replacement key if revoked.

403

Key/project mismatch, inactive entitlements, or plan/feature limits (PLAN_LIMIT_EXCEEDED, FEATURE_DISABLED).

Confirm the path projectId matches the key’s project. Check plan limits in the Customer portal.

404

Unknown imageId, or a scope/ownership mismatch that does not disclose other tenants’ resources.

Upload to this project first, then pass data._id. Do not reuse IDs from another project.

422 / 400

Invalid body (for example both imageId and url, unsupported fit/format, or invalid dimensions).

Send exactly one of imageId or url. Use supported fits (cover, contain, inside, fill) and formats (jpeg, png, webp, avif, gif).

Next steps