Skip to main content

AI tools API

Run a workspace's AI tools from your own app or backend — for example a photo booth app that turns a guest's selfie into the event's framed portrait with Image Studio. A run goes through exactly the same checks as one started by a photo in a conversation: the plan, the tool's switch, the operator's stop button, the per-person and per-workspace limits, the wallet, metering and automatic deletion.

Before you start​

  1. In the panel, open Settings → AI → AI tools → Image Studio → Profiles, edit the profile you want and turn on Allow through the API. A profile that is not turned on for the API does not exist for these calls.
  2. Open Settings → Developer → API keys, create a key and tick Run AI tools (tools:run). The token (cwz_…) is shown once. An existing key can get the scope later from the same page — its token does not change.

The key is a server credential — the same kind the commerce API uses. Keep it on your server; never put it in a browser or an app bundle. Refusals: 401 missing_token / unknown_token, 403 revoked_token, 403 missing_scope:tools:run.

1. Read the profile​

GET /api/tools/image_studio/profiles?locale=en
Authorization: Bearer cwz_…
{
"data": [
{
"module": "image_studio",
"profile": "summit",
"name": "Summit 2026",
"label": "Create your portrait",
"intro": "Take a selfie or upload a photo and get it back as a styled portrait.",
"formats": [
{ "key": "editorial", "label": "Magazine cover" },
{ "key": "pop_art", "label": "Pop art" }
],
"per_day": 3,
"max_upload_mb": 10,
"consent": {
"required": true,
"text": "To create your portrait I will send your photo to an AI image service. …",
"version": "9f2c…",
"accept": "I agree",
"decline": "No thanks"
}
}
]
}

Consent is yours to collect, and you attest it. Show consent.text to the person before you take their photo. When they accept, send consent.version (the sha256 of that exact text) with the run. If the workspace changes the text, the version changes and an old one is refused — show the new text and ask again.

formats are the profile's ready styles. Send one as format, or none for a surprise.

2. Start a run​

In the examples CWZ_KEY is the key and CWZ_API the address your panel's API answers on.

curl -X POST "$CWZ_API/api/tools/image_studio/runs" \
-H "Authorization: Bearer $CWZ_KEY" \
-H "Accept: application/json" \
-H "Idempotency-Key: 7d3f0b6e-2c4b-4f21-9d1a-0c6b9e1f5a44" \
-F profile=summit \
-F reference=user-4711 \
-F consent_version=9f2c… \
-F format=pop_art \
-F locale=en \
-F image=@selfie.jpg
Field
profileThe profile's short code.
imageJPEG, PNG or WebP, up to max_upload_mb.
referenceYour id for the person (up to 191 characters). The per-person daily limit counts by it; it is never shown to anyone and is forgotten by DELETE.
consent_versionRequired when consent.required is true.
formatOptional: a key from formats.
localeOptional: the language of message.
{
"data": {
"id": "0199…",
"module": "image_studio",
"profile": "summit",
"reference": "user-4711",
"status": "pending",
"error_code": null,
"format": "pop_art",
"message": "Your portrait is being prepared (about 30 seconds)…",
"result_available": false,
"created_at": "2026-10-13T12:00:00+00:00",
"finished_at": null
}
}

The answer is 201 with the run. A run that a check stops right away — the person's daily limit, a paused service, an empty wallet — is still 201, with status: "refused", an error_code and, in message, the workspace's own sentence for the person. Calls that never become a run:

Statuserror
404module_unavailableThe tool is not on for this workspace
404unknown_profileNo such profile, or not turned on for the API
409consent_requiredMissing or old consent_version — the body carries the current consent.text and consent.version
413image_too_largeBigger than max_upload_mb
422bad_image, unknown_formatNot a JPEG/PNG/WebP photo; a format the profile does not have
429Too many starts for this key — back off
400idempotency_key_invalidThe Idempotency-Key header is empty, longer than 255 characters or not printable ASCII
409idempotency_key_reusedThis Idempotency-Key was already used with a different request
409idempotency_key_in_progressThe first request with this key is still being processed — retry after Retry-After seconds

Retrying safely: Idempotency-Key​

A run costs money, and a network error does not tell you whether the run started. Send an Idempotency-Key header (a fresh UUID per new run) and reuse the same key only when you retry that request:

  • Same key, same server key, same request (fields and the photo's bytes): you get the original response back — the same status and body, with the header Idempotent-Replayed: true — and no second run is started or billed. The replay is the response as it was then (status: "pending"); poll the run for its current state.
  • Same key with a different request: 409 idempotency_key_reused. Use a new key for a new run.
  • Two requests with the same key at the same moment: one starts the run, the other gets 409 idempotency_key_in_progress — wait and retry with the same key to read the result.
  • Keys belong to one server key and are kept for 24 hours; after that the same key starts a new run.
  • Only a response that created a run is kept. An error before any run existed (409 consent_required, 413, 422, 404) leaves the key free for the corrected request.
  • Without the header every request is a new run, as before.

3. Wait for it​

Poll the run every few seconds:

curl "$CWZ_API/api/tools/runs/0199…" \
-H "Authorization: Bearer $CWZ_KEY" -H "Accept: application/json"

status is pending, then delivered, refused (the photo was not suitable — no person, too many people, unclear… message says so in the workspace's words) or failed. A generation usually takes 20–60 seconds; a slow one is retried once automatically, so allow a few minutes before giving up.

Or let us tell you: subscribe a webhook endpoint (panel: Settings → Webhooks) to tool_run.completed (delivered) and tool_run.failed (refused or failed). The event's data is the run exactly as above — signed like every other event, and never carrying a file link.

4. Fetch the result​

curl "$CWZ_API/api/tools/runs/0199…/result" \
-H "Authorization: Bearer $CWZ_KEY" -H "Accept: application/json"
{
"data": {
"url": "https://…signed…",
"expires_at": "2026-10-13T12:06:00+00:00",
"mime": "image/jpeg",
"filename": "portrait-1a2b3c4d.jpg",
"size_bytes": 284311,
"width": 1080,
"height": 1350
}
}

The link is signed and lives a few minutes. Download the file; do not store the link — ask again for a fresh one. 409 not_ready while the run is not delivered; 410 result_deleted once automatic deletion or your DELETE removed it.

5. Erase it​

curl -X DELETE "$CWZ_API/api/tools/runs/0199…" \
-H "Authorization: Bearer $CWZ_KEY"

204: the photo and the result are deleted now and your reference is forgotten. The run row stays, without any image, for the workspace's own accounting. A run that is being generated at this moment answers 409 in_progress; a queued one is cancelled.

Without a DELETE, files follow the profile's Automatic deletion, else the workspace's. If neither is set (empty or 0, the default), nothing is deleted automatically: the photo and the result are kept until you DELETE the run or the person asks the workspace to erase their data. After a DELETE, a retry with the run's Idempotency-Key still returns that run, without your reference.

API runs have no conversation: the widget's Also post the result into the visitor's chat setting never applies to them.

Limits and billing​

  • Per person per day (by reference), per workspace per minute and per day, and how many at once — the tool's and the profile's own limits.
  • Starts per key per minute (default 60), on top of the shared API throttle.
  • On platform keys every successful generation is billed from the workspace wallet, exactly as in a conversation; with the workspace's own provider keys the provider bills it.

Every run appears in the panel under Recent runs with API as its source.