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
/api/v1/jobs — create a draft. Body: { name, brandId?, params, aiMode?, provider?, model? }./api/v1/jobs?limit=25&status=done — list./api/v1/jobs/:id — the job plus every image and its metadata./api/v1/jobs/:id/queue — reserve allowance and start. Fails with 402 if the plan has no images left./api/v1/jobs/:id/cancel — stop; finished images are kept and counted./api/v1/jobs/:id/retry — re-queue only the failed images./api/v1/jobs/:id — delete the job and its files.Uploads
/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./api/v1/jobs/:id/files — what has been uploaded so far.Results and downloads
/api/v1/jobs/:id/download — everything as a ZIP. ?only=images skips the CSV/HTML/JSON./api/v1/jobs/:id/sheet — the upload CSV on its own./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.
/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./api/v1/posters/runs — recent runs./api/v1/posters/runs/{id} — status (queued, running, done, failed, cancelled), stage, credits, the posters made so far and a progress log./api/v1/posters/runs/{id}/stop — stop a run. Posters already finished are kept and only they are charged./api/v1/posters/runs/{id}/items/{itemId}/file — one poster (PNG). Add ?download=1 for an attachment./api/v1/posters/runs/{id}/download — everything as one ZIP: posters, HTML report and the saved brand./api/v1/posters/runs/{id} — delete a finished run and its files./api/v1/posters/quote — the credit cost of a request, without starting it./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 } } }
401 unauthenticated— missing or revoked key.402 quota_exceeded— the plan's image, GBP or poster-credit allowance is spent.403 forbidden— the key lacks a scope, or the subscription is past due.413 payload_too_large— the image is over the per-file limit.429 rate_limited— back off; the message says for how long.
n8n / Make recipe
- HTTP Request — POST
/api/v1/jobs, savejob.id. - Loop over your binary files — PUT each to
/api/v1/jobs/{{id}}/files/{{filename}}. - HTTP Request — POST
/api/v1/jobs/{{id}}/queue. - Webhook node — receives
job.completedwith every image's metadata, ready to write into WordPress, Shopify or a sheet.