Hoosha Media API
The Hoosha API generates images, video, music and speech from frontier models behind one key, one credit balance and one polling contract.
Quickstart
Code
Every path ends with a slash; send requests to the exact paths shown here.
Authentication
Every request carries Authorization: Bearer sk-hoosha-…. Keys are created
and revoked in the Hoosha developer console and are shown once, at creation.
Twenty failed authentications with well-formed sk-hoosha- keys from one IP
address, within 15 minutes of the first failure, lock that IP out of key
authentication until those 15 minutes are up. During the lockout every key
sent from that IP, valid or not, is answered with 429 rate_limit_exceeded
without retry_after. A successful authentication before the lockout resets
the counter.
The asynchronous contract
Every generation endpoint is asynchronous and behaves identically:
POSTreturns 202 with anidthe moment the job is accepted and charged. Nothing is rendered yet.GET /generations/{id}/returnsprocessing, thensucceededorfailed.- On
succeeded,outputholds the results. Onfailed,failure_reasonexplains why, and the credits are refunded automatically shortly after;credits_refundedshows the refund once it has been made.
Poll every few seconds. Images finish in seconds, video in minutes. GET
requests do not count against your quota, so a long poll loop costs nothing
against it.
Credits
Credits are debited when the job is accepted, not when it finishes, so
credits_charged on the 202 is the most the generation can cost, with the
one exception in the last item below. Part of it comes back automatically in
these cases:
- Video models billed per second (
kling3,grok-imagine,seedance2,seedance25,kling-motion,lipsync) refund the undelivered seconds when the delivered clip is at least 2 seconds shorter than what was billed. seedance25ineditorextendmode is settled once the video is delivered, as described under Video models.- The
high,xhighandmaxlevels of thechatgpt-image-2.5-*models hold an estimate and settle to the actual cost once the image is delivered. The difference usually comes back; a very long prompt can settle a little above the hold, and the rest is then taken from your balance without showing incredits_refunded.
A generation that fails on the provider side is refunded automatically, so
you never pay for a failed result. GET /models/ returns the price of every
model and parameter combination it can list, and the tables in this document
are generated from that same source. The cases it cannot list are described
under Video models.
On /videos/, a duration, quality, aspect_ratio, model_level or other
per-model choice outside the model's set in GET /models/ is rejected with
400 invalid_request naming the field and the legal values, before anything
is charged. /images/ checks its fields as described under Image models. An
omitted duration, quality or aspect_ratio takes the model's default.
Prompt and style limits count UTF-16 code units, so an emoji counts as two characters.
Image models
Common fields: model, prompt, aspect_ratio, quality on the
quality-tiered models, level and background on the
chatgpt-image-2.5-* models, output_format on nano-banana-2-pro, plus
optional input images for editing.
| model | name | credits | prompt max | input images | qualities |
|---|---|---|---|---|---|
nano-banana-2-1 | Nano Banana 2.1 | 8 / 11 / 20 | 20,000 | 10 | 1K / 2K / 4K |
nano-banana-2-pro | Nano Banana 2 | 9 / 11 / 14 | 20,000 | 14 | 1K / 2K / 4K |
chatgpt-image-2.5-flare | ChatGPT Image 2.5 Flare | 9 / 9 / 13 | 20,000 | 16 | 1K / 2K / 4K |
chatgpt-image-2.5-sunburst | ChatGPT Image 2.5 Sunburst | 9 / 9 / 13 | 20,000 | 16 | 1K / 2K / 4K |
grok-imagine-2 | Grok Imagine 2 | 4 | 8,000 | 5 | — |
chatgpt-image-2.0 | ChatGPT Image 2.0 | 9 / 9 / 14 | 20,000 | 16 | 1K / 2K / 4K |
hoosha-v1 | Hoosha V1 | 5 | 20,000 | 5 | — |
chatgpt-image-1.5 | ChatGPT Image 1.5 | 8 | 3,000 | 16 | — |
flux_kontext | Flux Kontext | 8 | 3,000 | 1 | — |
seedream4 | SeeDream 4.5 | 8 | 3,000 | 10 | — |
Credits are per image. nano-banana-2-1 adds 0.3 credits per input image
and rounds the total up to a whole credit. aspect_ratio defaults to 4:3,
except on chatgpt-image-2.0, the chatgpt-image-2.5-* models and
nano-banana-2-1 (auto), and on chatgpt-image-1.5 and grok-imagine-2
(1:1). quality defaults to 2K on the tiered models (1K on
nano-banana-2-1) and is ignored elsewhere; on a tiered model, a value
other than 1K, 2K or 4K fails with invalid_quality.
chatgpt-image-2.0, the chatgpt-image-2.5-* models, chatgpt-image-1.5,
nano-banana-2-1 and grok-imagine-2 check aspect_ratio; an unsupported
value fails with invalid_aspect_ratio. The other image models accept any
value and replace a ratio they cannot render: with auto on hoosha-v1,
1:1 on nano-banana-2-pro and seedream4, and 16:9 on
flux_kontext. flux_kontext cannot render the 4:3 default, so a
flux_kontext request without aspect_ratio renders 16:9. Use the ratios
listed in GET /models/.
chatgpt-image-2.0 limits, checked before you are billed: it accepts
auto, 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9,
1:2, 2:1, 1:3, 3:1, 9:21 and 21:9. auto and the 5:4 4:5
3:1 1:3 9:21 ratios render at 1K only (and default to 1K when quality
is omitted), and 1:1 caps at 2K. Asking for a higher quality than the ratio
allows fails with unsupported_quality_for_aspect_ratio. chatgpt-image-1.5
accepts only 1:1, 2:3, 3:2.
chatgpt-image-2.5-flare and chatgpt-image-2.5-sunburst accept
auto, 1:1, 3:2, 2:3, 4:3, 3:4, 16:9, 9:16, 21:9, 27:16,
16:27, 9:8 and 8:9. auto renders at 1K only and defaults to 1K when
quality is omitted. level sets the render effort: low and medium
(the default) cost the table price. high, xhigh and max need a fixed
aspect_ratio (unsupported_aspect_ratio_for_level otherwise) and 2K or
4K (unsupported_quality_for_level otherwise). At those three levels the
credits below, plus 3 per input image, are held when the request is
accepted and settle to the actual cost once the image is delivered.
| Aspect ratio | high 2K | high 4K | xhigh 2K | xhigh 4K | max 2K | max 4K |
|---|---|---|---|---|---|---|
| 1:1 | 21 | 34 | 36 | 59 | 80 | 133 |
| 3:2 | 11 | 23 | 19 | 40 | 42 | 89 |
| 2:3 | 11 | 23 | 19 | 40 | 42 | 89 |
| 4:3 | 13 | 25 | 23 | 44 | 50 | 99 |
| 3:4 | 13 | 25 | 23 | 44 | 50 | 99 |
| 16:9 | 9 | 19 | 15 | 34 | 32 | 75 |
| 9:16 | 9 | 19 | 15 | 34 | 32 | 75 |
| 21:9 | 6 | 13 | 10 | 21 | 22 | 46 |
| 27:16 | 9 | 20 | 16 | 35 | 35 | 79 |
| 16:27 | 9 | 20 | 16 | 35 | 35 | 79 |
| 9:8 | 17 | 30 | 30 | 53 | 67 | 117 |
| 8:9 | 17 | 30 | 30 | 53 | 67 | 117 |
background is auto, opaque or transparent on these two models at
every level; any other value fails with invalid_background, and a level
outside the five fails with invalid_level.
grok-imagine-2 accepts auto, 1:1, 2:3, 3:2, 16:9 and 9:16.
auto follows the input image, so auto without one fails with
invalid_aspect_ratio. nano-banana-2-1 always returns JPEG.
output_format (nano-banana-2-pro only) is png (the default) or jpg;
jpeg is read as jpg, and anything else fails with invalid_output_format.
Video models
Common fields: model, prompt, duration in seconds, quality,
aspect_ratio, plus the per-model fields documented on the request body.
Send duration, quality and model_level as JSON strings ("10", not
10). A number passes validation, but several models then replace it with a
different value: duration: 10 on kling renders 5 seconds.
| model | name | credits | duration | qualities | levels | audio |
|---|---|---|---|---|---|---|
grok-imagine | Grok Imagine 1.5 | 6–75 | 3–15 s | 480 / 720 / 1080 | — | yes |
kling3 | Kling 3.0 Turbo | 33–210 | 3–15 s | 720 / 1080 | — | yes |
seedance25 | Seedance 2.5 | 68–2820 | 4–30 s | 480 / 720 / 1080 | — | yes |
seedance2 | Seedance 2 | 28–1980 | 4–15 s | 480 / 720 / 1080 / 4k | pro / mini / fast | yes |
kling-motion | Kling Motion | 35–80 | from input | 720 / 1080 | 2.6 / 3.0 | yes |
genjutsu | Genjutsu | 70–555 | from input | 480 / 720 / 1080 | standard / lite | yes |
gemini-omni-video | Google Omni | 60–130 | 4 / 6 / 8 / 10 s | 720 / 1080 / 4k | — | yes |
veo3 | Veo 3.1 | 30 | 8 s | 720 | — | yes |
runway | Runway | 15–30 | 5 / 10 s | 720 / 1080 | — | no |
kling | Kling 2.5 Turbo | 40–80 | 5 / 10 s | 1080 | — | no |
seedance | Seedance v1 | 20–100 | 5 / 10 s | 720 / 1080 | — | no |
lipsync | Lipsync | 65–100 | from input | auto | v2-pro / v3 | yes |
duration, quality and aspect_ratio must come from each model's
documented set; anything else is rejected before billing. An omitted field
takes the model's default from GET /models/. Where the catalog lists no
default, kling3 uses 5 seconds at 720 and gemini-omni-video uses 8
seconds at 720.
Per-second models charge the requested length up front. kling-motion,
genjutsu and lipsync take their length from the input instead: the
reference video, or for lipsync the output length described under
sync_mode. In the GET /models/ grid these three are priced for a nominal
5-second output, and the real charge scales per second. Seedance models count
video inputs toward the billed seconds: reference footage adds its length to
the charged duration. On seedance25 a video input also switches every
billed second to the lower with-video rate (11 / 23 / 57 credits at 480 /
720 / 1080 instead of 17 / 38 / 94). gemini-omni-video with a video1
input decides the output length itself and is billed a flat 110 / 110 / 155
credits at 720 / 1080 / 4k, a price the GET /models/ grid does not list.
seedance25 edit and extend are billed at the with-video rate:
editlets the model decide the output length, so it holds credits for twice the input seconds plus 3 seconds.extendcharges for the input seconds plus the requestedduration.
Both are settled once the video is delivered: the charge is reduced to the input seconds plus the delivered seconds, and the difference is refunded. The charge never goes up.
aspect_ratio: image makes the output follow the input image where the model
supports it.
Music models
| model | name | credits | prompt max (simple) | prompt max (custom) | style max |
|---|---|---|---|---|---|
suno-v6 | Suno V6 | 15 | 3,000 | 5,000 | 1,000 |
Two modes. Simple (customMode: false, the default) takes a short
description and lets the model choose everything else. Custom
(customMode: true) takes a longer prompt as the lyrics, a style and a
title. Every request returns two tracks for the one price.
Speech models
Billed on the total length of the dialogue text: the rate per 1,000 characters times the character count, divided by 1,000 and rounded up to a whole credit once per request. The count is taken after trimming each line and includes audio tags. At 12 credits per 1,000 characters, 300 characters cost 4 credits.
| model | name | credits / 1,000 chars | max lines | total chars | max distinct voices |
|---|---|---|---|---|---|
eleven-dialogue-v4 | ElevenLabs v4 | 12 | 6 | 5–5,000 | 10 |
eleven-dialogue-v4-turbo | ElevenLabs v4 Turbo | 6 | 6 | 5–5,000 | 10 |
eleven-dialogue-v3 | ElevenLabs v3 | 12 | 6 | 5–5,000 | 10 |
Inline audio tags such as [laughs], [whispers] or [excited] direct the
delivery and are not spoken, but they count toward the billed length. A line
built only from tags or emoji is rejected with no_speakable_text, because
the provider would charge for it and return silence.
Voices
| name | character | gender | id |
|---|---|---|---|
| شایان | گوینده | male | EkK5I93UQWFDigLMpZcX |
| غزاله | صدای زنانه و صمیمی | female | 5l5f8iK3YPeGga21rQIX |
| لئو | صدای بم و مقتدر | male | scOwDtmlUjD3prqpp97I |
| الی | گوینده | female | Z3R5wn05IrDiVCyEkUrK |
| سهند | صدای رسا و خوشبیان | male | NNl6r8mD7vthiJatiJt1 |
| سینا | صدای صمیمی و راحت | male | 1SM7GgM6IMuvQlz2BwM3 |
| آروین | صدای پخته و صمیمی | male | NOpBlnGInO9m6vDvFkFC |
| هلیا | صدای جوان و پرانرژی | female | BZgkqPqms7Kj9ulSkVzn |
| پارسا | صدای جوان و شیک | male | IRHApOXLvnW57QJPQH2P |
| کاوه | گوینده | male | ZthjuvLPty3kTMaNKVKb |
| لیلی | صدای آهسته و نجواگونه | female | uTwXcxvoi6bVu6ejzt6B |
| آوا | صدای شاد و پرانرژی | female | kPzsL2i3teMYv0FxEYQ6 |
| نیما | صدایی طبیعی و روان | male | UgBBYS2sOqTuMpoF3BR0 |
| ترانه | صدای زنانه، پرکاریزما و نجواگونه | female | BpjGufoPiobT79j2vtj4 |
| بهرام | صدای عمیق و آرامشبخش | male | nPczCjzI2devNBz1zQrb |
| دلسا | صدای دخترانه و پرهیجان | female | uYXf8XasLslADfZ2MB4u |
| سارا | صدای شفاف و قاطع | female | 56AoDkrOh6qfVPDXZ7Pt |
| فربد | صدای گرم و مثبت | male | Sq93GQT4X1lKDXsQcixO |
| لادن | صدای مرموز و باوقار | female | flHkNRp1BlvT73UL6gyz |
| روژین | صدای نرم و گیرا | female | wJqPPQ618aTW29mptyoc |
| اردشیر | صدای عمیق و سنگین | male | EiNlNiXeDU1pqqOPrYMO |
| داریوش | صدای بم و صمیمی | male | 6F5Zhi321D3Oq7v1oNT4 |
| رومینا | صدای جذاب و بازیگوش | female | eVItLK1UvXctxuaRV2Oq |
| بنیامین | صدای عمیق و تسکیندهنده | male | LruHrtVF6PSyGItzMNHS |
| نیایش | صدایی ملایم و آرامشبخش | female | 1wGbFxmAM3Fgw63G1zZJ |
| سهراب | صدای آرام و متین | male | hqfrgApggtO1785R4Fsn |
| روزبه | صدای گرم و آرامشبخش | male | MJ0RnG71ty4LH3dvNfSd |
| لونا | صدای دختر بچه شاد و بازیگوش | female | ocZQ262SsZb9RIxcQBOj |
| شهرزاد | گوینده | female | ndcUYGFbbd96WXiZVVaQ |
| ننه جون | صدای مادربزرگ مهربان | female | 0rEo3eAjssGDUCXHYENf |
GET /models/ returns the same voices under the speech model's voices, next
to the accepted languages.
Idempotency
Send an Idempotency-Key header (or a client_request_id body field) to make
a submission safe to retry. The header wins if both are sent, and a key
longer than 200 characters is cut to its first 200. The key is fingerprinted
together with the request's prompt (for speech, its dialogue), so the same
key with a different prompt is a different request. No other field is
compared: the same key and prompt with a different model or parameters
replays the first response.
- A replay within 15 minutes of an accepted request returns the original body with status 200 instead of 202, and is not charged again.
- A replay while the first request is still in flight returns
409
request_in_progress. Retry in a moment. - A rejected request is not stored, so retrying it with the same key runs it again.
- After a
503 provider_unavailable, retry with a new key. The same key is answered with409for up to 60 seconds on/images/,/music/and/speech/, and on/videos/with a202for the generation that failed. - On
/videos/, only requests without upload ids can be replayed. A replay of a request that used upload ids is rejected with400 invalid_request, because the first request consumed them. - The balance is checked before the replay: on
/images/a replay is answered with402once your balance is zero, and on/videos/once it is below the price.
Rate limits
Only writes count: every POST to the generation endpoints, including ones
rejected for invalid input, counts against your account's per-minute quota,
returned by GET /credits/ as rate_limit_per_minute (10 by default).
Uploads have their own ceiling of 10,000 per 24 hours. GET requests do not
count against your quota.
A throttled response is 429 rate_limit_exceeded, with retry_after seconds
in the error body and the same value in the Retry-After header. The
authentication lockout also answers 429 rate_limit_exceeded, without
either.
Media inputs
Endpoints that accept input media take it three ways, and they can be mixed.
/images/ and /videos/ read upload ids from different fields. A field the
endpoint does not read is ignored, and the job runs without that input.
| Way | /images/ | /videos/ | Notes |
|---|---|---|---|
| Hosted URL | image_urls (repeatable) | image_urls (repeatable) | Must be https:// and hosted outside Hoosha. Images only. |
| Upload id | uploads (array) | positional image1…imageN, video1…videoN, audio1…audioN | Ids from POST /uploads/. Single-use. Expire about 20 minutes after upload. |
| Direct file | multipart parts image1…imageN | multipart parts image1…imageN | Send the request as multipart/form-data. |
On /images/, a positional image1 is read only as a file part, so an upload
id sent there is ignored. On /videos/, uploads is ignored, and an upload
id must be in its 36-character form.
Positional fields matter for models that assign meaning to input order:
first-frame-last-frame uses image1 as the first frame and image2 as the
last. Hosted URLs from image_urls fill the lowest free imageN slots, in
order. Every endpoint except /speech/ accepts multipart/form-data as well
as JSON. /speech/ needs JSON for its dialogue array, and direct-file
uploads need multipart, because JSON cannot carry raw files.
Direct files differ by endpoint. /images/ accepts png, jpg, jpeg,
webp, gif, bmp, tiff, tif, heic and heif, converts everything
except PNG and JPEG to JPEG, and ignores parts above the model's input-image
cap. /videos/ accepts png, jpg, jpeg, webp and gif, up to 10 MB
each.
POST /uploads/ accepts:
| Kind | Extensions | Max size | Duration |
|---|---|---|---|
| Image | png jpg jpeg webp gif bmp tiff tif heic heif | 30 MB | — |
| Video | mp4 mov webm m4v | 200 MB | ≤ 30 s |
| Audio | mp3 wav m4a | 15 MB | 2–30 s |
Images and video are validated by decoding the file, not by trusting its
extension, and an image is stored in the format its content shows. Audio
duration is checked when the file can be read. gif, bmp, tiff, webp,
heic and heif images are converted to JPEG, PNG and JPEG images larger
than 4,096 px on the long edge are downscaled, and m4a audio is converted to
MP3, so the stored file may differ from what you sent.
An upload id is single-use and expires about 20 minutes after it is
created, so submit the generation that uses it within that time. Once any
request claims an id, later references fail, even when that request is then
rejected (for example for credits, image count or an input rule). Upload the
file again before retrying a rejected request. Unused uploads share one pool
per account, app uploads included: each POST /uploads/ discards the oldest
unused ones beyond 1,000, and each upload made in the Hoosha app trims the
pool to the newest 55.
Errors
Errors returned by the API use one envelope:
Code
code is stable and safe to branch on; message is English prose and may
change. Some codes add fields, listed below.
| Status | code | Extra fields |
|---|---|---|
| 400 | invalid_request, bad_request, invalid_model, prompt_required, prompt_too_long, invalid_quality, invalid_aspect_ratio, unsupported_quality_for_aspect_ratio, invalid_level, invalid_background, unsupported_aspect_ratio_for_level, unsupported_quality_for_level, invalid_output_format, style_too_long, dialogue_required, too_many_lines, invalid_line_format, empty_line, no_speakable_text, invalid_voice, too_many_voices, text_too_short, text_too_long, invalid_stability, invalid_language, invalid_image_url, invalid_upload, too_many_images, invalid_file, unsupported_image_format, image_processing_failed, pricing_unavailable | — |
| 401 | unauthorized | — |
| 402 | insufficient_credits | required_credits, except on /videos/ and on /images/ when the balance is zero |
| 404 | not_found | — |
| 405 | method_not_allowed | — |
| 409 | request_in_progress | — |
| 415 | unsupported_media_type | — |
| 429 | rate_limit_exceeded | retry_after, except on the authentication lockout |
| 500 | invalid_file (POST /uploads/ could not store the file) | — |
| 503 | provider_unavailable | id (images, music, speech) |
bad_request means the body could not be parsed as JSON or multipart.
Output URLs
Result URLs are signed and valid for at least 24 hours. Re-poll the
generation to mint fresh ones. The underlying media is retained for 90 days;
after that output is empty, except that music items keep title and
duration with url and cover_image set to null.
/images/ or
/videos/. Use this when the file is not already on a public https URL.
Accepted formats and limits are listed under Media inputs on the overview.GET /models/.id is the one returned by the submitting POST, not the
provider's id.