API reference

A REST API over JSON. Available on Pro and above. Base URL: https://app.example.com. Every path below also answers under /api/v1/….

Authentication

Create a key in Settings → API keys. Send it as a bearer token. Keys are workspace-scoped: the workspace is implied by the key, so there is no account id to pass.

curl -H "Authorization: Bearer isk_xxxxx_yyyyy" https://app.example.com/api/v1/jobs

The three-call flow

Create a job, PUT each image into it, then queue it. Uploading one file per request means per-file progress and per-file retries, with no multipart parsing on either side.

#1 create
JOB=$(curl -s -X POST $BASE/api/v1/jobs \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"name":"Solar client — August","params":{
        "location":"Gulberg, Lahore, Pakistan",
        "keywords":["solar installation","net metering"],
        "businessName":"Nexablack Solar",
        "businessDescription":"We install rooftop solar for homes and factories.",
        "purpose":"gbp"}}' | jq -r .job.id)

#2 upload (repeat per image)
curl -X PUT "$BASE/api/v1/jobs/$JOB/files/roof-install-01.jpg" \
  -H "Authorization: Bearer $KEY" --data-binary @roof-install-01.jpg

#3 queue
curl -X POST $BASE/api/v1/jobs/$JOB/queue \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{}'

Jobs

POST/api/v1/jobs — create a draft. Body: { name, brandId?, params, aiMode?, provider?, model? }.
GET/api/v1/jobs?limit=25&status=done — list.
GET/api/v1/jobs/:id — the job plus every image and its metadata.
POST/api/v1/jobs/:id/queue — reserve allowance and start. Fails with 402 if the plan has no images left.
POST/api/v1/jobs/:id/cancel — stop; finished images are kept and counted.
POST/api/v1/jobs/:id/retry — re-queue only the failed images.
DELETE/api/v1/jobs/:id — delete the job and its files.

Uploads

PUT/api/v1/jobs/:id/files/<filename> — raw image bytes as the body. JPEG, PNG, WebP, AVIF, TIFF, HEIC. The type is checked from the file's magic bytes, not its name.
GET/api/v1/jobs/:id/files — what has been uploaded so far.

Results and downloads

GET/api/v1/jobs/:id/download — everything as a ZIP. ?only=images skips the CSV/HTML/JSON.
GET/api/v1/jobs/:id/sheet — the upload CSV on its own.
GET/api/v1/jobs/:id/file/<name> — one finished file.

A finished item looks like this:

{
  "sourceName": "IMG_4821.jpg",
  "outputName": "rooftop-solar-installation-gulberg-lahore.jpg",
  "width": 2048, "height": 1365,
  "sourceBytes": 4218342, "outputBytes": 288104, "savedPercent": 93,
  "metadata": {
    "metaTitle": "Rooftop Solar Installation in Gulberg, Lahore",
    "metaDescription": "A 12 kW rooftop solar array installed on a home in Gulberg…",
    "altText": "Technician mounting solar panels on a rooftop in Gulberg, Lahore",
    "caption": "Our crew commissioning a 12 kW system in Gulberg.",
    "tags": ["solar installation", "rooftop solar", "lahore"],
    "latitude": 31.5204, "longitude": 74.3587
  }
}

Webhooks

Set params.webhookUrl when creating a job and we POST to it when the job finishes:

{ "event": "job.completed", "job": { "id": "job_…", "status": "done", "doneCount": 42 }, "items": [ … ] }

One attempt, 15-second timeout; the delivery status is stored on the job. For live progress inside a browser use the SSE stream at /api/events.

Job parameters

{
  "location": "Gulberg, Lahore, Pakistan",
  "locations": [ { "label": "DHA Phase 5", "latitude": 31.47, "longitude": 74.40 } ],
  "locationOrder": "shuffle",              // or "random"
  "keywords": ["solar installation"],
  "businessName": "Nexablack Solar",
  "businessDescription": "…",
  "websiteUrl": "https://example.com",
  "purpose": "gbp",                        // gbp | website | ecommerce | social | print | general | custom
  "pageType": "service-detail",
  "section": "hero",
  "language": "English",
  "brief": { "subject": "Site photos from a 12 kW rooftop install…" },
  "imageNotes": { "IMG_4821.jpg": "This is the inverter, not a meter box." },
  "spec": {
    "format": "webp", "quality": 82, "maxKB": 300,
    "width": 1920, "height": 1080, "variants": [640, 1024, 1600],
    "namePattern": "{slug}-{section}", "metadataPolicy": "full", "keepGps": true
  },
  "webhookUrl": "https://n8n.example.com/webhook/images"
}

Any field of spec overrides the preset chosen by purpose/page/section. Call POST /api/v1/spec with the same purpose/page/section to see the resolved spec before you run anything.

Errors and limits

{ "error": { "code": "quota_exceeded", "message": "This job needs 40 images but only 12 are left…",
             "details": { "required": 40, "remaining": 12, "resetsAt": 1790430588623 } } }

n8n / Make recipe

  1. HTTP Request — POST /api/v1/jobs, save job.id.
  2. Loop over your binary files — PUT each to /api/v1/jobs/{{id}}/files/{{filename}}.
  3. HTTP Request — POST /api/v1/jobs/{{id}}/queue.
  4. Webhook node — receives job.completed with every image's metadata, ready to write into WordPress, Shopify or a sheet.