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.

Poster Studio

Design on-brand posters from a website address. A run is queued and worked in the background: create it, poll it (or take a webhook), then download the files. The same key and workspace apply as for jobs.

POST/api/v1/posters/runs — start a run. Returns the run and its credit quote. 402 quota_exceeded if the plan's poster credits cannot cover it.
GET/api/v1/posters/runs — recent runs.
GET/api/v1/posters/runs/{id} — status (queued, running, done, failed, cancelled), stage, credits, the posters made so far and a progress log.
POST/api/v1/posters/runs/{id}/stop — stop a run. Posters already finished are kept and only they are charged.
GET/api/v1/posters/runs/{id}/items/{itemId}/file — one poster (PNG). Add ?download=1 for an attachment.
GET/api/v1/posters/runs/{id}/download — everything as one ZIP: posters, HTML report and the saved brand.
DELETE/api/v1/posters/runs/{id} — delete a finished run and its files.
POST/api/v1/posters/quote — the credit cost of a request, without starting it.
GET/api/v1/posters/meta — sizes, looks, tones, goals, layouts and pricing, for building your own form.
curl -X POST $BASE/api/v1/posters/runs \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "url": "nexablack.com",
    "concepts": 3,
    "size": "ig-portrait",
    "look": "",
    "tone": "", "objective": "",
    "campaign": "Autumn sale, 20% off",
    "qa": true, "heroes": false, "bothRatios": false,
    "webhookUrl": "https://n8n.example.com/webhook/posters"
  }'

Useful fields: url (or brandId for a saved client profile, or fromRunId to reuse the brand saved by an earlier run without fetching the site again), concepts (1 to 24), size (a preset id, WxH or a ratio like 4:5), look, looks, templates, voice, accent, fontDisplay, fontBody, grain, vignette, density, tone, objective, language, audience, campaign, ctaText, mustInclude, avoid, readingAge, calendarText (one poster per row, which overrides concepts), qa, photos, heroes, bothRatios.

Credits. One credit per finished poster, plus 1 with AI review, plus 2 with generated heroes, doubled for both ratios. The run's whole cost is held when it is queued and whatever it did not deliver is released when it ends.

Webhook. If webhookUrl is set (paid plans, public addresses only) we POST poster_run.completed or poster_run.failed with the run, a files list of poster links and a download link to the ZIP. The file links need the same API key.

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.