API Reference

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

GET/api/v1/accountAuth required

Fetch 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
GET/api/v1/account/creditsAuth required

Fetch the account-level credit balance used by the API platform.

Response

200 OK
{
  "account_id": "acc_...",
  "plan": "pro",
  "credits": 487
}

Error codes

401Unauthorized
GET/api/v1/account/credits/ledgerAuth required

Fetch 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
GET/api/v1/account/limitsAuth required

Fetch 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
GET/api/v1/account/usageAuth required

Fetch 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

401Unauthorized

Projects

GET/api/v1/projectsAuth required

List projects for the authenticated account.

Response

200 OK
{
  "projects": [
    {
      "id": "proj_...",
      "name": "Default",
      "slug": "default"
    }
  ]
}

Error codes

401Unauthorized
POST/api/v1/projectsAuth required

Create 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 error
403Forbidden
GET/api/v1/projects/:idAuth required

Fetch a single project.

Response

200 OK
{
  "id": "...",
  "name": "Marketing site",
  "slug": "marketing-site"
}

Error codes

404Project not found
PATCH/api/v1/projects/:idAuth required

Update a project.

Request

{
  "name": "Marketing site v2"
}

Response

200 OK
{
  "id": "...",
  "name": "Marketing site v2"
}

Error codes

400Validation error
403Forbidden
DELETE/api/v1/projects/:idAuth required

Archive a project.

Response

200 OK
{
  "id": "...",
  "status": "archived"
}

Error codes

403Forbidden

Project Keys

GET/api/v1/projects/:id/keysAuth required

List project API keys.

Response

200 OK
{
  "project_id": "...",
  "keys": [
    {
      "id": "...",
      "key_prefix": "ie_live_abcd...",
      "status": "active"
    }
  ]
}

Error codes

404Project not found
POST/api/v1/projects/:id/keysAuth required

Create 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
POST/api/v1/projects/:id/keys/:keyId/revokeAuth required

Revoke an existing project API key.

Response

200 OK
{ "revoked": true }

Error codes

403Forbidden
POST/api/v1/projects/:id/keys/:keyId/rotateAuth required

Rotate 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

403Forbidden

Extraction

POST/api/v1/jobs

Submit 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 URL
409Idempotency key reused with a different request body
402Insufficient credits
429Rate limit exceeded — check Retry-After header

Jobs

GET/api/v1/jobs/:idAuth required

Fetch 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
GET/api/v1/jobs/:id/itemsAuth required

Fetch 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
GET/api/v1/jobs/:id/streamAuth required

Server-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 user
404Job not found
GET/api/v1/jobs/:id/resultAuth required

Fetch 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 processing
403Job belongs to a different user
422Job failed — error message in body
GET/api/v1/jobs/:id/downloadAuth required

Download 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 complete
404Job not found or no images
POST/api/v1/jobs/:id/cancelAuth required

Cancel a pending or queued job. Cancelled jobs refund reserved credits.

Response

200 OK
{ "cancelled": true }

Error codes

404Job not found
409Only pending or queued jobs can be cancelled
POST/api/v1/jobs/:id/retryAuth required

Create a fresh retry job from the original input. Retries consume credits again.

Response

202 Accepted
{
  "job_id": "new-job-id"
}

Error codes

404Job not found
402Insufficient credits
409Only failed or cancelled jobs can be retried
GET/api/v1/jobs/:id/downloadsAuth required

Fetch 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 complete
404Job not found

Webhooks

GET/api/v1/projects/:id/webhooksAuth required

List 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
POST/api/v1/projects/:id/webhooksAuth required

Create 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
PATCH/api/v1/projects/:id/webhooks/:webhookIdAuth required

Update webhook URL, subscriptions, or status.

Request

{
  "status": "disabled"
}

Response

200 OK
{
  "id": "...",
  "status": "disabled"
}

Error codes

403Forbidden
DELETE/api/v1/projects/:id/webhooks/:webhookIdAuth required

Delete a webhook endpoint.

Response

200 OK
{ "deleted": true }

Error codes

403Forbidden
POST/api/v1/projects/:id/webhooks/:webhookId/testAuth required

Enqueue a test webhook delivery.

Response

202 Accepted
{
  "delivery_id": "..."
}

Error codes

403Forbidden
GET/api/v1/projects/:id/webhook-deliveriesAuth required

List 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
GET/api/v1/projects/:id/webhook-deliveries/:deliveryIdAuth required

Fetch a single webhook delivery payload and status.

Response

200 OK
{
  "id": "...",
  "event_type": "job.completed",
  "status": "delivered",
  "payload": { ... }
}

Error codes

404Delivery not found

Dashboard

GET/api/dashboard/jobsAuth required

Paginated 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
POST/api/dashboard/keysAuth required

Create 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
DELETE/api/dashboard/keys/:idAuth required

Revoke an API key. Immediate effect — in-flight requests using it will fail.

Response

200 OK
{ "deleted": true }

Error codes

401Not authenticated

Billing

GET/api/billing/checkoutAuth required

Initiate 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 plan
302Unauthenticated → redirected to /login
POST/api/webhooks/payment

Dodo 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 type

Rate limits

TierLimitWindow
Guest (no auth)3 requestsper day per IP
Free plan10 requestsper day
Pro / AgencyUnlimited

When rate-limited the API returns 429 with a Retry-After header (seconds until reset).