- 1–12 images per request; billed per successfully delivered image ($0.08 each)
- URL output only; no image-to-image / streaming
- Image URLs expire in 72 hours
Text-to-image · async jobs. Submit POST /v1/images/generations, then poll Get task status.\
Model name is fixed grok-imagine-2.0-ext. Not supported: reference images, stream, or response_format values other than url.
For object layers or selected-region editing, see Layers and region editing.
Do not put API keys in browser bundles (VITE_* / NEXT_PUBLIC_*, LocalStorage, etc.). Prefer calling your own BFF from the browser; keep the OpenVeer key on the server.
Request example
curl --request POST \
--url https://api.openveer.com/v1/images/generations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Idempotency-Key: 8eacb46d-ef4e-4f4e-82de-830bd8bc67e2' \
--header 'X-OpenVeer-Response-Version: 2026-07-27' \
--data '{
"model": "grok-imagine-2.0-ext",
"prompt": "A red apple on a white ceramic plate, clean studio product photo",
"n": 1,
"size": "1:1",
"resolution": "quality",
"response_format": "url"
}'import requests
import uuid
url = "https://api.openveer.com/v1/images/generations"
payload = {
"model": "grok-imagine-2.0-ext",
"prompt": "A red apple on a white ceramic plate, clean studio product photo",
"n": 1,
"size": "1:1",
"resolution": "quality",
"response_format": "url",
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json",
"Accept": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
"X-OpenVeer-Response-Version": "2026-07-27",
}
response = requests.post(url, json=payload, headers=headers)
print(response.status_code, response.json())const url = "https://api.openveer.com/v1/images/generations";
const payload = {
model: "grok-imagine-2.0-ext",
prompt: "A red apple on a white ceramic plate, clean studio product photo",
n: 1,
size: "1:1",
resolution: "quality",
response_format: "url",
};
const headers = {
Authorization: "Bearer <token>",
"Content-Type": "application/json",
Accept: "application/json",
"Idempotency-Key": crypto.randomUUID(),
"X-OpenVeer-Response-Version": "2026-07-27",
};
fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(payload),
})
.then(async (response) => {
console.log(response.status, await response.json());
})
.catch((error) => console.error("Error:", error));Response example
{
"code": 202,
"request_id": "2026081111342261665927mpb4IPDb",
"data": {
"id": "task_01KZQE5CM0Y3KZ6M1N1BK619MX",
"object": "generation.task",
"type": "image",
"status": "pending",
"progress": 0,
"poll_url": "/v1/tasks/task_01KZQE5CM0Y3KZ6M1N1BK619MX"
}
}{
"request_id": "20260811...",
"error": {
"message": "`response_format` for grok-imagine-2.0-ext only supports `url` (got: b64_json)",
"type": "invalid_response_format",
"param": "",
"code": "invalid_response_format"
}
}{
"error": {
"code": 401,
"message": "Authentication failed. Please check your API key",
"type": "authentication_error"
}
}{
"error": {
"code": 402,
"message": "Insufficient balance. Please top up and try again",
"type": "payment_required"
}
}{
"error": {
"code": 429,
"message": "Too many requests. Please try again later",
"type": "rate_limit_error"
}
}Capabilities and limits
| Dimension | Contract |
|---|---|
| Model | Fixed grok-imagine-2.0-ext |
| Capability | Text-to-image only |
| Mode | Async task |
Count n |
1–12, default 1 |
size |
7 aspect ratios + 5 pixel aliases (below) |
| Output | response_format=url only (also the default) |
| Quality | Public field resolution; verified value quality |
| Not supported | Image-to-image, stream=true, public quality, style, b64_json / base64 |
| Billing | Fixed unit price; charge successfully delivered images |
Auth and recommended headers
Authorization stringBearer token. Get a key from the API Key page.
Authorization: Bearer YOUR_API_KEY| Header | Notes |
|---|---|
Content-Type |
application/json (submit) |
Accept |
application/json |
Idempotency-Key |
Strongly recommended. New UUID per user-confirmed generation; network retries must reuse the same key and body |
X-OpenVeer-Response-Version |
Prefer 2026-07-27 for a stable submit shape (data.id) |
Request parameters
model stringFixed value: grok-imagine-2.0-ext
nsfw_check booleanWhether to run content moderation before submitting the image task.
true: useomni-moderation-latestto review prompts and input imagesfalseor omitted: do not send a moderation request, adding no moderation cost or latency (default)
prompt stringPrompt. Must be non-empty after trim. Trim before submit.
n integerImage count: 1–12. Explicit 0 errors. Omit for 1.
size stringAspect ratio. Prefer ratio strings (UI should only show ratios):
size |
Orientation | Typical use |
|---|---|---|
1:1 |
Square | Product, avatar |
2:3 |
Portrait | Poster, full-body |
3:2 |
Landscape | Photo, wide scene |
3:4 |
Portrait | E-commerce, people |
4:3 |
Landscape | Display art |
9:16 |
Vertical | Story / short-video cover |
16:9 |
Wide | Banner, video cover |
Pixel aliases: 1024x1024 (1:1), 1024x1792 (2:3), 1792x1024 (3:2), 720x1280 (9:16), 1280x720 (16:9).
Values outside the whitelist return 400 invalid_size (e.g. 1:2, 2:1, 4:5, auto).
Actual pixels for a given ratio may differ from the alias table (e.g. 1:1 may return 1408×1408). Trust the returned image; do not rewrite size from measured pixels.
resolution stringQuality-mode field. Verified value: quality.
- Omit (model is quality-mode by default), or
- Pass
resolution: "quality"explicitly
Not a 1K / 2K / 4K pixel tier; framing is controlled by size.
Do not send a public quality field — you get 400 invalid_quality. Use resolution.
response_format stringOnly url is allowed. May be omitted. b64_json / base64 → 400 invalid_response_format.
webhook stringOptional public HTTPS base URL. On terminal status the platform POSTs {webhook}/callback. Server-side only — see Webhook.
Unsupported parameters
| Parameter | Behavior |
|---|---|
quality |
400 invalid_quality → use resolution |
style |
400 invalid_style |
image_urls / image_with_roles |
400 invalid_image_input |
stream: true |
400 invalid_stream |
response_format: "b64_json" / "base64" |
400 invalid_response_format |
Build requests with a whitelist; do not forward a generic image-form object from other models.
Request examples
Minimal
{
"model": "grok-imagine-2.0-ext",
"prompt": "A red apple on a white ceramic plate, clean studio product photo"
}Recommended
{
"model": "grok-imagine-2.0-ext",
"prompt": "A red apple on a white ceramic plate, clean studio product photo",
"n": 1,
"size": "1:1",
"resolution": "quality",
"response_format": "url"
}Submit response
Prefer X-OpenVeer-Response-Version: 2026-07-27. Success is HTTP 202; task id is data.id (do not rely on legacy data[0].task_id).
Persist:
data.idfor pollingrequest_idfor gateway debugging- the
Idempotency-Keyfor safe retries when outcome is unknown - original request params for UI / support
Idempotency and safe retries
Image generation is billable — strongly recommend Idempotency-Key (1–191 printable ASCII chars; UUID is easiest; retained \~24 hours).
| Scenario | Behavior | Action |
|---|---|---|
| Same key + same body already done | Replay; header Idempotency-Replayed: true |
Use the same task id |
| Same key still in flight | 409 idempotency_in_progress + Retry-After |
Wait, retry same key and body |
| Same key, different body | 409 idempotency_key_reused |
New logical job needs a new key |
| Outcome indeterminate | 409 idempotency_result_indeterminate |
Do not mint a new key; investigate with the old one |
On POST network timeout when you cannot tell if the server accepted the job, do not immediately create a new key — retry with the same key / body / response version.
Poll tasks
GET /v1/tasks/{task_id}?language=en
Authorization: Bearer YOUR_API_KEY
Accept: application/jsonOptional language: zh / en / ko / ja (failure message localization only). See Get task status.
Statuses
status |
Terminal | Handling |
|---|---|---|
pending / processing |
No | Keep polling (result may be absent — not a failure) |
completed |
Yes | Parse result.images |
failed |
Yes | Show error.message; cost is 0 (pre-charge refunded) |
unknown |
No | Short retries; if it persists, contact support with the task id |
Poll about every 2 seconds; cap near 10 minutes or 120 attempts. Honor Retry-After on 429. Tasks are kept \~3 days by default — keep the task id if the client times out.
Completed example
{
"code": 200,
"data": {
"id": "task_01KZQE5CM0Y3KZ6M1N1BK619MX",
"status": "completed",
"progress": 100,
"created": 1786419262,
"completed": 1786419275,
"actual_time": 13,
"estimated_time": 100,
"cost": 0.08,
"credits_cost": 0.8,
"result": {
"images": [
{
"expires_at": 1786505675,
"url": [
"https://example.com/image/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.jpg"
],
"image_ids": [
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
]
}
]
}
}
}Parsing url and image_ids
result.images[]
├─ url[] ← authoritative display/download field (array)
├─ image_ids[] ← optional opaque IDs
└─ expires_at ← Unix seconds; multiply by 1000 for JS Date- Use
url[]for display; whenn>1, walk all entries - Pair by index only if
image_ids.length === url.length - Missing
image_idsstill allows display - Links last 72 hours — download promptly; also trust
expires_at
Billing
Base price \$0.08 per image (successful deliveries):
n |
Estimated base |
|---|---|
| 1 | \$0.08 |
| 4 | \$0.32 |
| 8 | \$0.64 |
| 12 | \$0.96 |
- Pre-submit UI should say “estimate”; final USD is
data.cost data.credits_costis the credits view (currently \~ USD × 10)- Pre-charge by requested count; settle on successful count (partial refunds if partial failure)
- Full failure:
cost=0, pre-charge refunded - Do not build price keys from
resolution; this model is flat per image
Webhook (optional)
{
"webhook": "https://your-service.example.com/openveer"
}- Provide a base URL; the platform calls
{base}/callback - Must be public and pass SSRF checks
- If
webhook_secretis set, signature ishex(HMAC-SHA256(secret, raw_body))over raw bytes - Callback body matches task query
data(no extra{code,data}wrapper) - Still keep low-frequency polling as a fallback
Common errors
| HTTP | error.code |
Cause | Action |
|---|---|---|---|
| 400 | invalid_request |
Empty prompt / bad JSON | Validate input |
| 400 | invalid_n |
n outside 1–12 |
Clamp count |
| 400 | invalid_size |
Size not whitelisted | Fixed select options |
| 400 | invalid_response_format |
Not url |
Fix or omit |
| 400 | invalid_quality |
Public quality sent |
Use resolution |
| 400 | invalid_style |
style sent |
Remove |
| 400 | invalid_image_input |
Reference images | Switch models |
| 400 | invalid_stream |
stream=true |
Remove |
| 400 | invalid_idempotency_key |
Bad key | Use UUID |
| 401 | Auth failure | Bad key | Fix server credentials |
| 402 | Payment required | Low balance | Top up |
| 409 | idempotency_* |
Idempotency conflict | See table above |
| 429 | Rate limit | Too fast | Honor Retry-After |
| 5xx | Server error | — | Keep Idempotency-Key; do not blindly rotate |
Prefer error.message for UI. Do not surface raw auth internals to end users.
Differences from 1.5 (summary)
| Item | Grok Imagine 1.5 | 2.0 Ext |
|---|---|---|
| Model | grok-imagine-1.5-openveer, etc. |
grok-imagine-2.0-ext |
| Image-to-image | Supported (see 1.5 docs) | Not supported |
| Count | See 1.5 docs | 1–12 |
| Quality field | See 1.5 docs | resolution (quality); never public quality |
| Output | See 1.5 docs | URL only |
| URL TTL | See 1.5 docs (often 24h) | 72 hours |
| Unit price | See 1.5 docs | \$0.08 / image |
This documentation is built and hosted on Mintlify, a developer documentation platform.