ImageExtract REST API
Authenticate with a session cookie (browser) or an API key via the Authorization header.
Authentication
Most endpoints accept either a valid Supabase session cookie (set automatically in the browser) or an API key passed in the Authorization header.
Authorization: Bearer ie_live_<your-api-key>
API keys are created in the Dashboard and stored as SHA-256 hashes — only the prefix is ever shown again.
Webhook Signature Verification
Outbound webhooks include X-ImageExtract-Signature, X-ImageExtract-Timestamp, and X-ImageExtract-Delivery-Id. The signature is HMAC-SHA256 over timestamp + "." + raw_body.
// Node.js example
import crypto from 'node:crypto'
export function verifyImageExtractWebhook(secret, timestamp, rawBody, signature) {
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}# Python example
import hmac
import hashlib
def verify_imageextract_webhook(secret, timestamp, raw_body, signature):
payload = f"{timestamp}.{raw_body}".encode("utf-8")
expected = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Account
/api/v1/accountAuth requiredFetch the authenticated account summary and membership role.
Response
200 OK
{
"id": "acc_...",
"name": "Acme",
"slug": "acme",
"plan": "pro",
"status": "active",
"membership_role": "owner"
}Error codes
401Unauthorized/api/v1/account/creditsAuth requiredFetch the account-level credit balance used by the API platform.
Response
200 OK
{
"account_id": "acc_...",
"plan": "pro",
"credits": 487
}Error codes
401Unauthorized/api/v1/account/credits/ledgerAuth requiredFetch recent account credit ledger entries.
Request
// Query params: ?limit=50
Response
200 OK
{
"account_id": "acc_...",
"entries": [
{
"entry_type": "job_debit",
"amount": -5,
"balance_after": 482
}
]
}Error codes
401Unauthorized/api/v1/account/limitsAuth requiredFetch account plan limits and platform caps.
Response
200 OK
{
"account_id": "acc_...",
"limits": {
"plan": "pro",
"credits_included": 5000,
"daily_credit_cap": -1,
"api_access": true,
"ads_enabled": false,
"bulk_max_urls": 5,
"document_max_files": 5,
"document_max_mb": 50,
"url_worker_concurrency": 10
}
}Error codes
401Unauthorized/api/v1/account/usageAuth requiredFetch account usage rollup over a time window.
Request
// Query params: ?since=2026-04-01T00:00:00Z&until=2026-04-30T23:59:59Z
Response
200 OK
{
"account_id": "acc_...",
"jobs_total": 42,
"credits_debited": 68,
"images_extracted": 912
}Error codes
401UnauthorizedProjects
/api/v1/projectsAuth requiredList projects for the authenticated account.
Response
200 OK
{
"projects": [
{
"id": "proj_...",
"name": "Default",
"slug": "default"
}
]
}Error codes
401Unauthorized/api/v1/projectsAuth requiredCreate a new project within the authenticated account.
Request
{
"name": "Marketing site",
"site_url": "https://example.com",
"default_webhook_url": "https://example.com/webhooks/imageextract",
"slug": "marketing-site"
}Response
201 Created
{
"id": "...",
"account_id": "...",
"name": "Marketing site",
"slug": "marketing-site"
}Error codes
400Validation error403Forbidden/api/v1/projects/:idAuth requiredFetch a single project.
Response
200 OK
{
"id": "...",
"name": "Marketing site",
"slug": "marketing-site"
}Error codes
404Project not found/api/v1/projects/:idAuth requiredUpdate a project.
Request
{
"name": "Marketing site v2"
}Response
200 OK
{
"id": "...",
"name": "Marketing site v2"
}Error codes
400Validation error403Forbidden/api/v1/projects/:idAuth requiredArchive a project.
Response
200 OK
{
"id": "...",
"status": "archived"
}Error codes
403ForbiddenProject Keys
/api/v1/projects/:id/keysAuth requiredList project API keys.
Response
200 OK
{
"project_id": "...",
"keys": [
{
"id": "...",
"key_prefix": "ie_live_abcd...",
"status": "active"
}
]
}Error codes
404Project not found/api/v1/projects/:id/keysAuth requiredCreate a new project API key. Full key is returned once.
Request
{
"label": "CI integration"
}Response
201 Created
{
"key": "ie_live_...",
"row": {
"id": "...",
"key_prefix": "ie_live_abcd..."
}
}Error codes
403Forbidden/api/v1/projects/:id/keys/:keyId/revokeAuth requiredRevoke an existing project API key.
Response
200 OK
{ "revoked": true }Error codes
403Forbidden/api/v1/projects/:id/keys/:keyId/rotateAuth requiredRotate a project API key by creating a replacement and revoking the previous key.
Response
200 OK
{
"key": "ie_live_...",
"previous_key_id": "...",
"row": {
"id": "...",
"key_prefix": "ie_live_abcd..."
}
}Error codes
403ForbiddenExtraction
/api/v1/jobsSubmit a URL or bulk extraction job as JSON, or upload document files as multipart form data. Supports Idempotency-Key for safe retries.
Request
// Optional header:
Idempotency-Key: 6f4a2c0a-...
{
"type": "url",
"input": "https://example.com"
}
// Bulk (up to 5 URLs):
{
"type": "bulk",
"input": ["https://a.com", "https://b.com"]
}
// Document upload (authenticated, multipart/form-data):
files: <binary file 1>
files: <binary file 2>
... up to 5 files total, 50MB total
}Response
202 Accepted
{
"job_id": "550e8400-e29b-41d4-..."
}Error codes
400Invalid payload or SSRF-blocked URL409Idempotency key reused with a different request body402Insufficient credits429Rate limit exceeded — check Retry-After headerJobs
/api/v1/jobs/:idAuth requiredFetch job status and grouped progress summary. For bulk and document jobs this includes group counts and source-group summaries.
Response
200 OK
{
"id": "550e8400-...",
"type": "bulk",
"status": "processing",
"credits_used": 2,
"image_count": 18,
"partial": true,
"completed_groups": 1,
"total_groups": 2,
"groups": [
{
"id": "9f2d...",
"label": "https://example.com",
"kind": "url",
"status": "done",
"image_count": 18,
"duration_ms": 9230
}
]
}Error codes
404Job not found/api/v1/jobs/:id/itemsAuth requiredFetch canonical grouped job items for bulk or multi-document jobs. Each item tracks item-level status, timing, errors, and image counts.
Response
200 OK
{
"job_id": "550e8400-...",
"items": [
{
"id": "f6cb...",
"kind": "document",
"position": 0,
"source_label": "slides.pptx",
"status": "done",
"image_count": 12,
"duration_ms": 3101,
"error_code": null,
"error_message": null
}
]
}Error codes
404Job not found/api/v1/jobs/:id/streamAuth requiredServer-Sent Events stream for real-time job progress. Use this for the web app; external integrations should prefer polling or webhooks.
Response
text/event-stream
data: {"status":"queued"}
data: {"status":"processing","progress":42}
data: {"status":"done","result_url":"/api/v1/jobs/:id/result"}
data: {"status":"failed","error":"Timeout"}Error codes
403Job belongs to a different user404Job not found/api/v1/jobs/:id/resultAuth requiredFetch the full extraction result (ImageResult[]) for a completed job.
Response
200 OK
{
"job_id": "550e8400-...",
"type": "url",
"image_count": 14,
"credits_used": 1,
"images": [
{
"id": "...",
"url": "https://...image-url-or-presigned-r2...",
"source_url": "https://example.com/hero.jpg",
"source_kind": "web",
"filename": "hero.jpg",
"width": 1920,
"height": 1080,
"aspect_ratio": 1.778,
"format": "jpeg",
"file_size": 245120,
"color_space": "srgb",
"has_alpha": false,
"alt_text": "Hero image",
"title": null,
"aria_label": null,
"is_lazy_loaded": false,
"storage_url": null,
"storage_key": null,
"document_page": null,
"document_slide": null,
"document_asset_hash": null,
"document_occurrence_count": null
}
],
"extracted_at": "2026-04-05T12:00:00Z"
}Error codes
202Job still processing403Job belongs to a different user422Job failed — error message in body/api/v1/jobs/:id/downloadAuth requiredDownload all extracted images as a ZIP archive.
Response
200 OK Content-Type: application/zip Content-Disposition: attachment; filename="imageextract-550e8400.zip" <binary ZIP>
Error codes
202Job not yet complete404Job not found or no images/api/v1/jobs/:id/cancelAuth requiredCancel a pending or queued job. Cancelled jobs refund reserved credits.
Response
200 OK
{ "cancelled": true }Error codes
404Job not found409Only pending or queued jobs can be cancelled/api/v1/jobs/:id/retryAuth requiredCreate a fresh retry job from the original input. Retries consume credits again.
Response
202 Accepted
{
"job_id": "new-job-id"
}Error codes
404Job not found402Insufficient credits409Only failed or cancelled jobs can be retried/api/v1/jobs/:id/downloadsAuth requiredFetch a download manifest with job ZIP, per-group ZIP, and per-image download URLs.
Response
200 OK
{
"job_id": "550e8400-...",
"zip_url": "/api/v1/jobs/550e8400-.../downloads/zip",
"groups": [
{
"group_id": "abc123",
"zip_url": "/api/v1/jobs/550e8400-.../downloads/groups/abc123.zip"
}
],
"images": [
{
"image_id": "img_1",
"filename": "hero.jpg",
"download_url": "/api/v1/jobs/550e8400-.../downloads/images/img_1"
}
]
}Error codes
202Job not yet complete404Job not foundWebhooks
/api/v1/projects/:id/webhooksAuth requiredList webhook endpoints for a project.
Response
200 OK
{
"project_id": "...",
"webhooks": [
{
"id": "...",
"url": "https://example.com/webhook",
"subscribed_events": ["job.completed", "job.failed"],
"status": "active"
}
]
}Error codes
404Project not found/api/v1/projects/:id/webhooksAuth requiredCreate a new project webhook endpoint. The signing secret is returned once.
Request
{
"url": "https://example.com/webhook",
"subscribed_events": ["job.completed", "job.failed"]
}Response
201 Created
{
"id": "...",
"url": "https://example.com/webhook",
"secret": "whsec_..."
}Error codes
403Forbidden/api/v1/projects/:id/webhooks/:webhookIdAuth requiredUpdate webhook URL, subscriptions, or status.
Request
{
"status": "disabled"
}Response
200 OK
{
"id": "...",
"status": "disabled"
}Error codes
403Forbidden/api/v1/projects/:id/webhooks/:webhookIdAuth requiredDelete a webhook endpoint.
Response
200 OK
{ "deleted": true }Error codes
403Forbidden/api/v1/projects/:id/webhooks/:webhookId/testAuth requiredEnqueue a test webhook delivery.
Response
202 Accepted
{
"delivery_id": "..."
}Error codes
403Forbidden/api/v1/projects/:id/webhook-deliveriesAuth requiredList webhook deliveries for a project.
Request
// Query params: ?limit=50
Response
200 OK
{
"project_id": "...",
"deliveries": [
{
"id": "...",
"event_type": "job.completed",
"status": "delivered",
"attempt": 1
}
]
}Error codes
404Project not found/api/v1/projects/:id/webhook-deliveries/:deliveryIdAuth requiredFetch a single webhook delivery payload and status.
Response
200 OK
{
"id": "...",
"event_type": "job.completed",
"status": "delivered",
"payload": { ... }
}Error codes
404Delivery not foundDashboard
/api/dashboard/jobsAuth requiredPaginated job history for the authenticated user.
Request
// Query params: ?page=1&limit=20
Response
200 OK
{
"jobs": [...],
"total": 84,
"page": 1,
"limit": 20,
"has_more": true
}Error codes
401Not authenticated/api/dashboard/keysAuth requiredCreate a new API key. The full key is returned once — store it securely.
Request
{
"label": "My script" // optional
}Response
200 OK
{
"key": "ie_live_a1b2c3d4...",
"row": {
"id": "...",
"key_prefix": "ie_live_a1b2...",
"label": "My script",
"last_used": null,
"created_at": "2026-04-05T12:00:00Z"
}
}Error codes
401Not authenticated/api/dashboard/keys/:idAuth requiredRevoke an API key. Immediate effect — in-flight requests using it will fail.
Response
200 OK
{ "deleted": true }Error codes
401Not authenticatedBilling
/api/billing/checkoutAuth requiredInitiate a Dodo Payments checkout. Redirects to the payment page.
Request
// Query params: ?plan=starter // or: pro | scale
Response
302 Redirect → Dodo Payments checkout URL
Error codes
400Invalid plan302Unauthenticated → redirected to /login/api/webhooks/paymentDodo Payments webhook endpoint. Verifies signature, records the event, and applies billing/credit lifecycle changes.
Request
// Sent by Dodo — do not call directly // Signature verified via webhook-signature header
Response
200 OK
{ "received": true }Error codes
400Signature mismatch or unknown event typeRate limits
| Tier | Limit | Window |
|---|---|---|
| Guest (no auth) | 3 requests | per day per IP |
| Free plan | 10 requests | per day |
| Pro / Agency | Unlimited | — |
When rate-limited the API returns 429 with a Retry-After header (seconds until reset).