Consola

Referencia API

Convenciones comunes

URL base

Todos los endpoints se sirven bajo el prefijo /v1.

Autenticación

Las llamadas a los endpoints /v1/* usan autenticación de Clave API (no JWT). La clave API se pasa mediante el siguiente encabezado:

EncabezadoFormatoDescripción
AuthorizationBearer <api_key>Estilo OpenAI. El endpoint compatible con Anthropic también acepta x-api-key con anthropic-version: 2023-06-01.

Las claves faltantes o inválidas devuelven 401.

Verificación previa de saldo

Todos los endpoints de llamada a modelos ejecutan una verificación previa de saldo antes de la ejecución:

  • Un saldo insuficiente devuelve Insufficient credit, asignado a:
    • Protocolo OpenAI: HTTP 400, code = insufficient_quota
    • Protocolo Anthropic: HTTP 402, type = billing_error
  • Algunos endpoints también estiman un costo mínimo por modelo para una segunda verificación previa.

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

Endpoint compatible con OpenAI Chat Completions. Soporta streaming y no streaming, llamadas a herramientas, modo JSON y entrada multimodal.

CampoTipoRequeridoDescripción
modelStringSíNombre del modelo.
messagesMessage[]SíMensajes de la conversación.
streamBooleanNoModo streaming, predeterminado false.
temperatureDoubleNoTemperatura de muestreo.
max_tokensIntegerNoMáximo de tokens de salida.
top_pDoubleNoMuestreo de núcleo.
presence_penaltyDoubleNo—
frequency_penaltyDoubleNo—
toolsTool[]NoDefiniciones de herramientas.
tool_choiceString|ObjectNoauto / none / required / función específica.
response_formatObjectNo{type, json_schema:{name,schema,strict}}; text/json_object/json_schema.
parallel_tool_callsBooleanNo—
metadataMapNoMetadatos de paso a través.

Campos de Message:

CampoTipoDescripción
roleStringsystem / user / assistant / tool.
contentString|ArrayTexto plano o arreglo de bloques de contenido multimodal ([{type:"text",text},{type:"image_url",image_url:{url}}]).
tool_call_idStringVincula a tool_calls cuando role=tool.
tool_callsToolCall[]Presente cuando role=assistant hace llamadas a herramientas.
CampoTipoDescripción
typeStringFijo function.
functionObjectDefinición de función.
function.nameStringNombre de la función.
function.descriptionStringDescripción de la función.
function.parametersObjectJSON Schema para las entradas.
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}
}

Campos de respuesta (no streaming):

CampoTipoDescripción
idStringID de completación.
objectStringFijo chat.completion.
createdLongMarca de tiempo de creación (segundos).
modelStringNombre del modelo.
choicesChoice[]{index, message:{role, content, tool_calls?}, finish_reason}.
usageObject{prompt_tokens, completion_tokens, total_tokens}.
CampoTipoDescripción
idStringID de llamada a herramienta.
typeStringFijo function.
functionObjectDetalles de la llamada a función.
function.nameStringNombre de la función.
function.argumentsObjectArgumentos de la función.

Ejemplo de respuesta en streaming:

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

Endpoint compatible con OpenAI Responses. Usa input en lugar de messages, instructions en lugar de un mensaje del sistema, y un bloque text en lugar de response_format.

CampoTipoRequeridoDescripción
modelStringSíNombre del modelo.
inputString|ArraySíCadena plana (mensaje de usuario) o arreglo de objetos de mensaje.
instructionsStringNoPrompt del sistema.
streamBooleanNoPredeterminado false.
max_output_tokensIntegerNoMáximo de tokens de salida.
temperatureDoubleNoPredeterminado 1.
top_pDoubleNo—
toolsTool[]NoNivel superior {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_idStringNoID de respuesta previa para conversación multi-turno.
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}
}

Campos de respuesta (no streaming):

CampoTipoDescripción
idStringID de respuesta.
objectStringFijo response.
modelStringNombre del modelo.
statusStringpor ejemplo completed.
created_atLongMarca de tiempo de creación (segundos).
outputArrayElementos de salida. Elementos de mensaje: {id, type:"message", role, content:[{type:"output_text", text}], status}. Elementos de llamada a herramienta: {type:"function_call", id, name, call_id, arguments, status}.
usageObject{input_tokens, output_tokens, total_tokens}. Para modelos Claude, input_tokens incluye cache_read y output_tokens incluye cache_write.

El streaming sigue los eventos de la API de Responses:

EventoDescripción
response.createdInicio del flujo de respuesta.
response.output_text.deltaActualización incremental de salida de texto.
response.completedFin del flujo de respuesta.

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

Endpoint compatible con Anthropic Messages. Acepta encabezados x-api-key y anthropic-version: 2023-06-01. Los bloques de contenido soportan text, image, tool_use, tool_result, thinking y redacted_thinking.

CampoTipoRequeridoCampo JSONDescripción
modelStringSímodelNombre del modelo.
messagesMessage[]SímessagesMensajes de la conversación.
systemString|ArrayNosystemPrompt del sistema, cadena o [{type,text}].
maxTokensIntegerSímax_tokensMáximo de tokens de salida.
streamBooleanNostreamStreaming.
temperatureDoubleNotemperature—
topPDoubleNotop_p—
topKIntegerNotop_k—
toolsTool[]NotoolsDefiniciones de herramientas (input_schema).
toolChoiceObjectNotool_choice—
metadataMapNometadata—
thinkingObjectNothinkingConfiguración de pensamiento extendido.
stopSequencesObjectNostop_sequences—
anthropicBetaObjectNoanthropic_betaEncabezado de función beta.
CampoTipoDescripción
roleStringRol del mensaje, por ejemplo user / assistant.
contentString|ContentBlock[]Texto plano o un arreglo de bloques de contenido.
CampoTipoDescripción
typeStringUno de text, image, tool_use, tool_result, thinking, redacted_thinking.
textStringPresente cuando el tipo es text.
sourceObjectPresente cuando el tipo es image.

Ejemplos de bloque de imagen:

{ "type": "image", "source": { "type": "base64", "media_type": "...", "data": "..." } }
{ "type": "image", "source": { "type": "url", "url": "..." } }
CampoTipoDescripción
nameStringNombre de la función.
descriptionStringDescripción de la función.
input_schemaObjectJSON Schema para las entradas.
cache_controlObjectControl de caché opcional.
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}
}

Campos de respuesta (no streaming):

CampoTipoDescripción
idStringID del mensaje.
typeStringFijo message.
roleStringFijo assistant.
modelStringNombre del modelo.
contentContentBlock[]Bloques de contenido de la respuesta (por ejemplo {type:"text", text}, {type:"tool_use", ...}).
stop_reasonStringpor ejemplo end_turn, tool_use, max_tokens.
usageObject{input_tokens, output_tokens}.
EventoDescripción
message_startInicio del flujo de mensajes.
content_block_startInicio de un nuevo bloque de contenido.
content_block_deltaActualización incremental para un bloque de contenido.
content_block_stopFin de un bloque de contenido.
message_deltaActualización incremental para el mensaje.
message_stopFin del flujo de mensajes.

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

Devuelve todos los modelos API en línea y habilitados.

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": "..."
    }
  ]
}

Campos de cada entrada de modelo (data[]):

CampoTipoDescripción
idStringID del modelo.
objectStringFijo model.
display_nameStringNombre para mostrar.
createdLongMarca de tiempo de creación (segundos).
owned_byStringPropietario / proveedor.
input_modalitiesString[]por ejemplo ["text","image"].
output_modalitiesString[]por ejemplo ["text"].
context_lengthIntegerLongitud máxima de contexto.
descriptionStringDescripción del modelo.

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

Devuelve un único modelo con la misma estructura que una entrada de lista. Devuelve HTTP 404 cuando el modelo no existe.

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

Respuesta exitosa: un objeto de modelo único con los mismos campos que una entrada de lista de /v1/models.

Cuando el modelo no existe, devuelve 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

Consulta las resoluciones, relaciones y cantidades máximas soportadas por un modelo de imagen antes de llamar a /v1/image-generations. No requiere autenticación.

CampoTipoDescripción
idStringID del modelo.
objectStringFijo image_model.
displayNameStringNombre para mostrar.
descriptionStringDescripción del modelo.
iconStringURL del icono.
createdLongMarca de tiempo de creación (segundos).
maxCountIntegerMáximo de imágenes por solicitud.
fileMaxIntegerMáximo de imágenes de referencia. Cuando es 0, no se admite la generación de imagen a partir de imagen.
resolutionsString[]Resoluciones soportadas, por ejemplo ["720p","1080p"].
ratiosString[]Relaciones de aspecto soportadas, por ejemplo ["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

Consulta los valores de videoType soportados, el rango de duración, las resoluciones y las relaciones de un modelo de video antes de llamar a /v1/video-generations. No requiere autenticación.

CampoTipoDescripción
idStringID del modelo.
objectStringFijo video_model.
displayNameStringNombre para mostrar.
descriptionStringDescripción del modelo.
iconStringURL del icono.
createdLongMarca de tiempo de creación (segundos).
allowedVideoTypesVideoTypeOption[]Lista de videoType soportados.
videoDurationMinIntegerSegundos mínimos por clip.
videoDurationMaxIntegerSegundos máximos por clip.
videoDurationSuggestInteger[]Pasos de duración recomendados, por ejemplo [5,8,10].
resolutionsString[]Resoluciones soportadas.
ratiosString[]Relaciones de aspecto soportadas.
resolutionOptionsResolutionOption[]Combinaciones estructuradas de resolución+relación+tamaño.
fileMaxIntegerMáximo de recursos de referencia.

Campos de VideoTypeOption:

CampoTipoDescripción
codeIntegerEl valor de videoType que se pasa a /v1/video-generations.
nameStringNombre localizado del tipo (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
    }
  ]
}

Descripción de campos de niveles de precio

Los 4 endpoints de consulta de modelos /v1/models, /v1/models/{model}, /v1/image-models y /v1/video-models devuelven el precio de facturación efectivo para el llamante (usuario de API Key), totalmente consistente con el cobro real, y devuelven todos los niveles de precio del modelo.

Diferencia de nomenclatura: /v1/models y /v1/models/{model} usan el estilo OpenAI snake_case (price_tiers); /v1/image-models y /v1/video-models usan camelCase (priceTiers). Ambas estructuras son idénticas.

price_tiers / priceTiers es un array; cada elemento es un nivel de precio:

CampoTipoDescripción
outputPricedecimalPrecio unitario de salida efectivo para el llamante actual
cachePricedecimalPrecio de caché (modelos generales, usado por la fórmula de facturación anterior)
cacheReadPricedecimalPrecio unitario de lectura de caché (exclusivo de Bedrock Claude)
cacheWritePricedecimalPrecio unitario de escritura de caché (exclusivo de Bedrock Claude)
ratiodecimalMultiplicador de facturación. El modo FIXED es 1; el modo RATIO es el multiplicador de usuario/grupo
modestringModo de precio: RATIO / FIXED
planIdstringID del plan de precios coincidente, puede ser null

Los tres tipos de modelo comparten la misma estructura; solo varía el relleno de los campos descriptivos. Los campos no aplicables son null:

Tipo de modeloUNITCampos descriptivos efectivos
Textotokenregion / bandMin / bandMax
Imagenimageresolution / clarity
Videovideoresolution / clarity

Significado del precio: los valores inputPrice / outputPrice / cacheReadPrice / cacheWritePrice devueltos son los precios unitarios de facturación finales para el usuario de API Key actual tras resolver la cadena de precios (chain)→ guidance → precio base, consistentes con el cobro real. El precio unitario que ve el llamante varía según la cadena de precios a la que pertenece (reseller / distributor / empresa / configuración a nivel de usuario).

Deducción real = uso ÷ quantity × precio unitario correspondiente × ratio. Para video por segundos: deducción real = duration × outputPrice × ratio.

Límites y manejo de excepciones (los endpoints de solo lectura no devolverán 500 por problemas de configuración de precios)

EscenarioComportamiento
Fallo de resolución de un solo nivel (política de precios deniega acceso, configuración ausente)Omitir ese nivel, registrar log warn y continuar resolviendo los niveles restantes
Todos los niveles de un modelo fallan al resolverseArray vacío [], el modelo se devuelve normalmente
El modelo no tiene configuración de preciosArray vacío []
El análisis de precios lanza una excepción no capturadatry-catch general, devolver array vacío, el endpoint sigue devolviendo 200

Ejemplo de respuesta (/v1/models, modelo de texto):

{
  "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": "por cada 1k tokens",
          "inputPrice": 0.0025,
          "outputPrice": 0.01,
          "cachePrice": 0.00125,
          "cacheReadPrice": 0,
          "cacheWritePrice": 0,
          "ratio": 1,
          "mode": "RATIO",
          "planId": null
        }
      ]
    }
  ]
}

Ejemplo de respuesta (/v1/image-models, modelo de imagen, la estructura de priceTiers es la misma, unit es 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": "por imagen",
          "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

Envía asíncronamente una tarea de generación de imágenes. Devuelve un taskId inmediatamente; recupere el resultado consultando GET /v1/image-generations/{taskId} o a través de un webhook callbackUrl.

El model, los valores soportados de resolution / ratio, el límite superior de count y el límite de subida de imágenes de referencia (fileMax) deben obtenerse primero de GET /v1/image-models. Solo se aceptan los valores anunciados por la especificación de ese modelo.

CampoTipoRequeridoDescripción
textStringSíPrompt.
modelStringSíNombre del modelo.
imageUrlsString[]NoURLs de imágenes de referencia (image-to-image).
countIntegerNoNúmero de imágenes (≥0).
resolutionStringNoResolución (ver /v1/image-models).
ratioStringNoRelación de aspecto.
callbackUrlStringNoURL del webhook a nivel de tarea.
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"}
}

Respuestas de error:

// 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}

Consulta una tarea de generación de imágenes. status es pending / success / failed. images es un arreglo de URLs de imágenes serializado en JSON; text contiene cualquier descripción de texto adjunta por el modelo (por ejemplo, salida multimodal de Gemini), null en caso contrario.

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
  }
}

Campos de respuesta data:

CampoTipoDescripción
taskIdStringID de la tarea.
statusStringpending / success / failed.
errorMessageStringMotivo de falla, null en caso de éxito.
imagesStringArreglo de URLs de imágenes serializado en JSON, por ejemplo "[\"https://.../1.png\"]".
textStringDescripción de texto adjunta por el modelo (por ejemplo, salida multimodal de Gemini); null en caso contrario.

Tarea no encontrada:

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

Si se proporcionó callbackUrl al enviar, el servidor envía el resultado final success / failed a través del webhook con la misma estructura de data.

Ejemplo completo (envío + consulta)

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

Envía asíncronamente una tarea de generación de video. Devuelve un taskId inmediatamente; recupere el resultado consultando GET /v1/video-generations/{taskId} o a través de un webhook callbackUrl.

El model, los valores permitidos de videoType, el rango de duración (videoDurationMin/Max), la resolution / ratio soportadas y el límite de subida de recursos de referencia (fileMax) deben obtenerse primero de GET /v1/video-models. Solo se aceptan los códigos de videoType listados en allowedVideoTypes de ese modelo.

CampoTipoRequeridoDescripción
textStringSíPrompt.
modelStringSíNombre del modelo.
videoTypeIntegerSí1 text-to-video / 2 image-to-video (primer frame) / 3 image-to-video (primer y último frame) / 4 image-to-video (referencia) / 5 toda referencia.
imageUrlsString[]NoURLs de recursos de imagen.
videoUrlsVideoUrl[]|String[]NoURLs de recursos de video.
audioUrlsString[]NoURLs de recursos de audio.
resolutionStringNoResolución.
ratioStringNoRelación de aspecto.
durationLongNoSegundos (>0).
callbackUrlStringNoURL del webhook a nivel de tarea.

Ejemplos para cada videoType:

1. Texto a video (videoType=1)

Genera un video solo a partir de un prompt de texto; no se necesitan recursos de referencia.

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. Imagen a video - primer frame (videoType=2)

Proporcione un único frame inicial en imageUrls; el modelo genera un video que comienza desde ese 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. Imagen a video - primer y último frame (videoType=3)

Proporcione tanto el primer como el último frame en imageUrls (orden: [primero, último]); el modelo genera un video de transición entre los dos 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. Imagen a video - referencia (videoType=4)

Proporcione una o más imágenes de referencia en imageUrls; el modelo usa su estilo/contenido como referencia (no como primer/último frame forzado) para generar el 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. Toda referencia (videoType=5)

Referencias mixtas de imagen / video / audio. Referencie recursos por posición en el prompt: la 1ª entrada en imageUrls es @图片 1, la 1ª en videoUrls es @视频 1, la 1ª en audioUrls es @音频 1. videoUrls también acepta cadenas de URL simples.

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
  }'

Respuesta de envío (los cinco tipos):

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

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

Consulta una tarea de generación de video. status es pending / success / failed; videoUrl es la URL del video generado y lastFrameUrl es la URL del último frame (escenarios de image-to-video).

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
  }
}

Campos de respuesta data:

CampoTipoDescripción
statusStringpending / success / failed.
videoUrlStringURL del video generado.
lastFrameUrlStringURL del último frame (escenarios de image-to-video); null en caso contrario.
messageStringMotivo de falla, null en caso de éxito.

Si se proporcionó callbackUrl al enviar, el servidor envía el resultado final a través del webhook con la misma estructura de data.

Ejemplo completo (envío + consulta)

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

Devuelve el saldo de la cuenta dividido en tres monederos: plan mensual, paquetes de recursos y crédito de pago por uso.

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
  }
}

Campos de respuesta:

CampoTipoDescripción
totalCreditBigDecimalSaldo total.
totalResourceCreditBigDecimalSuma de los saldos de paquetes de recursos.
wallets.monthlyPlanWalletDetailPlan mensual (null si no hay).
wallets.resourcePacksWalletDetail[]Lista de paquetes de recursos.
wallets.payAsYouGoBigDecimalSaldo de pago por uso.

Campos de WalletDetailVO::

CampoTipoDescripción
idStringId del monedero.
creditBigDecimalCréditos del saldo.
nameStringNombre del monedero.

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

Detalles de facturación de llamadas a modelos paginadas, con instantánea por precio (priceSnapshotId), ordenados por tiempo de creación del orden de forma descendente. Solo se devuelven los registros de cobro normales (reason = model usage).

Parámetros de consulta:

ParámetroTipoRequeridoPredeterminadoDescripción
pageIntegerNo1Número de página, basado en 1.
sizeIntegerNo20Tamaño de página (paginado por priceSnapshotId).
startTimeLocalDateTimeNo—Hora de inicio, formato yyyy-MM-ddTHH:mm:ss, filtra por orderCreatedAt de la instantánea.
endTimeLocalDateTimeNo—Hora de fin, formato 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"

Contenedor de respuesta:

{
  "code": 0,
  "message": "success",
  "data": { ... }
}
CampoTipoDescripción
recordsUsageDetailVO[]Registros de la página actual.
totalLongConteo total.
currentLongPágina actual.
sizeLongTamaño de página.
pagesLongTotal de páginas.

Campos de UsageDetailVO:

CampoTipoDescripción
priceSnapshotIdStringId de la instantánea de precio.
taskIdStringId de la tarea.
creditBigDecimalMonto cobrado.
modelStringNombre del modelo.
modelTypeStringtext / image / video.
inputTokensLongTokens de entrada; null para imagen/video.
outputTokensLongTokens de salida.
totalTokensLongTotal de tokens.
cacheReadTokensLongTokens leídos de caché.
cacheWriteTokensLongTokens escritos en caché.
imageCountIntegerCantidad de imágenes; establecido para modelos de imagen.
imageResolutionStringResolución de imagen, p. ej. 720P.
imageRatioStringRelación de aspecto de imagen, p. ej. 1:1.
videoResolutionStringResolución de video, p. ej. 1080p.
videoRatioStringRelación de aspecto de video, p. ej. 16:9.
videoDurationSecLongDuración del video en segundos.
orderCreatedAtLocalDateTimeTiempo de creación del orden (orderCreatedAt de la instantánea).
creditDetailsCreditDetailItem[]Detalles de orden bajo esta instantánea (de credit_order_t).

Campos de CreditDetailItem:

CampoTipoDescripción
creditBigDecimalMonto cobrado por esta orden.
deductionSourceStringOrigen de deducción (Balance / Monthly Package / Resource Package).
packageNameStringNombre del paquete; null si no hay paquete.

Convención de valores nulos: solo se completan los campos relevantes para cada modelType; el resto son null. text completa los campos de tokens; image completa imageCount/imageResolution/imageRatio; video completa videoResolution/videoRatio/videoDurationSec.

Ejemplo de respuesta:

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

Lista paginada de las transacciones de recarga pagadas (status=2) del usuario actual, ordenadas por created_at de forma descendente.

ParámetroTipoRequeridoPredeterminadoDescripción
pageIntegerNo1Número de página.
sizeIntegerNo20Tamaño de página.
startTimeStringNo—Hora de inicio, yyyy-MM-dd HH:mm:ss, inclusivo.
endTimeStringNo—Hora de fin, yyyy-MM-dd HH:mm:ss, inclusivo.
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"

Contenedor de respuesta:

{
  "code": 0,
  "message": "success",
  "data": { ... }
}
CampoTipoDescripción
recordsTransactionVO[]Transacciones de la página actual.
totalLongConteo total.
currentLongPágina actual.
sizeLongTamaño de página.
pagesLongTotal de páginas.

Campos de TransactionVO:

CampoTipoDescripción
orderNoStringNúmero de orden.
thirdPartyOrderNoStringNúmero de orden de terceros.
amountBigDecimalMonto de la orden.
actualAmountBigDecimalMonto efectivamente pagado.
discountBigDecimalMonto de descuento.
paymentMethodStringMétodo de pago (wechat / alipay / ustd / stripe / wallyt etc.).

Campos de TransactionVO:

CampoTipoDescripción
serviceFeeAmountBigDecimalMonto de la tarifa de servicio.
paymentChannelStringPlataforma de pago.
sourceStringOrigen de la orden (recharge / package_purchase etc.).
packageNameStringNombre del paquete (establecido para compras de paquetes; null para recargas simples).
createdAtLocalDateTimeTiempo de creación.
{
  "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
  }
}