Quick Start
API Reference
Error Reference
AI Agents

API Reference

Complete reference for the img.pro REST API. All requests go to https://api.img.pro/v1.

Authentication

Include your API key as a Bearer token:

http
Authorization: Bearer YOUR_API_KEY

Create keys from the dashboard or via POST /v1/keys.

The upload and import endpoints work without authentication. Anonymous uploads expire after 30 days and are rate limited by IP (20/hour, 100/day). Include a Bearer token for ownership, permanent storage, and higher limits.

Keep your API key secret

Never expose your API key in client-side code or public repositories.

POST /v1/keys

Create an API key. No authentication required.

Parameter Type Required Description
email string Yes Email address for the account
name string No Label for this key (default: "Default")
Request
bash
curl -X POST "https://api.img.pro/v1/keys" \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com", "name": "my-agent"}'
Response
json
{
  "key": "img_live_abc123...",
  "key_id": "key_7x9k2m",
  "status": "active",
  "verified": false,
  "limits": {
    "uploads": 100,
    "storage_mb": 5120,
    "retention_days": 30
  },
  "message": "Key active. Check your email to verify and unlock permanent storage."
}

Rate limited to 5 per email/hour and 20 per IP/hour.

POST /v1/upload

Upload an image file. Authentication optional.

Parameter Type Required Description
file File Yes Image file. Supported: JPEG, PNG, GIF, WebP, AVIF, HEIC, SVG, BMP, ICO. Max 70 MB (10 MB for SVG, 20 MB anonymous).
tags string No Comma-separated tags (max 10, each up to 50 chars)
namespace string No Project identifier (2–64 chars, lowercase alphanumeric + hyphens)
public string No true (default) or false. Controls whether the viewer page (url) is publicly accessible. The CDN src URL is always available to the owner.
ttl string No Time-to-live: seconds (e.g., 3600) or duration (5m90d). Omit for permanent (verified accounts).
name string No Override filename
date string No Editorial display date (e.g., 2024-01-15)
external_id string No Dedup key — unique per team. If a non-expired record with this ID exists, it is returned instead (HTTP 200).
any field string No Any other field is stored as custom metadata (e.g., description, author, license)

Authenticated

Request
bash
curl -X POST "https://api.img.pro/v1/upload" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@photo.jpg" \
  -F "tags=hero,homepage" \
  -F "namespace=my-project"
Response
json
{
  "id": "abc123",
  "name": "photo.jpg",
  "url": "https://img.pro/abc123",
  "src": "https://src.img.pro/xyz/abc123.jpeg",
  "width": 1920,
  "height": 1080,
  "filesize": 245678,
  "editable": true,
  "tags": ["hero", "homepage"],
  "namespace": "my-project",
  "created_at": 1704067200
}

Anonymous (no auth)

Request
bash
curl -X POST "https://api.img.pro/v1/upload" \
  -F "file=@photo.jpg"

Same response shape, plus expires_at (30-day TTL) and an upgrade hint:

Response
json
{
  "id": "abc123",
  "name": "photo.jpg",
  "url": "https://img.pro/abc123",
  "src": "https://src.img.pro/xyz/abc123.jpeg",
  "width": 1920,
  "height": 1080,
  "filesize": 245678,
  "expires_at": 1706659200,
  "created_at": 1704067200,
  "upgrade": {
    "type": "register",
    "url": "https://img.pro/api/quick-start",
    "message": "Get an API key for permanent storage and 100 uploads/month",
    "label": "Create API Key"
  }
}
url is present when the image is public (the default). status ("processing" or "failed") appears only when the image is not yet ready — otherwise omitted. Optional fields (tags, namespace, editable, expires_at, sizes) are omitted when not applicable.

POST /v1/import

Import an image from a URL. Authentication optional. Same parameters as upload except file is replaced by url.

Parameter Type Required Description
url string Yes Image URL to import (must be publicly accessible, 30s timeout)
tags string No Comma-separated tags
namespace string No Project identifier
public boolean No Show public viewer page (default: true)
ttl string No Time-to-live (e.g., 7d)
name string No Override filename (default: extracted from URL)
date string No Editorial display date
external_id string No Dedup key — unique per team
any field string No Stored as custom metadata
Request
bash
curl -X POST "https://api.img.pro/v1/import" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/image.jpg",
    "namespace": "imports",
    "description": "Imported from example.com"
  }'
Response
json
{
  "id": "def456",
  "name": "image.jpg",
  "url": "https://img.pro/def456",
  "src": "https://src.img.pro/xyz/def456.jpeg",
  "width": 2560,
  "height": 1440,
  "filesize": 384291,
  "editable": true,
  "namespace": "imports",
  "description": "Imported from example.com",
  "source_url": "https://example.com/image.jpg",
  "created_at": 1704067200
}

source_url is set automatically to the imported URL. If external_id matches an existing non-expired record, the existing record is returned (HTTP 200, X-Deduplicated: true).

GET /v1/media

List media with filtering and cursor-based pagination.

Parameter Type Required Description
namespace string No Filter by namespace
tags string No Filter by tags (comma-separated)
tag_mode string No any (default) or all
ids string No Comma-separated IDs to fetch specific items
limit integer No 1–100 (default: 50)
cursor string No Pagination cursor from previous response
Request
bash
curl "https://api.img.pro/v1/media?namespace=my-project&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
json
{
  "data": [
    {
      "id": "abc123",
      "name": "photo.jpg",
      "url": "https://img.pro/abc123",
      "src": "https://src.img.pro/xyz/abc123.jpeg",
      "width": 1920,
      "height": 1080,
      "filesize": 245678,
      "created_at": 1704067200
    }
  ],
  "next_cursor": "eyJpZCI6MTIzfQ",
  "has_more": true
}

GET /v1/media/:id

Get a single media item. Public media can be fetched without authentication.

Request
bash
curl "https://api.img.pro/v1/media/abc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
json
{
  "id": "abc123",
  "name": "photo.jpg",
  "url": "https://img.pro/abc123",
  "src": "https://src.img.pro/xyz/abc123.jpeg",
  "width": 1920,
  "height": 1080,
  "filesize": 245678,
  "editable": true,
  "tags": ["hero", "homepage"],
  "namespace": "my-project",
  "created_at": 1704067200
}

PATCH /v1/media/:id

Update metadata. Fields are merged — send null to remove a custom field.

Parameter Type Required Description
name string No Update filename
date string No Editorial display date
tags array No Replace tags (array of strings)
public boolean No Toggle public viewer page. CDN src is unaffected.
namespace string No Move to different namespace
external_id string No Dedup key
any field string|null No Merged with existing metadata. null removes the field.
Request
bash
curl -X PATCH "https://api.img.pro/v1/media/abc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tags": ["featured"], "description": "Updated caption", "camera": null}'
Response
json
{
  "id": "abc123",
  "name": "photo.jpg",
  "url": "https://img.pro/abc123",
  "src": "https://src.img.pro/xyz/abc123.jpeg",
  "width": 1920,
  "height": 1080,
  "filesize": 245678,
  "editable": true,
  "tags": ["featured"],
  "namespace": "my-project",
  "description": "Updated caption",
  "created_at": 1704067200
}

DELETE /v1/media/:id

Permanently delete a media item and all its CDN variants. Returns 204 No Content.

Request
bash
curl -X DELETE "https://api.img.pro/v1/media/abc123" \
  -H "Authorization: Bearer YOUR_API_KEY"

Batch Operations

PATCH /v1/media/batch

Update tags on up to 100 items at once.

Request
bash
curl -X PATCH "https://api.img.pro/v1/media/batch" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["abc123", "def456"], "tags": ["processed"], "tag_mode": "add"}'
Response
json
{
  "updated": 2
}

DELETE /v1/media/batch

Delete up to 100 items by ID, or all items in a namespace (up to 1,000).

Request
bash
# By IDs
curl -X DELETE "https://api.img.pro/v1/media/batch" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["abc123", "def456"]}'

# By namespace (deletes ALL items in namespace)
curl -X DELETE "https://api.img.pro/v1/media/batch" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"namespace": "old-project"}'
Response
json
{
  "deleted": 2
}

Namespace deletion is permanent

Deleting by namespace removes all images in that namespace. This cannot be undone.

GET /v1/usage

Current quota and usage statistics.

Request
bash
curl "https://api.img.pro/v1/usage" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
json
{
  "monthly": {
    "uploads": 42,
    "uploads_limit": 100,
    "uploads_remaining": 58,
    "resets_at": 1709251200
  },
  "totals": {
    "media_stored": 142,
    "storage_used_bytes": 52428800,
    "storage_limit_bytes": 5368709120,
    "storage_remaining_bytes": 5316280320
  },
  "plan": "free",
  "verified": true
}

GET /v1/namespaces

List namespaces with item counts and storage usage.

Request
bash
curl "https://api.img.pro/v1/namespaces" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
json
{
  "data": [
    { "namespace": "project-beta", "count": 128, "storage_bytes": 52428800 },
    { "namespace": "project-alpha", "count": 45, "storage_bytes": 12345678 }
  ],
  "next_offset": 100
}

CDN

Every upload returns a src URL on Cloudflare's global CDN:

text
https://src.img.pro/{team_hash}/{uid}.{format}?size={s|m|l}

Sizes

The ?size= parameter constrains the short edge (minimum dimension), preserving aspect ratio. Images smaller than the target are never upscaled.

Param Short Edge Use Case
?size=s 320px Thumbnails, avatars
?size=m 640px Cards, previews, mobile
?size=l 1080px Full display, hero images
(omit) Original Source file, no transform

Formats

Change the file extension to convert on the fly:

  • .webp — Best compression for web (recommended)
  • .jpg — Universal compatibility
  • .png — Lossless, supports transparency
  • .avif — Next-gen compression (smaller than WebP)
  • .gif — Animated images

Format conversion happens at the edge and is cached after the first request.

Custom Fields

Any field you send that isn't a reserved name is stored as metadata.

Set fields

Include them alongside other parameters — no wrapper needed:

Request
bash
curl -X POST "https://api.img.pro/v1/upload" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@photo.jpg" \
  -F "description=Sunset over the Pacific" \
  -F "author=Jane Doe" \
  -F "license=cc-by-4.0"

Read fields

Custom fields appear at the top level of the response — what you send is what you get back:

Response
json
{
  "id": "abc123",
  "name": "photo.jpg",
  "url": "https://img.pro/abc123",
  "src": "https://src.img.pro/xyz/abc123.jpeg",
  "description": "Sunset over the Pacific",
  "author": "Jane Doe",
  "license": "cc-by-4.0",
  "created_at": 1704067200
}

Update fields

PATCH merges with existing metadata. Set a field to null to remove it:

Request
bash
curl -X PATCH "https://api.img.pro/v1/media/abc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"alt_text": "A vivid sunset", "camera": null}'

Reserved fields

These names are used by the system and silently ignored if sent as custom fields:
id name url src width height filesize status tags namespace external_id date editable expires_at created_at sizes public ttl file

Errors

Every error includes a machine-readable error code and a human-readable message. Some include an action object — see the Error Reference for full details.

401unauthorizedMissing or invalid API key
403forbiddenKey lacks required permission
403quota_exceededUpload or storage limit reached
403registration_closedAccount does not accept new keys
403key_limit_reachedUnverified: max 3 active keys
404not_foundMedia item not found
409external_id_duplicateAnother item uses this external_id
422validation_errorInvalid parameters (per-field errors)
429rate_limitedToo many requests
500upload_failedServer error during upload
500import_failedServer error during import
502fetch_failedCould not fetch import URL

Quota Headers

Every authenticated response includes quota information in headers:

X-Monthly-Uploads-UsedUploads used this billing period
X-Monthly-Uploads-LimitMonthly upload limit
X-Monthly-Uploads-RemainingUploads remaining
X-Storage-UsedStorage used (bytes)
X-Storage-LimitStorage limit (bytes)
X-Storage-RemainingStorage remaining (bytes)