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:
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
POST /v1/keys
Create an API key. No authentication required.
| Parameter | Type | Required | Description |
|---|---|---|---|
| string | Yes | Email address for the account | |
| name | string | No | Label for this key (default: "Default") |
curl -X POST "https://api.img.pro/v1/keys" \
-H "Content-Type: application/json" \
-d '{"email": "dev@example.com", "name": "my-agent"}'
{
"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 (5m–90d). 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
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"
{
"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)
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:
{
"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 |
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"
}'
{
"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 |
curl "https://api.img.pro/v1/media?namespace=my-project&limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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.
curl "https://api.img.pro/v1/media/abc123" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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. |
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}'
{
"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.
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.
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"}'
{
"updated": 2
}
DELETE /v1/media/batch
Delete up to 100 items by ID, or all items in a namespace (up to 1,000).
# 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"}'
{
"deleted": 2
}
Namespace deletion is permanent
GET /v1/usage
Current quota and usage statistics.
curl "https://api.img.pro/v1/usage" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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.
curl "https://api.img.pro/v1/namespaces" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"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:
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:
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:
{
"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:
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.
unauthorizedMissing or invalid API keyforbiddenKey lacks required permissionquota_exceededUpload or storage limit reachedregistration_closedAccount does not accept new keyskey_limit_reachedUnverified: max 3 active keysnot_foundMedia item not foundexternal_id_duplicateAnother item uses this external_idvalidation_errorInvalid parameters (per-field errors)rate_limitedToo many requestsupload_failedServer error during uploadimport_failedServer error during importfetch_failedCould not fetch import URLQuota Headers
Every authenticated response includes quota information in headers:
X-Monthly-Uploads-UsedUploads used this billing periodX-Monthly-Uploads-LimitMonthly upload limitX-Monthly-Uploads-RemainingUploads remainingX-Storage-UsedStorage used (bytes)X-Storage-LimitStorage limit (bytes)X-Storage-RemainingStorage remaining (bytes)