Developer docs

ImgPanel API

Use scoped `ik_live_...` keys to upload, import, search, transform, sign, and organize image assets stored in Cloudflare R2 through ImgPanel.

Quick call
curl -X GET "https://api.imgpanel.com/v1/control/r2/assets?limit=20" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Quickstart

Create an API key in Settings, then call the runtime API.

1

Create key

Dashboard Settings -> API keys. Copy the full `ik_live_...` key immediately.

2

Set env

Store it as `IMGPANEL_API_KEY` in your server, job runner, or CI secret store.

3

Call API

Send the key as `x-api-key` or `Authorization: Bearer ik_live_...`.

OpenAPI

Import the ImgPanel runtime API into Postman, Insomnia, or SDK generators.

curl "https://api.imgpanel.com/v1/openapi.json"
Download spec

Authentication

Dashboard sessions are for the UI. Runtime integrations use scoped ImgPanel API keys. Cloudflare tokens stay server-side and are never returned to frontend code.

curl "https://api.imgpanel.com/v1/control/r2/assets?limit=20" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Runtime boundary

API keys cannot manage other API keys, Cloudflare account connections, or dashboard-only playground endpoints. Those actions require a logged-in dashboard session.

disabled keys return 401
expired keys return 401
missing scopes return 403
last_used_at is updated on use

Direct Browser Upload

Embed uploads in a CMS or admin UI without exposing ImgPanel API keys.

1. Server creates a token

curl -X POST "https://api.imgpanel.com/v1/control/r2/upload-tokens" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "clientUploadId": "cms-media-field-42",
    "expiresInSeconds": 900,
    "folder": "direct/inbox",
    "tags": ["direct-upload"],
    "metadata": { "source": "browser-direct-upload" }
  }'

2. Browser posts the file

async function uploadImage(file, tokenResponse) {
  const form = new FormData();
  form.set("file", file);
  form.set("token", tokenResponse.token);

  const response = await fetch(tokenResponse.upload_url, {
    method: "POST",
    body: form
  });

  if (!response.ok) {
    throw new Error(await response.text());
  }

  return response.json();
}

Token endpoint

Create tokens from your backend with an ImgPanel API key that has `uploads:write`; never create them directly from browser JavaScript.

Browser POST

Send `multipart/form-data` to the returned `upload_url` with fields `file` and `token`, or pass the token as `Authorization: Bearer ...`.

TTL

`expiresInSeconds` defaults to 900 seconds, accepts 60 to 3600 seconds, and returns `expires_at` so your UI can expire stale forms.

Allowed origins

Browser CORS for `/r2/direct-upload` is controlled by `IMGPANEL_DIRECT_UPLOAD_ORIGINS`; list exact HTTPS origins for embedded CMS/admin sites.

Constraints

Folder, tags, metadata, status, access, and upload preset rules are baked into the token. Browser-side overrides work only when the token was created with `allowOverrides`.

Security

Tokens are upload-only, team-scoped, signed, and short-lived. Keep API keys server-side and treat generated upload tokens as disposable form credentials.

Original Downloads And Recovery

Keep a clear mental model for current originals, version snapshots, and safe restore workflows.

# 1. Read the Cloudinary-style resource detail.
curl "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/detail?public_id=legacy/home/hero" \
  -H "x-api-key: $IMGPANEL_API_KEY"

# 2. Download the current original/base from the returned source_url.
curl -L "$SOURCE_URL" -o hero-original.jpg
# Restore an archived resource by public_id.
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/restore" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "public_ids": ["legacy/home/hero"] }'

Current original/base

The asset detail `source_url` and dashboard Download button target the current original/base file stored at the asset key. Derived assets are separate generated files.

Version snapshots

Snapshots are private recovery copies of a previous base file. Use the dashboard Versions panel to compare filename, type, and size before restoring one.

Restore behavior

Restoring a snapshot replaces the current base file bytes and metadata while keeping the same asset key. Existing public delivery URLs keep working, but they now serve the restored file after cache refresh.

Archive restore

Archive/restore changes lifecycle visibility without changing the object bytes. Version restore is the recovery path for rolling back a replacement.

Safe rollout

Download the current original before destructive edits, create a labeled snapshot, then replace or transform. Keep Cloudinary online until migrated public IDs and signed delivery flows are verified.

Workflow Examples

These examples are generated from the same workflow catalog used by the dashboard API Explorer.

List assets

GETAssets

Fetch indexed R2 assets with pagination, lifecycle counts, and Library facet counts for folders, tags, access, status, format, creators, and presets.

assets:read
/control/r2/assets?limit=20
curl -X GET "https://api.imgpanel.com/v1/control/r2/assets?limit=20" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Search assets

POSTSearch

Cloudinary-style expression search over the R2 index, including AND, OR, NOT, parentheses, metadata fields, and numeric/date comparisons.

search:readassets:read
/control/r2/search
curl -X POST "https://api.imgpanel.com/v1/control/r2/search" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "expression": "(tags=hero OR tags=product) AND NOT status=draft",
  "max_results": 20
}'

Import URL

POSTAssets

Import an image URL into R2 with tags, metadata, and optional Cloudinary-like public_id conflict policy. Set overwrite=true to replace an existing public_id.

uploads:write
/control/r2/assets
curl -X POST "https://api.imgpanel.com/v1/control/r2/assets" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "metadata": {
    "source": "api-explorer"
  },
  "overwrite": false,
  "public_id": "imports/flower-demo",
  "tags": [
    "api"
  ],
  "url": "https://www.imgpanel.com/images/transform-sample.png"
}'

Import website grab images

POSTIntegrations

Import selected website grab image candidates into R2. Grab imports derive stable public IDs from source paths, so overwrite and unique_filename control repeat-import conflicts.

uploads:write
/control/r2/grab/import
curl -X POST "https://api.imgpanel.com/v1/control/r2/grab/import" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "images": [
    {
      "source_page_url": "https://example.com/products",
      "url": "https://example.com/images/hero.jpg"
    }
  ],
  "overwrite": false,
  "presetId": "upr_example",
  "sourceSiteUrl": "https://example.com",
  "unique_filename": false
}'

Bulk edit assets

PATCHAssets

Bulk edit selected R2 assets: folder, access, status, tags, and metadata.

assets:write
/control/r2/assets/bulk
curl -X PATCH "https://api.imgpanel.com/v1/control/r2/assets/bulk" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "folder": "campaigns/summer",
  "keys": [
    "uploads/tm_example/2026/07/04/image.jpg"
  ],
  "metadata": {
    "campaign": "summer"
  },
  "metadataMode": "merge",
  "status": "approved",
  "tagMode": "add",
  "tags": [
    "hero",
    "approved"
  ]
}'

Bulk delete assets

POSTAssets

Permanently delete selected R2 assets. Archive active assets first from the dashboard when you need a reversible delete.

assets:delete
/control/r2/assets/bulk-delete
curl -X POST "https://api.imgpanel.com/v1/control/r2/assets/bulk-delete" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "keys": [
    "uploads/tm_example/2026/07/04/image.jpg"
  ]
}'

Generate derived asset

POSTTransforms

Generate and store an eager/derived transformed asset from an existing R2 source.

transforms:write
/control/r2/derived
curl -X POST "https://api.imgpanel.com/v1/control/r2/derived" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "options": {
    "format": "webp",
    "quality": 85,
    "width": 1200
  },
  "source_key": "uploads/tm_example/2026/07/04/image.jpg"
}'

Create derived job

POSTTransforms

Create an async derived-generation job for selected R2 sources. Run it with the job run endpoint until completed.

transforms:write
/control/r2/derived/jobs
curl -X POST "https://api.imgpanel.com/v1/control/r2/derived/jobs" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "keys": [
    "uploads/tm_example/2026/07/04/hero.jpg",
    "uploads/tm_example/2026/07/04/card.jpg"
  ],
  "options": {
    "fit": "scale-down",
    "format": "webp",
    "quality": 85,
    "width": 1200
  }
}'

Run derived job step

POSTTransforms

Process the next parallel step for a derived-generation job and return progress, created count, failed count, and item statuses.

transforms:write
/control/r2/derived/jobs/djob_example/run
curl -X POST "https://api.imgpanel.com/v1/control/r2/derived/jobs/djob_example/run" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{}'

Retry failed derived job items

POSTTransforms

Requeue failed items from a derived-generation job, then run the same job again to retry only failed sources.

transforms:write
/control/r2/derived/jobs/djob_example/retry-failed
curl -X POST "https://api.imgpanel.com/v1/control/r2/derived/jobs/djob_example/retry-failed" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{}'

Derived job logs

GETTransforms

Read recent logs for a derived-generation job, including run steps, completed items, failed items, retry events, and cancellation.

transforms:read
/control/r2/derived/jobs/djob_example/logs?limit=20
curl -X GET "https://api.imgpanel.com/v1/control/r2/derived/jobs/djob_example/logs?limit=20" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Inspect delivery URL

GETDelivery

Check that a generated delivery or Open Graph image URL is reachable and image-like, including cache-control, CDN cache status, age, ETag, and redirect diagnostics.

assets:read
/control/delivery/inspect?url=https%3A%2F%2Fwww.imgpanel.com%2Fcdn-cgi%2Fimage%2Ffit%3Dcover%2Cformat%3Dauto%2Cwidth%3D1200%2Fimages%2Ftransform-sample.png
curl -X GET "https://api.imgpanel.com/v1/control/delivery/inspect?url=https%3A%2F%2Fwww.imgpanel.com%2Fcdn-cgi%2Fimage%2Ffit%3Dcover%2Cformat%3Dauto%2Cwidth%3D1200%2Fimages%2Ftransform-sample.png" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Purge delivery cache

POSTDelivery

Invalidate CDN cache for selected ImgPanel delivery URLs. Pass connection_id to use a client delivery connection, or omit it for the workspace default/platform fallback.

assets:write
/control/delivery/purge
curl -X POST "https://api.imgpanel.com/v1/control/delivery/purge" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "urls": [
    "https://api.imgpanel.com/r2/uploads/tm_example/2026/07/04/hero.jpg",
    "https://www.imgpanel.com/cdn-cgi/image/fit=cover,format=auto,width=1200/images/transform-sample.png"
  ]
}'

List delivery profiles

GETDelivery

List reusable delivery profiles for responsive and social image snippets.

assets:read
/control/delivery/profiles
curl -X GET "https://api.imgpanel.com/v1/control/delivery/profiles" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Create delivery profile

POSTDelivery

Create a named delivery profile that can be reused from the asset details UI.

admin:write
/control/delivery/profiles
curl -X POST "https://api.imgpanel.com/v1/control/delivery/profiles" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "name": "Social hero",
  "settings": {
    "quality": "85",
    "sizes": "(max-width: 768px) 100vw, 768px",
    "socialImageHeight": "630",
    "socialImageWidth": "1200",
    "socialSiteName": "ImgPanel",
    "widths": "320, 640, 960, 1280, 1600"
  }
}'

Direct upload token

POSTIntegrations

Create a short-lived browser direct upload token. Call this from your server with an API key, then give only the returned token and upload_url to browser code.

uploads:write
/control/r2/upload-tokens
curl -X POST "https://api.imgpanel.com/v1/control/r2/upload-tokens" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "clientUploadId": "cms-media-field-42",
  "expiresInSeconds": 900,
  "folder": "direct/inbox",
  "metadata": {
    "source": "browser-direct-upload"
  },
  "tags": [
    "direct-upload"
  ]
}'

Asset picker token

POSTIntegrations

Create a short-lived public asset picker token for embedding ImgPanel media search into a CMS or admin UI without exposing a runtime API key.

assets:read
/control/r2/asset-picker-tokens
curl -X POST "https://api.imgpanel.com/v1/control/r2/asset-picker-tokens" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "expiresInSeconds": 900,
  "requestId": "cms-media-field-42",
  "targetOrigin": "https://cms.example.com"
}'

Public asset picker

GETIntegrations

List public R2 assets through a short-lived asset picker token. This endpoint is intended for iframe/embed picker flows and does not accept runtime API keys.

picker-token
/r2/asset-picker?token=PASTE_PICKER_TOKEN&search=hero&tag=cms&limit=20
curl -X GET "https://api.imgpanel.com/v1/r2/asset-picker?token=PASTE_PICKER_TOKEN&search=hero&tag=cms&limit=20"

Usage stats

GETStats

Read storage, delivery, activity, asset health, and optional Cloudflare GraphQL cache attribution stats.

admin:read
/control/r2/stats?sinceDays=30
curl -X GET "https://api.imgpanel.com/v1/control/r2/stats?sinceDays=30" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Media settings

PATCHAssets

Configure workspace media defaults: upload MIME/size limits, default access/status/folder, and the delivery base URL stored on newly uploaded R2 assets.

admin:write
/control/r2/settings
curl -X PATCH "https://api.imgpanel.com/v1/control/r2/settings" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "allowed_mime_types": [
    "image/jpeg",
    "image/png",
    "image/webp"
  ],
  "default_access": "private",
  "default_folder": "incoming",
  "default_status": "review",
  "delivery_base_url": "https://cdn.example.com/media",
  "max_upload_bytes": 10485760
}'

List metadata fields

GETAssets

List structured metadata fields, including conditional field rules used by Library forms and upload validation.

admin:read
/control/r2/metadata-fields
curl -X GET "https://api.imgpanel.com/v1/control/r2/metadata-fields" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Create metadata field

POSTAssets

Create a structured metadata field. Conditions make a field active only when other metadata values match.

admin:write
/control/r2/metadata-fields
curl -X POST "https://api.imgpanel.com/v1/control/r2/metadata-fields" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "conditions": [
    {
      "field": "asset_kind",
      "operator": "equals",
      "value": "photo"
    }
  ],
  "key": "model_release",
  "label": "Model release",
  "required": true,
  "sortOrder": 20,
  "type": "url"
}'

Update metadata field

PATCHAssets

Update a structured metadata field and its conditional visibility/validation rules.

admin:write
/control/r2/metadata-fields/mf_example
curl -X PATCH "https://api.imgpanel.com/v1/control/r2/metadata-fields/mf_example" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "conditions": [
    {
      "field": "asset_kind",
      "operator": "in",
      "values": [
        "photo",
        "portrait"
      ]
    }
  ],
  "key": "model_release",
  "label": "Model release",
  "required": true,
  "sortOrder": 20,
  "type": "url"
}'

List upload presets

GETAssets

List upload presets used by browser uploads, URL imports, direct uploads, and website grab imports.

admin:read
/control/presets
curl -X GET "https://api.imgpanel.com/v1/control/presets" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Create upload preset

POSTAssets

Create a reusable upload preset with folder/tags/metadata defaults, allowed formats, max size, moderation defaults, and optional eager transform JSON.

admin:write
/control/presets
curl -X POST "https://api.imgpanel.com/v1/control/presets" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "access": "private",
  "allowedFormats": [
    "jpg",
    "png",
    "webp"
  ],
  "folder": "products/incoming",
  "maxFileSize": 10485760,
  "metadata": {
    "source": "upload-preset"
  },
  "name": "Product uploads",
  "status": "review",
  "tags": [
    "product",
    "needs-review"
  ],
  "transform": {
    "format": "webp",
    "quality": 85,
    "width": 1600
  }
}'

Update upload preset

PATCHAssets

Update an upload preset. Future uploads using this preset receive the new defaults and constraints.

admin:write
/control/presets/upr_example
curl -X PATCH "https://api.imgpanel.com/v1/control/presets/upr_example" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "access": "public",
  "allowedFormats": [
    "jpg",
    "png",
    "webp",
    "avif"
  ],
  "folder": "products/approved",
  "maxFileSize": 15728640,
  "metadata": {
    "source": "upload-preset",
    "reviewed": true
  },
  "name": "Product uploads approved",
  "status": "approved",
  "tags": [
    "product",
    "approved"
  ],
  "transform": {
    "format": "webp",
    "quality": 82,
    "width": 1800
  }
}'

Audit activity

GETStats

Filter and export audit activity for uploads, imports, metadata edits, jobs, and delivery operations.

admin:read
/control/r2/activity?resource_type=asset&search=uploaded&export=true&limit=1000
curl -X GET "https://api.imgpanel.com/v1/control/r2/activity?resource_type=asset&search=uploaded&export=true&limit=1000" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Asset metadata backup

POSTAssets

Export selected asset metadata as JSON and include backup context for folders, tags, collections, recent audit activity, migration jobs, duplicate checksum groups, metadata schema, and review-status counts.

assets:read
/control/r2/assets/export
curl -X POST "https://api.imgpanel.com/v1/control/r2/assets/export" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "expires_in_seconds": 3600,
  "include_backup": true,
  "keys": [
    "uploads/tm_example/2026/07/hero.jpg"
  ],
  "signed_urls": true
}'

Transformation templates

GETTransforms

List reusable transformation templates.

transforms:read
/control/transformation-templates
curl -X GET "https://api.imgpanel.com/v1/control/transformation-templates" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Template versions

GETTransforms

List saved versions for a transformation template so an integration can audit or roll back named transform changes.

transforms:read
/control/transformation-templates/trn_example/versions
curl -X GET "https://api.imgpanel.com/v1/control/transformation-templates/trn_example/versions" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Restore template version

POSTTransforms

Restore a transformation template from a previous saved version. The restore itself becomes the newest version.

transforms:write
/control/transformation-templates/trn_example/versions/1/restore
curl -X POST "https://api.imgpanel.com/v1/control/transformation-templates/trn_example/versions/1/restore" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{}'

Collections

GETAssets

List team collections for asset picker/gallery flows.

collections:read
/control/r2/collections
curl -X GET "https://api.imgpanel.com/v1/control/r2/collections" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Create Cloudinary connection

POSTIntegrations

Save encrypted Cloudinary Admin API credentials for repeatable migration pulls. Responses only return safe hints; api_secret is never returned.

uploads:write
/control/r2/cloudinary/connections
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/connections" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "api_key": "1234567890",
  "api_secret": "encrypted-and-never-returned",
  "cloud_name": "demo",
  "default_prefix": "legacy/",
  "name": "Production Cloudinary"
}'

List Cloudinary connections

GETIntegrations

List saved Cloudinary connections for the workspace. Secrets are redacted.

uploads:read
/control/r2/cloudinary/connections
curl -X GET "https://api.imgpanel.com/v1/control/r2/cloudinary/connections" \
  -H "x-api-key: $IMGPANEL_API_KEY"

List Cloudinary-style resources

GETIntegrations

List ImgPanel R2 assets in a Cloudinary Resources API compatible shape. Supports max_results, next_cursor, prefix, tag, public_id, tags=false, context=false, and metadata=false.

uploads:read
/control/r2/cloudinary/resources?max_results=50&prefix=legacy/&tags=true&context=true&metadata=true
curl -X GET "https://api.imgpanel.com/v1/control/r2/cloudinary/resources?max_results=50&prefix=legacy/&tags=true&context=true&metadata=true" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Search Cloudinary-style resources

POSTIntegrations

Search ImgPanel R2 assets and return Cloudinary-style resources. Supports Cloudinary-like expression, tag/folder/metadata filters, cursor pagination, facets, and archive lifecycle counts.

uploads:readsearch:read
/control/r2/cloudinary/search
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/search" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "expression": "tags=hero AND metadata.campaign=summer",
  "max_results": 20,
  "sort_by": [
    {
      "uploaded_at": "desc"
    }
  ]
}'

Cloudinary-style upload

POSTIntegrations

Upload a remote image through a Cloudinary-like endpoint. Accepts multipart file uploads or JSON file/url remote uploads, maps upload_preset to ImgPanel presets, and returns a Cloudinary-style resource.

uploads:write
/control/r2/cloudinary/upload
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/upload" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "context": "alt=Homepage hero|caption=Uploaded through Cloudinary-compatible API",
  "file": "https://www.imgpanel.com/images/transform-sample.png",
  "overwrite": false,
  "public_id": "legacy/home/hero",
  "tags": "cloudinary,hero",
  "unique_filename": false,
  "upload_preset": "upr_example"
}'

Cloudinary-style resource detail

GETIntegrations

Read one Cloudinary-style resource by public_id, including metadata, derived assets, version snapshots, internal R2 key, and the current original/base source_url for download.

uploads:read
/control/r2/cloudinary/resources/detail?public_id=legacy/home/hero&archived=all
curl -X GET "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/detail?public_id=legacy/home/hero&archived=all" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Update Cloudinary-style resource

POSTIntegrations

Update Cloudinary-style resource context and metadata by public_id. Use metadata_mode=merge or replace.

uploads:write
/control/r2/cloudinary/resources/update
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/update" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "context": {
    "alt": "Homepage hero",
    "caption": "Imported from Cloudinary"
  },
  "metadata": {
    "campaign": "summer",
    "owner": "marketing"
  },
  "metadata_mode": "merge",
  "public_ids": [
    "legacy/home/hero"
  ]
}'

Delete Cloudinary-style resources

POSTIntegrations

Delete Cloudinary-style resources by public_id. mode=archive is the safe default; use mode=delete only for permanent R2 object deletion.

uploads:write
/control/r2/cloudinary/resources/delete
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/delete" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "mode": "archive",
  "public_ids": [
    "legacy/home/hero"
  ]
}'

Restore Cloudinary-style resources

POSTIntegrations

Restore previously archived Cloudinary-style resources by public_id. This is lifecycle recovery; use the dashboard asset Versions panel when you need to roll the current base file back to a snapshot.

uploads:write
/control/r2/cloudinary/resources/restore
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/restore" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "public_ids": [
    "legacy/home/hero"
  ]
}'

Rename Cloudinary-style resource

POSTIntegrations

Rename or move one Cloudinary-style resource by public_id. Updates the R2 object key, folder/filename metadata, and cloudinary_public_id.

uploads:write
/control/r2/cloudinary/resources/rename
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/rename" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "overwrite": false,
  "public_id": "legacy/home/hero",
  "to_public_id": "legacy/home/hero-renamed"
}'

List Cloudinary-style derived resources

GETIntegrations

List derived/generated resources for one Cloudinary-style public_id.

uploads:read
/control/r2/cloudinary/resources/derived?public_id=legacy/home/hero
curl -X GET "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/derived?public_id=legacy/home/hero" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Create Cloudinary-style derived resource

POSTIntegrations

Generate one derived/transformed resource for a Cloudinary-style public_id using ImgPanel transformation options or a transformation_template_id.

uploads:write
/control/r2/cloudinary/resources/derived
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/derived" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "options": {
    "format": "webp",
    "quality": 80,
    "width": 1200
  },
  "public_id": "legacy/home/hero",
  "tags": [
    "derived",
    "webp"
  ]
}'

Delete Cloudinary-style derived resource

DELETEIntegrations

Delete one derived/generated resource by derived_id, scoped to a Cloudinary-style public_id.

uploads:write
/control/r2/cloudinary/resources/derived?public_id=legacy/home/hero&derived_id=drv_example
curl -X DELETE "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/derived?public_id=legacy/home/hero&derived_id=drv_example" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{}'

List Cloudinary-style tags

GETIntegrations

List Cloudinary-style tags from the R2 asset index with counts and cursor pagination.

uploads:read
/control/r2/cloudinary/tags?max_results=100&prefix=hero
curl -X GET "https://api.imgpanel.com/v1/control/r2/cloudinary/tags?max_results=100&prefix=hero" \
  -H "x-api-key: $IMGPANEL_API_KEY"

List Cloudinary-style folders

GETIntegrations

List Cloudinary-style folders from ImgPanel R2 folder metadata. Pass path to list direct subfolders under a folder.

uploads:read
/control/r2/cloudinary/folders?path=legacy&max_results=100
curl -X GET "https://api.imgpanel.com/v1/control/r2/cloudinary/folders?path=legacy&max_results=100" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Add Cloudinary-style tag

POSTIntegrations

Add one or more tags to resources by Cloudinary public_id. Works for migrated Cloudinary assets and ImgPanel assets with folder/filename public IDs.

uploads:write
/control/r2/cloudinary/tags/add
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/tags/add" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "public_ids": [
    "legacy/home/hero"
  ],
  "tag": "approved"
}'

Remove Cloudinary-style tags

POSTIntegrations

Remove one or more tags from resources by Cloudinary public_id.

uploads:write
/control/r2/cloudinary/tags/remove
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/tags/remove" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "public_ids": [
    "legacy/home/hero"
  ],
  "tags": [
    "legacy",
    "needs-review"
  ]
}'

Remove all Cloudinary-style tags

POSTIntegrations

Remove all tags from resources by Cloudinary public_id.

uploads:write
/control/r2/cloudinary/tags/remove-all
curl -X POST "https://api.imgpanel.com/v1/control/r2/cloudinary/tags/remove-all" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "public_ids": [
    "legacy/home/hero"
  ]
}'

Create Cloudinary migration

POSTIntegrations

Create a step-run migration job from a Cloudinary-style asset manifest. Supports secure_url/url, public_id, folder, tags, metadata/context, upload presets, overwrite/unique_filename conflict policy, and retryable item failures.

uploads:write
/control/r2/migrations/cloudinary
curl -X POST "https://api.imgpanel.com/v1/control/r2/migrations/cloudinary" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "assets": [
    {
      "context": {
        "alt": "Hero image"
      },
      "folder": "legacy/home",
      "public_id": "legacy/home/hero",
      "secure_url": "https://res.cloudinary.com/demo/image/upload/sample.jpg",
      "tags": [
        "legacy",
        "hero"
      ]
    }
  ],
  "folder": "cloudinary-import",
  "metadata": {
    "migration_batch": "legacy-site"
  },
  "overwrite": false,
  "preset_id": "upr_example",
  "tags": [
    "cloudinary"
  ],
  "unique_filename": false
}'

Pull Cloudinary resources

POSTIntegrations

Pull image resources directly from the Cloudinary Admin API into a retryable ImgPanel migration job. Credentials are used only for the request and are not stored. Set overwrite or unique_filename to control public_id conflicts.

uploads:write
/control/r2/migrations/cloudinary/pull
curl -X POST "https://api.imgpanel.com/v1/control/r2/migrations/cloudinary/pull" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "api_key": "1234567890",
  "api_secret": "not-stored",
  "cloud_name": "demo",
  "folder": "cloudinary-import",
  "max_assets": 500,
  "overwrite": false,
  "page_size": 100,
  "prefix": "legacy/",
  "tags": [
    "cloudinary"
  ],
  "unique_filename": false
}'

Pull Cloudinary with connection

POSTIntegrations

Pull image resources from Cloudinary with a saved encrypted connection instead of sending api_key/api_secret every time. Uses the same overwrite and unique_filename conflict policy as manifest migrations.

uploads:write
/control/r2/migrations/cloudinary/pull
curl -X POST "https://api.imgpanel.com/v1/control/r2/migrations/cloudinary/pull" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{
  "connection_id": "cld_...",
  "folder": "cloudinary-import",
  "max_assets": 500,
  "overwrite": false,
  "page_size": 100,
  "prefix": "legacy/",
  "tags": [
    "cloudinary"
  ],
  "unique_filename": false
}'

Run migration step

POSTIntegrations

Process the next batch of a Cloudinary migration job and return progress, imported count, failed count, skipped count, and item statuses.

uploads:write
/control/r2/migrations/jobs/mjob_example/run
curl -X POST "https://api.imgpanel.com/v1/control/r2/migrations/jobs/mjob_example/run" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{}'

Migration job details

GETIntegrations

Read migration job progress and the first page of item statuses.

uploads:write
/control/r2/migrations/jobs/mjob_example
curl -X GET "https://api.imgpanel.com/v1/control/r2/migrations/jobs/mjob_example" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Retry failed migration items

POSTIntegrations

Requeue failed Cloudinary migration items so the next run step retries only failed imports.

uploads:write
/control/r2/migrations/jobs/mjob_example/retry-failed
curl -X POST "https://api.imgpanel.com/v1/control/r2/migrations/jobs/mjob_example/retry-failed" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  --data '{}'

Migration job logs

GETIntegrations

Read recent migration job logs, including run steps, imported items, failed items, retry events, and cancellation.

uploads:write
/control/r2/migrations/jobs/mjob_example/logs?limit=20
curl -X GET "https://api.imgpanel.com/v1/control/r2/migrations/jobs/mjob_example/logs?limit=20" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Webhook deliveries

GETIntegrations

Read recent signed webhook delivery attempts.

webhooks:read
/control/webhook-deliveries?limit=20
curl -X GET "https://api.imgpanel.com/v1/control/webhook-deliveries?limit=20" \
  -H "x-api-key: $IMGPANEL_API_KEY"

Transformation Parameters

Use Transform Studio, saved templates, or API options to generate Cloudflare-backed image variants.

OptionAliasesBehaviorExample
width`w`, `width`Resize output width in pixels.`w_800` -> `{ "width": 800 }`
height`h`, `height`Resize output height in pixels.`h_600` -> `{ "height": 600 }`
fit`c`, `crop`, `fit`Cloudflare fit mode: scale-down, contain, cover, crop, pad.`c_fill` -> `{ "fit": "cover" }`
gravity`g`, `gravity`Focus/crop gravity, including auto and focal-point workflows.`g_auto` -> `{ "gravity": "auto" }`
format`f`, `format`Change encoded format for derived assets and delivery URLs.`f_webp` -> `{ "format": "webp" }`
quality`q`, `quality`Compression quality or auto quality where supported.`q_80` -> `{ "quality": 80 }`
blur`e_blur`, `blur`Apply blur effect.`e_blur:20` -> `{ "blur": 20 }`
sharpen`e_sharpen`, `sharpen`Apply sharpening effect.`e_sharpen` -> `{ "sharpen": true }`
rotate`a`, `angle`, `rotate`Rotate or auto-orient output.`a_90` -> `{ "rotate": 90 }`
metadata`metadata`Control metadata stripping/preservation for delivery.`metadata=none`
curl "https://api.imgpanel.com/v1/control/r2/cloudinary/resources/derived" \
  -H "x-api-key: $IMGPANEL_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "public_id": "catalog/hero",
    "options": { "width": 1200, "format": "webp", "quality": 82 }
  }'
Named templates live in Transform Studio and can be applied from Library bulk actions or asset detail. Template versions are restorable, so production presets can be changed without losing the previous delivery recipe.

Annotation Export And Sharing

Transform Studio drawing tools export annotated images back to R2 as normal assets. Use the exported asset anywhere you use an uploaded file: collections, signed URLs, public delivery links, metadata review, or downstream derived jobs.

Open an R2 asset in Transform Studio.
Draw crop notes, arrows, labels, or review marks.
Export the annotated result to R2.
Share via collection, signed URL, or public delivery URL.

Website Grab Limits

Website grab is designed for controlled imports and competitive audits, not infinite crawls. Treat each run as an observable job with reviewable results.

Discovery

Crawler follows same-origin pages from the submitted URL and extracts image candidates from HTML, OpenGraph, CSS-backed sources where indexed, and direct image links.

Job control

Grab jobs are asynchronous. Use the dashboard job console or `/control/r2/grab/jobs` to review progress, cancel active jobs, and inspect messages.

Import policy

Only image MIME types accepted by the R2 upload pipeline are stored. Imports derive stable public IDs from source paths, so `overwrite` and `unique_filename` control repeat-import conflicts.

Operational limits

Keep each run scoped to a real site section. Very large sites should be split into multiple jobs so retries and review stay manageable.

External sites

Remote servers can block or rate-limit requests. ImgPanel records skipped URLs and failures in job messages instead of silently importing partial data.

Cloudinary Migration Cookbook

A practical cutover path from Cloudinary resources to ImgPanel R2 assets.

1. Save credentials

Create a Cloudinary connection in the dashboard with cloud name, API key, and API secret. Credentials are encrypted before storage.

2. Dry run with search

Use Cloudinary search or a small manifest first. Confirm folder mapping, tags, context metadata, and conflict policy before a large pull.

3. Start import job

Run a manifest import or Admin API pull. Choose whether public_id conflicts fail safely, overwrite existing R2 objects, or use unique_filename to keep both assets.

4. Watch the queue

Use Jobs to see running, completed, failed, cancelled, and retried work across Cloudinary migration, website grab, and derived assets.

5. Rebuild derivatives

Apply saved transformation templates from Library or asset detail when you need R2-hosted derived assets instead of Cloudinary derived URLs.

6. Cut over delivery

Update application URLs after rename/move checks. Keep Cloudinary available until signed/private URLs and cache behavior are verified in production.

Cloudinary SDK Facade

A small TypeScript helper for migration scripts that expect Cloudinary-shaped upload, admin, and search calls.

import { createImgPanelCloudinaryClient } from "@imgpanel/cloudinary-compat";

const cloudinary = createImgPanelCloudinaryClient({
  apiKey: process.env.IMGPANEL_API_KEY!
});

await cloudinary.uploader.upload("https://example.com/hero.jpg", {
  public_id: "legacy/home/hero",
  tags: "legacy,hero",
  overwrite: false
});

const search = await cloudinary.search
  .expression("tags=hero AND metadata.campaign=summer")
  .max_results(20)
  .execute();

The facade maps familiar methods like `uploader.upload`, `uploader.rename`, `api.resources`, `api.resource`, `api.delete_resources`, `api.restore`, and `search.expression().execute()` to ImgPanel's Cloudinary-compatible R2 endpoints.

The monorepo package entrypoint is `@imgpanel/cloudinary-compat`; it stays private until a public npm package name and release policy are chosen.

It is intentionally server-side and API-key based. It does not emulate Cloudinary delivery URL signing, client widgets, video APIs, add-ons, or billing/account APIs.

Cache Attribution

Cloudflare GraphQL and Logpush setup notes for CDN hit/miss reporting.

query ImgPanelCacheAttribution($zoneTag: string, $from: string, $to: string) {
  viewer {
    zones(filter: { zoneTag: $zoneTag }) {
      httpRequestsAdaptiveGroups(
        filter: {
          datetime_geq: $from
          datetime_lt: $to
        }
        limit: 100
      ) {
        dimensions {
          cacheStatus
          clientRequestPath
          coloCode
        }
        sum {
          requests
          edgeResponseBytes
        }
      }
    }
  }
}

ImgPanel Usage currently records Worker/R2 delivery requests. To split CDN hit, miss, revalidated, and colo-level traffic, configure a Cloudflare zone id and an API token with Analytics Read.

GraphQL is the low-latency dashboard path. Set `CLOUDFLARE_ZONE_ID` and `CLOUDFLARE_ANALYTICS_API_TOKEN` (or `CLOUDFLARE_API_TOKEN`) on the API Worker, then `/control/r2/stats` returns `cache_attribution` buckets for cache status, colo, and top paths.

Logpush `http_requests` remains the raw archive path for long retention, replay, and warehouse analysis.

Required mapping: delivery hostname to Cloudflare zone to asset path/public_id. Keep tokens server-side and never expose them to browser code.

Scopes

Give each integration the smallest useful scope set. Matching write scopes imply read access, for example `assets:write` can also read assets.

assets:read

List, inspect, sign, and export assets.

assets:write

Update metadata, folders, tags, favorites, and derived asset links.

assets:delete

Delete, archive, restore, and purge assets.

uploads:write

Upload files, import URLs, issue direct-upload tokens, and run grab jobs.

search:read

Run Cloudinary-style expression search over indexed assets.

folders:read

Read folder lists and counts.

collections:read

Read collections and collection assets.

collections:write

Create, update, share, and manage collection assets.

transforms:read

Read transformation templates and derived assets.

transforms:write

Create templates and generate derived assets.

webhooks:read

Read webhook endpoints and delivery logs.

webhooks:write

Create, test, rotate, and delete webhook endpoints.

admin:read

Read workspace context, stats, activity, and account-level config.

admin:write

Manage workspace-level configuration.

Errors

401 unauthorizedNo dashboard session or API key was provided.
401 invalid_api_keyThe supplied `ik_live_...` key does not match any stored hash.
401 api_key_disabledThe key is disabled or revoked.
401 api_key_expiredThe key expiration date is in the past.
403 missing_api_key_scopeThe key exists, but does not include a required scope for this endpoint.
403 session_requiredThis route is dashboard-only and cannot be called with an API key.