Console

API Reference

Common conventions

Base URL

All endpoints are served under the /v1 prefix.

Authentication

Calls to /v1/* endpoints use API Key authentication (not JWT). The API Key is passed via the following header:

HeaderFormatDescription
AuthorizationBearer <api_key>OpenAI-style. The Anthropic-compatible endpoint also accepts x-api-key with anthropic-version: 2023-06-01.

Missing or invalid keys return 401.

Balance pre-check

All model-calling endpoints run a balance pre-check before execution:

  • Insufficient balance returns Insufficient credit, mapped to:
    • OpenAI protocol: HTTP 400, code = insufficient_quota
    • Anthropic protocol: HTTP 402, type = billing_error
  • Some endpoints also estimate a minimum cost per model for a second pre-check.

Create Chat CompletionPOSThttps://api.basicrouter.ai/api/v1/chat/completions

OpenAI Chat Completions-compatible endpoint. Supports streaming and non-streaming, tool calls, JSON mode, and multimodal input.

FieldTypeRequiredDescription
modelStringYesModel name.
messagesMessage[]YesConversation messages.
streamBooleanNoStream mode, default false.
temperatureDoubleNoSampling temperature.
max_tokensIntegerNoMaximum output tokens.
top_pDoubleNoNucleus sampling.
presence_penaltyDoubleNo—
frequency_penaltyDoubleNo—
toolsTool[]NoTool definitions.
tool_choiceString|ObjectNoauto / none / required / specific function.
response_formatObjectNo{type, json_schema:{name,schema,strict}}; text/json_object/json_schema.
parallel_tool_callsBooleanNo—
metadataMapNoPass-through metadata.

Message fields:

FieldTypeDescription
roleStringsystem / user / assistant / tool.
contentString|ArrayPlain text or multimodal content block array ([{type:"text",text},{type:"image_url",image_url:{url}}]).
tool_call_idStringLinks to the tool_calls when role=tool.
tool_callsToolCall[]Present when role=assistant makes tool calls.
FieldTypeDescription
typeStringFixed function.
functionObjectFunction definition.
function.nameStringFunction name.
function.descriptionStringFunction description.
function.parametersObjectJSON Schema for inputs.
curl --request POST \
  --url https://api.basicrouter.ai/api/v1/chat/completions \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "model": "glm-5.2",
    "messages": [{"role": "user", "content": "Describe Hangzhou in one sentence."}],
    "stream": false,
    "temperature": 0.7
  }'
{
  "id": "chatcmpl-xxx",
  "object": "chat.completion",
  "created": 1721380000,
  "model": "glm-5.2",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "Hangzhou is ..."},
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 12, "completion_tokens": 18, "total_tokens": 30}
}

Response fields (non-streaming):

FieldTypeDescription
idStringCompletion id.
objectStringFixed chat.completion.
createdLongCreated timestamp (seconds).
modelStringModel name.
choicesChoice[]{index, message:{role, content, tool_calls?}, finish_reason}.
usageObject{prompt_tokens, completion_tokens, total_tokens}.
FieldTypeDescription
idStringTool call id.
typeStringFixed function.
functionObjectFunction call details.
function.nameStringFunction name.
function.argumentsObjectFunction arguments.

Streaming response example:

data: {"object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant","content":"..."}}]}

data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"..."}}]}

data: [DONE]

Create ResponsePOSThttps://api.basicrouter.ai/api/v1/responses

OpenAI Responses-compatible endpoint. Uses input instead of messages, instructions instead of a system message, and a text block instead of response_format.

FieldTypeRequiredDescription
modelStringYesModel name.
inputString|ArrayYesPlain string (user message) or message object array.
instructionsStringNoSystem prompt.
streamBooleanNoDefault false.
max_output_tokensIntegerNoMaximum output tokens.
temperatureDoubleNoDefault 1.
top_pDoubleNo—
toolsTool[]NoTop-level {type, name, description, parameters}.
tool_choiceString|ObjectNoauto/none/required/{type,name}.
textObjectNo{format:{type, name, schema, strict}}; text/json_object/json_schema.
metadataMapNo—
previous_response_idStringNoPrevious response id for multi-turn.
parallel_tool_callsBooleanNo—
curl --request POST \
  --url https://api.basicrouter.ai/api/v1/responses \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "model": "glm-5.2",
    "input": "Describe Hangzhou in one sentence.",
    "instructions": "Be concise.",
    "stream": false
  }'
{
  "id": "resp_xxx",
  "object": "response",
  "model": "glm-5.2",
  "status": "completed",
  "created_at": 1721380000,
  "output": [
    {
      "id": "msg_xxx",
      "type": "message",
      "role": "assistant",
      "content": [{"type": "output_text", "text": "Hangzhou is ..."}],
      "status": "completed"
    }
  ],
  "usage": {"input_tokens": 12, "output_tokens": 18, "total_tokens": 30}
}

Response fields (non-streaming):

FieldTypeDescription
idStringResponse id.
objectStringFixed response.
modelStringModel name.
statusStringe.g. completed.
created_atLongCreated timestamp (seconds).
outputArrayOutput items. Message items: {id, type:"message", role, content:[{type:"output_text", text}], status}. Tool-call items: {type:"function_call", id, name, call_id, arguments, status}.
usageObject{input_tokens, output_tokens, total_tokens}. For Claude models, input_tokens includes cache_read and output_tokens includes cache_write.

Streaming follows the Responses API events:

EventDescription
response.createdStart of the response stream.
response.output_text.deltaIncremental text output update.
response.completedEnd of the response stream.

Create Anthropic MessagePOSThttps://api.basicrouter.ai/api/v1/messages

Anthropic Messages-compatible endpoint. Accepts x-api-key and anthropic-version: 2023-06-01 headers. Content blocks support text, image, tool_use, tool_result, thinking, and redacted_thinking.

FieldTypeRequiredJSON fieldDescription
modelStringYesmodelModel name.
messagesMessage[]YesmessagesConversation messages.
systemString|ArrayNosystemSystem prompt, string or [{type,text}].
maxTokensIntegerYesmax_tokensMaximum output tokens.
streamBooleanNostreamStreaming.
temperatureDoubleNotemperature—
topPDoubleNotop_p—
topKIntegerNotop_k—
toolsTool[]NotoolsTool definitions (input_schema).
toolChoiceObjectNotool_choice—
metadataMapNometadata—
thinkingObjectNothinkingExtended thinking config.
stopSequencesObjectNostop_sequences—
anthropicBetaObjectNoanthropic_betaBeta feature header.
FieldTypeDescription
roleStringMessage role, e.g. user / assistant.
contentString|ContentBlock[]Plain text or an array of content blocks.
FieldTypeDescription
typeStringOne of text, image, tool_use, tool_result, thinking, redacted_thinking.
textStringPresent when type is text.
sourceObjectPresent when type is image.

Examples:

{ "type": "image", "source": { "type": "base64", "media_type": "...", "data": "..." } }
{ "type": "image", "source": { "type": "url", "url": "..." } }
FieldTypeDescription
nameStringFunction name.
descriptionStringFunction description.
input_schemaObjectJSON Schema for inputs.
cache_controlObjectOptional cache control.
curl --request POST \
  --url https://api.basicrouter.ai/api/v1/messages \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "anthropic-version: 2023-06-01" \
  --header "Content-Type: application/json" \
  --data '{
    "model": "claude-sonnet-4.6",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Describe Hangzhou in one sentence."}]
  }'
{
  "id": "msg_xxx",
  "type": "message",
  "role": "assistant",
  "model": "claude-sonnet-4.6",
  "content": [{"type": "text", "text": "Hangzhou is ..."}],
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 12, "output_tokens": 18}
}

Response fields (non-streaming):

FieldTypeDescription
idStringMessage id.
typeStringFixed message.
roleStringFixed assistant.
modelStringModel name.
contentContentBlock[]Response content blocks (e.g. {type:"text", text}, {type:"tool_use", ...}).
stop_reasonStringe.g. end_turn, tool_use, max_tokens.
usageObject{input_tokens, output_tokens}.
EventDescription
message_startStart of the message stream.
content_block_startStart of a new content block.
content_block_deltaIncremental update for a content block.
content_block_stopEnd of a content block.
message_deltaIncremental update for the message.
message_stopEnd of the message stream.

List ModelsGEThttps://api.basicrouter.ai/api/v1/models

Returns all online, enabled API models.

curl --request GET \
  --url https://api.basicrouter.ai/api/v1/models \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"
{
  "object": "list",
  "data": [
    {
      "id": "glm-5.2",
      "object": "model",
      "display_name": "glm-5.2",
      "created": 1721380000,
      "owned_by": "Zai",
      "input_modalities": ["text", "image"],
      "output_modalities": ["text"],
      "context_length": 128000,
      "description": "..."
    }
  ]
}

Each model entry (data[]) fields:

FieldTypeDescription
idStringModel id.
objectStringFixed model.
display_nameStringDisplay name.
createdLongCreated timestamp (seconds).
owned_byStringOwner / vendor.
input_modalitiesString[]e.g. ["text","image"].
output_modalitiesString[]e.g. ["text"].
context_lengthIntegerMaximum context length.
descriptionStringModel description.

Retrieve ModelGEThttps://api.basicrouter.ai/api/v1/models/{model}

Returns a single model with the same shape as a list entry. Returns HTTP 404 when the model does not exist.

curl --request GET \
  --url https://api.basicrouter.ai/api/v1/models/gpt-5.5 \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"

Success response: a single model object with the same fields as a /v1/models list entry.

When the model does not exist, returns HTTP 404:

{"error": {"message": "The model 'xxx' does not exist", "type": "invalid_request_error", "code": "invalid_model_error"}}

List Image ModelsGEThttps://api.basicrouter.ai/api/v1/image-models

Query the resolutions, ratios, and maximum counts supported by an image model before calling /v1/image-generations. No authentication required.

FieldTypeDescription
idStringModel id.
objectStringFixed image_model.
displayNameStringDisplay name.
descriptionStringModel description.
iconStringIcon URL.
createdLongCreated timestamp (seconds).
maxCountIntegerMax images per request.
fileMaxIntegerMax reference images. When 0, image-to-image is not supported.
resolutionsString[]Supported resolutions, e.g. ["720p","1080p"].
ratiosString[]Supported aspect ratios, e.g. ["1:1","3:2"].
curl --request GET \
  --url https://api.basicrouter.ai/api/v1/image-models \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"
{
  "object": "list",
  "data": [
    {
      "id": "gpt-image-2",
      "object": "image_model",
      "displayName": "GPT Image 1",
      "description": "...",
      "icon": "...",
      "created": 1721380000,
      "maxCount": 4,
      "fileMax": 10,
      "resolutions": ["720p", "1080p"],
      "ratios": ["1:1", "3:2"]
    }
  ]
}

List Video ModelsGEThttps://api.basicrouter.ai/api/v1/video-models

Query the supported videoType values, duration range, resolutions, and ratios for a video model before calling /v1/video-generations. No authentication required.

FieldTypeDescription
idStringModel id.
objectStringFixed video_model.
displayNameStringDisplay name.
descriptionStringModel description.
iconStringIcon URL.
createdLongCreated timestamp (seconds).
allowedVideoTypesVideoTypeOption[]Supported videoType list.
videoDurationMinIntegerMinimum seconds per clip.
videoDurationMaxIntegerMaximum seconds per clip.
videoDurationSuggestInteger[]Recommended duration steps, e.g. [5,8,10].
resolutionsString[]Supported resolutions.
ratiosString[]Supported aspect ratios.
resolutionOptionsResolutionOption[]Structured resolution+ratio+size combos.
fileMaxIntegerMax reference assets.

VideoTypeOption fields:

FieldTypeDescription
codeIntegerThe videoType value to pass to /v1/video-generations.
nameStringLocalized type name (text-to-video / image-to-video / ...).
curl --request GET \
  --url https://api.basicrouter.ai/api/v1/video-models \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"
{
  "object": "list",
  "data": [
    {
      "id": "sora-2",
      "object": "video_model",
      "displayName": "Sora 2",
      "description": "...",
      "icon": "...",
      "created": 1721380000,
      "allowedVideoTypes": [
        {"code": 1, "name": "text-to-video"},
        {"code": 2, "name": "image-to-video"},
        {"code": 3, "name": "image-to-video (first/last frame)"}
      ],
      "videoDurationMin": 5,
      "videoDurationMax": 10,
      "videoDurationSuggest": [5, 8, 10],
      "resolutions": ["1080p", "720p"],
      "ratios": ["16:9", "9:16"],
      "fileMax": 5
    }
  ]
}

Price tier fields

The four model query endpoints /v1/models, /v1/models/{model}, /v1/image-models, and /v1/video-models return the effective billing price for the current caller (API key user), fully consistent with actual charges, and return all price tiers of the model.

Field naming difference: /v1/models and /v1/models/{model} use OpenAI-style snake_case (price_tiers); /v1/image-models and /v1/video-models use camelCase (priceTiers). The structures are identical.

price_tiers / priceTiers is an array; each element is a price tier:

FieldTypeDescription
outputPricedecimalEffective output unit price for the current caller
cachePricedecimalCache price (general models, used by the legacy billing formula)
cacheReadPricedecimalCache read unit price (Bedrock Claude only)
cacheWritePricedecimalCache write unit price (Bedrock Claude only)
ratiodecimalBilling multiplier. FIXED mode is 1; RATIO mode is the user/group multiplier
modestringPricing mode: RATIO / FIXED
planIdstringMatched pricing plan ID, may be null

The three model types share the same structure; only the descriptive field values differ. Non-applicable fields are null:

Model typeUNITEffective description fields
Texttokenregion / bandMin / bandMax
Imageimageresolution / clarity
Videovideoresolution / clarity

Price meaning: the returned inputPrice / outputPrice / cacheReadPrice / cacheWritePrice values are the final billing unit prices for the current API key user after step-by-step resolution through the price chain (chain)→ guidance → base price, consistent with actual charges. The unit price seen by callers differs based on their pricing chain (reseller / distributor / enterprise / user-level configuration).

Actual deduction = usage ÷ quantity × corresponding unit price × ratio. For per-second video billing: actual deduction = duration × outputPrice × ratio.

Boundary & exception handling (read-only endpoints will not return 500 due to price configuration issues)

ScenarioBehavior
Single tier parse failure (pricing policy denies access, config missing)Skip that tier, log a warning, and continue parsing remaining tiers
All tiers of a model fail to parseEmpty array [], model still returned normally
Model has no price configurationEmpty array []
Price parsing throws an uncaught exceptionOverall try-catch, return empty array, endpoint still returns 200

Response example (/v1/models, text model):

{
  "object": "list",
  "data": [
    {
      "id": "gpt-4o",
      "object": "model",
      "display_name": "gpt-4o",
      "context_length": 128000,
      "price_tiers": [
        {
          "region": "GLOBAL",
          "bandMin": null,
          "bandMax": null,
          "resolution": null,
          "clarity": null,
          "unit": "token",
          "quantity": 1000,
          "description": "per 1k tokens",
          "inputPrice": 0.0025,
          "outputPrice": 0.01,
          "cachePrice": 0.00125,
          "cacheReadPrice": 0,
          "cacheWritePrice": 0,
          "ratio": 1,
          "mode": "RATIO",
          "planId": null
        }
      ]
    }
  ]
}

Response example (/v1/image-models, image model; priceTiers structure is the same, unit is image):

{
  "object": "list",
  "data": [
    {
      "id": "dall-e-3",
      "object": "image_model",
      "displayName": "dall-e-3",
      "resolutions": ["1024x1024", "1792x1024", "1024x1792"],
      "priceTiers": [
        {
          "region": null,
          "bandMin": null,
          "bandMax": null,
          "resolution": "1024x1024",
          "clarity": "standard",
          "unit": "image",
          "quantity": 1,
          "description": "per image",
          "inputPrice": 0,
          "outputPrice": 0.04,
          "cachePrice": 0,
          "cacheReadPrice": 0,
          "cacheWritePrice": 0,
          "ratio": 1,
          "mode": "RATIO",
          "planId": null
        }
      ]
    }
  ]
}

Create Image GenerationPOSThttps://api.basicrouter.ai/api/v1/image-generations

Asynchronously submit an image generation task. Returns a taskId immediately; retrieve the result by polling GET /v1/image-generations/{taskId} or via a callbackUrl webhook.

The model, supported resolution / ratio values, count upper limit, and reference-image upload limit (fileMax) must be obtained from GET /v1/image-models first. Only values advertised by that model's spec are accepted.

FieldTypeRequiredDescription
textStringYesPrompt.
modelStringYesModel name.
imageUrlsString[]NoReference image URLs (image-to-image).
countIntegerNoNumber of images (≥0).
resolutionStringNoResolution (see /v1/image-models).
ratioStringNoAspect ratio.
callbackUrlStringNoTask-level webhook URL.
curl --request POST \
  --url https://api.basicrouter.ai/api/v1/image-generations \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "model": "seedream-4.5",
    "text": "A cat drinking water by the river",
    "count": 1,
    "resolution": "2k",
    "ratio": "1:1",
    "imageUrls": []
  }'
{
  "code": 200,
  "message": "image task is commit",
  "data": {"taskId": "img_xxx"}
}

Error responses:

// Insufficient credit
{ "code": 500, "message": "Insufficient credit" }

// Model not found
{ "code": 404, "message": "Model not found: xxx" }

Retrieve Image GenerationGEThttps://api.basicrouter.ai/api/v1/image-generations/{taskId}

Poll an image generation task. status is pending / success / failed. images is a JSON-stringified array of image URLs; text carries any model-attached text description (e.g. Gemini multimodal output), null otherwise.

curl --request GET \
  --url https://api.basicrouter.ai/api/v1/image-generations/img_xxx \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"
{
  "code": 200,
  "message": "success",
  "data": {
    "taskId": "img_xxx",
    "status": "success",
    "errorMessage": null,
    "images": "[\"https://.../1.png\"]",
    "text": null
  }
}

Response data fields:

FieldTypeDescription
taskIdStringTask id.
statusStringpending / success / failed.
errorMessageStringFailure reason, null on success.
imagesStringJSON-stringified array of image URLs, e.g. "[\"https://.../1.png\"]".
textStringModel-attached text description (e.g. Gemini multimodal output); null otherwise.

Task not found:

{ "code": 500, "message": "task not found" }

If callbackUrl was supplied on submit, the server pushes the final success / failed result via webhook with the same data shape.

Complete example (submit + poll)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class ImageGenerationExample {
  private static final String BASE = "https://api.basicrouter.ai/api/v1";
  private static final String API_KEY = System.getenv("BASICROUTER_API_KEY");

  public static void main(String[] args) throws Exception {
    HttpClient http = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10)).build();

    // 1. Submit the task.
    String body = "{"
        + "\"model\":\"seedream-4.5\","
        + "\"text\":\"A cat drinking water by the river\","
        + "\"count\":1,"
        + "\"resolution\":\"2k\","
        + "\"ratio\":\"1:1\","
        + "\"imageUrls\":[]"
        + "}";
    HttpResponse<String> submit = http.send(
        HttpRequest.newBuilder(URI.create(BASE + "/image-generations"))
            .header("Authorization", "Bearer " + API_KEY)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body)).build(),
        HttpResponse.BodyHandlers.ofString());

    String taskId = extract(submit.body(), "taskId");
    System.out.println("taskId = " + taskId);

    // 2. Poll until terminal status.
    String status = "pending";
    while ("pending".equals(status)) {
      Thread.sleep(15_000L);
      HttpResponse<String> poll = http.send(
          HttpRequest.newBuilder(URI.create(BASE + "/image-generations/" + taskId))
              .header("Authorization", "Bearer " + API_KEY).GET().build(),
          HttpResponse.BodyHandlers.ofString());
      status = extract(poll.body(), "status");
      System.out.println("status = " + status);
    }

    if (!"success".equals(status)) {
      throw new RuntimeException("image generation failed: " + status);
    }
    // images is a JSON-stringified array of URLs.
    String images = extract(pollResult(http, taskId), "images");
    System.out.println("images = " + images);
  }

  // Minimal JSON field extractor — use Jackson/Gson in production.
  private static String extract(String json, String field) {
    int i = json.indexOf("\"" + field + "\":");
    if (i < 0) return null;
    i += field.length() + 3;
    if (json.charAt(i) == '\"') {
      int end = json.indexOf('\"', i + 1);
      return json.substring(i + 1, end);
    }
    int end = i;
    while (end < json.length() && "0123456789.".indexOf(json.charAt(end)) >= 0) end++;
    return json.substring(i, end);
  }

  private static String pollResult(HttpClient http, String taskId) throws Exception {
    return http.send(HttpRequest.newBuilder(URI.create(BASE + "/image-generations/" + taskId))
        .header("Authorization", "Bearer " + API_KEY).GET().build(),
        HttpResponse.BodyHandlers.ofString()).body();
  }
}
import os
import time
import requests

BASE = "https://api.basicrouter.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['BASICROUTER_API_KEY']}"}

# 1. Submit the task.
resp = requests.post(
    f"{BASE}/image-generations",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "model": "seedream-4.5",
        "text": "A cat drinking water by the river",
        "count": 1,
        "resolution": "2k",
        "ratio": "1:1",
        "imageUrls": [],
    },
)
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]
print(f"taskId = {task_id}")

# 2. Poll until terminal status.
while True:
    time.sleep(15)
    poll = requests.get(f"{BASE}/image-generations/{task_id}", headers=HEADERS)
    poll.raise_for_status()
    data = poll.json()["data"]
    status = data["status"]
    print(f"status = {status}")
    if status != "pending":
        break

if status != "success":
    raise RuntimeError(f"image generation failed: {data.get('errorMessage')}")

# images is a JSON-stringified array of URLs.
import json
images = json.loads(data["images"])
print(f"images = {images}")

Create Video GenerationPOSThttps://api.basicrouter.ai/api/v1/video-generations

Asynchronously submit a video generation task. Returns a taskId immediately; retrieve the result by polling GET /v1/video-generations/{taskId} or via a callbackUrl webhook.

The model, allowed videoType values, duration range (videoDurationMin/Max), supported resolution / ratio, and reference-asset upload limit (fileMax) must be obtained from GET /v1/video-models first. Only videoType codes listed in that model's allowedVideoTypes are accepted.

FieldTypeRequiredDescription
textStringYesPrompt.
modelStringYesModel name.
videoTypeIntegerYes1 text-to-video / 2 image-to-video (first frame) / 3 image-to-video (first+last frame) / 4 image-to-video (reference) / 5 all reference.
imageUrlsString[]NoImage asset URLs.
videoUrlsVideoUrl[]|String[]NoVideo asset URLs.
audioUrlsString[]NoAudio asset URLs.
resolutionStringNoResolution.
ratioStringNoAspect ratio.
durationLongNoSeconds (>0).
callbackUrlStringNoTask-level webhook URL.

Examples for each videoType:

1. Text to video (videoType=1)

Generate a video from a text prompt only; no reference assets needed.

curl --request POST \
  --url https://api.basicrouter.ai/api/v1/video-generations \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "videoType": 1,
    "text": "A cat jumping on a bed",
    "resolution": "480p",
    "ratio": "16:9",
    "duration": 4,
    "model": "seedance-2.0"
  }'

2. Image to video - first frame (videoType=2)

Provide a single starting frame in imageUrls; the model generates a video starting from that frame.

curl --request POST \
  --url https://api.basicrouter.ai/api/v1/video-generations \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "videoType": 2,
    "text": "Happily shaking head",
    "resolution": "480p",
    "ratio": "16:9",
    "duration": 4,
    "model": "seedance-2.0",
    "imageUrls": ["https://basicrouter-flie.oss-accelerate.aliyuncs.com/test/first-frame.png"]
  }'

3. Image to video - first and last frame (videoType=3)

Provide both the first and last frame in imageUrls (order: [first, last]); the model generates a transition video between the two frames.

curl --request POST \
  --url https://api.basicrouter.ai/api/v1/video-generations \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "videoType": 3,
    "text": "Put on the hat",
    "resolution": "480p",
    "ratio": "16:9",
    "duration": 4,
    "model": "seedance-2.0",
    "imageUrls": [
      "https://basicrouter-flie.oss-accelerate.aliyuncs.com/test/first-frame.png",
      "https://basicrouter-flie.oss-accelerate.aliyuncs.com/test/last-frame.png"
    ]
  }'

4. Image to video - reference (videoType=4)

Provide one or more reference images in imageUrls; the model uses their style/content as reference (not as a forced first/last frame) to generate the video.

curl --request POST \
  --url https://api.basicrouter.ai/api/v1/video-generations \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "videoType": 4,
    "text": "Two cats playing together",
    "resolution": "480p",
    "ratio": "16:9",
    "duration": 4,
    "model": "kling-v3-omni-video",
    "imageUrls": [
      "https://basicrouter-flie.oss-accelerate.aliyuncs.com/test/ref-1.png",
      "https://basicrouter-flie.oss-accelerate.aliyuncs.com/test/ref-2.png"
    ]
  }'

5. All reference (videoType=5)

Mixed image / video / audio references. Reference assets by position in the prompt: the 1st entry in imageUrls is @图片 1, the 1st in videoUrls is @视频 1, the 1st in audioUrls is @音频 1. videoUrls also accepts plain URL strings.

curl --request POST \
  --url https://api.basicrouter.ai/api/v1/video-generations \
  --header "Authorization: Bearer $BASICROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "videoType": 5,
    "text": "Use the first-person framing of @视频 1 and @音频 1 as background music. First-person tea ad; start frame is @图片 1 ... end frame is @图片 2.",
    "model": "seedance-2.0",
    "imageUrls": [
      "https://ark-project.tos-cn-beijing.volces.com/doc_image/r2v_tea_pic1.jpg",
      "https://ark-project.tos-cn-beijing.volces.com/doc_image/r2v_tea_pic2.jpg"
    ],
    "videoUrls": ["https://ark-project.tos-cn-beijing.volces.com/doc_video/r2v_tea_video1.mp4"],
    "audioUrls": ["https://ark-project.tos-cn-beijing.volces.com/doc_audio/r2v_tea_audio1.mp3"],
    "resolution": "1080p",
    "ratio": "16:9",
    "duration": 11
  }'

Submit response (all five types):

{
  "code": 200,
  "message": "success",
  "data": {"taskId": "vid_xxx"}
}

Retrieve Video GenerationGEThttps://api.basicrouter.ai/api/v1/video-generations/{taskId}

Poll a video generation task. status is pending / success / failed; videoUrl is the generated video URL and lastFrameUrl is the last-frame URL (image-to-video scenarios).

curl --request GET \
  --url https://api.basicrouter.ai/api/v1/video-generations/vid_xxx \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"
{
  "code": 200,
  "message": "success",
  "data": {
    "status": "success",
    "videoUrl": "https://.../out.mp4",
    "lastFrameUrl": null,
    "message": null
  }
}

Response data fields:

FieldTypeDescription
statusStringpending / success / failed.
videoUrlStringGenerated video URL.
lastFrameUrlStringLast-frame URL (image-to-video scenarios); null otherwise.
messageStringFailure reason, null on success.

If callbackUrl was supplied on submit, the server pushes the final result via webhook with the same data shape.

Complete example (submit + poll)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class VideoGenerationExample {
  private static final String BASE = "https://api.basicrouter.ai/api/v1";
  private static final String API_KEY = System.getenv("BASICROUTER_API_KEY");

  public static void main(String[] args) throws Exception {
    HttpClient http = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10)).build();

    // 1. Submit the task (videoType=1: text-to-video).
    String body = "{"
        + "\"videoType\":1,"
        + "\"text\":\"A cat jumping on a bed\","
        + "\"resolution\":\"480p\","
        + "\"ratio\":\"16:9\","
        + "\"duration\":4,"
        + "\"model\":\"seedance-2.0\""
        + "}";
    HttpResponse<String> submit = http.send(
        HttpRequest.newBuilder(URI.create(BASE + "/video-generations"))
            .header("Authorization", "Bearer " + API_KEY)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body)).build(),
        HttpResponse.BodyHandlers.ofString());

    String taskId = extract(submit.body(), "taskId");
    System.out.println("taskId = " + taskId);

    // 2. Poll until terminal status. Video tasks take longer — poll every 20s.
    String status = "pending";
    String lastBody = null;
    while ("pending".equals(status)) {
      Thread.sleep(20_000L);
      HttpResponse<String> poll = http.send(
          HttpRequest.newBuilder(URI.create(BASE + "/video-generations/" + taskId))
              .header("Authorization", "Bearer " + API_KEY).GET().build(),
          HttpResponse.BodyHandlers.ofString());
      lastBody = poll.body();
      status = extract(lastBody, "status");
      System.out.println("status = " + status);
    }

    if (!"success".equals(status)) {
      throw new RuntimeException("video generation failed: " + status);
    }
    String videoUrl = extract(lastBody, "videoUrl");
    System.out.println("videoUrl = " + videoUrl);
  }

  // Minimal JSON field extractor — use Jackson/Gson in production.
  private static String extract(String json, String field) {
    int i = json.indexOf("\"" + field + "\":");
    if (i < 0) return null;
    i += field.length() + 3;
    if (json.charAt(i) == '\"') {
      int end = json.indexOf('\"', i + 1);
      return json.substring(i + 1, end);
    }
    int end = i;
    while (end < json.length() && "0123456789.".indexOf(json.charAt(end)) >= 0) end++;
    return json.substring(i, end);
  }
}
import os
import time
import requests

BASE = "https://api.basicrouter.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['BASICROUTER_API_KEY']}"}

# 1. Submit the task (videoType=1: text-to-video).
resp = requests.post(
    f"{BASE}/video-generations",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "videoType": 1,
        "text": "A cat jumping on a bed",
        "resolution": "480p",
        "ratio": "16:9",
        "duration": 4,
        "model": "seedance-2.0",
    },
)
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]
print(f"taskId = {task_id}")

# 2. Poll until terminal status. Video tasks take longer — poll every 20s.
while True:
    time.sleep(20)
    poll = requests.get(f"{BASE}/video-generations/{task_id}", headers=HEADERS)
    poll.raise_for_status()
    data = poll.json()["data"]
    status = data["status"]
    print(f"status = {status}")
    if status != "pending":
        break

if status != "success":
    raise RuntimeError(f"video generation failed: {data.get('message')}")

print(f"videoUrl = {data['videoUrl']}")
if data.get("lastFrameUrl"):
    print(f"lastFrameUrl = {data['lastFrameUrl']}")

Retrieve Billing BalanceGEThttps://api.basicrouter.ai/api/v1/billing/balance

Returns the account balance split into three wallets: monthly plan, resource packs, and pay-as-you-go credit.

curl --request GET \
  --url https://api.basicrouter.ai/api/v1/billing/balance \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"
{
  "totalCredit": 128.50,
  "totalResourceCredit": 30.00,
  "wallets": {
    "monthlyPlan": {"id": "pkg_xxx", "credit": 50.00, "name": "Monthly plan"},
    "resourcePacks": [
      {"id": "rp_xxx", "credit": 30.00, "name": "Video resource pack"}
    ],
    "payAsYouGo": 48.50
  }
}

Response fields:

FieldTypeDescription
totalCreditBigDecimalTotal balance.
totalResourceCreditBigDecimalSum of resource-pack balances.
wallets.monthlyPlanWalletDetailMonthly plan (null if none).
wallets.resourcePacksWalletDetail[]Resource pack list.
wallets.payAsYouGoBigDecimalPay-as-you-go balance.

WalletDetailVO fields::

FieldTypeDescription
idStringWallet id.
creditBigDecimalBalance credits.
nameStringWallet name.

List UsageGEThttps://api.basicrouter.ai/api/v1/usage

Paginated model-call billing details, snapshoted by price (priceSnapshotId), ordered by order creation time descending. Only normal charge records (reason = model usage) are returned.

Query parameters:

ParameterTypeRequiredDefaultDescription
pageIntegerNo1Page number, 1-based.
sizeIntegerNo20Page size (paginated by priceSnapshotId).
startTimeLocalDateTimeNo—Start time, format yyyy-MM-ddTHH:mm:ss, filters by snapshot orderCreatedAt.
endTimeLocalDateTimeNo—End time, format yyyy-MM-ddTHH:mm:ss.
curl --request GET \
  --url "https://api.basicrouter.ai/api/v1/usage?page=1&size=20&startTime=2026-07-01T00:00:00&endTime=2026-07-31T23:59:59" \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"

Response wrapper:

{
  "code": 0,
  "message": "success",
  "data": { ... }
}
FieldTypeDescription
recordsUsageDetailVO[]Current page records.
totalLongTotal count.
currentLongCurrent page.
sizeLongPage size.
pagesLongTotal pages.

UsageDetailVO fields:

FieldTypeDescription
priceSnapshotIdStringPrice snapshot id.
taskIdStringTask id.
creditBigDecimalCharged amount.
modelStringModel name.
modelTypeStringtext / image / video.
inputTokensLongInput tokens; null for image/video.
outputTokensLongOutput tokens.
totalTokensLongTotal tokens.
cacheReadTokensLongCache-read tokens.
cacheWriteTokensLongCache-write tokens.
imageCountIntegerImage count; set for image models.
imageResolutionStringImage resolution, e.g. 720P.
imageRatioStringImage aspect ratio, e.g. 1:1.
videoResolutionStringVideo resolution, e.g. 1080p.
videoRatioStringVideo aspect ratio, e.g. 16:9.
videoDurationSecLongVideo duration in seconds.
orderCreatedAtLocalDateTimeOrder creation time (snapshot orderCreatedAt).
creditDetailsCreditDetailItem[]Order details under this snapshot (from credit_order_t).

CreditDetailItem fields:

FieldTypeDescription
creditBigDecimalAmount charged by this order.
deductionSourceStringDeduction source (Balance / Monthly Package / Resource Package).
packageNameStringPackage name; null if no package.

Null-value convention: only the fields relevant to each modelType are populated; the rest are null. text populates the token fields; image populates imageCount/imageResolution/imageRatio; video populates videoResolution/videoRatio/videoDurationSec.

Response example:

{
  "code": 200,
  "message": "success",
  "data": {
    "records": [
      {
        "priceSnapshotId": "snap_9f3c1a2b",
        "taskId": "task_5e8a1c33",
        "credit": 0.0342,
        "model": "glm-5.2",
        "modelType": "text",
        "inputTokens": 1280,
        "outputTokens": 642,
        "totalTokens": 1922,
        "cacheReadTokens": 0,
        "cacheWriteTokens": 0,
        "imageCount": null,
        "imageResolution": null,
        "imageRatio": null,
        "videoResolution": null,
        "videoRatio": null,
        "videoDurationSec": null,
        "orderCreatedAt": "2026-07-18T14:23:11",
        "creditDetails": [
          {
            "credit": 0.0342,
            "deductionSource": "balance",
            "packageName": ""
          }
        ]
      },
      {
        "priceSnapshotId": "snap_a12f77c0",
        "taskId": "task_c71e44a2",
        "credit": 1.8000,
        "model": "seedance-2.0",
        "modelType": "video",
        "inputTokens": null,
        "outputTokens": null,
        "totalTokens": null,
        "cacheReadTokens": null,
        "cacheWriteTokens": null,
        "imageCount": null,
        "imageResolution": null,
        "imageRatio": null,
        "videoResolution": "1080p",
        "videoRatio": "16:9",
        "videoDurationSec": 8,
        "orderCreatedAt": "2026-07-17T22:41:09",
        "creditDetails": [
          {
            "credit": 1.5000,
            "deductionSource": "Monthly Package",
            "packageName": "基础月度套餐"
          },
          {
            "credit": 0.3000,
            "deductionSource": "Resource Package",
            "packageName": "byteplus视频资源包"
          }
        ]
      }
    ],
    "total": 128,
    "current": 1,
    "size": 20,
    "pages": 7
  }
}

List Billing TransactionsGEThttps://api.basicrouter.ai/api/v1/billing/transactions

Paginated list of the current user's paid (status=2) recharge transactions, ordered by created_at descending.

ParameterTypeRequiredDefaultDescription
pageIntegerNo1Page number.
sizeIntegerNo20Page size.
startTimeStringNo—Start time, yyyy-MM-dd HH:mm:ss, inclusive.
endTimeStringNo—End time, yyyy-MM-dd HH:mm:ss, inclusive.
curl --request GET \
  --url "https://api.basicrouter.ai/api/v1/billing/transactions?page=1&size=20&startTime=2026-07-01%2000:00:00&endTime=2026-07-31%2023:59:59" \
  --header "Authorization: Bearer $BASICROUTER_API_KEY"

Response wrapper:

{
  "code": 0,
  "message": "success",
  "data": { ... }
}
FieldTypeDescription
recordsTransactionVO[]Current page transactions.
totalLongTotal count.
currentLongCurrent page.
sizeLongPage size.
pagesLongTotal pages.

TransactionVO fields:

FieldTypeDescription
orderNoStringOrder number.
thirdPartyOrderNoStringThird-party order number.
amountBigDecimalOrder amount.
actualAmountBigDecimalActually paid amount.
discountBigDecimalDiscount amount.
paymentMethodStringPayment method (wechat / alipay / ustd / stripe / wallyt etc.).

TransactionVO fields:

FieldTypeDescription
serviceFeeAmountBigDecimalService fee amount.
paymentChannelStringPayment platform.
sourceStringOrder source (recharge / package_purchase etc.).
packageNameStringPackage name (set for package purchases; null for plain recharges).
createdAtLocalDateTimeCreation time.
{
  "code": 200,
  "message": "success",
  "data": {
    "records": [
      {
        "orderNo": "R20260718abc123",
        "thirdPartyOrderNo": "wx_pay_xxx",
        "amount": 50.00,
        "actualAmount": 48.50,
        "discount": 1.50,
        "paymentMethod": "wechat",
        "serviceFeeAmount": 0.00,
        "paymentChannel": "wechat",
        "source": "recharge",
        "packageName": null,
        "createdAt": "2026-07-18T14:23:11"
      }
    ],
    "total": 28,
    "current": 1,
    "size": 20,
    "pages": 2
  }
}