---
name: oairegbox-api-gateway
description: Integrate the OAIREGBOX AI gateway into an application. Use for model discovery, OpenAI-compatible chat and image calls, async video tasks, token usage checks, and model-specific media input rules.
---

# OAIREGBOX API Gateway

## Configuration

Read these variables from the project root `.env`. Never put an API key in browser code, source control, logs, or responses.

```dotenv
OAIREGBOX_API_BASE_URL=https://newapi-2.oairegbox.cc
OAIREGBOX_API_KEY=sk-your-api-key
```

If the user was issued another gateway address, use that address instead. Remove a trailing `/v1` from `OAIREGBOX_API_BASE_URL`; endpoints below include `/v1` themselves.

Before writing the key, ensure `.env` is listed in `.gitignore`. If `.env` exists, append only the missing `OAIREGBOX_*` variables and preserve all existing values.

## Operating Rules

1. Load `OAIREGBOX_API_BASE_URL` and `OAIREGBOX_API_KEY` from `.env` before making a request.
2. First call `GET {BASE_URL}/v1/models` with `Authorization: Bearer {API_KEY}`. This is the live source of models visible to this exact token.
3. Do not invent a model name, model price, or parameter. The model list is live, but model-specific prices and media parameters are documented at `https://docs.oairegbox.cc/`.
4. Use the least invasive read-only request first. Do not submit paid generation jobs until the user explicitly asks to generate.
5. Treat result URLs as opaque. Use the returned `video_url` or image URL directly; do not rewrite its host or extract upstream details.
6. Never retry an accepted async video task by resubmitting it merely because the client timed out. Poll its existing task ID first.
7. Video tasks are slow. Use a polling deadline of **at least 15 minutes** before declaring a task lost.
8. A `completed` video task is **not automatically a usable result**. For video-extension models you must verify the output before delivering it — see "Video Extension".

## Verify The Token And Discover Models

```bash
curl -sS "$OAIREGBOX_API_BASE_URL/v1/models" \
  -H "Authorization: Bearer $OAIREGBOX_API_KEY"
```

The response is OpenAI-style:

```json
{
  "object": "list",
  "data": [{"id": "model-name", "object": "model"}]
}
```

Only recommend IDs returned by this call. A model omitted from this response is not available to the current token or token group.

## Calling Entrypoints

| Need | Endpoint | Behavior |
|---|---|---|
| Chat / text / supported multimodal | `POST /v1/chat/completions` | OpenAI-compatible; use `stream: true` for streaming output. |
| Image generation | `POST /v1/images/generations` | OpenAI-compatible; result is returned synchronously. |
| Image editing | `POST /v1/images/edits` | OpenAI-compatible; check the target model page for accepted reference input. |
| Video generation | `POST /v1/videos` | Async task; poll `GET /v1/videos/{task_id}`. |
| Visible model list | `GET /v1/models` | Token-scoped, read-only discovery. |
| Token usage | `GET /api/usage/token/` | Token-scoped quota, usage, limits and expiration. |
| Account models (enabled) | `GET /api/user/models` | Account-scoped: every enabled model across the account's usable groups. Needs an account credential. |
| Account balance | `GET /api/user/self` | Account-scoped: `data.quota` (remaining), `used_quota`, `group`. Needs an account credential. |
| Model prices | `GET /api/pricing` | Model→price catalog. Anonymous = base prices; account credential = account-specific ratios/discounts. |

Use `Authorization: Bearer $OAIREGBOX_API_KEY` (the relay `sk-` token) for the `/v1/*` and `/api/usage/token/` rows. Use an **account credential** (see "Account-Level Access" below) for the `/api/user/*` rows. A relay `sk-` token returns `401` on `/api/user/*`.

Use `Authorization: Bearer $OAIREGBOX_API_KEY` for every endpoint above.

## Chat Example

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url=f"{os.environ['OAIREGBOX_API_BASE_URL'].rstrip('/')}/v1",
    api_key=os.environ["OAIREGBOX_API_KEY"],
)

response = client.chat.completions.create(
    model="MODEL_ID_FROM_V1_MODELS",
    messages=[{"role": "user", "content": "你好"}],
    stream=True,
)

for event in response:
    delta = event.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
```

## Video Task Pattern

Use the model-specific documentation before choosing fields such as `size`, `seconds`, `image`, `images`, `input_reference`, `reference_videos`, or base64 inputs. Different video models do not accept the same parameters.

```python
import os
import time
import requests

base_url = os.environ["OAIREGBOX_API_BASE_URL"].rstrip("/")
headers = {
    "Authorization": f"Bearer {os.environ['OAIREGBOX_API_KEY']}",
    "Content-Type": "application/json",
}

submit = requests.post(
    f"{base_url}/v1/videos",
    headers=headers,
    json={
        "model": "MODEL_ID_FROM_V1_MODELS",
        "prompt": "Replace with the requested prompt",
        "aspect_ratio": "16:9",
    },
    timeout=45,
)
submit.raise_for_status()
task = submit.json()
task_id = task.get("task_id") or task.get("id")
if not task_id:
    raise RuntimeError(f"No task ID returned: {task}")

while True:
    detail = requests.get(f"{base_url}/v1/videos/{task_id}", headers=headers, timeout=30)
    detail.raise_for_status()
    result = detail.json()
    status = str(result.get("status", "")).lower()
    if status == "completed":
        video_url = result.get("video_url") or result.get("data", [{}])[0].get("url")
        if not video_url:
            raise RuntimeError(f"Task completed without a video URL: {result}")
        print(video_url)
        break
    if status == "failed":
        raise RuntimeError(result.get("fail_reason") or result.get("error") or result)
    time.sleep(4)
```

## Video Extension (`omni-extend-20s`)

Lengthens a clip the user already has. Submit a clip of **≤ 12 seconds / ≤ 12 MB**; the task returns **the original clip plus roughly 10 seconds of new footage as one file**. Do not concatenate anything yourself.

```python
body = {
    "model": "omni-extend-20s",          # or omni-extend-20s-no-water
    "prompt": "continue naturally",       # keep it short; see below
    "videos": ["data:video/mp4;base64,..."],   # or a public https URL
    # "images": ["data:image/png;base64,..."], # optional props/elements
    # DO NOT send aspect_ratio -- it auto-follows the source clip
}
```

Multipart also works: `-F "model=omni-extend-20s" -F "input_video=@clip.mp4"`.

### Field rules

- The request **must** carry a video (`videos`, `video_url`, or multipart `input_video`). A text-only request is rejected.
- Reference media can be a public **URL**, a `data:...;base64,...` string, or a multipart file — mixed freely in the same request.

| To pass | Preferred field | Also accepted | Limit |
|---|---|---|---|
| Reference video | `videos` (array) | `video_url`, `video`, `input_video` | max 2, each ≤ 12 MB / ≤ 12 s / ≤ 1920x1080 |
| Reference image | `images` (array) | `image_url`, `first_image_url`, `last_image_url` | max 5, each ≤ 12 MB |

- In a JSON body, `input_reference` accepts only a **single string** through the gateway; an array returns `invalid_json` (`cannot unmarshal array ... of type string`). Use `images` for multiple images.
- A URL must be **anonymously downloadable** — the server sends none of your auth headers. Use presigned/public links, pointing at the file itself (redirects are followed). Give presigned URLs **≥30 minutes** of validity: median job time is ~3.5 min and the tail reaches ~12 min.
- **Put video URLs in `videos`/`video_url`, never in `input_reference`.** Mixed fields classify a value as video by **file extension** (`.mp4 .mov .webm .m4v .avi .mkv .mpeg .ogv`, query string stripped). An extensionless CDN link such as `https://cdn.example.com/asset/9f3a1c` is treated as an image, silently degrading the request to image-to-video.
- **Do not send `aspect_ratio`.** Omitting it makes the output follow the source clip. Sending it can crop or flip vertical footage.
- `video_action` is optional; `omni-extend-20s` defaults to extension.
- `seconds` / `duration` are accepted but ignored. One call adds ~10 s, no more.

### Mandatory post-check: detect a "reshoot"

The upstream model sometimes returns a **regenerated clip of the same length instead of a lengthened one**. The task still reports `completed` and the call is still billed. Always verify:

```python
if out_duration < src_duration + 2.0:
    # RESHOOT: not lengthened. Discard and resubmit the same request.
    # Retry up to 3 times; most succeed on the 2nd attempt.
```

About 11% of calls come back this way, and roughly half of those also flip the aspect ratio. Never deliver a result without this check.

### Retry policy depends on the failure

| Outcome | Action |
|---|---|
| Reshoot (duration not increased) | **Retry** the same request, up to 3 attempts |
| `video_route_misroute` | **Retry**, intermittent |
| `content_policy` | **Do not retry.** Change the source clip |

**The `content_policy` code is misleading.** Across 168 inspected failures, only **1** was an actual content review; the rest were the upstream declining to route the request to the video model at all. Visually clean footage gets this code routinely.

Consequently **nothing you do to the file helps**: re-encoding measured **1/10** versus **2/15** for doing nothing — retrying is worse than not retrying, and burns 10-15 minutes. Same for changing bitrate, resolution, or prompt wording. It accounts for ~88% of failures. Rejections are not billed.

The only effective response is **different subject matter** (see the success-rate list below).

### What the prompt can and cannot control

Measured across 140 generations (7 prompt styles × 20 source clips), every prompt style landed at 10–13 / 20 — **prompt wording does not move the success rate.** Use `continue naturally` and put the effort into choosing source material.

- **Can control**: actions, props, camera movement. A reference image's contents will appear in the new footage (10/11).
- **Can control**: the *language* of the narration (English / Mandarin / Cantonese all hold).
- **Cannot control**: the *words* spoken. It will talk, on topic, in the right language — but the wording is its own. If exact script lines are required, request silent footage and dub in post, or use `omni-fast` instead (see below).

**Verbatim dialogue is a `omni-fast` capability, not an extension capability.** Same 8 scenes, same line, only the model/phrasing varied:

| Call | Verbatim | Notes |
|---|---|---|
| `omni-fast` (t2v/i2v), line in quotes | **8/8** | Use this when exact lines matter |
| `omni-extend-20s`, line in quotes | **0/6** | Speaks on topic, rewords it (ordered-token match 0.25) |
| `omni-extend-20s`, `a voice over says: "..."` | **0/7** | Worse — 3 of 7 came back completely silent |

Never use the voice-over phrasing on an extension request; it suppresses speech. If a user needs both exact dialogue *and* a real person/product preserved frame-for-frame, those are mutually exclusive in one call — extend for fidelity, then dub.

Success depends mostly on subject matter. High: product ads (14/14), product demos (13/14), talking-head/commerce (15/21), animals & nature (12/14). Low: young children (0/14), pure action with no dialogue (1/14), emotional-conflict drama (9/28). Pure-action clips are the dangerous case — they return `completed` but were reshoots 5/5.

### Going beyond ~20 seconds: tail-window chaining

Failure rate tracks the **length of the reference you submit**, not the length of the output (10 s reference 2/2, 20 s 2/4, 28 s 0/2). So never feed the whole accumulated film back in — feed only its last 10 seconds:

```
round 1: 10 s source                                    -> film = 10 s
round 2: cut the film's last 10 s -> extend -> get 20 s
         keep only the newly appended second half,
         append it to the film                          -> film = 20 s
round 3: cut the last 10 s again -> extend -> keep new  -> film = 30 s
```

The reference then stays at 10 s forever. Verified to 8 rounds / 79 s. Every round needs its own reshoot check and retry, because the risk compounds — chained reshoot rate is ~27% versus ~11% from original material. Keep chains to 6–8 rounds.

When chaining footage that was itself generated from a reference image, restate character identity (ethnicity, hair, age, build, clothing) verbatim in **every** round's prompt. A reference image fixes framing, wardrobe and set, but not who the person is; characters otherwise drift between rounds.

## Token Usage

```bash
curl -sS "$OAIREGBOX_API_BASE_URL/api/usage/token/" \
  -H "Authorization: Bearer $OAIREGBOX_API_KEY"
```

Read `data.total_available`, `data.total_used`, `data.unlimited_quota`, `data.model_limits`, and `data.expires_at`. This is a single token's quota. For account-wide balance and model prices, see "Account-Level Access" below.

## Account-Level Access

Two credential kinds authenticate different scopes:

- A relay `sk-` token (the `OAIREGBOX_API_KEY`) authenticates `/v1/*` and `/api/usage/token/` — a single token's view.
- An **account credential** authenticates `/api/user/*` and `/api/pricing` — the whole account's view (every enabled model, the shared wallet balance, account-specific prices). A relay `sk-` token returns `401` on `/api/user/*`.

Obtain an account credential (either one), then send it as `Authorization: Bearer <credential>`:

1. **Personal access token (recommended)** — in the console, Personal Settings → generate a system access token (or `GET /api/user/token` while logged in). Long-lived, resettable, unaffected by CAPTCHA/2FA. Best for server-side automation.
2. **Username + password → JWT** — `POST /api/user/login` with a JSON body `{"username": "...", "password": "..."}` returns `data.access_token`. For interactive/one-off use only: **each login creates a 30-day login session, and repeated programmatic logins exhaust the account's active-session limit (default 50) and temporarily lock the account.** For automation always use option 1 (a personal access token is stateless and creates no session). If locked out, click "revoke other sessions" on any signed-in device, or reset the password.

```bash
ACCOUNT_TOKEN="personal access token, or the access_token from /api/user/login"

# Account balance (CNY = data.quota / 500000; data.used_quota is spent)
curl -sS "$OAIREGBOX_API_BASE_URL/api/user/self" -H "Authorization: Bearer $ACCOUNT_TOKEN"

# Account's enabled models across all usable groups (deduped; add ?group=auto to filter)
curl -sS "$OAIREGBOX_API_BASE_URL/api/user/models" -H "Authorization: Bearer $ACCOUNT_TOKEN"

# Model price catalog (anonymous = base prices; account token = account-specific ratios)
curl -sS "$OAIREGBOX_API_BASE_URL/api/pricing" -H "Authorization: Bearer $ACCOUNT_TOKEN"
```

- Quota unit: `quota / 500000` = CNY.
- `/api/pricing` per model: `quota_type` (`1` = per-call fixed price, `0` = per-token), `model_price` (per-call unit price, before group ratio), `model_ratio` (per-token), `enable_groups`, `published_price` (a clean customer-facing price when set); top level: `group_ratio`, `usable_group`. Per-call price ≈ `model_price × group_ratio[group]`.
- The account token is the account key: keep it secret, never put it in browser code, reset it in the console if leaked.

## Files And Reference Media

There is currently no general-purpose `POST /v1/files` upload API. Do not attempt to use it.

For reference images, videos, or audio, follow the target model page:

- Many models accept a public URL, a `data:image/...;base64,...` string, or multipart form data.
- Some models accept only a subset of those forms.
- Video reference limits and supported sizes differ by model.
- Never assume a local filesystem path is accepted by the gateway. Convert it using the target model's documented multipart input or host it at a reachable URL when the model requires a URL.

## Failure Handling

- `401`: verify the key and base URL.
- `403`: check the token group and model visibility from `GET /v1/models`.
- `429`: wait and retry with backoff; do not fan out parallel retries.
- Async task `failed`: preserve the task ID and error details; do not duplicate an accepted task before investigating.
- Image or video input validation error: reread the target model's parameter table instead of guessing a compatible field name.

