Skip to content

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.0IDA Q Image 2
Model idida-q-imageida-q-image-2
SubmitPOST /v1/ai/generate/imagePOST /v1/ai/generate/image
PollGET /v1/ai/generate/image/ida-q/{job_id}GET /v1/ai/generate/image/ida-q-image-2/{job_id}
Sizeimage_size: 1K, 1.5K, 2Ksize_tier: 1K, HD, FULL
Presets—fast, balanced (default), max
Images per job2 at 1K, 1 above1–4, by size tier and preset (table)
Extras—style, palette, layout boxes, transparent background
Priceper delivered image, by sizeper 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.

python
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']}")
typescript
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);
bash
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:

json
{
  "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 ​

ParameterTypeDefaultDescription
modelstring—"ida-q-image-2".
promptstring—Required. Up to 6000 characters.
size_tierstring"1K"1K, HD or FULL. Not every ratio offers every tier (below).
presetstring"balanced"fast, balanced or max. FULL has no max.
aspect_ratiostring"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_imagesinteger11 up to the cap for the tier and preset (table).
transparentbooleanfalseRender on a transparent background. Priced separately.
seedintegerrandom0 to 4294967295.
stylestring—auto, photo, cinematic, product, poster, typography, illustration, 3d_render, anime, watercolor, minimal_vector, retro_print.
palettestring or array—A named palette (ember, fresh, jungle, pastel, mono, neon, earth, ocean) or 1–8 colours as "#RRGGBB".
layoutarray—Up to 12 boxes that place objects and text. See Layout boxes.
job_idstring (UUID)generatedYour 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:

preset1KHDFULL
fast421
balanced411
max11—

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, needs desc, up to 500 characters) or "text" (needs text, up to 200 characters; desc optional).
  • box: {x, y, w, h} in output pixels of the image this request renders, and it must lie inside it — for example 1024×1024 for 1K 1:1, 1360×768 for 1K 16:9, 2048×2048 for FULL 1:1.
  • palette: optional, 1–8 "#RRGGBB" colours for that box.
json
{
  "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:

json
{
  "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.

bash
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:

json
{
  "job_id": "3b9c2f1e-7d4a-4e0b-9a51-0c6f2d8e4a17",
  "status": "processing",
  "progress": 40,
  "estimated_seconds": 166
}

Once complete:

json
{
  "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:

json
{
  "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: keys 1K, HD, FULL (the delivered size of 1K, 1.5K, 2K).
  • ida-q-image-2: keys TIER:PRESET, with +TRANSPARENT for a transparent background — for example 1K: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.

json
{
  "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
  }
}
StatuscodeWhenExtra fields
400INVALID_REQUESTIDA 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
422INVALID_REQUESTIDA Q Image 2 parameters (preset, tier, ratio, num_images, layout and the rest).—
422CONTENT_POLICYThe prompt breaks the content policy.categories
401—Missing or revoked API key.—
402insufficient_fundsThe wallet cannot pay for the job. Same body as every 402.required_usd, balance_usd, shortfall_usd
403PLAN_REQUIREDThe account's plan does not include this model, size or preset.—
409JOB_ID_CONFLICTThe job_id is already used for a different job.—
429TOO_MANY_ACTIVEYou already have the maximum number of active jobs.active_limit
429DAILY_LIMITThe daily image limit is used up.daily_remaining
429QUEUE_FULLThe queue is full.eta_seconds, retry_after and a Retry-After header (30–3600 s)
500PRICING_UNAVAILABLEThe model (IDA Q 1.0 or IDA Q Image 2) has no price for this size or combination. Refused, not run for free.—
503MODEL_DISABLEDThe model is switched off, or not available on your account yet.—
503MODERATION_UNAVAILABLEThe content check is unavailable.retryable, retry_after: 30, Retry-After: 30
503TEMPORARY_ERRORThe 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.


  • Image Generation — the shared /v1/ai/generate/image endpoint 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.