# img.pro — Full API Reference > Image hosting API with global CDN. Upload or import images, organize with namespaces and tags, resize and convert on the fly. Base URL: `https://api.img.pro` Authentication: `Authorization: Bearer ` header. Some endpoints work without auth (anonymous mode). ## Getting Started To use img.pro as an AI agent or in an automated workflow: 1. Create an API key: `POST /v1/keys` with an email address (no browser needed) 2. Save the key from the response — it's shown once 3. Upload images with `POST /v1/upload` or `POST /v1/import` 4. Verify the email (one-click link) to unlock permanent storage The key is active immediately. Unverified keys have 30-day retention; verification makes storage permanent. --- ## POST /v1/keys — Create API Key No authentication required. Creates an account and API key in one request. Request: ```json POST https://api.img.pro/v1/keys Content-Type: application/json { "email": "you@company.com", "name": "my-agent" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | email | string | Yes | Valid email address | | name | string | No | Key name (default: "Default") | Response (201): ```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." } ``` Errors: - `422 validation_error` — Invalid email - `403 key_limit_reached` — Max 3 keys for unverified accounts (action: verify email) - `429 rate_limited` — 5/email/hour, 20/IP/hour (action: wait) - `403 registration_closed` — Team has closed registration (action: redirect to dashboard) --- ## POST /v1/upload — Upload Image Accepts multipart form data. Works with or without authentication. Request: ```bash curl -X POST https://api.img.pro/v1/upload \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@photo.jpg" \ -F "name=My Photo" \ -F "tags=hero,homepage" \ -F "namespace=my-project" \ -F "public=true" ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | file | binary | Yes | Image file (JPEG, PNG, WebP, GIF, SVG, HEIC, AVIF, TIFF, BMP) | | name | string | No | Display name (default: filename) | | tags | string | No | Comma-separated, max 10 tags, each max 50 chars | | namespace | string | No | Project grouping (2-64 chars, lowercase + hyphens) | | public | boolean | No | Show on viewer page (default: true) | | external_id | string | No | Your unique ID — prevents duplicate uploads per team | | date | string | No | ISO 8601 or Unix timestamp for custom ordering | | ttl | integer | No | Seconds until expiry (min 3600). Authenticated only. | | metadata | JSON | No | Arbitrary key-value pairs (or send as top-level form fields) | Response (201): ```json { "id": "abc123", "name": "photo.jpg", "url": "https://img.pro/abc123", "src": "https://src.img.pro/t/abc123.jpeg", "width": 1920, "height": 1080, "filesize": 245678, "extension": "jpeg", "editable": true, "tags": ["hero", "homepage"], "namespace": "my-project", "public": true, "created_at": 1704067200 } ``` Anonymous uploads also include: - `expires_at` — Unix timestamp when the image will be deleted - `upgrade` — Object with `type`, `url`, `message`, `label` guiding to registration File size limits: - Anonymous: 20 MB - Authenticated: 70 MB (10 MB for SVG) Custom metadata: Any form field not in the reserved list above is stored as metadata. Example: `-F "author=Jane Doe" -F "license=cc-by-4.0"`. These appear at the top level of the response. Errors: - `400 missing_file` — No file in request - `400 file_too_large` — Exceeds size limit - `400 unsupported_format` — Not a supported image type - `409 duplicate` — `external_id` already exists (returns existing media) - `413 quota_exceeded` — Monthly upload or storage quota reached (action: upgrade) - `429 rate_limited` — Too many requests (action: wait) --- ## POST /v1/import — Import from URL Fetches an image from a URL and stores it. Same response format as upload. Request: ```json POST https://api.img.pro/v1/import Authorization: Bearer YOUR_API_KEY Content-Type: application/json { "url": "https://example.com/photo.jpg", "name": "Imported Photo", "tags": "landscape,nature", "namespace": "my-project", "public": true, "external_id": "ext-123", "metadata": { "source": "unsplash", "author": "Jane Doe" } } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | url | string | Yes | URL of image to import (must return an image content type) | All other fields are the same as upload. Response format is identical. Additional errors: - `400 missing_url` — No URL provided - `400 fetch_failed` — Could not fetch the URL - `400 unsupported_format` — URL did not return a supported image type --- ## GET /v1/media — List Images Authentication required. Request: ```bash curl https://api.img.pro/v1/media?namespace=my-project&tags=hero&limit=20 \ -H "Authorization: Bearer YOUR_API_KEY" ``` | Param | Type | Description | |-------|------|-------------| | namespace | string | Filter by namespace | | tags | string | Comma-separated tags to filter by | | tag_mode | string | `all` (default) or `any` — require all tags or any | | cursor | string | Pagination cursor from previous response | | limit | integer | Results per page (1-100, default 50) | Response (200): ```json { "items": [ { "id": "abc123", "name": "photo.jpg", "url": "https://img.pro/abc123", "src": "https://src.img.pro/t/abc123.jpeg", "width": 1920, "height": 1080, "filesize": 245678, "tags": ["hero", "homepage"], "namespace": "my-project", "public": true, "created_at": 1704067200 } ], "cursor": "eyJpZCI6MTIzfQ", "has_more": true } ``` --- ## GET /v1/media/:id — Get Image Details Works without authentication for public images. With auth, returns any image owned by the team. Request: ```bash curl https://api.img.pro/v1/media/abc123 \ -H "Authorization: Bearer YOUR_API_KEY" ``` Response (200): Same shape as a single item from the list endpoint, plus metadata fields at the top level. Errors: - `404 not_found` — Image doesn't exist or isn't accessible --- ## PATCH /v1/media/:id — Update Image Authentication required. Request: ```json PATCH https://api.img.pro/v1/media/abc123 Authorization: Bearer YOUR_API_KEY Content-Type: application/json { "name": "Updated Name", "tags": "new-tag,another", "namespace": "new-project", "public": false, "date": "2025-06-15", "external_id": "ext-456", "metadata": { "author": "Updated Author", "old_field": null } } ``` All fields are optional. Set metadata keys to `null` to remove them. Response (200): Updated media object. --- ## DELETE /v1/media/:id — Delete Image Authentication required. Soft delete — image is marked deleted but can't be recovered via API. Request: ```bash curl -X DELETE https://api.img.pro/v1/media/abc123 \ -H "Authorization: Bearer YOUR_API_KEY" ``` Response (200): ```json { "deleted": true } ``` --- ## PATCH /v1/media/batch — Batch Update Authentication required. Update up to 100 images at once. Request: ```json PATCH https://api.img.pro/v1/media/batch Authorization: Bearer YOUR_API_KEY Content-Type: application/json { "ids": ["abc123", "def456", "ghi789"], "update": { "tags": "new-tag", "tag_mode": "add", "namespace": "my-project", "public": true } } ``` | Field | Type | Description | |-------|------|-------------| | ids | string[] | Image IDs to update (max 100) | | update.tags | string | Tags to apply | | update.tag_mode | string | `replace` (default), `add`, or `remove` | | update.namespace | string | Set namespace | | update.public | boolean | Set visibility | Response (200): ```json { "updated": 3 } ``` --- ## DELETE /v1/media/batch — Batch Delete Authentication required. Request: ```json DELETE https://api.img.pro/v1/media/batch Authorization: Bearer YOUR_API_KEY Content-Type: application/json { "ids": ["abc123", "def456"] } ``` Or delete by namespace: ```json { "namespace": "old-project" } ``` Response (200): ```json { "deleted": 2 } ``` --- ## GET /v1/usage — Check Quota Authentication required. Request: ```bash curl https://api.img.pro/v1/usage \ -H "Authorization: Bearer YOUR_API_KEY" ``` Response (200): ```json { "plan": "free", "uploads_used": 42, "uploads_limit": 100, "uploads_remaining": 58, "storage_used_bytes": 1073741824, "storage_limit_bytes": 5368709120, "resets_at": 1706745600, "verified": true } ``` --- ## GET /v1/namespaces — List Namespaces Authentication required. Returns namespace aggregations. Request: ```bash curl https://api.img.pro/v1/namespaces \ -H "Authorization: Bearer YOUR_API_KEY" ``` Response (200): ```json { "namespaces": [ { "namespace": "my-project", "count": 150, "storage_bytes": 52428800 }, { "namespace": "blog-posts", "count": 42, "storage_bytes": 15728640 } ] } ``` --- ## CDN Transforms Base URL: `https://src.img.pro/{team_hash}/{uid}.{extension}` Resize with `?size=` query parameter: | Size | Short edge | Use case | |------|-----------|----------| | s | 320px | Thumbnails, icons | | m | 640px | Cards, mobile | | l | 1080px | Full display | | (none) | Original | Full resolution | Convert format by changing the file extension: - `.jpeg` / `.jpg` — JPEG - `.png` — PNG - `.webp` — WebP (best compression) - `.avif` — AVIF Sizes constrain the short edge (minimum dimension), preserving aspect ratio. Images smaller than the target size are never upscaled. All transforms are cached globally at the edge. --- ## Plans & Pricing | Plan | Uploads/mo | Storage | Retention | Max file | Price | |------|-----------|---------|-----------|----------|-------| | Anonymous | 20/hr, 100/day per IP | Shared | 30 days | 20 MB | Free | | Unverified key | 100/mo | 5 GB | 30 days | 70 MB | Free | | Free (verified) | 100/mo | 5 GB | Permanent | 70 MB | Free | | Pro | 1,000/mo | 50 GB | Permanent | 70 MB | $10/mo | | Scale | 10,000/mo | 500 GB | Permanent | 70 MB | $49/mo | | Max | 100,000/mo | 5 TB | Permanent | 70 MB | $299/mo | Quotas reset on the first of each month. Upgrade links are signed one-click URLs (no login required) — included in quota error responses. --- ## Error Format Every error response follows this shape: ```json { "error": "error_code", "message": "Human-readable description", "action": { "type": "upgrade | verify | wait | redirect", "url": "https://...", "label": "Button text", "seconds": 3600 } } ``` | Action type | When | What to do | |------------|------|------------| | upgrade | Quota reached | Show `action.url` to user — signed upgrade link | | verify | Email not verified | Show `action.url` to user — verification link | | wait | Rate limited | Retry after `action.seconds`. Also check `Retry-After` header | | redirect | Registration closed | Direct user to `action.url` | Common error codes: - `validation_error` (422) — Invalid input, check `errors` object for field-level details - `unauthorized` (401) — Missing or invalid API key - `forbidden` (403) — Key doesn't have required ability (read/write) - `not_found` (404) — Resource doesn't exist or isn't accessible - `duplicate` (409) — `external_id` already exists (response includes existing media) - `quota_exceeded` (413) — Upload or storage quota reached - `rate_limited` (429) — Too many requests - `file_too_large` (400) — File exceeds size limit - `unsupported_format` (400) — Not a supported image type --- ## Quota Headers Authenticated upload/import responses include these headers: ``` X-Monthly-Uploads-Used: 42 X-Monthly-Uploads-Limit: 100 X-Monthly-Uploads-Remaining: 58 X-Storage-Used: 1073741824 X-Storage-Limit: 5368709120 X-Storage-Remaining: 4294967296 ``` Anonymous responses include rate limit headers: ``` X-RateLimit-Limit: 20 X-RateLimit-Remaining: 18 X-RateLimit-Reset: 1704070800 X-RateLimit-Limit-Daily: 100 X-RateLimit-Remaining-Daily: 95 ``` --- ## Links - Interactive docs: https://img.pro/api - Quick start guide: https://img.pro/api/quick-start - API reference: https://img.pro/api/reference - Error reference: https://img.pro/api/errors - AI agents guide: https://img.pro/api/ai-agents - OpenAPI spec: https://img.pro/openapi.yaml - Pricing: https://img.pro/pricing