Permalink to ImagesImages

Two routes, both asynchronous:

RoutePurpose
POST /v1/images/generationstext to image
POST /v1/images/editsimage 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

bash
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"
  }'
json
{
  "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

FieldTypeNotes
modelstringrequired
promptstringrequired
ninteger1–10, default 1. Each image becomes one asset ordinal
sizestringe.g. "1024x1024", or "auto"
resolutionstringA model-published output tier, e.g. "1k", "2k", "4k"; independent of aspect ratio
qualitystringe.g. low, medium, high, hd, standard, auto
stylestringvendor-defined
aspect_ratiostringe.g. "16:9"
negative_promptstring
backgroundstringe.g. transparent, opaque, auto
output_formatstringpng, jpeg, webp
response_formatstringurl or b64_json — anything else is a 400
seedinteger
watermarkboolean
reference_imagesarraysee below; images is accepted as a compatibility alias
maskstring or objectsee 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:

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

bash
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.png

Multipart field names:

PartKindNotes
image, image[]fileone or more source images, in the order sent
maskfileat most one
model, prompt, n, size, resolution, quality, style, response_format, background, output_format, negative_prompt, aspect_ratio, seed, watermarkvaluesame 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

python
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)
js
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

StatuscodeMeaning
400missing_model / missing_promptboth are required
400invalid_nn must be between 1 and 10
400invalid_response_formatnot url or b64_json
400missing_image/v1/images/edits with no source image
400invalid_multipartthe multipart body could not be parsed
400unexpected_filea file part other than image, image[] or mask
400invalid_mask / empty_maskmask is neither a URL nor a {b64|url} object
400unsupported_image_formatthe bytes are not PNG, JPEG or WebP
400empty_image / unreadable_imagea file part carried nothing usable
413image_too_largea single file exceeds 25 MiB
413body_too_largeJSON over 10 MiB, or a multipart body over 100 MiB
413form_field_too_largea scalar multipart field exceeds 64 KiB
404model_not_foundno enabled provider serves this model for images

The shared job, billing and rate-limit errors are listed in Errors and limits.