Browse documentation

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.

What this is good for

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.

Keep keys server-side

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.

FieldTypeDefaultNotes
promptstringrequired

1 to 4,000 characters of plain language. Refer to attached images as image[0], image[1], and so on.

imagesarraynone

Up to 4 items, each either { "url": "https://..." } or { "base64": "..." }. Mixing is allowed. See Attaching images below.

max_rendersinteger1

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 422 max_renders_above_plan.

model_iduuidbest model for the plan

Pins an image model. Get the ids from GET /api/v1/models (see List models below). A video model id answers 422 invalid_model.

aspect_ratiostring"auto"

auto matches the first attached image, snapped to the nearest ratio the model supports, or falls back to 1:1 with no image. Otherwise a ratio the model supports, for example 16:9, 3:2, or 1:1; an unsupported ratio answers 422 invalid_parameter.

resolutioninteger1024

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 public http or https URL. Private and loopback hosts are rejected.
  • { "base64": "..." } - raw base64 or a data: 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.

Request bodies are capped at 4.5 MB

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"
}
FieldNotes
statuspending, completed, or failed.
image_url

null until completed, then a permanent public URL on Visualizee storage. The render is also filed in your workspace under the API folder, one file per key per day.

width, heightnull until completed.
modelThe display name of the model that rendered it.
credits, refundedWhat the render cost, and whether that charge has been returned after a failure.
errornull, 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_charged and credits_remaining.
  • A render that fails on the engine is refunded automatically, and the status endpoint shows refunded: true for 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_credits with required and available in details, 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 }
  }
}
HTTPCodeMeaning
400invalid_jsonThe body is not valid JSON.
401unauthorizedMissing, malformed, or revoked API key.
402insufficient_creditsNot enough credits. details has required and available.
403api_access_not_includedThe account's plan does not include the API.
403account_access_expiredThe account's access has lapsed.
404not_foundNo such render, or it belongs to another account (status endpoint).
422invalid_request

Schema validation failed. details is an array of path and message pairs, one per problem.

422invalid_imageAn image could not be fetched or decoded. details.index says which one.
422too_many_imagesMore than 4 images.
422unknown_modelmodel_id does not match any model.
422invalid_modelmodel_id is a video model.
422invalid_parameter

A value the model does not support, for example an aspect ratio it cannot render.

422plan_limitThe request exceeds a plan limit, for example resolution.
422max_renders_above_planmax_renders is above the plan's batch cap.
422no_render

Vizzy finished the turn without rendering, for example a request that cannot be made safe. message carries Vizzy's explanation and details has assistant and tool_errors. Nothing is charged.

504timeoutThe Vizzy turn did not finish within the request budget. Retry.
500internal_errorSomething failed on our side. Retry, and contact support if it persists.
Vizzy can decline

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

LimitValue
Prompt length1 to 4,000 characters
Images per request4
Image size8 MB decoded; JPEG, PNG, or WebP
Request body4.5 MB in total, base64 included
Renders per requestmax_renders up to 8 on Pro, 16 on Max
ResolutionUncapped on Pro and Max; otherwise the plan's image ceiling
Submission timeUp to about 60 seconds for the Vizzy turn, then 504 timeout
Rate limitsNone 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 serverREST API
Who calls itAn AI assistant (Claude, Claude Code, Cursor, Lovable) on your behalfYour own code, scripts, and pipelines
AuthOAuth 2.1, sign in once in the browser, no keysStatic vz_live_ API key in a bearer header
PlansEvery planPro, Max and enterprise
VideoYes, render_videoNot yet
Best forInteractive work, one-off renders while you write or buildUnattended, 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.

Next