# 图片 (https://hypit.ai/zh/api-reference/images/)

> 图片生成与编辑。两个端点都是异步的，返回一个 Job。

两条路由，都是异步的：

| 路由                            | 用途       |
| ----------------------------- | -------- |
| `POST /v1/images/generations` | 文生图      |
| `POST /v1/images/edits`       | 图生图，可带蒙版 |

两者都返回 `202 Accepted` 加一个 [Job 信封](/zh/api-reference/jobs)，成品从
`GET /v1/jobs/{job_id}/assets` 取。

<Callout type="warn" title="`client.images.generate()` 用不了">
  OpenAI SDK 的这个封装期待同步的 `{"data": [{"url": …}]}` 响应体，而我们返回的是 Job。这两条路由
  请用普通 HTTP 调用。
</Callout>

## 生成 [#生成]

```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": "一只纸船顺着雨水沟漂走，浅景深",
    "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
}
```

### 请求体 [#请求体]

| 字段                 | 类型              | 说明                                             |
| ------------------ | --------------- | ---------------------------------------------- |
| `model`            | string          | **必填**                                         |
| `prompt`           | string          | **必填**                                         |
| `n`                | integer         | 1–10，默认 `1`。每张图对应一个产物序号                        |
| `size`             | string          | 如 `"1024x1024"`，或 `"auto"`                     |
| `resolution`       | string          | 模型公布的输出档位，如 `"1k"`、`"2k"`、`"4k"`；与宽高比独立        |
| `quality`          | string          | 如 `low`、`medium`、`high`、`hd`、`standard`、`auto` |
| `style`            | string          | 各厂商自定义                                         |
| `aspect_ratio`     | string          | 如 `"16:9"`                                     |
| `negative_prompt`  | string          |                                                |
| `background`       | string          | 如 `transparent`、`opaque`、`auto`                |
| `output_format`    | string          | `png`、`jpeg`、`webp`                            |
| `response_format`  | string          | `url` 或 `b64_json`，其余值返回 `400`                 |
| `seed`             | integer         |                                                |
| `watermark`        | boolean         |                                                |
| `reference_images` | array           | 见下；`images` 是兼容别名                              |
| `mask`             | string 或 object | 见下                                             |

支持档位的图片模型推荐传 `aspect_ratio` + `resolution`。`4k` 不是固定的 `3840x2160`，实际像素尺寸由模型和比例决定。旧的 `size` 像素写法仍可用于支持它的模型；如果同时填写 `aspect_ratio`，两者必须一致。`metadata.resolution` 作为兼容别名仍然接受，与顶层 `resolution` 不一致时返回 `400`。

网关会在创建任务、冻结额度前检查可用渠道是否支持所选档位和比例；不支持的组合返回明确的参数错误，不会悄悄改用默认值。计价使用同一分辨率档位，具体价格见[模型卡片](/zh/api-reference/models)的 `pricing.resolution_ratio` 或 `pricing.media_rates`。

<Callout type="info" title="`response_format` 不影响你怎么取结果">
  它会被校验并转发给上游，但 Job 的产物一律通过 assets 接口以签名链接交付，没有任何路径会在响应里
  直接返回 base64。
</Callout>

### 厂商透传 [#厂商透传]

上表没有建模的字段会原样转发给上游。这是为了让只在某一家上游存在的功能仍然可达。两条限制：整个
额外字段块在体积和嵌套深度上有上限；任何看起来像媒体 URL 的字符串必须是真实的 `http(s)` 或
`data:` URL——从额外字段里夹带的 URL 会被 `url_in_extra` 拒绝，而不是被悄悄抓取。

### JSON 里的参考图 [#json-里的参考图]

`reference_images`（以及兼容别名 `images`）用于在 JSON 请求里附带素材。每一项要么是裸 URL 字符串，要么是一个对象：

```json
{
  "model": "nano-banana-pro",
  "prompt": "把这只船放进暴风雪里",
  "reference_images": [
    { "url": "https://example.com/boat.png" },
    { "b64": "iVBORw0KGgo…", "mime_type": "image/png", "filename": "boat.png" }
  ]
}
```

`mask` 接受同样的对象，也接受普通 URL 或 `data:` URL 字符串。

你提供的每个 URL 都走受保护的抓取器：只允许 `http`、`https` 和 `data:`，拒绝内网与链路本地地址，
并逐跳复核重定向。校验不过就是 `400`（`invalid_media_url`、`unsupported_media_url_scheme` 或
`invalid_input_url`），不会产生任何扣费。

## 编辑 [#编辑]

`POST /v1/images/edits` 接受 `multipart/form-data` 的真实文件分片，也接受上面那种带
`reference_images` 的 JSON 请求体。至少要有一张源图。

```bash
curl https://hypit.ai/v1/images/edits \
  -H "Authorization: Bearer $HYPIT_API_KEY" \
  -F model=nano-banana-pro \
  -F 'prompt=把天空改成阴天' \
  -F image=@boat.png \
  -F mask=@sky-mask.png
```

multipart 字段名：

| 分片                                                                                                                                                            | 类型 | 说明            |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -- | ------------- |
| `image`、`image[]`                                                                                                                                             | 文件 | 一张或多张源图，按发送顺序 |
| `mask`                                                                                                                                                        | 文件 | 最多一个          |
| `model`、`prompt`、`n`、`size`、`resolution`、`quality`、`style`、`response_format`、`background`、`output_format`、`negative_prompt`、`aspect_ratio`、`seed`、`watermark` | 值  | 语义与 JSON 字段一致 |

限制：

* 单个上传文件 **25 MiB**
* 整个 multipart 请求体 **100 MiB**
* `/v1/images/generations` 的 JSON 请求体 **10 MiB**
* 单个 multipart 标量字段 64 KiB

上传声明的 `Content-Type` 会被忽略——我们嗅探字节内容，只接受 **PNG、JPEG 和 WebP**，其余一律
`400 unsupported_image_format`。

## 完整流程 [#完整流程]

```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": "一只纸船顺着雨水沟漂走",
        "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: "一只纸船顺着雨水沟漂走",
    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);
}
```

## 这两条路由特有的错误 [#这两条路由特有的错误]

| 状态    | `code`                             | 含义                                    |
| ----- | ---------------------------------- | ------------------------------------- |
| `400` | `missing_model` / `missing_prompt` | 两者都必填                                 |
| `400` | `invalid_n`                        | `n` 必须在 1–10 之间                       |
| `400` | `invalid_response_format`          | 不是 `url` 或 `b64_json`                 |
| `400` | `missing_image`                    | `/v1/images/edits` 没有提供源图             |
| `400` | `invalid_multipart`                | multipart 请求体无法解析                     |
| `400` | `unexpected_file`                  | 出现了 `image`、`image[]`、`mask` 之外的文件分片  |
| `400` | `invalid_mask` / `empty_mask`      | mask 既不是 URL 也不是 `{b64\|url}` 对象      |
| `400` | `unsupported_image_format`         | 字节内容不是 PNG、JPEG 或 WebP                |
| `400` | `empty_image` / `unreadable_image` | 文件分片里没有可用内容                           |
| `413` | `image_too_large`                  | 单个文件超过 25 MiB                         |
| `413` | `body_too_large`                   | JSON 超过 10 MiB，或 multipart 超过 100 MiB |
| `413` | `form_field_too_large`             | 单个标量字段超过 64 KiB                       |
| `404` | `model_not_found`                  | 没有启用的上游为这个模型提供图片能力                    |

任务、计费和限流相关的公共错误见[错误与限流](/zh/api-reference/errors)。