IDA Q: Self-Hosted Image Models
This page previously described a different product
Earlier revisions of this page documented IDA Q as an interactive "intelligent assistant" with its own chat endpoint (POST /v1/ai/ida-q), SSE streaming, a cost-preflight planner, multi-turn threads, and an embeddable iframe widget. None of that exists in production — there is no such endpoint and no such assistant.
IDA Q is FOTOhub's proprietary, self-hosted image generation family. Both models are submitted like any other image model through POST /v1/ai/generate/image, and — because they run on our own capacity-limited queue rather than a synchronous provider — they are asynchronous: you get a job_id back and poll for the result.
What it is
IDA Q runs entirely on FOTOhub's own infrastructure rather than reselling a third-party image API. Generation goes through a real, capacity-limited queue: expect minutes, not seconds, depending on size, preset and how many jobs are ahead of yours. Every submit response carries estimated_seconds, queue_position and queue_depth.
| IDA Q 1.0 | IDA Q Image 2 | |
|---|---|---|
| Model id | ida-q-image | ida-q-image-2 |
| Submit | POST /v1/ai/generate/image | POST /v1/ai/generate/image |
| Poll | GET /v1/ai/generate/image/ida-q/{job_id} | GET /v1/ai/generate/image/ida-q-image-2/{job_id} |
| Size | image_size: 1K, 1.5K, 2K | size_tier: 1K, HD, FULL |
| Presets | — | fast, balanced (default), max |
| Images per job | 2 at 1K, 1 above | 1–4, by size tier and preset (table) |
| Extras | — | style, palette, layout boxes, transparent background |
| Price | per delivered image, by size | per delivered image, by size tier × preset (× transparent) |
Both are billed from your USD wallet when the job is admitted. Prices are published by GET /v1/pricing, not on this page.
Submitting an IDA Q 1.0 job
Use the standard image generation endpoint and set model to ida-q-image. Because this model is async, the response is a 202 with a job_id and a poll_url — not an image.
import os
import requests
API_KEY = os.environ["FOTOHUB_API_KEY"]
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
payload = {
"model": "ida-q-image",
"prompt": "Editorial studio macro photograph of a titanium chronograph wristwatch on dark polished slate, soft directional key light, 8k detail",
"num_images": 1,
"image_size": "1K",
"aspect_ratio": "1:1",
}
res = requests.post("https://apis.fotohub.app/v1/ai/generate/image", headers=headers, json=payload)
data = res.json()
print(f"job_id: {data['job_id']} status: {data['status']} poll_url: {data['poll_url']}")const res = await fetch("https://apis.fotohub.app/v1/ai/generate/image", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FOTOHUB_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "ida-q-image",
prompt: "Editorial studio macro photograph of a titanium chronograph wristwatch on dark polished slate, soft directional key light, 8k detail",
num_images: 1,
image_size: "1K",
aspect_ratio: "1:1",
}),
});
const data = await res.json();
console.log(data.job_id, data.status, data.poll_url);curl -s -X POST "https://apis.fotohub.app/v1/ai/generate/image" \
-H "Authorization: Bearer $FOTOHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ida-q-image",
"prompt": "Editorial studio macro photograph of a titanium chronograph wristwatch on dark polished slate, soft directional key light, 8k detail",
"num_images": 1,
"image_size": "1K",
"aspect_ratio": "1:1"
}'num_images above what one job renders at the delivered size (2 at 1K, 1 at 1.5K and 2K) is lowered to that cap rather than refused.
Submit response (202 Accepted)
Illustrative values:
{
"model": "ida-q-image",
"job_id": "9f8e7d6c-5b4a-4321-8765-abcdef012345",
"status": "queued",
"duplicate": false,
"num_images": 1,
"resolution": "standard",
"cost_usd": 0.13,
"currency": "USD",
"billing": {
"cost_usd": 0.13,
"balance_usd": 48.721,
"currency": "USD",
"method": "wallet",
"model": "prepaid"
},
"estimated_seconds": 90,
"queue_position": 1,
"queue_depth": 3,
"poll_url": "https://apis.fotohub.app/v1/ai/generate/image/ida-q/9f8e7d6c-5b4a-4321-8765-abcdef012345"
}The wallet is debited in the same step that admits the job to the queue. A refusal (model off, queue full, a limit reached) writes nothing and charges nothing — see Errors.
IDA Q Image 2
ida-q-image-2 is always asynchronous: the submit answers 202, and you poll GET /v1/ai/generate/image/ida-q-image-2/{job_id} (also returned as poll_url).
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
model | string | — | "ida-q-image-2". |
prompt | string | — | Required. Up to 6000 characters. |
size_tier | string | "1K" | 1K, HD or FULL. Not every ratio offers every tier (below). |
preset | string | "balanced" | fast, balanced or max. FULL has no max. |
aspect_ratio | string | "1:1" | 1:1, 4:3, 3:4, 3:2, 2:3, 16:9, 9:16, 21:9, 9:21, 16:10, 4:5, 5:4, 1:4, 4:1. |
num_images | integer | 1 | 1 up to the cap for the tier and preset (table). |
transparent | boolean | false | Render on a transparent background. Priced separately. |
seed | integer | random | 0 to 4294967295. |
style | string | — | auto, photo, cinematic, product, poster, typography, illustration, 3d_render, anime, watercolor, minimal_vector, retro_print. |
palette | string or array | — | A named palette (ember, fresh, jungle, pastel, mono, neon, earth, ocean) or 1–8 colours as "#RRGGBB". |
layout | array | — | Up to 12 boxes that place objects and text. See Layout boxes. |
job_id | string (UUID) | generated | Your id for the job, and its idempotency key. Alternatively send an Idempotency-Key header. |
Every parameter is checked before anything is charged. An invalid value is a 422 with code: "INVALID_REQUEST", an error sentence naming the problem and charged: false.
Sizes and presets
Images per job:
preset | 1K | HD | FULL |
|---|---|---|---|
fast | 4 | 2 | 1 |
balanced | 4 | 1 | 1 |
max | 1 | 1 | — |
1:1, 4:3, 3:4, 3:2, 2:3, 16:10, 4:5 and 5:4 go up to FULL; 16:9, 9:16, 21:9 and 9:21 up to HD; 1:4 and 4:1 are 1K only. Asking for a combination that is not offered is a 422 that suggests a smaller size_tier or a faster preset.
Layout boxes
Each layout entry is {type, box, desc, text, palette}:
type:"obj"(an object, needsdesc, up to 500 characters) or"text"(needstext, up to 200 characters;descoptional).box:{x, y, w, h}in output pixels of the image this request renders, and it must lie inside it — for example 1024×1024 for1K1:1, 1360×768 for1K16:9, 2048×2048 forFULL1:1.palette: optional, 1–8"#RRGGBB"colours for that box.
{
"model": "ida-q-image-2",
"prompt": "Summer sale poster for a lemonade brand, bright and fresh",
"size_tier": "1K",
"preset": "balanced",
"aspect_ratio": "4:5",
"num_images": 2,
"style": "poster",
"palette": "fresh",
"layout": [
{ "type": "obj", "box": { "x": 200, "y": 300, "w": 512, "h": 600 }, "desc": "a tall glass of lemonade with ice and mint" },
{ "type": "text", "box": { "x": 96, "y": 60, "w": 720, "h": 180 }, "text": "SUMMER SALE" }
]
}Submit response (202 Accepted)
Illustrative values:
{
"model": "ida-q-image-2",
"job_id": "3b9c2f1e-7d4a-4e0b-9a51-0c6f2d8e4a17",
"status": "queued",
"duplicate": false,
"num_images": 2,
"size_tier": "1K",
"preset": "balanced",
"aspect_ratio": "4:5",
"transparent": false,
"cost_usd": 0.26,
"currency": "USD",
"billing": {
"cost_usd": 0.26,
"balance_usd": 48.461,
"currency": "USD",
"method": "wallet",
"model": "prepaid"
},
"estimated_seconds": 166,
"queue_position": 1,
"queue_depth": 2,
"poll_url": "https://apis.fotohub.app/v1/ai/generate/image/ida-q-image-2/3b9c2f1e-7d4a-4e0b-9a51-0c6f2d8e4a17"
}Retrying a submit is safe. The job id is the idempotency key: your job_id, else one derived from your Idempotency-Key header, else a new one. Resending a job that was already admitted returns that job with "duplicate": true and is not charged again; the response then describes the job as it was admitted, not the repeated request.
Polling for the result
Poll the model's poll route with the job_id until status is completed, failed or cancelled.
curl -s "https://apis.fotohub.app/v1/ai/generate/image/ida-q-image-2/3b9c2f1e-7d4a-4e0b-9a51-0c6f2d8e4a17" \
-H "Authorization: Bearer $FOTOHUB_API_KEY"While queued or running:
{
"job_id": "3b9c2f1e-7d4a-4e0b-9a51-0c6f2d8e4a17",
"status": "processing",
"progress": 40,
"estimated_seconds": 166
}Once complete:
{
"job_id": "3b9c2f1e-7d4a-4e0b-9a51-0c6f2d8e4a17",
"status": "completed",
"progress": 100,
"estimated_seconds": 166,
"images": ["https://s3point.fotohub.app/generations/img_abc123.png", "https://s3point.fotohub.app/generations/img_abc124.png"],
"metadata": { "model": "ida-q-image-2" },
"result": { "seed": 42, "width": 912, "height": 1136, "num_images": 2, "requested_images": 2, "has_alpha": false },
"billing": { "method": "wallet", "currency": "USD", "cost_usd": 0.26, "refunded_usd": 0.0, "refunded": false, "refund_pending": false },
"refunded": false
}result is returned by IDA Q Image 2. When fewer images are delivered than were requested, the difference is refunded and shows in billing.refunded_usd.
On failure:
{
"job_id": "3b9c2f1e-7d4a-4e0b-9a51-0c6f2d8e4a17",
"status": "failed",
"progress": 0,
"error": "Generation failed. Please try again.",
"billing": { "method": "wallet", "currency": "USD", "cost_usd": 0.26, "refunded_usd": 0.26, "refunded": true, "refund_pending": false },
"refunded": true
}A failed or cancelled job is refunded to the wallet; billing shows what was charged and what came back. refund_pending: true means the refund is recorded but not paid yet; it is paid automatically. When the model declines the prompt, the failure also carries "error_code": "SAFETY_BLOCKED". Jobs submitted before the current queue have no billing block.
Pricing
Both models are priced per delivered image. The full grid is in GET /v1/pricing?model=ida-q-image and GET /v1/pricing?model=ida-q-image-2, under legs.output.tiers_usd:
ida-q-image: keys1K,HD,FULL(the delivered size of1K,1.5K,2K).ida-q-image-2: keysTIER:PRESET, with+TRANSPARENTfor a transparent background — for example1K:FAST,HD:BALANCED,FULL:BALANCED+TRANSPARENT.
For these two models price_usd is the lowest tier ("from"). A job costs the tier price times num_images; the submit response's cost_usd is the exact amount taken.
Errors
Every refusal below happens before the job is queued: nothing is charged, and the body says charged: false. The code is in detail.code, and detail.error is a sentence you can show.
{
"detail": {
"error": "The IDA Q queue is full. Try again shortly.",
"code": "QUEUE_FULL",
"charged": false,
"message": "Your wallet was not charged for this request.",
"eta_seconds": 4200,
"retry_after": 600
}
}| Status | code | When | Extra fields |
|---|---|---|---|
400 | INVALID_REQUEST | IDA Q 1.0 parameters, a job_id that is not a UUID, or a combination the queue does not take (size over the tier, settings not offered, or — IDA Q Image 2 only — too many images). IDA Q 1.0 never refuses for too many images: it lowers num_images to the size's maximum. | max_images (IDA Q Image 2 only) when too many images were asked for |
422 | INVALID_REQUEST | IDA Q Image 2 parameters (preset, tier, ratio, num_images, layout and the rest). | — |
422 | CONTENT_POLICY | The prompt breaks the content policy. | categories |
401 | — | Missing or revoked API key. | — |
402 | insufficient_funds | The wallet cannot pay for the job. Same body as every 402. | required_usd, balance_usd, shortfall_usd |
403 | PLAN_REQUIRED | The account's plan does not include this model, size or preset. | — |
409 | JOB_ID_CONFLICT | The job_id is already used for a different job. | — |
429 | TOO_MANY_ACTIVE | You already have the maximum number of active jobs. | active_limit |
429 | DAILY_LIMIT | The daily image limit is used up. | daily_remaining |
429 | QUEUE_FULL | The queue is full. | eta_seconds, retry_after and a Retry-After header (30–3600 s) |
500 | PRICING_UNAVAILABLE | The model (IDA Q 1.0 or IDA Q Image 2) has no price for this size or combination. Refused, not run for free. | — |
503 | MODEL_DISABLED | The model is switched off, or not available on your account yet. | — |
503 | MODERATION_UNAVAILABLE | The content check is unavailable. | retryable, retry_after: 30, Retry-After: 30 |
503 | TEMPORARY_ERROR | The job could not be queued right now. | retryable, retry_after: 5, job_id, Retry-After: 5 |
Retrying. On TEMPORARY_ERROR resend with the same job_id (it is returned in the body): a job that did get queued is answered as a duplicate and never charged twice. On a 429 wait for Retry-After. Fix the request on 400/422.
Related
- Image Generation — the shared
/v1/ai/generate/imageendpoint IDA Q submits through, including the other (synchronous) models available on it. - Error Handling — the status codes and error bodies shared by every endpoint.
- Gabriel AI — FOTOhub's actual prompt-routing/classification endpoint, if you were looking for an assistant that decides which model or pipeline to call.

