# Developer API

Registered users can create `sk_live_*` keys in the web console and call the video generation API from their own backend programs.

## Authentication

Use a user-created SK as a Bearer token:

```bash
Authorization: Bearer sk_live_xxx
```

`X-Api-Key: sk_live_xxx` is also accepted for compatibility, but Bearer auth is preferred.

## Create A Video Task

```bash
curl -X POST 'https://canseedream.com/api/v3/contents/generations/tasks' \
  -H 'Authorization: Bearer sk_live_xxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: my-request-001' \
  -d '{
    "model": "video",
    "provider_route": "tc_pool",
    "content": [
      {
        "type": "text",
        "text": "一个电影感的竖屏短视频，主体自然运动，画面清晰"
      },
      {
        "type": "image_url",
        "image_url": {
          "url": "https://example.com/reference.png"
        }
      },
      {
        "type": "audio_url",
        "audio_url": {
          "url": "https://example.com/reference.mp3"
        }
      }
    ],
    "aspect_ratio": "9:16",
    "generate_audio": true,
    "enhance": false,
    "enhance_settings": {
      "targetResolution": "1080p",
      "targetFps": "source"
    },
    "number_of_runs": 1
  }'
```

If an integration can only configure an API base URL and cannot send `provider_route`,
use one of the provider-prefixed base URLs currently returned by
`GET /health` in `defaults.videoProviders`, for example:

```text
https://canseedream.com/<enabled-provider>/api/v3
```

For example, `https://canseedream.com/<enabled-provider>/api/v3/contents/generations/tasks`
is equivalent to calling `/api/v3/contents/generations/tasks` with the matching
`"provider_route"`. The old `/api/v3` path remains supported. If both
the URL prefix and body specify a provider, they must match. Do not hard-code a
route that is absent from `defaults.videoProviders`; disabled or 已下架 routes are not
available even if an older document mentioned them.

When enabled by the administrator, the Chili route uses `/lajiao_pool/api/v3`
and is equivalent to `"provider_route": "lajiao_pool"`.

Response:

```json
{
  "id": "cstask_xxx",
  "request_id": "csreq_xxx",
  "model": "video",
  "status": "queued",
  "created": 1710000000,
  "tasks": [
    {
      "id": "cstask_xxx",
      "status": "queued",
      "progress": 0,
      "content": null
    }
  ],
  "idempotent": false
}
```

Generation parameters are normalized to match the web UI and the selected provider route:

- `tc_pool`: the default account-pool route; duration is `auto`; concrete seconds should be written in the prompt
- `dq_pool`: pass `duration` from 4 to 15 seconds; omitted or `auto` uses the server default
- `dq7q`: independent 720p route; supports `duration` from 4 to 15 seconds and uses the server-side reference limits
- `aggc`: Seedance2.0 full route; pass `duration` from 4 to 15 seconds; supports `1:1`, `9:16`, `16:9`, up to 9 images, 3 audio files, and 3 videos
- `aggc_1080`: Seedance2.0 full 1080p route; pass `duration` from 4 to 15 seconds; supports `1:1`, `9:16`, `16:9`, up to 9 images, 3 audio files, and 3 videos
- `rich_pool`: Rich video route; pass `duration` from 4 to 15 seconds; supports `adaptive`, `1:1`, `9:16`, `16:9`, `4:3`, `3:4`, `21:9`, up to 9 images, 3 audio files, and 3 videos
- `doufu_pool`: Doufu video route; pass `duration` from 4 to 15 seconds; uses 720p and supports `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `21:9`, `9:21`; upstream refunds failed tasks
- `mz_pool`: Mazhu video route; pass `duration` from 4 to 15 seconds; fixed to Seedance 2.0 at 720p; supports `16:9`, `9:16`, `1:1`, `4:3`, `3:2`, `2:3`, `2:1`, with up to 10 reference images; upstream refunds failed tasks
- `danke_pool`: Viraldance933 route; supports server-configured 720p or 1080p, `duration` from 4 to 15 seconds, `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, up to 9 images, 3 audio files, and 3 videos
- `lanmei_pool`: isolated Blueberry route; supports server-configured 720p or 1080p, user-selectable `duration` from 4 to 15 seconds, `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, up to 9 images, 3 audio files, and 3 videos
- `ximei_pool`: isolated Prune route using `viraldance900`; fixed 720p, user-selectable `duration` from 4 to 15 seconds, `16:9` or `9:16`, up to 9 images, and no audio/video references; failed generations release user points by default
- `xigua_pool`: isolated Watermelon route using Paipu `lec-seedance-2-0`; fixed 720p, `duration` of 5, 10, or 15 seconds, `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, or `9:16`, and up to 9 image references; failed generations release user points, while ambiguous submissions remain protected and are never submitted twice
- `caomei_pool`: isolated Strawberry route using `viraldance431`; supports user-selectable `duration` from 4 to 15 seconds, `16:9`, `9:16`, and `1:1`, up to 4 images, 3 videos, and 1 audio file; failed generations release user points by default
- `qiezi_pool`: isolated MDD Keji route using the server-configured model and resolution (`seedance-2.0-720p` and `720p` by default); supports 4-15 seconds, up to 9 images, 3 videos, and 3 audio files; failed generations release user points by default
- `lajiao_pool`: isolated server-configured video route; supports 4-15 seconds, up to 9 images and 15 total references; audio and video references allow 3 items combined; when `durationSeconds` is provided, each must be at least 2 seconds and known durations must stay within 15 seconds combined; omitting this compatibility field does not block API submission; audio-only references are rejected, failed generations release user points by default, and ambiguous non-idempotent submissions are never sent twice
- `juzi_pool`: isolated OmniArt cookie account-pool route using `seedance-2.0-mini` at 720p by default; user-selectable `duration` from 4 to 15 seconds, prompts up to 5000 characters by default, `21:9`, `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, up to 9 images, 3 videos, and 3 audio files, with 12 references total
- `mantou_pool`: cookie account-pool route; supports 480p, 720p, and 1080p, `duration` from 4 to 15 seconds, and the server-configured reference limits
- `shutiao_pool`: isolated Fries route using the Seedance 2 protocol; fixed at 720p, supports `duration` from 4 to 15 seconds, up to 9 images, 3 audio files, and 3 videos; keys and concurrency are isolated from `kele_pool`
- `pingguo_pool`: isolated Aivide 2.0 route; supports server-configured 480p or 720p, `duration` from 4 to 15 seconds, `16:9` or `9:16`, up to 9 images, and up to 3 audio/video references combined
- `qyfast`: duration is configured server-side as 5, 10, or 15 seconds; reference images only by default
- only routes currently returned by `/health` in `defaults.videoProviders` are open; disabled or下架 routes are not supported by this API
- `model` is retained for third-party compatibility; the actual upstream model is selected by the server-side route configuration
- up to 4 runs per request
- points are reserved before tasks enter the queue

Optional video post-processing is controlled by server env. When `VIDEO_ENHANCE_ENABLED=true` and `VIDEO_ENHANCE_MODE=user`, send `enhance: true` to enable post-generation upscaling/frame interpolation. `targetResolution` accepts `source`, `720p`, `1080p`, `2k`, or `4k`; `source` keeps the generated video's resolution and is only valid when `targetFps` is a numeric frame rate, so it means frame interpolation only. `targetFps` accepts `source`, `24`, `30`, `48`, or `60`. The server validates and prices these values; client-supplied prices are ignored. The base video points and enhancement points are reserved atomically. A 480p result can be enhanced to 720p. For a 720p result, the upscaling choices begin at 1080p, while `source` + a numeric FPS remains available for 720p-only frame interpolation. A same-resolution request with `targetFps=source` and a lower-resolution target are rejected. If enhancement fails for any reason, the original generated video remains the user-visible result and the enhancement points are released; the base video charge is unaffected. If base generation fails, enhancement points are released while the base-video portion follows the selected provider's normal failure policy. `VIDEO_ENHANCE_MODE=force` applies the configured enhancement to every video task and ignores a false client flag; `off` disables it.

Pricing is separated between the standalone `/enhance` page and video-generation post-processing. The standalone page uses `ENHANCE_USER_POINTS` and `ENHANCE_60FPS_USER_POINTS`. Video-generation post-processing uses `VIDEO_ENHANCE_USER_POINTS` and `VIDEO_ENHANCE_60FPS_USER_POINTS`, with optional per-route overrides such as `VIDEO_PROVIDER_DOUFU_POOL_ENHANCE_POINTS` and `VIDEO_PROVIDER_DOUFU_POOL_ENHANCE_60FPS_POINTS`. All enhancement prices are server-controlled and clamped to at least 1 point; setting a client-side value cannot make an enhancement free.

You can also submit direct arrays instead of `content` items:

```json
{
  "provider_route": "tc_pool",
  "prompt": "A cinematic 10s vertical video. Use @Image1 as the subject and follow the rhythm of @Audio1.",
  "image_urls": ["https://example.com/reference.png"],
  "audio_urls": ["https://example.com/reference.mp3"],
  "video_urls": [
    { "url": "https://example.com/reference.mov", "durationSeconds": 6 }
  ],
  "aspect_ratio": "9:16"
}
```

## Query A Task

```bash
curl 'https://canseedream.com/api/v3/contents/generations/tasks/cstask_xxx' \
  -H 'Authorization: Bearer sk_live_xxx'
```

When finished, `content.video_url` uses the primary video domain, and `content.backup_video_url` keeps the original configured result URL.

## List Tasks

```bash
curl 'https://canseedream.com/api/v3/contents/generations/tasks?limit=20' \
  -H 'Authorization: Bearer sk_live_xxx'
```

## Points Consistency

The web UI and Developer API both call the same `createGenerationRequest` service. The account points wallet is reserved atomically in `app_users.video_quota_reserved`, then each task either consumes or releases its reserved points when it completes or fails.

Each SK can also have its own points limit. When an SK submits a task, the system checks and reserves both account points and SK points. This prevents one exposed or overloaded SK from consuming more than the budget assigned to it.

If a task stays queued because no generation resource is available, it will fail after the configured timeout and release the reserved points. API responses use `NoAvailableResource` for this case.

## API Key Management

Users can create, reveal, revoke, and delete SKs in the web console. New SKs are stored encrypted for later reveal; old SKs created before this feature may need to be deleted and recreated if their full secret cannot be revealed.

When creating an SK, leaving the points limit blank or setting it to `0` means the SK has no extra per-key limit. The account points wallet is still checked and reserved for every task. If an SK still has reserved points from running tasks, revoke it first and delete it after those tasks finish.

## OpenAI-Compatible Synchronous Image API

This API uses the existing `sk_img_*` authentication, GPT Image 2 account pool, persistent tasks, and unified points wallet. Enable it only after applying migration `023_openai_image_sync.sql`:

```env
IMAGE_SYNC_API_ENABLED=true
IMAGE_SYNC_MODEL_NAME=gpt-image-2
IMAGE_SYNC_MODEL_ALIASES=gpt-img-2,gpt_image_2
IMAGE_SYNC_DEFAULT_SIZE=auto
IMAGE_SYNC_MAX_JSON_BODY_BYTES=12582912
IMAGE_SYNC_MAX_BASE64_FILE_BYTES=8388608
IMAGE_SYNC_MAX_BASE64_TOTAL_BYTES=8388608
IMAGE_SYNC_REQUIRE_IDEMPOTENCY_KEY=false
IMAGE_SYNC_REQUIRE_LIMITED_KEY=true
IMAGE_SYNC_REQUIRE_RESULT_BACKUP=false
RESULT_BACKUP_ENABLED=true
RESULT_BACKUP_IMAGE_ENABLED=true
```

For theft containment, this synchronous endpoint rejects image API keys whose points limit is `0` (unlimited). Create or use an `sk_img_*` key with a finite points limit. `Idempotency-Key` is recommended but optional by default for OpenAI-compatible canvas clients. When omitted, the server reuses an identical request while its original task is still active; after that task reaches a terminal state, the same prompt can create a new image normally. Set `IMAGE_SYNC_REQUIRE_IDEMPOTENCY_KEY=true` to restore strict header enforcement.

OpenAI-compatible canvas configuration:

```text
Interface: OpenAI compatible
Base URL: https://canseedream.com
API Key: sk_img_xxx
Model ID: gpt-image-2
```

The common model aliases `gpt-img-2`, `gpt_image_2`, and `GPT Image 2` are also accepted and use the same existing GPT Image 2 account pool.

Text-to-image:

```bash
curl -X POST 'https://canseedream.com/v1/images/generations' \
  -H 'Authorization: Bearer sk_img_xxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: image-request-001' \
  -d '{
    "model": "gpt-image-2",
    "prompt": "A clean product photograph on a white background",
    "size": "auto",
    "quality": "auto",
    "response_format": "url",
    "n": 1
  }'
```

Reference-image editing uses the OpenAI SDK-compatible multipart endpoint:

```bash
curl -X POST 'https://canseedream.com/v1/images/edits' \
  -H 'Authorization: Bearer sk_img_xxx' \
  -H 'Idempotency-Key: image-edit-001' \
  -F 'model=gpt-image-2' \
  -F 'prompt=Keep the subject and replace the background with a studio set' \
  -F 'image=@reference-1.png' \
  -F 'image=@reference-2.jpg' \
  -F 'response_format=url'
```

Remote reference images can use the JSON `image_urls` extension:

```bash
curl -X POST 'https://canseedream.com/v1/images/edits' \
  -H 'Authorization: Bearer sk_img_xxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: image-edit-url-001' \
  -d '{
    "model": "gpt-image-2",
    "prompt": "Keep the subject from @Image1 and use the clothing style from @Image2",
    "image_urls": [
      "https://example.com/reference-1.png",
      "https://example.com/reference-2.jpg"
    ],
    "size": "auto",
    "quality": "auto",
    "response_format": "url",
    "n": 1
  }'
```

JSON requests can also provide a Base64 reference image. A full image Data URL is recommended because it carries the MIME type explicitly:

```bash
curl -X POST 'https://canseedream.com/v1/images/edits' \
  -H 'Authorization: Bearer sk_img_xxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: image-edit-base64-001' \
  -d '{
    "model": "gpt-image-2",
    "prompt": "Keep the subject and replace the background with a studio set",
    "image_base64": "data:image/png;base64,iVBORw0KGgo...",
    "size": "auto",
    "quality": "auto",
    "response_format": "url"
  }'
```

Multiple Base64 reference images use the `image_base64s` array. Array order is preserved, so the first entry is `@Image1`, the second is `@Image2`, and so on:

```bash
curl -X POST 'https://canseedream.com/v1/images/edits' \
  -H 'Authorization: Bearer sk_img_xxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: image-edit-multi-base64-001' \
  -d '{
    "model": "gpt-image-2",
    "prompt": "Keep the subject from @Image1 and use the clothing style from @Image2",
    "image_base64s": [
      "data:image/png;base64,iVBORw0KGgo...",
      "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
    ],
    "size": "auto",
    "quality": "auto",
    "response_format": "url",
    "n": 1
  }'
```

The JSON extensions `image_urls`, `image_base64`, and `image_base64s` are accepted on both endpoints. `image` and `images` also accept image Data URLs or objects shaped as `{ "b64_json": "...", "mime_type": "image/png" }`. Base64 supports PNG, JPEG, GIF, WebP, and AVIF; decoded bytes are signature-checked, written to temporary storage, and represented in persistent tasks only by a pending-file key and SHA-256 fingerprint. The Base64 string itself is not stored in task JSON or the database. JSON body, per-image, total decoded, reference-count, and upload-time limits are enforced by `IMAGE_SYNC_MAX_JSON_BODY_BYTES`, `IMAGE_SYNC_MAX_BASE64_FILE_BYTES`, `IMAGE_SYNC_MAX_BASE64_TOTAL_BYTES`, `IMAGE_SYNC_MAX_REFERENCE_IMAGES`, and `IMAGE_SYNC_UPLOAD_TIMEOUT_SECONDS`.

Public HTTPS references and Base64 references may be mixed in one JSON request. Use a single `images` array when their exact `@Image1`, `@Image2`, ... order matters; array entries may be HTTPS strings, image Data URLs, or `b64_json` objects, and their order is preserved in both the task and idempotency fingerprint. Local, private, credential-bearing, custom-port, and non-HTTPS URLs are rejected. Multipart file uploads remain available and are limited independently by `IMAGE_SYNC_MAX_BODY_BYTES` and `IMAGE_SYNC_MAX_FILE_BYTES`. A minimum wallet and key-quota check runs before generation; the exact price is reserved atomically after all validated settings and references are known.

`size` accepts `auto`, `1024x1024`, `1536x1024`, `1024x1536`, `2048x2048`, `2048x1152`, `3840x2160`, and `2160x3840`. The synchronous API defaults to `auto` when `size` is omitted; this default is isolated from the regular image API and can be changed with `IMAGE_SYNC_DEFAULT_SIZE`.

`IMAGE_SYNC_TRUSTED_PROXY_HOPS` defaults to `1` for the included single-Nginx deployment. Set it to the number of trusted reverse proxies in front of Node so per-IP abuse limits use the correct address.

Successful responses use the OpenAI image response shape and return URLs only:

```json
{
  "created": 1784592000,
  "data": [
    { "url": "https://canseedream.com/api/local-results/image/123?token=..." }
  ]
}
```

Base64 reference input does not enable Base64 output. `response_format=b64_json`, streaming, masks, and unsupported image parameters return an OpenAI-shaped `400` error instead of being silently ignored. The response includes `X-Request-Id` and `X-Task-Id`; partial multi-image results also include `X-Partial-Failure-Count`.

The HTTP request waits up to `IMAGE_SYNC_TIMEOUT_SECONDS`. A timeout returns `504 generation_timeout`, but the persistent task continues in the existing worker and retains its normal points settlement. Retrying with the same `Idempotency-Key` and identical input waits for the same task without submitting or charging again. Canvas clients that cannot send the header receive the same protection while the identical task remains active. Reusing an explicit key with different prompt, settings, URLs, or file contents returns `409 idempotency_conflict`.

An image task is completed and charged only after the upstream result has been validated and stored in the local result cache. MinIO backup is optional and runs asynchronously when enabled. The signed result route always reads the local file first, then transparently falls back to a private MinIO backup after local cleanup. Without a synced backup, the URL lifetime is capped by `LOCAL_RESULT_TTL_DAYS`; with a backup it uses `IMAGE_SYNC_RESULT_URL_TTL_DAYS` (30 days by default). Set `IMAGE_SYNC_REQUIRE_RESULT_BACKUP=true` only when new synchronous requests must be rejected unless durable backup is configured.

If an upstream image succeeds but cannot be stored locally, the task remains at 100% and retries only the result download; it does not resubmit generation or consume user points. After `IMAGE_RESULT_MIRROR_MAX_WAIT_MINUTES` (30 minutes by default), the task fails, releases the user and image-key reservations, and releases the pool account.
