Developers
API reference
A small, versioned REST API for generating images and code headlessly — no canvas required. Authenticate with an API key, enqueue a generation, and poll for the result.
Base URL & versioning#
All endpoints live under a single versioned prefix:
https://vmotif.com/api/v1The API is at v1. Changes within v1are additive only — new fields may appear, but existing fields and shapes won't change underneath you. Every response carries an x-request-id header; include it when contacting support.
Authentication#
Every request needs a bearer token — an API key you create in Settings → API keys. Keys start with vmk_live_ and are shown once at creation, so store them somewhere safe.
Authorization: Bearer vmk_live_xxxxxxxxxxxx/api/v1. Treat it like a password: never commit it or expose it in client-side code. Revoke a leaked key from the same settings page and the API rejects it immediately.A missing, malformed, revoked, or unknown key returns a single 401 shape — the API never discloses which keys exist.
Generate an image or code#
Generation is asynchronous. You POST a request, get back a job id immediately, then poll until it completes. This keeps the connection short even when a model takes a while.
curl -X POST https://vmotif.com/api/v1/generations \
-H "Authorization: Bearer vmk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"type": "image",
"prompt": "a minimalist mountain logo, single line",
"model": "google/gemini-3-pro-image",
"aspectRatio": "1:1"
}'The response returns right away with a job id:
{ "id": "0c2f…", "status": "pending" }Request fields:
- type
"image"or"code". Required. Must match the model's category.- prompt
- The instruction. Required, up to 50,000 characters.
- model
- A model id from
GET /models. Optional — defaults to a sensible image or code model based ontype. - images
- Up to 10 input images, each
{ url, mediaType }. URLs must behttpsordata:. - textInputs
- Up to 10 text inputs, each
{ content, label?, language? }, fed to the model as context. - aspectRatio
- For images: one of
1:1,16:9,9:16,4:3,3:4,3:2,2:3. - targetLanguage
- For code: the output language (e.g.
tsx,css,json).
type (e.g. a text model with type: "image") is rejected with 400up front, so you never burn budget on a job that can't succeed.Poll for the result#
Poll the job until status is completed or failed. This endpoint is never cached.
curl https://vmotif.com/api/v1/generations/0c2f… \
-H "Authorization: Bearer vmk_live_xxx"An image result:
{
"id": "0c2f…",
"status": "completed",
"result": {
"type": "image",
"url": "https://…",
"mediaType": "image/png",
"aspectRatio": "1:1",
"costMicros": 39000
},
"error": null
}A code result returns the generated text instead:
{
"id": "1a9b…",
"status": "completed",
"result": {
"type": "code",
"code": "export function Button() { … }",
"structuredOutput": null,
"costMicros": 4200
},
"error": null
}result is null until the job completes; error holds the failure message when status is failed. A job id that isn't yours, doesn't exist, or wasn't created via the API returns 404— ids can't be probed across accounts.
Read endpoints#
Three read-only endpoints help you build against the API:
GET /models— the catalog of models usable via/generations, with each model's id, category, provider, modalities, and the prices you're actually billed.GET /usage— your AI budget for the current period. Pre-flight with this before generating.GET /workflows— summaries of your workflows (id,name,updated_at). Supports?limit(≤100) and?offset.
/usage reports budget in microdollars (1_000_000 = $1); a null budget means unlimited:
{
"tier": "pro",
"token_budget_micros": 20000000,
"used_micros": 3450000,
"remaining_micros": 16550000,
"resets_at": "2026-06-01T00:00:00Z"
}Run a workflow#
Beyond single generations, you can execute an entire saved workflow headlessly. The API resolves the graph, runs each prompt node in dependency order — parallelizing independent nodes — and feeds each node's output into the prompts downstream, the same way the canvas does when you press Run all.
curl -X POST https://vmotif.com/api/v1/workflows/7f3a…/runs \
-H "Authorization: Bearer vmk_live_xxx"It returns immediately with a run id; the work continues server-side:
{ "id": "b81c…", "status": "pending" }Poll the run for progress and per-output results:
{
"id": "b81c…",
"workflowId": "7f3a…",
"status": "running",
"progress": { "total": 3, "completed": 1 },
"results": [
{
"promptNodeId": "node-a",
"outputNodeId": "out-a",
"type": "image",
"status": "completed",
"url": "https://…",
"mediaType": "image/png",
"costMicros": 39000
}
],
"error": null
}status moves pending → running → completed or failed. Each entry in results appears as that output settles, so you can stream progress. Per-output status is completed, failed, or skipped; image outputs carry url, while code, screen, and design outputs carry their text in code.
- Supported outputs: image, code, and screen (HTML) and design nodes. Video outputs are reported as
skippedin this version. - Stops on failure: if any output fails, nodes downstream of it are abandoned (they depend on the missing result) and the run is marked
failed— earlier results are still returned. - Time budget: a run executes within a single ~5 minute server window. Very large workflows may exceed it; split them or run nodes individually via
/generations.
Only workflows you own are runnable; an unknown or unowned id returns 404. Each generated output draws on the same budget as a single generation.
Errors#
Every error uses the same envelope, with a stable machine-readable type:
{ "error": { "type": "invalid_request", "message": "…" } }- invalid_request
- 400 — a body field is missing, malformed, or incompatible.
- unauthorized
- 401 — missing, malformed, revoked, or unknown API key.
- rate_limited
- 429 — too many requests; honor the
retry-afterheader. - insufficient_quota
- 429 — your AI budget for the period is exhausted.
- not_found
- 404 — the resource doesn't exist or isn't yours.
- server_error
- 500 (or 503 when a dependency is briefly unavailable).
Rate limits & budget#
Generation calls draw on the same AI budget and generation limits as your plan in the app — the API isn't a separate allowance. Read endpoints have their own, more generous limit. When you hit a limit you get a 429 with a retry-after header (in seconds); back off and retry after that window.
503 rather than letting an unmetered request through. Retry with backoff.