Visualizee REST API
Render images from your own code with a static API key. One POST submits a render through Vizzy, one GET polls the result. Available on the Pro and Max plans and enterprise tiers.
Visualizee has a small REST API for the cases where a chat assistant is the wrong shape: your own scripts, a batch job over a product catalogue, a CMS plugin, or a pipeline that renders on a schedule. You authenticate with a static API key, POST a plain-language prompt plus up to four images, and poll one endpoint for the result.
The base URL is on the app domain, not this site:
https://visualizee.app/api/v1
Every request runs a headless Vizzy turn - the same assistant as the app chat, with the same system prompt, prompt enhancement, and brand and policy rules. You describe what you want; Vizzy writes the render prompt, fills in what you did not pin, and submits the render. Version 1 renders images only; a /render/video endpoint will follow.
Rendering every SKU in a catalogue into the same studio setup, generating a hero image for each new blog post from a build script, or dropping a "restyle this photo" button into your own product. If you want to render by talking to an assistant instead, use the MCP server.
Get an API key
API keys are available on the Pro and Max plans and custom enterprise tiers.
Open the API section
Sign in at visualizee.app, open Account, and scroll to the API section.
Create a key
Click Create key and give it a name, for example "catalogue pipeline". The key starts with vz_live_ and is shown once - copy it now and store it as a server-side secret.
Manage it later
The same section lists each key with its renders, credits spent, and last-used time. Revoke a key at any time; requests with a revoked key answer 401 unauthorized immediately.
A key spends your credits. Never ship it in a browser bundle, a mobile app, or a public repository - call the API from your server and pass results on. If a key leaks, revoke it on the account page and create a new one; per-key stats show whether anything was rendered in the meantime.
Authentication
Send the key as a bearer token on every request:
Authorization: Bearer vz_live_...
A missing, malformed, or revoked key answers 401 unauthorized. A valid key on a plan without API access answers 403 api_access_not_included, and a key on an account whose access has lapsed answers 403 account_access_expired.
Render an image
POST https://visualizee.app/api/v1/render/image
Content-Type: application/json
The call is synchronous up to the point of submission: it runs the Vizzy turn, which can take up to about 60 seconds, then returns the ids of the renders it submitted. It does not wait for the images themselves - poll the status endpoint for those.
Request body
Unknown fields are rejected with 422 invalid_request.
| Field | Type | Default | Notes |
|---|---|---|---|
prompt | string | required | 1 to 4,000 characters of plain language. Refer to attached images as |
images | array | none | Up to 4 items, each either |
max_renders | integer | 1 | 1 to 16. An upper bound on how many images the request may produce. Vizzy renders one image
unless the prompt clearly asks for several (variations, angles) and never pads to the
maximum. Must not exceed your plan's batch cap (Pro: 8, Max: 16), or the call answers
|
model_id | uuid | best model for the plan | Pins an image model. Get the ids from |
aspect_ratio | string | "auto" |
|
resolution | integer | 1024 | Pixel budget for a 1:1 image; other ratios keep the same pixel count. Capped by your plan (Max is uncapped) and by the values the model supports. |
Attaching images
Each entry in images is one of:
{ "url": "https://..." }- a publichttporhttpsURL. Private and loopback hosts are rejected.{ "base64": "..." }- raw base64 or adata:URL. JPEG, PNG, or WebP, up to 8 MB decoded.
Every image is downloaded or decoded, normalized (EXIF rotation applied, long edge resized to 2048px, re-encoded as a JPEG of roughly 300 KB), and re-hosted before the render starts. An image that cannot be fetched or decoded answers 422 invalid_image with the offending index in details, and nothing is charged.
The whole request body, base64 payloads included, must stay under 4.5 MB. For larger files, or more than one or two photos, host them somewhere public and pass URLs instead.
In the prompt, refer to attached images by position: image[0] is the first entry, image[1] the second. image[0] is the base image to edit unless the prompt says otherwise; the others are references for style, materials, or parts. The same preserve/change contract that works in the app works here:
"Place image[0] in a dark photo studio with soft rim light. Keep the car's body, paint, and stance exactly as they are; match the wheel style from image[1]."
With no images attached, the prompt is a text-to-image request.
Response
201 Created:
{
"summary": "Placed the coupe in a charcoal studio with soft rim light.",
"renders": [{ "id": "6e1f3d2a-9b7c-4e1d-8f55-2a0c7b9e4d11", "status": "pending" }],
"credits_charged": 80,
"credits_remaining": 311920
}
summary is Vizzy's one-to-two sentence description of what it rendered. renders holds one entry per submitted render, up to max_renders. Credits are charged at this point; see Cost and credits below for refunds.
Check a render
GET https://visualizee.app/api/v1/render/status/{id}
Returns the state of one render. Poll every few seconds - images usually finish in 10 to 60 seconds. A render owned by another account answers 404 not_found.
{
"id": "6e1f3d2a-9b7c-4e1d-8f55-2a0c7b9e4d11",
"status": "completed",
"image_url": "https://storage.visualizee.app/renders/6e1f3d2a.jpg",
"width": 1536,
"height": 1024,
"model": "Grok Imagine 2.0",
"credits": 80,
"refunded": false,
"error": null,
"created_at": "2026-09-02T12:00:00Z",
"completed_at": "2026-09-02T12:00:41Z"
}
| Field | Notes |
|---|---|
status | pending, completed, or failed. |
image_url |
|
width, height | null until completed. |
model | The display name of the model that rendered it. |
credits, refunded | What the render cost, and whether that charge has been returned after a failure. |
error | null, or the engine's message when status is failed. |
List models
GET https://visualizee.app/api/v1/models
Returns the image models you can pin with model_id, best first, with the credit cost of a 1024 px square render and the ratios, resolutions, and input-image count each one supports. The entry marked default: true is the model used when model_id is omitted.
{
"models": [
{
"id": "8a8a2a68-a43f-4818-83ba-a49ad0cb004a",
"name": "Grok Imagine 2.0",
"tier": "base",
"default": true,
"credits_at_1024": 80,
"aspect_ratios": ["1:1", "4:3", "3:4", "16:9", "9:16"],
"resolutions": [1024, 2048],
"max_input_images": 4
}
]
}
tier is base or pro. Pro renders count against the plan's monthly pro-render quota; the models reference describes what each model is good at.
Full example with curl
Submit a render that restyles a product photo, then poll until it finishes:
export VISUALIZEE_API_KEY="vz_live_..."
# 1. Submit
curl -s https://visualizee.app/api/v1/render/image \
-H "Authorization: Bearer $VISUALIZEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Place image[0] in a bright Scandinavian living room, morning light from the left. Keep the chair exactly as it is.",
"images": [{ "url": "https://example.com/chair.jpg" }],
"aspect_ratio": "3:2"
}'
# -> { "summary": "...", "renders": [{ "id": "6e1f...", "status": "pending" }], ... }
# 2. Poll (repeat every few seconds until status is "completed" or "failed")
curl -s https://visualizee.app/api/v1/render/status/6e1f3d2a-9b7c-4e1d-8f55-2a0c7b9e4d11 \
-H "Authorization: Bearer $VISUALIZEE_API_KEY"
To send a local file instead of a URL, base64-encode it into the body:
curl -s https://visualizee.app/api/v1/render/image \
-H "Authorization: Bearer $VISUALIZEE_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg img "$(base64 -i chair.jpg)" '{
prompt: "Place image[0] on a concrete plinth in a white studio.",
images: [{ base64: $img }]
}')"
Node example
The same flow with fetch, no dependencies:
const API = 'https://visualizee.app/api/v1';
const headers = {
Authorization: `Bearer ${process.env.VISUALIZEE_API_KEY}`,
'Content-Type': 'application/json',
};
const submit = await fetch(`${API}/render/image`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt:
'Place image[0] in a dark photo studio with soft rim light, three-quarter front view. Keep the car exactly as it is.',
images: [{ url: 'https://example.com/car.jpg' }],
aspect_ratio: '3:2',
}),
});
if (!submit.ok) {
const { error } = await submit.json();
throw new Error(`${error.code}: ${error.message}`);
}
const { summary, renders } = await submit.json();
console.log(summary);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
for (const { id } of renders) {
let render;
do {
await sleep(4000);
render = await fetch(`${API}/render/status/${id}`, { headers }).then((r) => r.json());
} while (render.status === 'pending');
if (render.status === 'completed') console.log(render.image_url);
else console.error(`Render ${id} failed: ${render.error}`);
}
Cost and credits
- A render costs the same credits as the same render in the app. The price depends on the model and resolution; see plans & credits and the per-model costs on the models page.
- Credits are charged when the render is submitted, and the response reports
credits_chargedandcredits_remaining. - A render that fails on the engine is refunded automatically, and the status endpoint shows
refunded: truefor it. - A request that never reaches a render - a validation error, a plan limit, a bad image, or a policy refusal - is never charged.
- If the account cannot cover the request, the call answers
402 insufficient_creditswithrequiredandavailableindetails, and nothing is submitted.
The Vizzy turn itself is free; you pay for renders only.
Errors
Every error is a JSON envelope with a stable code you can branch on, a human-readable message, and sometimes details:
{
"error": {
"code": "insufficient_credits",
"message": "This request needs 160 credits; 40 are available.",
"details": { "required": 160, "available": 40 }
}
}
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 401 | unauthorized | Missing, malformed, or revoked API key. |
| 402 | insufficient_credits | Not enough credits. details has required and available. |
| 403 | api_access_not_included | The account's plan does not include the API. |
| 403 | account_access_expired | The account's access has lapsed. |
| 404 | not_found | No such render, or it belongs to another account (status endpoint). |
| 422 | invalid_request | Schema validation failed. |
| 422 | invalid_image | An image could not be fetched or decoded. details.index says which one. |
| 422 | too_many_images | More than 4 images. |
| 422 | unknown_model | model_id does not match any model. |
| 422 | invalid_model | model_id is a video model. |
| 422 | invalid_parameter | A value the model does not support, for example an aspect ratio it cannot render. |
| 422 | plan_limit | The request exceeds a plan limit, for example resolution. |
| 422 | max_renders_above_plan | max_renders is above the plan's batch cap. |
| 422 | no_render | Vizzy finished the turn without rendering, for example a request that cannot be made safe. |
| 504 | timeout | The Vizzy turn did not finish within the request budget. Retry. |
| 500 | internal_error | Something failed on our side. Retry, and contact support if it persists. |
Because every request goes through Vizzy, the same content rules as the app apply. A named real person or a copyrighted character is not recreated - Vizzy swaps it for a generic description and renders that, and the summary tells you what it did. Requests that cannot be made safe at all end without a render and answer 422 no_render with Vizzy's explanation in message, and cost nothing.
Limits
| Limit | Value |
|---|---|
| Prompt length | 1 to 4,000 characters |
| Images per request | 4 |
| Image size | 8 MB decoded; JPEG, PNG, or WebP |
| Request body | 4.5 MB in total, base64 included |
| Renders per request | max_renders up to 8 on Pro, 16 on Max |
| Resolution | Uncapped on Pro and Max; otherwise the plan's image ceiling |
| Submission time | Up to about 60 seconds for the Vizzy turn, then 504 timeout |
| Rate limits | None in v1; credits are the limiter |
Deliberately not in v1: idempotency keys, callbacks or webhooks to the caller (polling only), rate limits, and video.
MCP or REST API: which one
Both surfaces render with your account and file results into your workspace. They differ in who is doing the calling:
| MCP server | REST API | |
|---|---|---|
| Who calls it | An AI assistant (Claude, Claude Code, Cursor, Lovable) on your behalf | Your own code, scripts, and pipelines |
| Auth | OAuth 2.1, sign in once in the browser, no keys | Static vz_live_ API key in a bearer header |
| Plans | Every plan | Pro, Max and enterprise |
| Video | Yes, render_video | Not yet |
| Best for | Interactive work, one-off renders while you write or build | Unattended, repeatable, high-volume rendering |
If a person is in the loop and talking to an assistant, use MCP. If a machine is in the loop, use the REST API.
OpenAPI spec
A machine-readable description of both endpoints, the request and response schemas, the bearer auth scheme, and the error envelope:
https://visualizee.ai/openapi/v1.json
Feed it to a client generator, an API explorer, or an agent that builds its own integration.