Permalink to ImagesImages
Two routes, both asynchronous:
| Route | Purpose |
|---|---|
POST /v1/images/generations | text to image |
POST /v1/images/edits | image to image, with optional mask |
Both answer 202 Accepted with a job envelope. The finished pixels are
collected from GET /v1/jobs/{job_id}/assets.
`client.images.generate()` will not work
The OpenAI SDK helper expects a synchronous {"data": [{"url": …}]} body. Ours returns a job. Call
these two routes over plain HTTP.
Permalink to generateGenerate
curl https://hypit.ai/v1/images/generations \
-H "Authorization: Bearer $HYPIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "nano-banana-pro",
"prompt": "a paper boat drifting down a rain gutter, shallow depth of field",
"n": 1,
"aspect_ratio": "16:9",
"resolution": "1k"
}'{
"id": "job_5a0ce31f78b246d9c0117e42",
"status": "queued",
"kind": "image",
"model": "nano-banana-pro",
"progress": 0,
"created_at": 1787824589,
"dispatch_deadline_at": 1787825189
}Permalink to request-bodyRequest body
| Field | Type | Notes |
|---|---|---|
model | string | required |
prompt | string | required |
n | integer | 1–10, default 1. Each image becomes one asset ordinal |
size | string | e.g. "1024x1024", or "auto" |
resolution | string | A model-published output tier, e.g. "1k", "2k", "4k"; independent of aspect ratio |
quality | string | e.g. low, medium, high, hd, standard, auto |
style | string | vendor-defined |
aspect_ratio | string | e.g. "16:9" |
negative_prompt | string | |
background | string | e.g. transparent, opaque, auto |
output_format | string | png, jpeg, webp |
response_format | string | url or b64_json — anything else is a 400 |
seed | integer | |
watermark | boolean | |
reference_images | array | see below; images is accepted as a compatibility alias |
mask | string or object | see below |
For models with output tiers, prefer aspect_ratio + resolution. A 4k image is not a fixed 3840x2160 canvas: the model and aspect ratio determine the actual pixels. Legacy pixel size remains available on models that support it. When also supplying aspect_ratio, both must describe the same shape. metadata.resolution remains a compatibility alias; contradictory aliases return 400.
Before creating a job or reserving credits, the gateway checks that an available route supports the requested tier and shape. Unsupported combinations return a parameter error instead of silently selecting a default. Pricing uses the same tier; see pricing.resolution_ratio or pricing.media_rates on the model card.
`response_format` does not change how you collect the result
It is validated and forwarded to the upstream, but a job's artifacts are always delivered through the assets API as signed links. There is no path that returns base64 inline.
Permalink to vendor-passthroughVendor passthrough
Any key the schema above does not model is forwarded to the upstream verbatim. That is how features
that only exist on one vendor stay reachable. Two limits apply: the whole extra block is capped in
size and nesting depth, and any string that looks like a media URL must be a real http(s) or
data: URL — a URL smuggled through an extra field is rejected with url_in_extra rather than
silently fetched.
Permalink to reference-images-in-jsonReference images in JSON
reference_images (or the compatibility alias images) attaches source material to a JSON request. Each entry is either a bare URL
string or an object:
{
"model": "nano-banana-pro",
"prompt": "put the boat in a snowstorm",
"reference_images": [
{ "url": "https://example.com/boat.png" },
{ "b64": "iVBORw0KGgo…", "mime_type": "image/png", "filename": "boat.png" }
]
}mask accepts the same object, or a plain URL / data: URL string.
Every URL you supply is fetched through a guarded fetcher: http, https and data: only, no
private or link-local addresses, and every redirect is re-checked. A URL that fails the check is a
400 (invalid_media_url, unsupported_media_url_scheme or invalid_input_url) and nothing is
charged.
Permalink to editEdit
POST /v1/images/edits takes either multipart/form-data with real file parts, or the same JSON
body as above with reference_images. At least one source image is required.
curl https://hypit.ai/v1/images/edits \
-H "Authorization: Bearer $HYPIT_API_KEY" \
-F model=nano-banana-pro \
-F 'prompt=make the sky overcast' \
-F image=@boat.png \
-F mask=@sky-mask.pngMultipart field names:
| Part | Kind | Notes |
|---|---|---|
image, image[] | file | one or more source images, in the order sent |
mask | file | at most one |
model, prompt, n, size, resolution, quality, style, response_format, background, output_format, negative_prompt, aspect_ratio, seed, watermark | value | same semantics as the JSON fields |
Limits:
- 25 MiB per uploaded file
- 100 MiB for the whole multipart body
- 10 MiB for a JSON body on
/v1/images/generations - 64 KiB per multipart scalar field
The upload's declared Content-Type is ignored — we sniff the bytes, and only PNG, JPEG and WebP
are accepted. Anything else is 400 unsupported_image_format.
Permalink to end-to-endEnd to end
import os, time, httpx
BASE = os.environ.get("HYPIT_BASE_URL", "https://hypit.ai/v1")
AUTH = {"Authorization": f"Bearer {os.environ['HYPIT_API_KEY']}"}
job = httpx.post(
f"{BASE}/images/generations",
headers=AUTH,
json={
"model": "nano-banana-pro",
"prompt": "a paper boat drifting down a rain gutter",
"n": 1,
"aspect_ratio": "16:9",
"resolution": "1k",
},
timeout=60,
)
job.raise_for_status()
job_id = job.json()["id"]
while True:
state = httpx.get(f"{BASE}/jobs/{job_id}", headers=AUTH, timeout=30).json()
if state["status"] in {"succeeded", "failed", "queue_expired"}:
break
time.sleep(3)
if state["status"] != "succeeded":
raise RuntimeError(f"{state['status']}: {state.get('error_code')} {state.get('error')}")
for a in httpx.get(f"{BASE}/jobs/{job_id}/assets", headers=AUTH, timeout=30).json()["items"]:
data = httpx.get(a["url"], follow_redirects=True, timeout=None).content
open(f"image-{a['ordinal']}.png", "wb").write(data)import { writeFile } from "node:fs/promises";
const BASE = process.env.HYPIT_BASE_URL ?? "https://hypit.ai/v1";
const AUTH = { Authorization: `Bearer ${process.env.HYPIT_API_KEY}` };
const TERMINAL = new Set(["succeeded", "failed", "queue_expired"]);
const created = await fetch(`${BASE}/images/generations`, {
method: "POST",
headers: { ...AUTH, "Content-Type": "application/json" },
body: JSON.stringify({
model: "nano-banana-pro",
prompt: "a paper boat drifting down a rain gutter",
n: 1,
aspect_ratio: "16:9",
resolution: "1k",
}),
});
if (!created.ok) throw new Error(`${created.status} ${await created.text()}`);
const { id } = await created.json();
let job;
do {
await new Promise((r) => setTimeout(r, 3_000));
job = await (await fetch(`${BASE}/jobs/${id}`, { headers: AUTH })).json();
} while (!TERMINAL.has(job.status));
if (job.status !== "succeeded") throw new Error(`${job.status}: ${job.error_code} ${job.error}`);
const { items } = await (await fetch(`${BASE}/jobs/${id}/assets`, { headers: AUTH })).json();
for (const a of items) {
const bytes = Buffer.from(await (await fetch(a.url)).arrayBuffer());
await writeFile(`image-${a.ordinal}.png`, bytes);
}Permalink to errors-specific-to-these-routesErrors specific to these routes
| Status | code | Meaning |
|---|---|---|
400 | missing_model / missing_prompt | both are required |
400 | invalid_n | n must be between 1 and 10 |
400 | invalid_response_format | not url or b64_json |
400 | missing_image | /v1/images/edits with no source image |
400 | invalid_multipart | the multipart body could not be parsed |
400 | unexpected_file | a file part other than image, image[] or mask |
400 | invalid_mask / empty_mask | mask is neither a URL nor a {b64|url} object |
400 | unsupported_image_format | the bytes are not PNG, JPEG or WebP |
400 | empty_image / unreadable_image | a file part carried nothing usable |
413 | image_too_large | a single file exceeds 25 MiB |
413 | body_too_large | JSON over 10 MiB, or a multipart body over 100 MiB |
413 | form_field_too_large | a scalar multipart field exceeds 64 KiB |
404 | model_not_found | no enabled provider serves this model for images |
The shared job, billing and rate-limit errors are listed in Errors and limits.