BazaarLinkBazaarLink
Sign in
DocsAPI ReferenceSDK ReferenceAgentic UsageAI Skills

API Reference

An overview of BazaarLink’s API

BazaarLink provides one OpenAI-compatible request and response schema across models and providers, so you can integrate once and switch models without rewriting your application.

Customer routing fields are ignored
If present, provider (the entire object), transforms, route, top-level service_tier, and preset are removed before upstream requests. These fields cannot select an upstream or price tier. On Chat Completions and Responses, the models array is supported as a local fallback list: BazaarLink resolves each model through its own routing and applies its customer price, without forwarding the list upstream. Customer :nitro, :floor, and :exacto suffixes are ignored; :eco remains BazaarLink's economy tier and maps internally to BazaarLink :floor. :free remains a separate catalog model variant.
Web search add-on costs
Web search requested with plugins, :online, or web_search_options keeps its existing behavior and may incur an upstream add-on charge on the key used for the request: BazaarLink's platform key or your BYOK key.

OpenAPI Specification

The complete BazaarLink API is documented with the OpenAPI specification. You can access it in YAML or JSON format:

Use these specifications with Swagger UI, Postman, or any OpenAPI-compatible code generator to explore the API or generate client libraries.

Requests

Completions Request Format

The request body for chat completions is sent to the following endpoint:

POST/v1/chat/completions

For the complete list of supported fields, see Parameters。

Request Schema
// Definitions of subtypes are below
type Request = {
  // Required by BazaarLink Chat Completions
  model: string;                    // Use an id or alias from GET /v1/models
  messages: Message[];             // Must contain at least one message

  // Generation
  stream?: boolean;
  temperature?: number;
  max_tokens?: number;             // Positive integer
  max_completion_tokens?: number;  // Alias mapped to max_tokens upstream
  n?: number;
  seed?: number;
  stop?: string | string[];

  // Sampling
  top_p?: number;
  top_k?: number;
  frequency_penalty?: number;
  presence_penalty?: number;
  repetition_penalty?: number;
  min_p?: number;
  top_a?: number;
  // Sampling values are forwarded without range validation; models may clamp or ignore them.

  // Token probabilities
  logit_bias?: Record<number, number>;
  logprobs?: boolean;
  top_logprobs?: number;

  // Stable end-user identifier for abuse prevention
  user?: string;

  // Tool calling
  tools?: Tool[];
  tool_choice?: ToolChoice;
  parallel_tool_calls?: boolean;

  // Structured output
  response_format?: ResponseFormat;
  structured_outputs?: boolean;

  // Plugin configuration — forwarded to upstreams that support plugins.
  plugins?: Plugin[];

  // Multimodal and image-generation options
  modalities?: Array<"text" | "image">;
  image_config?: Record<string, unknown>;
  input_audio?: InputAudio;

  // Provider-specific reasoning controls
  reasoning_effort?: "low" | "medium" | "high";
  include_reasoning?: boolean;
  reasoning?: ReasoningConfig;
  thinking?: ThinkingConfig;
  enable_thinking?: boolean;

};

// Message types
type Message =
  | SystemMessage
  | UserMessage
  | AssistantMessage
  | ToolMessage;

type SystemMessage = {
  role: "system";
  content: string | ContentPart[];
  name?: string;
};

type UserMessage = {
  role: "user";
  content: string | ContentPart[];
  name?: string;
};

type AssistantMessage = {
  role: "assistant";
  content?: string | ContentPart[] | null;
  name?: string;
  tool_calls?: ToolCall[];
};

type ToolMessage = {
  role: "tool";
  content: string;
  tool_call_id: string;
  name?: string;
};

// Multimodal content parts
type ContentPart =
  | TextContentPart
  | ImageContentPart
  | FileContentPart
  | AudioContentPart
  | VideoContentPart;

type TextContentPart = {
  type: "text";
  text: string;
};

type ImageContentPart = {
  type: "image_url";
  image_url: {
    url: string;                    // Remote URL or base64 data URI
    detail?: string;
  };
};

type FileContentPart = {
  type: "file";
  file: {
    filename?: string;
    file_data: string;              // Base64 data URI
  };
};

type AudioContentPart = {
  type: "input_audio";
  input_audio: InputAudio;
};

type InputAudio = {
  data: string;                     // Raw base64 without a data URI prefix
  format: string;                   // For example "wav" or "mp3"
};

type VideoContentPart = {
  type: "video_url";
  video_url: {
    url: string;                    // Remote URL or base64 data URI
  };
};

// Tool calling subtypes
type FunctionDescription = {
  name: string;
  description?: string;
  parameters: object;               // JSON Schema object
};

type Tool = {
  type: "function";
  function: FunctionDescription;
};

type ToolChoice =
  | "none"
  | "auto"
  | "required"
  | {
      type: "function";
      function: {
        name: string;
      };
    };

type ToolCall = {
  id: string;
  type: "function";
  function: {
    name: string;
    arguments: string;
  };
};

// Structured output
type ResponseFormat =
  | {
      type: "json_object";
    }
  | {
      type: "json_schema";
      json_schema: {
        name: string;
        strict?: boolean;
        schema: object;
      };
    };

// Upstream plugin configuration
type Plugin = {
  id: string;
  enabled?: boolean;
  [key: string]: unknown;
};

// Reasoning controls vary by model family
type ReasoningConfig = {
  effort?: "low" | "medium" | "high";
  max_tokens?: number;
  exclude?: boolean;
};

type ThinkingConfig = {
  type?: "enabled" | "disabled";
  budget_tokens?: number;
};

Structured Output

Force the model to return valid JSON matching a schema. This is essential for building reliable applications that parse model outputs programmatically.

  • json_object — basic JSON mode; the model returns valid JSON.
  • json_schema — strict schema mode; the model output must match the supplied JSON Schema.

Plugins

BazaarLink forwards the plugins array to the selected upstream route. Plugin availability depends on the chosen model and provider; the web plugin is also enabled by the :online model variant.

JSON
{
  "model": "openai/gpt-4o",
  "messages": [
    { "role": "user", "content": "What happened today?" }
  ],
  "plugins": [
    { "id": "web" }
  ]
}

Optional Headers

Identify your application in request headers to enable usage tracking, dashboard visibility, and fine-grained analytics.

TypeScript
await fetch("https://api.bazaarlink.ai/v1/chat/completions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer <BAZAARLINK_API_KEY>",
    "Content-Type": "application/json",
    "HTTP-Referer": "https://your-app.example",
    "X-Title": "Your App"
  },
  body: JSON.stringify({
    model: "openai/gpt-4o",
    messages: [{ role: "user", content: "Hello!" }]
  })
});

Assistant Prefill

Add a partial assistant message as the last item to request continuation on compatible model routes.

How it works
BazaarLink preserves and forwards the final assistant message. Continuation behavior is implemented by the selected upstream model and provider, so it is not guaranteed on every route.
TypeScript
const response = await client.chat.completions.create({
  model: "anthropic/claude-sonnet-4.6",
  messages: [
    { role: "user", content: "What is the meaning of life?" },
    // Intentional partial response; compatible routes continue from here.
    { role: "assistant", content: "My best answer is" }
  ]
});

Responses

BazaarLink normalizes completion responses across models and providers into one OpenAI-compatible shape.

CompletionsResponse Format

The choices field is always an array. Streaming responses use delta; non-streaming responses use message. Usage and cost details are returned when available.

Response Schema
type Response = {
  id: string;
  object: "chat.completion" | "chat.completion.chunk";
  created: number;
  model: string;
  choices: Array<NonStreamingChoice | StreamingChoice>;
  usage?: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
    cost?: number;
    prompt_tokens_details?: {
      cached_tokens: number;
      cache_write_tokens?: number;
      audio_tokens?: number;
    };
    completion_tokens_details?: {
      reasoning_tokens?: number;
      image_tokens?: number;
    };
  };
};

type NonStreamingChoice = {
  index: number;
  finish_reason: string | null;
  message: {
    role: "assistant";
    content: string | null;
    tool_calls?: ToolCall[];
    reasoning?: string;                                      // thinking text: when the request asked for it, or by default for the models listed in the reasoning docs section
    reasoning_details?: Array<{ type: string; text?: string }>;
  };
};

type StreamingChoice = {
  index: number;
  finish_reason: string | null;
  delta: {
    role?: string;
    content?: string | null;
    tool_calls?: ToolCall[];
    reasoning?: string;
    reasoning_details?: Array<{ type: string; text?: string }>;
  };
};

Finish Reason

finish_reason uses BazaarLink-normalized values such as stop, length, tool_calls, content_filter, and error.

JSON
{
  "finish_reason": "stop"
}

Querying Cost and Stats

Use the opaque BazaarLink request id from the response id or x-request-id with GET /v1/generation; non-streaming responses also return it in x-generation-id. Stats may take a few seconds after completion; retry a 404 briefly.

TypeScript
const generation = await fetch(
  "https://api.bazaarlink.ai/v1/generation?id=<GENERATION_ID>",
  {
    headers: {
      Authorization: "Bearer <BAZAARLINK_API_KEY>"
    }
  }
).then((response) => response.json());

Agent registration

When enabled, agents can create an account through an unpaid or x402-paid registration door.

Registration needs a small proof of work. Fetch GET https://bazaarlink.ai/api/v1/agents/register/challenge, find a solution string whose SHA-256 of challenge:solution has at least the stated number of leading zero bits (a few seconds), and send pow_challenge and pow_solution with the request. A challenge works once, from the IP that fetched it, for 120 seconds. An agent key that is never used is disabled after seven days.

GEThttps://bazaarlink.ai/api/v1/agents/register/challenge
POSThttps://api.bazaarlink.ai/api/v1/agents/register

Unpaid registration accepts name, optional description, and optional referral_code. The 201 response includes the API key, credits, seven-day claim token and expiry, upgrade URL, referral code, free_model auto:free, base URL, and docs link. It uses the existing free-model mechanism; free web search requires a verified email, so it is not available to unpaid agents.

POSThttps://api.bazaarlink.ai/api/v1/agents/register/x402

Paid registration starts with HTTP 402 requirements: scheme exact, network eip155:196, asset USD-T0, and payTo. Resend the request with X-PAYMENT to receive the same registration fields plus payment { txHash, network, amountUsd }. Paid agents follow normal paid-account rules. Existing accounts can top up at /api/x402/topup.

A human can claim an unpaid agent at /claim during the seven-day token lifetime. Registration is limited to one request per IP in 24 hours and configured cloud-provider caps; rejected requests return 429 with Retry-After.

POSThttps://api.bazaarlink.ai/api/x402/topup

Chat Completions

The primary endpoint. Compatible with the OpenAI Chat Completions API.

POST/v1/chat/completions

Request Body

modelrequired
string
Use an id or alias listed by GET /v1/models, such as "claude-sonnet-4.6" or "anthropic/claude-sonnet-4.6".
messagesrequired
Message[]
Array of message objects with role and content
stream
boolean
If true, returns a Server-Sent Events stream. Default: false
temperature
number
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
max_tokens
integer
Maximum number of tokens to generate
max_completion_tokens
integer
Alias for max_tokens (OpenAI o-series compatible). Both are accepted; whichever is provided takes effect
top_p
number
Nucleus sampling probability mass. Default: 1
top_k
integer
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
frequency_penalty
number
Penalize repeated tokens. Range: [-2, 2]. Default: 0
presence_penalty
number
Penalize tokens based on presence. Range: [-2, 2]. Default: 0
repetition_penalty
number
Reduce token repetition from input. Range: (0, 2]. Default: 1
min_p
number
Minimum probability relative to the top token. Range: [0, 1]. Default: 0
top_a
number
Dynamic top-P based on highest-probability token. Range: [0, 1]. Default: 0
seed
integer
Integer seed for deterministic sampling. Not guaranteed for all models
n
integer
Number of completions to generate. Default: 1
user
string
End-user identifier for monitoring and abuse detection. Has no effect on billing. OpenAI-family models only — see note below.
stop
string | string[]
Stop sequences — generation halts when encountered
logit_bias
object
Map token IDs to bias values [-100, 100] added before sampling. OpenAI-family models only — see note below.
logprobs
boolean
Return log probabilities of each output token. OpenAI-family models only — see note below.
top_logprobs
integer
Number of most-likely tokens to return per position (requires logprobs: true). Range: 0–20. OpenAI-family models only — see note below.
tools
Tool[]
List of tools (functions) the model may call
tool_choice
string | object
Controls tool use: "auto", "none", or specific tool
parallel_tool_calls
boolean
Enable parallel function calling when tools are provided. Default: true. OpenAI-family models only — see note below.
response_format
object
Force structured JSON output. See Structured Output section
structured_outputs
boolean
Request strict JSON-schema-conformant output on providers that support it. Passed through as-is
reasoning
object
Provider-specific reasoning/thinking configuration. Passed through as-is
reasoning_effort
string
OpenAI o-series style reasoning effort: "low", "medium", or "high". Passed through as-is
include_reasoning
boolean
true returns the thinking text in message.reasoning (delta.reasoning when streaming); false turns thinking off for the models that think by default (see the reasoning section), so no thinking is billed. Without reasoning_effort, reasoning or thinking, only those models think and return it by default; other models return it only when asked
user, logprobs, top_logprobs, logit_bias, and parallel_tool_calls only reach OpenAI-family models
These five parameters are stripped from the upstream request whenever the resolved model isn't recognized as OpenAI's own — sending them to anthropic/claude-*, google/gemini-*, or any other non-OpenAI target returns 200 with the parameter silently ignored, not an error. If you're setting one of these and the effect isn't showing up, check whether the target model is OpenAI's.

Request Schema (TypeScript)

// Definitions of subtypes are below
type Request = {
  // Required by BazaarLink Chat Completions
  model: string;                    // Use an id or alias from GET /v1/models
  messages: Message[];             // Must contain at least one message

  // Generation
  stream?: boolean;
  temperature?: number;
  max_tokens?: number;             // Positive integer
  max_completion_tokens?: number;  // Alias mapped to max_tokens upstream
  n?: number;
  seed?: number;
  stop?: string | string[];

  // Sampling
  top_p?: number;
  top_k?: number;
  frequency_penalty?: number;
  presence_penalty?: number;
  repetition_penalty?: number;
  min_p?: number;
  top_a?: number;
  // Sampling values are forwarded without range validation; models may clamp or ignore them.

  // Token probabilities
  logit_bias?: Record<number, number>;
  logprobs?: boolean;
  top_logprobs?: number;

  // Stable end-user identifier for abuse prevention
  user?: string;

  // Tool calling
  tools?: Tool[];
  tool_choice?: ToolChoice;
  parallel_tool_calls?: boolean;

  // Structured output
  response_format?: ResponseFormat;
  structured_outputs?: boolean;

  // Plugin configuration — forwarded to upstreams that support plugins.
  plugins?: Plugin[];

  // Multimodal and image-generation options
  modalities?: Array<"text" | "image">;
  image_config?: Record<string, unknown>;
  input_audio?: InputAudio;

  // Provider-specific reasoning controls
  reasoning_effort?: "low" | "medium" | "high";
  include_reasoning?: boolean;
  reasoning?: ReasoningConfig;
  thinking?: ThinkingConfig;
  enable_thinking?: boolean;

};

// Message types
type Message =
  | SystemMessage
  | UserMessage
  | AssistantMessage
  | ToolMessage;

type SystemMessage = {
  role: "system";
  content: string | ContentPart[];
  name?: string;
};

type UserMessage = {
  role: "user";
  content: string | ContentPart[];
  name?: string;
};

type AssistantMessage = {
  role: "assistant";
  content?: string | ContentPart[] | null;
  name?: string;
  tool_calls?: ToolCall[];
};

type ToolMessage = {
  role: "tool";
  content: string;
  tool_call_id: string;
  name?: string;
};

// Multimodal content parts
type ContentPart =
  | TextContentPart
  | ImageContentPart
  | FileContentPart
  | AudioContentPart
  | VideoContentPart;

type TextContentPart = {
  type: "text";
  text: string;
};

type ImageContentPart = {
  type: "image_url";
  image_url: {
    url: string;                    // Remote URL or base64 data URI
    detail?: string;
  };
};

type FileContentPart = {
  type: "file";
  file: {
    filename?: string;
    file_data: string;              // Base64 data URI
  };
};

type AudioContentPart = {
  type: "input_audio";
  input_audio: InputAudio;
};

type InputAudio = {
  data: string;                     // Raw base64 without a data URI prefix
  format: string;                   // For example "wav" or "mp3"
};

type VideoContentPart = {
  type: "video_url";
  video_url: {
    url: string;                    // Remote URL or base64 data URI
  };
};

// Tool calling subtypes
type FunctionDescription = {
  name: string;
  description?: string;
  parameters: object;               // JSON Schema object
};

type Tool = {
  type: "function";
  function: FunctionDescription;
};

type ToolChoice =
  | "none"
  | "auto"
  | "required"
  | {
      type: "function";
      function: {
        name: string;
      };
    };

type ToolCall = {
  id: string;
  type: "function";
  function: {
    name: string;
    arguments: string;
  };
};

// Structured output
type ResponseFormat =
  | {
      type: "json_object";
    }
  | {
      type: "json_schema";
      json_schema: {
        name: string;
        strict?: boolean;
        schema: object;
      };
    };

// Upstream plugin configuration
type Plugin = {
  id: string;
  enabled?: boolean;
  [key: string]: unknown;
};

// Reasoning controls vary by model family
type ReasoningConfig = {
  effort?: "low" | "medium" | "high";
  max_tokens?: number;
  exclude?: boolean;
};

type ThinkingConfig = {
  type?: "enabled" | "disabled";
  budget_tokens?: number;
};

Example Request

curl https://api.bazaarlink.ai/v1/chat/completions \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Explain quantum computing in one paragraph."}
    ],
    "temperature": 0.7,
    "max_tokens": 512
  }'

Response

{
  "id": "85e6f35c71a31f19-SJC",
  "object": "chat.completion",
  "created": 1740000000,
  "model": "openai/gpt-4o",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Quantum computing leverages quantum mechanics..."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 28,
    "completion_tokens": 74,
    "total_tokens": 102,
    "cost": 0.0006480,
    "prompt_tokens_details": {
      "cached_tokens": 0
    },
    "completion_tokens_details": {
      "reasoning_tokens": 0
    }
  }
}

Response Schema (TypeScript)

BazaarLink returns a standardized response and removes upstream-specific fields. usage.cost is the USD amount charged to the customer for this request.

For requests longer than 100 seconds, we recommend using stream:true. For non-streaming requests, after about 12 seconds (the default, adjustable through deployment settings), a 200 response is sent first and keepalive whitespace starts; if an error occurs afterward, it is returned as 200 + error JSON. Keepalive is whitespace before the JSON body, which standard JSON parsers ignore.

type Response = {
  id: string;
  object: "chat.completion" | "chat.completion.chunk";
  created: number;                 // Unix timestamp
  model: string;
  choices: (NonStreamingChoice | StreamingChoice)[];
  usage?: ResponseUsage;
};

type NonStreamingChoice = {
  index: number;
  finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | null;
  message: {
    role: "assistant";
    content: string | null;
    tool_calls?: ToolCall[];
    reasoning?: string;                                      // thinking text: when the request asked for it, or by default for the models listed in the reasoning docs section
    reasoning_details?: Array<{ type: string; text?: string }>;
  };
};

type StreamingChoice = {
  index: number;
  finish_reason: string | null;
  delta: {
    role?: string;
    content?: string | null;
    tool_calls?: ToolCall[];
    reasoning?: string;
    reasoning_details?: Array<{ type: string; text?: string }>;
  };
};

type ResponseUsage = {
  prompt_tokens: number;
  completion_tokens: number;
  total_tokens: number;
  cost: number;                      // USD amount charged to the customer for this request
  prompt_tokens_details?: {
    cached_tokens: number;           // Tokens served from prompt cache (reduced cost)
    cache_write_tokens?: number;     // Tokens written to cache in this request
    audio_tokens?: number;
  };
  completion_tokens_details?: {
    reasoning_tokens?: number;       // Thinking/reasoning tokens (e.g. o3, Qwen3, DeepSeek R1)
    image_tokens?: number;
  };
};

type ToolCall = {
  id: string;
  type: "function";
  function: { name: string; arguments: string };
};

Image Generation

BazaarLink offers two image-generation paths: (A) /v1/chat/completions with modalities: ["image"] — the native path, supports SSE streaming and mixed text+image output; recommended for new integrations. (B) /v1/images/generations — OpenAI DALL·E-compatible request shape, but responses are SSE event streams (required for slow models to dodge the 100 s upstream timeout). Both paths emit the same SSE event protocol — endpoint choice is purely a request-shape preference. Image EDITING (modifying an existing image) uses POST /v1/images/edits — OpenAI images.edit compatible, multipart/form-data with your source image(s). Edit-capable models have modality text+image->image (e.g. qwen/qwen-image-3); pure-generation models are text->image — check each model's modality in GET /v1/models.

Response format

/v1/images/generations returns OpenAI-compatible sync JSON by default (since 2026-07-25) — client.images.generate() works with no wrapper. Pass stream: true to opt into the SSE event stream instead, which delivers progress for models with long generation times.

Non-streaming requests that run longer than 20 seconds are answered with HTTP 200 first (whitespace keep-alives follow), so a failure that arrives later is returned as 200 with an error body: check for a top-level `error` object (we also add `"object": "error"` and `"http_status"`). If your client's read timeout is long (the OpenAI SDKs default to 10 minutes), send `X-BazaarLink-Sync-Wait: 90` (whole seconds, 1–90, default 20) to receive the real status code for failures up to that time; with the Python SDK use `default_headers={"X-BazaarLink-Sync-Wait": "90"}`, with the Node SDK `defaultHeaders: { "X-BazaarLink-Sync-Wait": "90" }`. On Windows, send JSON from a UTF-8 file with `curl --data-binary @body.json`: text typed after `-d` in the console is not UTF-8 and is rejected immediately with 400 `invalid_utf8`.

A. /v1/chat/completions (native, recommended)

POST/v1/chat/completions

The canonical streaming path. Recommended for any new integration.

curl -N https://api.bazaarlink.ai/v1/chat/completions \
  -H "Authorization: Bearer $BL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4-image-2",
    "messages": [{"role":"user","content":"a red cat on a sofa"}],
    "modalities": ["image","text"],
    "stream": true
  }'

Image-to-image: include an image_url part in the content array. Supports data URIs or https image URLs (http:// is rejected), up to 8 images, ~10MB per data URI. The message carrying images must also include a text part (the edit instruction). Some models additionally support image_config (e.g. {"strength": 0.7}, 0–1 — lower stays closer to the source image), passed through to the upstream as-is.

curl -N https://api.bazaarlink.ai/v1/chat/completions \
  -H "Authorization: Bearer $BL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4-image-2",
    "messages": [{"role":"user","content":[
      {"type":"image_url","image_url":{"url":"data:image/png;base64,..."}},
      {"type":"text","text":"change the background to a night city"}
    ]}],
    "modalities": ["image"],
    "stream": true
  }'

Image Edits (OpenAI-compatible)

POST/v1/images/edits

The OpenAI SDK's client.images.edit() works directly (multipart upload, sync JSON response returning data: [{ url }]). Same limits as image-to-image: up to 8 images, 10MB each; mask and response_format=b64_json are not supported yet.

curl https://api.bazaarlink.ai/v1/images/edits \
  -H "Authorization: Bearer $BL_API_KEY" \
  -F model="openai/gpt-5.4-image-2" \
  -F image=@cat.png \
  -F prompt="change the background to a night city"

B. /v1/images/generations (DALL·E-compatible)

POST/v1/images/generations

OpenAI DALL-E request shape. Sync JSON ({ created, data: [{ url }] }) is the default and works with client.images.generate() out of the box; pass stream: true to get the SSE event stream documented below instead.

curl https://api.bazaarlink.ai/v1/images/generations \
  -H "Authorization: Bearer $BL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-image-1",
    "prompt": "a red cat on a sofa",
    "size": "1024x1024"
  }'
modelrequired
string
Model id, e.g. google/gemini-2.5-flash-image
promptrequired
string
Text prompt
size
string
Output size (auto-mapped)
n
integer
Number of images (default 1)
# Streaming variant — progressive delivery for long generations
curl -N https://api.bazaarlink.ai/v1/images/generations \
  -H "Authorization: Bearer $BL_API_KEY" \
  -H "Accept: text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5.4-image-2","prompt":"a red cat on a sofa","stream":true}'

SSE event protocol

Ignore SSE comment lines and unknown or ping events; handle the documented image, usage, and done events.

: keep-alive

event: image
data: {"index": 0, "url": "https://api.bazaarlink.ai/v1/images/proxy/<token>"}

event: image-cached     # bazaarlink Redis-backed proxy URL (1 hr TTL)
data: {"index": 0, "url": "https://api.bazaarlink.ai/v1/images/proxy/<token>"}

event: usage            # final cost / token count
data: {"promptTokens": 12, "completionTokens": 7080, "cost": 0.226, "durationMs": 163400, "imageCount": 1}

event: done
data: {}

Supported image models

Model IDModalityi2i (edits)

Video Generation

Asynchronous three-step flow (submit → poll → content). Video generation takes 30 s–5 min, which doesn't fit the synchronous request/response shape of chat-completions — so BazaarLink exposes it as a dedicated /v1/videos endpoint using a job-id pattern: submit returns a vjob_* ID → poll status → fetch bytes on completion. Calling a video model via /chat/completions or /images/generations returns 400 (code: wrong_endpoint_for_video). Billing settles against real usage.cost when the job reaches completed.

Video tasks

One endpoint covers several tasks. Which task runs is decided by which fields you send — a single model can do image-to-video, keyframes, and continuation. Not every model supports every task; an unsupported request returns 400.

Text-to-video
prompt
Generate from a text prompt alone — no input media.
Image-to-video
frame_images: [ first frame ]
Your image IS the picture: it becomes the first frame and gets animated, staying faithful to the original. E.g. a photo of a cat → that same cat turns its head in the same scene.
Keyframes (first + last)
frame_images: [ first, last ]
Give a start and an end image; the model interpolates the motion in between.
Continuation
input_video
Extend an existing clip. The requested duration must be greater than the source video's length.
Reference-to-video
input_references: [ images ]
Your images are REFERENCES, not frames: the model keeps the subject/style and generates a brand-new scene. E.g. a cat photo + "dancing in a forest" → a new video where the cat's look is preserved but the scene and motion are new (1–9 references). Difference from image-to-video: i2v stays faithful to the exact picture; r2v re-casts the subject into new footage.
Video editing
input_video + prompt
Edit an existing video — change the scene, style, or motion. Billed on input-video seconds + output seconds.

1. Submit job (returns vjob_xxx immediately)

POST/v1/videos
modelrequired
string
Model id, e.g. bytedance/seedance-2.0
promptrequired
string
Text prompt
duration
integer
Duration in seconds (model-dependent)
resolution
string
Resolution — 480p / 720p / 1080p etc.
generate_audio
boolean
Generate audio track (true/false)
frame_images
array
First/last frame images (image-to-video, keyframes). Objects { type: "image_url", image_url: { url }, frame_type: "first_frame" | "last_frame" } or plain URL strings.
input_references
array
Reference images for reference-to-video — subject/style guidance, not exact frames.
input_video
string|object
Source video URL for continuation and video editing. Must be publicly fetchable by the upstream.
aspect_ratio
string
Aspect ratio, e.g. 16:9 or 9:16. Ignored when an input image dictates the ratio.
watermark
boolean
Add a watermark. Default false.
callback_url
string
Webhook URL called once the job reaches a terminal state — HTTPS only, SSRF-checked. No signature in v1; treat it as a hint and confirm via GET /videos/{id}.
curl https://api.bazaarlink.ai/v1/videos \
  -H "Authorization: Bearer $BL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bytedance/seedance-2.0",
    "prompt": "a bird flying over mountains",
    "duration": 3,
    "resolution": "720p",
    "generate_audio": false
  }'
# → 202 { "id": "vjob_xxx", "status": "pending" }
Note
Credits are reserved at submit using a worst-case estimate. On completion, usage.cost is the USD amount charged to the customer for that request.

2. Poll status

GET/v1/videos/{id}
curl -H "Authorization: Bearer $BL_API_KEY" \
  https://api.bazaarlink.ai/v1/videos/vjob_xxx
Note
Each GET must be ≥ 8 s apart to actually hit the upstream (avoids rate limits).
Note
When status=failed, the full reserve amount is refunded to the user.

3. Fetch video content (MP4)

GET/v1/videos/{id}/content
curl -H "Authorization: Bearer $BL_API_KEY" \
  -o output.mp4 \
  https://api.bazaarlink.ai/v1/videos/vjob_xxx/content

Good to know

  • Allowed resolutions differ per model — an unsupported value returns 400 listing the supported ones.
  • Input images/videos must be reachable from a public URL. Hotlink-protected hosts (e.g. some wikis) fail.
  • Input media passes upstream content moderation and can occasionally be rejected.
  • For continuation, the requested duration must exceed the source video length.
  • Output aspect ratio follows the input image — a square image yields a square video.
  • Video editing is billed on the input video's seconds plus the generated output's seconds.
  • input_video (editing / continuation) must be a PUBLIC URL. Videos you generate here are served behind your API key, so the upstream can't fetch them — host your source video on a publicly-reachable URL.
  • Webhook payloads are unsigned — verify status/amount via GET /videos/{id} before acting, and note unsigned_urls there are absolute (unlike the relative paths in the poll response).

Supported video models

Model IDModalityTasks
x-ai/grok-imagine-video-1.5-litetext+image->videot2v, i2v
bytedance/seedance-2.5text+image+audio+video->video—
google/veo-3.1text+image->videot2v, i2v
bytedance/seedance-1-5-protext+image->videot2v, i2v
bytedance/seedance-2.0text+image+audio+video->videot2v, i2v
bytedance/seedance-2.0-fasttext+image+audio+video->videot2v, i2v
minimax/hailuo-3-maxtext+image->videot2v, i2v

Speech synthesis

Convert text to audio using an id from /v1/models?output_modalities=speech. Billing uses the model page price per million characters. The complete audio is buffered before returning binary data; incremental streaming is not supported. Routing control fields provider, transforms, route, service_tier, and preset are ignored.

POST/v1/audio/speech
curl https://api.bazaarlink.ai/v1/audio/speech \
  -H "$BAZAARLINK_AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{"model":"<speech-model-id>","input":"Hello","voice":"alloy","response_format":"mp3"}' \
  --output speech.mp3

# Voice settings (elevenlabs/* models; the X-Voice-Settings response header says applied or ignored)
curl -D - https://api.bazaarlink.ai/v1/audio/speech \
  -H "$BAZAARLINK_AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{"model":"elevenlabs/eleven-v4","input":"Hello","voice":"george","stability":0.4,"similarity_boost":0.8,"style":0.2,"use_speaker_boost":true,"language_code":"en","audio_effects":{"distance":0.3}}' \
  --output speech.mp3

Audio transcription

Use multipart/form-data model and file, or JSON model and input_audio:{data,format} (raw base64). Formats: wav, mp3, mp4, m4a, ogg, flac, webm, aac. Decoded audio: maximum 25 MiB; request body: 50 MiB for base64. audio.transcription_max_seconds_by_model sets the per-model limit, falling back to audio.transcription_max_seconds and then 60 seconds. Longer audio returns 413 audio_too_long before processing and is not charged. response_format: json or verbose_json only; timestamp_granularities word or segment requires verbose_json. stream is unsupported (400 unsupported_parameter); oversized audio: 413 audio_too_large. provider is ignored. Billing uses database model prices per usage.seconds or input/output token; per-minute price = 60 × per-second price. usage.cost is the customer charge in USD.

POST/v1/audio/transcriptions
curl https://api.bazaarlink.ai/v1/audio/transcriptions \
  -H "$BAZAARLINK_AUTH_HEADER" \
  -F model="<transcription-model-id>" \
  -F file=@audio.wav \
  -F response_format=json

curl https://api.bazaarlink.ai/v1/audio/transcriptions \
  -H "$BAZAARLINK_AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{"model":"<transcription-model-id>","input_audio":{"data":"<raw-base64-audio>","format":"wav"},"response_format":"verbose_json","timestamp_granularities":["word","segment"]}'

Document reranking

Send query and documents to receive ranked index and relevance_score values. Billing unit: search, at the model page price multiplied by usage.search_units rather than document count. Streaming (stream) is not supported.

POST/v1/rerank
curl https://api.bazaarlink.ai/v1/rerank \
  -H "$BAZAARLINK_AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{"model":"your-rerank-model-id","query":"capital of France","documents":["Paris is the capital of France.","Berlin is in Germany."],"top_n":1}'
POST/v1/chat/completions

Use audio or input_audio with /v1/chat/completions and an audio-capable model. Configured input/output token rates apply uniformly to inclusive reported text and audio tokens.

curl https://api.bazaarlink.ai/v1/chat/completions \
  -H "$BAZAARLINK_AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{"model":"<audio-chat-model-id>","messages":[{"role":"user","content":"Say hi."}],"stream":true,"modalities":["text","audio"],"audio":{"voice":"alloy","format":"pcm16"},"max_tokens":32}'

Text to speech (TTS) guide

POST /v1/audio/speech is OpenAI-compatible: send JSON, get the audio file back as binary. Speech models include the ElevenLabs family (elevenlabs/eleven-v4, eleven-v4-turbo, eleven-v3, eleven-v3-conversational, eleven-multilingual-v2, eleven-flash-v2.5, eleven-flash-v2, eleven-turbo-v2.5, eleven-turbo-v2). The live list is GET /v1/models?output_modalities=speech. GET /v1/models returns text models only by default, so add ?output_modalities=speech (or all) to list them.

POST/v1/audio/speech

Parameters

ParameterTypeRequiredDescription
modelstringYesSpeech model id, e.g. elevenlabs/eleven-flash-v2.5.
inputstringYesText to synthesize: up to 4,096 characters per request (10,000 for elevenlabs/eleven-v4 and eleven-v4-turbo). Split longer text into several requests.
voicestringYesVoice to use. Its format depends on the model — see Voice rules below.
response_formatstringNomp3 (default) or pcm. opus, aac, flac and wav are accepted by the API but currently rejected by ElevenLabs models with 400.
speednumberNoSpeaking speed from 0.25 to 4.0. Not every model applies it.
instructionsstringNoStyle instructions for the voice. Not every model applies it.
stabilitynumberNoVoice stability, 0–1 (default 0.5). Lower is more expressive, higher is more even. Takes effect on elevenlabs/* models when the request is served by our direct voice route; the X-Voice-Settings response header says applied or ignored.
similarity_boostnumberNoSimilarity to the original voice, 0–1 (default 0.75). Not used by Eleven v3.
stylenumberNoStyle exaggeration, 0–1 (default 0). Above 0 adds latency. Not used by Eleven v4.
use_speaker_boostbooleanNoBoosts similarity to the original speaker (default true); adds a little latency.
clone_strengthnumberNoClone strength, 0–1 (0 is recommended for most voices). Only for your own professional clone voices (voice_…) trained for Eleven v4.
language_codestringNoTwo-letter lowercase language code (ISO 639-1) such as ar, ja or en, to fix the spoken language. Not used by multilingual-v2 models.
audio_effectsobjectNoOptional audio-effects object: filter_preset_id (e.g. phone, old_radio), environment_id (reverb space, e.g. small_room, hall), background_noise_id (e.g. cafe), distance (0–1), send_level (reverb amount, 0–1) and pan (−1…1). IDs use lowercase letters, digits and underscores; no extra charge. Reverb or a non-zero pan produces stereo audio and cannot be combined with pcm output.

The seven voice settings above are all optional; leaving them out changes nothing. A value out of range returns 400 and is neither sent nor billed. The response header X-Voice-Settings tells you whether your settings were used: applied = used; ignored = a fallback route answered, so your settings were not used (the audio is still produced); no header = you sent no voice settings. For example, stability 0 reads with more expression, stability 1 more evenly.

Voice rules

ElevenLabs models (elevenlabs/…) take one of these voice names (case-insensitive): george, sarah, adam, alice, bella, bill, brian, callum, charlie, chris, daniel, eric, harry, jessica, laura, liam, lily, matilda, river, roger, will. voice may be omitted for ElevenLabs models (default george). Any other value returns 400 invalid_voice. Your own custom voice (voice_…) is described in the Custom voices section below.

OpenAI-style voice names such as alloy, echo or nova only work with models that list them. Check the model page for each model's voices.

Custom voices (cloning) are being opened in stages: see the Custom voices section below (POST /v1/voices). Until they are enabled for your account, use one of the 21 preset names above or a public voice ID.

Sending alloy to an ElevenLabs model fails
An ElevenLabs model given a voice that is neither a voice ID nor one of the supported names (for example alloy or Rachel) returns 400 invalid_voice and the message lists the accepted names. Failed requests are not charged.

Built-in voices

Set voice to one of the names below (lowercase). Each voice has a short preview clip of our own, and GET /api/v1/audio/voices returns the full list (gender, age group, accent, style and preview URLs).

?lang= (en, zh-TW, zh-CN; default follows Accept-Language) only chooses the language of description. The preview URLs do not change with it: preview_urls.en (same as preview_url) is the English clip and preview_urls.zh (same as preview_url_zh) is the Chinese clip.

VoiceDescriptionPreview
georgeMale, middle-aged, British accent. Mature and steady, suited to narration and storytelling.
sarahFemale, young adult, American accent. Professional and clear, suited to broadcast-style reading.
adamMale, middle-aged, American accent. Everyday and approachable, suited to short social videos.
aliceFemale, middle-aged, British accent. Professional and informative, suited to explainers and teaching.
bellaFemale, middle-aged, American accent. Professional and informative, suited to explainers and teaching.
billMale, senior, American accent. Crisp and direct, suited to advertising voice-overs.
brianMale, middle-aged, American accent. Polished and refined, suited to social and brand content.
callumMale, middle-aged, American accent. Expressive, suited to characters and animation.
charlieMale, young adult, Australian accent. Energetic and lively, suited to conversation.
chrisMale, middle-aged, American accent. Relaxed and casual, suited to conversation.
danielMale, middle-aged, British accent. Formal and measured, suited to news-style and educational reading.
ericMale, middle-aged, American accent. Polished and warm, suited to conversation.
harryMale, young adult, American accent. Rough and gritty, suited to character voices.
jessicaFemale, young adult, American accent. Bright and cute, suited to conversation.
lauraFemale, young adult, American accent. Playful with attitude, suited to social content.
liamMale, young adult, American accent. Confident and upbeat, suited to social content.
lilyFemale, middle-aged, British accent. Confident and clear, suited to explainers and teaching.
matildaFemale, middle-aged, American accent. Upbeat and friendly, suited to explainers and teaching.
riverNeutral voice, middle-aged, American accent. Calm and even, suited to conversation.
rogerMale, middle-aged, American accent. Polished and confident, suited to conversation.
willMale, young adult, American accent. Laid-back and easygoing, suited to conversation.

Emotion and action tags (audio tags)

Write tags in square brackets directly inside input, for example [laughs], [whispers], [excited], [sighs], [crying], [curious] or [softly]. They are sent to the model exactly as typed and shape how the following words are performed. Keep the tags in English even when the text is in another language.

curl https://api.bazaarlink.ai/v1/audio/speech \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"elevenlabs/eleven-v3","input":"[laughs] That was actually funny. [whispers] Do not tell anyone.","voice":"george","response_format":"mp3"}' \
  --output audio-tags.mp3

Supported by elevenlabs/eleven-v3 and the elevenlabs/eleven-v4 family. Other speech models do not interpret tags and may read the brackets aloud, so try a short sentence first.

Tags count as input characters and are billed like the rest of the text.

Output formats

response_format defaults to mp3 (Content-Type audio/mpeg) when omitted; pcm (audio/pcm, raw 16-bit PCM) also works. The API accepts opus, aac, flac and wav, but ElevenLabs models currently reject them with 400. Save the file using the Content-Type response header.

Pricing

Speech is billed per character of input. The per-character price is on each model page under /models and in GET /v1/models as pricing.character (USD per character). Model pages show it per 1M characters.

Examples

curl https://api.bazaarlink.ai/v1/audio/speech \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"elevenlabs/eleven-flash-v2.5","input":"Hello from BazaarLink.","voice":"george","response_format":"mp3"}' \
  --output speech.mp3

Common errors

StatusCause and fix
400Invalid request: model, input or voice missing; input over 4,096 characters; speed outside 0.25–4.0; unsupported response_format; Unknown voice (wrong voice for this model); unknown_model (check the id on /models).
401Missing or invalid API key. Send Authorization: Bearer with your BazaarLink key.
402Insufficient credits. Top up and retry.
403A management key cannot call models, or the account is suspended. Use a standard API key.
429Rate limit reached. Wait for the Retry-After header and retry.
502The model service is temporarily unavailable or the audio transfer was interrupted. Retry; failed requests are not charged.

Full parameter reference: Speech synthesis

Speech to text (transcription) guide

POST /v1/audio/transcriptions is OpenAI-compatible: upload audio as multipart/form-data (or send base64 input_audio as JSON) and get text back. Transcription models include elevenlabs/scribe-v2 and elevenlabs/scribe-v2-medical. The live list is GET /v1/models?output_modalities=transcription. GET /v1/models returns text models only by default, so add ?output_modalities=transcription (or all) to list them.

POST/v1/audio/transcriptions

Parameters

ParameterTypeRequiredDescription
modelstringYesTranscription model id, e.g. elevenlabs/scribe-v2.
filefileYesAudio file (multipart). Supported: wav, mp3, mp4, m4a, ogg, flac, webm, aac. Up to 25 MiB. JSON clients can send input_audio: {data: base64, format} instead.
response_formatstringNojson (default) returns {text}; verbose_json adds language, duration and segments/words.
languagestringNoOptional language hint such as en or zh. Leave empty to auto-detect.
promptstringNoOptional context text to help with names and terms. Not every model applies it.
timestamp_granularities[]string[]Noword and/or segment. Only applied with response_format=verbose_json; ignored otherwise. Word-level timestamps are not returned yet; segments are.

Output formats

response_format accepts json or verbose_json only (text, srt and vtt are not supported). stream is not supported. The per-model audio limit is 60 seconds by default; longer audio returns 413 audio_too_long before processing and is not charged. Split longer recordings into segments.

Pricing

Transcription is billed per second of audio. The per-second price is on each model page under /models and in GET /v1/models as pricing.second (USD per second); the per-minute price is 60 × that value. The response usage shows the billed seconds and usage.cost the amount charged in USD.

Examples

curl https://api.bazaarlink.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -F model="elevenlabs/scribe-v2" \
  -F file=@audio.mp3 \
  -F response_format=verbose_json \
  -F "timestamp_granularities[]=word"

Common errors

StatusCause and fix
400Invalid request: model or file missing, empty audio, response_format other than json/verbose_json, stream=true sent (unsupported_parameter), or a timestamp_granularities value other than word/segment.
401Missing or invalid API key.
402Insufficient credits. Top up and retry.
413audio_too_large: decoded audio is over 25 MiB. audio_too_long: audio exceeds the per-model limit (60 seconds by default), is rejected before processing, and is not charged. Compress or split the audio.
429Rate limit reached. Wait for the Retry-After header and retry.
502The model service is temporarily unavailable or the audio transfer was interrupted. Retry; failed requests are not charged.

Full parameter reference: Audio transcription

Custom voices (voice cloning)

Custom voices (voice cloning)
Available by application only. Custom voices are opened account by account: contact customer support to apply. Until your account is enabled, every /v1/voices request answers 403 voice_cloning_not_enabled (and 404 not_found while the feature is switched off for everyone). The limits and the price on this page are defaults and may change.

Upload a short recording of a voice you are allowed to use and get back a voice id of ours (voice_…). Pass that id as the voice of POST /v1/audio/speech. A voice can be used only by the person who created it.

Endpoints

POST/v1/voices

Create a voice (multipart/form-data)

GET/v1/voices

List your voices (limit 1–100, after = a voice id for the next page)

GET/v1/voices/{id}

Get one of your voices

DELETE/v1/voices/{id}

Delete a voice (takes effect at once)

POST/v1/voices/report

Report a voice (no API key needed)

Create a voice: fields

FieldTypeRequiredDescription
namestringYesVoice name, 1–60 characters.
filesfile[]Yes1 to 5 audio recordings of the voice (mp3, wav, m4a, ogg, flac, webm).
consentbooleanYesMust be true: you confirm you have the right to clone and use this voice (see Consent).
consent_statement_versionstringYesThe consent statement version you confirmed. Currently "v1".
descriptionstringNoOptional, up to 200 characters.
remove_background_noisebooleanNoOptional boolean, default false. Cleans background noise in the samples before cloning.

The request is synchronous: a 201 means the voice is ready to use. Send an Idempotency-Key header (any unique string) and reuse it when you retry, so a retry never creates, or charges for, a second voice.

Response

201 with id (voice_…), object ("voice"), name, description, status ("ready"), created_at (Unix seconds) and usage.cost_usd (the creation fee charged). Nothing else about how the voice is made leaves our systems.

GET /v1/voices returns only your own voices: { "object": "list", "data": [ … ], "has_more": false }. Another person's voice id answers 404 voice_not_found, exactly like an id that does not exist.

GET /v1/voices/{id} returns one voice (same fields as above, without usage).

DELETE /v1/voices/{id} returns { "id": "voice_…", "object": "voice", "deleted": true } and takes effect at once: speech requests that use the voice fail immediately. Deleting is free; the creation fee is not refunded.

Limits (defaults)

  • Up to 3 voices per person.
  • 1 to 5 audio files per voice, each up to 10 MB and 25 MB in total; formats: mp3, wav, m4a, ogg, flac, webm. About 30 seconds to 5 minutes of clean speech in total works best.
  • Up to 3 creations per day (UTC day).
  • Your account needs a verified e-mail address and a balance of at least US$5.
  • Names and descriptions that imitate public figures, or sound like official or staff voices, are refused (invalid_voice_name).
  • Recordings are used only to create the voice and are not stored by us. We keep the voice name, its owner, the consent record and timestamps.

Price

Creating a voice costs a one-time fee (default US$1.00); the amount charged is returned in usage.cost_usd, and you are charged only when the voice was created successfully. Using a custom voice in speech costs the same per character as the model's other voices. Deleting is free.

Consent

Every creation must send consent=true and consent_statement_version="v1". If the statement is updated, creation answers 400 consent_version_mismatch and you confirm again with the new version.

Draft of the statement you confirm (the final wording is under review and may change):

I confirm that I own the voice in these recordings or have the written permission of the person whose voice it is, that this person is an adult and has agreed to have their voice cloned and synthesized, and that I will not use the voice to impersonate a real person, to deceive, to harass, or for political persuasion, fraud or any unlawful purpose. I understand the voice can be removed and my account suspended if I break these rules.

Use a custom voice

Pass the voice id as the voice of POST /v1/audio/speech (voice=voice_…) together with a speech model of the elevenlabs/* family. The input limit, formats and per-character price are the model's own. A voice id that does not exist, was deleted or blocked, or belongs to someone else answers 400 invalid_voice, and a custom voice cannot be used with other models.

Examples

curl https://api.bazaarlink.ai/v1/voices \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F name="My narrator" \
  -F description="Warm and calm" \
  -F consent=true \
  -F consent_statement_version=v1 \
  -F files=@sample-1.mp3 \
  -F files=@sample-2.wav

curl "https://api.bazaarlink.ai/v1/voices?limit=20" \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY"

curl -X DELETE https://api.bazaarlink.ai/v1/voices/voice_0123456789abcdef01234567 \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY"

curl https://api.bazaarlink.ai/v1/audio/speech \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"elevenlabs/eleven-flash-v2.5","input":"Hello, this is my own voice.","voice":"voice_0123456789abcdef01234567","response_format":"mp3"}' \
  --output my-voice.mp3

Report a voice

If a voice imitates you or someone else without permission, report it: POST /v1/voices/report (no API key needed) with a JSON body of voice_id (voice_…) or sample_url, reason (impersonation, fraud, harassment, minor, political or other), and optionally details and contact (an e-mail address). It always answers 202. We review reports and remove voices that break the rules; accounts can be suspended.

curl https://api.bazaarlink.ai/v1/voices/report \
  -H "Content-Type: application/json" \
  -d '{"voice_id":"voice_0123456789abcdef01234567","reason":"impersonation","details":"...","contact":"me@example.com"}'

Responsible use

Do not clone the voices of public figures, celebrities, politicians or anyone who has not agreed. Do not use a cloned voice to make people believe they are talking to the real person, to deceive, harass, or for political persuasion or fraud.

Errors

StatusCodeMeaning
400bad_requestMalformed request: not multipart/form-data, no audio file, description too long, or limit outside 1–100.
400consent_requiredconsent=true is missing.
400consent_version_mismatchThe consent statement version is out of date; confirm again with the current version.
400invalid_voice_nameThe name is empty, longer than 60 characters, or not allowed.
400too_many_filesMore than 5 audio files.
401invalid_api_keyMissing or invalid API key.
402insufficient_balanceBalance too low (the creation fee or the minimum balance).
403email_unverifiedVerify your e-mail address first.
403voice_cloning_not_enabledYour account is not enabled for custom voices yet; contact customer support to apply.
403voice_cloning_disabledCustom voices are not enabled for this service right now.
404voice_not_foundNo such voice (or it is not yours).
404not_foundNot found, including every /v1/voices request while the feature is not enabled for your account.
405method_not_allowedThat method is not available on this path.
409duplicate_requestThe same Idempotency-Key is still being processed; wait and retry.
413file_too_largeA file is over 10 MB or the files together are over 25 MB.
415unsupported_file_typeAudio type not supported; use mp3, wav, m4a, ogg, flac, webm.
422voice_rejectedThe voice provider's check did not accept the sample (for example a well-known or built-in voice, or a known AI-synthesised voice). The voice is not kept and you were not charged. Use a clear recording of your own voice, or of a voice you have permission to use.
429voice_create_rate_limitedDaily creation limit reached; try again tomorrow (UTC).
429voice_limit_reachedYou have reached the maximum number of custom voices.
502upstream_failedThe voice could not be created; you were not charged. Retry with a new Idempotency-Key.
503voice_capacity_fullCustom voice capacity is full for now; you were not charged. Try again later.
504upstream_timeoutThe voice service did not answer in time; you were not charged. Retry with the same Idempotency-Key.

Data API

Coming soon — Public social and commerce data through three BazaarLink endpoints. This service is off by default. Requests will return service_paused until it is enabled.

Submit a social data request

POST/v1/data/social

Request parameters

CodeTypeRequiredMeaning
platformstringYesPlatform id from the catalog, such as twitter or instagram.
actionstringYesAction supported by that platform; see the catalog for the available pairs.
paramsobjectNoAction-specific inputs. Use required_params and optional_params in the catalog.
modesync | asyncNoOptional. async returns a job to poll; async-only actions return a job even when omitted.

Response format

HTTP 200 · data.social · id · object · platform · action · count · results · usage.cost

{
  "id": "req_example",
  "object": "data.social",
  "platform": "twitter",
  "action": "search",
  "count": 1,
  "results": [
    {
      "id": "post_example",
      "url": "https://x.com/someone/status/post_example",
      "text": "Example post",
      "like_count": 120,
      "reply_count": 8,
      "retweet_count": 14,
      "view_count": 5400,
      "posted_at": "2026-10-07T00:48:38+00:00",
      "author_username": "someone",
      "author_name": "Someone",
      "platform": "twitter"
    }
  ],
  "usage": {
    "cost": 0.0048
  }
}

Async jobs

An async-only action or mode async returns HTTP 202 with an opaque job id. Poll GET /v1/data/jobs/{id}: HTTP 202 means pending, and HTTP 200 returns the completed data.social response. A job expires after 15 minutes (HTTP 410 job_expired). Unknown jobs and jobs owned by another account return HTTP 404 not_found. A completed job is charged once on the first successful fetch.

{
  "platform": "instagram",
  "action": "discover",
  "params": {
    "keyword": "coffee"
  },
  "mode": "async"
}

HTTP 202

{
  "id": "job_example",
  "object": "data.job",
  "status": "pending",
  "platform": "instagram",
  "action": "discover"
}

Poll an async job

GET/v1/data/jobs/{id}

HTTP 202 pending → HTTP 200 data.social · HTTP 410 job_expired · HTTP 404 not_found

{
  "id": "job_example",
  "object": "data.social",
  "platform": "instagram",
  "action": "discover",
  "count": 1,
  "results": [
    {
      "id": "post_example",
      "url": "https://www.instagram.com/p/example/",
      "text": "Example result",
      "platform": "instagram"
    }
  ],
  "usage": {
    "cost": 0.0096
  }
}

List available actions and prices

GET/v1/data/catalog

object · product · platforms · actions · required_params · optional_params · max_limit · async · price_usd

{
  "object": "data.catalog",
  "product": "BazaarLink Data API",
  "platforms": [
    {
      "platform": "twitter",
      "actions": [
        {
          "action": "search",
          "label": "Keyword search",
          "required_params": [
            "keyword"
          ],
          "optional_params": [
            "sort"
          ],
          "max_limit": 200,
          "async": false,
          "price_usd": 0.0048
        }
      ]
    },
    {
      "platform": "instagram",
      "actions": [
        {
          "action": "discover",
          "label": "Discover",
          "required_params": [
            "keyword"
          ],
          "optional_params": [],
          "max_limit": null,
          "async": true,
          "price_usd": 0.0096
        }
      ]
    }
  ]
}

Billing

Prices are tiered by platform and action. GET /v1/data/catalog shows the customer price for each enabled action. A successful call is charged once; failed calls and zero-result calls are free. Data API and web search share one daily free allowance.

Error codes

{"error":{"code":"<code>","message":"<fixed message>"}}

CodeHTTPMeaning
unauthorized401API key missing or invalid.
forbidden403Request is not allowed.
invalid_request400Invalid request body.
invalid_params400Action parameters were rejected.
unsupported_capability400Platform or action is unavailable.
insufficient_credits402Insufficient credits.
budget_cap_reached402 or policy statusAccount spending limit reached.
body_too_large413Request body exceeds the size limit.
free_quota_exhausted432Daily free allowance exhausted.
rate_limited429Rate limit reached; respect Retry-After.
data_unavailable503Data service is temporarily unavailable.
timeout504Request timed out.
bad_upstream_response502Data service returned an invalid response.
job_expired410Job expired after 15 minutes.
not_found404Job unknown or owned by another account.
job_failed503Async job failed.
service_paused503Service is not enabled yet or is paused.

Examples

curl

curl https://api.bazaarlink.ai/v1/data/social \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"platform":"twitter","action":"search","params":{"keyword":"BazaarLink","limit":5}}'

Python (requests)

import os
import requests

response = requests.post(
    "https://api.bazaarlink.ai/v1/data/social",
    headers={"Authorization": f"Bearer {os.environ['BAZAARLINK_API_KEY']}"},
    json={"platform": "twitter", "action": "search", "params": {"keyword": "BazaarLink", "limit": 5}},
    timeout=30,
)
response.raise_for_status()
print(response.json())

JavaScript (fetch)

const response = await fetch("https://api.bazaarlink.ai/v1/data/social", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BAZAARLINK_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ platform: "twitter", action: "search", params: { keyword: "BazaarLink", limit: 5 } }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());

Submit a decisions request

Evaluate a state against named questions with a decisions-output model. This alpha endpoint returns non-streaming JSON. Find available model ids with GET /v1/models?output_modalities=decisions; use this endpoint for Clef instead of chat/completions. Input tokens are billed at the selected model's customer price. Sufficient credits are required. Set BAZAARLINK_API_KEY to your API key; the sample builds its complete Authorization header in BAZAARLINK_AUTH_HEADER.

View the decisions API reference

export BAZAARLINK_AUTH_HEADER="Authorization: $(printf '%s %s' 'Bearer' "$BAZAARLINK_API_KEY")"

curl https://api.bazaarlink.ai/v1/alpha/decisions \
  -H "$BAZAARLINK_AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "cloudflare/clef-flash",
    "state": "The support agent issued a full refund.",
    "questions": {
      "refunded": {
        "type": "noul",
        "instructions": "Was a refund issued?"
      }
    }
  }'

Video Inputs

Send video files to models that support video input for analysis, captioning, or questions about scenes and events. Works with a direct URL or a base64 data URI — a URL is more efficient for publicly accessible video; base64 is for local files or private video.

Supported Formats

MP4 (H.264)MPEGMOVWebM
response = client.chat.completions.create(
    model="google/gemini-3.5-flash",
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "video_url",
                "video_url": {"url": "https://example.com/video.mp4"},
            },
            {"type": "text", "text": "What is happening in this video?"},
        ],
    }],
)

Full API reference →

PDF Inputs

Send PDF documents directly in messages for models that support PDF input natively (e.g. Claude, Gemini). BazaarLink forwards the file straight to the model — billed as ordinary input tokens, no extra charge or processing step.

Supported Formats

  • PDF documents (text, images, tables, scanned)
  • Base64-encoded data URL (`data:application/pdf;base64,...`)
  • Multi-page documents
  • Password-free PDFs only
import base64

with open("document.pdf", "rb") as f:
    pdf_data = base64.b64encode(f.read()).decode()

response = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.6",
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "file",
                "file": {
                    "filename": "document.pdf",
                    "file_data": f"data:application/pdf;base64,{pdf_data}",
                },
            },
            {"type": "text", "text": "Summarize this document."},
        ],
    }],
)

Responses API

An OpenAI Responses API-compatible endpoint for stateless multi-turn conversations, tool calling, and multimodal inputs. Ideal for agents and frameworks that use the OpenAI Python SDK ≥ 1.x with client.responses.create().

POST/v1/responses
Note
Accepts the same authentication and model routing as Chat Completions.

Request Body

modelrequired
string
Use an id or alias listed by GET /v1/models, such as "claude-sonnet-4.6" or "anthropic/claude-sonnet-4.6".
inputrequired
string | Item[]
User input — a plain string (single message) or an array of input items for multi-turn / multimodal conversations.
instructions
string
System-level instructions, equivalent to a system message. Must be re-sent on every request.
stream
boolean
If true, returns Responses API SSE stream events. Event types: response.created, response.output_text.delta, response.completed.
max_output_tokens
integer
Maximum number of output tokens to generate (includes reasoning tokens for o-series models).
temperature
number
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
top_p
number
Nucleus sampling probability mass. Default: 1
tools
Tool[]
Tool (function) definitions — same JSON Schema format as Chat Completions (accepts both the flat Responses shape and the nested shape). OpenAI's own built-in hosted tools (web_search_preview, file_search, computer_use_preview) are not supported; web search is available via plugins: [{id:"web"}] — supported only on some model routes.
tool_choice
string | object
Controls tool use: "auto", "none", or specific tool
parallel_tool_calls
boolean
Enable parallel function calling when tools are provided. Default: true. OpenAI-family models only — see note below.
response_format
object
Force structured JSON output. See Structured Output section
previous_response_id
string
This endpoint is stateless — passing a non-null value returns 400 (invalid_prompt) immediately, it is never accepted and ignored. Use stateless mode instead: pass the full conversation history in the input array.

Request Schema (TypeScript)

type ResponsesRequest = {
  model: string;                    // Listed id or alias from GET /v1/models
  input: string | InputItem[];      // string or multi-turn array

  // Optional
  instructions?: string;            // System-level message
  stream?: boolean;                 // Default: false
  max_output_tokens?: number;
  temperature?: number;
  top_p?: number;
  tools?: Tool[];
  tool_choice?: "auto" | "none" | "required" | object;
  parallel_tool_calls?: boolean;    // Default: true
  previous_response_id?: string;    // Not supported — use full input array
};

type InputItem =
  | { type?: "message"; role: "user" | "assistant" | "system" | "developer"; content: string | ContentBlock[] }
  | { type: "function_call_output"; call_id: string; output: string }   // tool result
  | { type: "function_call"; call_id: string; name: string; arguments: string };

type ContentBlock =
  | { type: "input_text"; text: string }
  | { type: "input_image"; image_url: string; detail?: "auto" | "low" | "high" };

Example Request

curl https://api.bazaarlink.ai/v1/responses \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4-mini",
    "instructions": "You are a helpful assistant.",
    "input": "What is the capital of Taiwan?"
  }'

Response Format

BazaarLink returns a standardized response and removes upstream-specific fields. The opaque BazaarLink request id in the response matches x-request-id; use it with GET /v1/generation. Non-streaming responses also return the same id in x-generation-id. usage.cost is the USD amount charged to the customer. Stats may take a few seconds after completion; retry a 404 briefly.

// Non-streaming response object
type ResponsesResponse = {
  id: string;             // Opaque BazaarLink request id; format may change
  object: "response";
  created_at: number;
  completed_at: number;
  status: "completed" | "failed" | "incomplete";
  model: string;
  output: OutputItem[];
  usage: {
    input_tokens: number;   // equivalent to prompt_tokens
    output_tokens: number;  // equivalent to completion_tokens
    total_tokens: number;
    cost?: number;          // USD amount charged to the customer for this request
  } | null;
  error: null | { code: string; message: string };
};

type OutputItem =
  | {
      type: "message";
      id: string;
      role: "assistant";
      status: "completed";
      content: Array<{ type: "output_text"; text: string; annotations: [] }>;
    }
  | { type: "function_call"; id: string; call_id: string; name: string; arguments: string; status: "completed" };

Migrating from Chat Completions

Replace messages with input (string or array), use instructions instead of a system-role message, and read output[0].content[0].text instead of choices[0].message.content.

# Chat Completions (before)
response = client.chat.completions.create(
    model="openai/gpt-5.4-mini",
    messages=[
        {"role": "system", "content": "You are helpful."},
        {"role": "user",   "content": "Hello"},
    ]
)
text = response.choices[0].message.content

# Responses API (after)
response = client.responses.create(
    model="openai/gpt-5.4-mini",
    instructions="You are helpful.",
    input="Hello"
)
text = response.output[0].content[0].text

Limitations

  • previous_response_id or store: true is rejected with 400 (error code invalid_prompt) — not accepted and ignored. Always use stateless mode: pass the full conversation history in the input array.
  • OpenAI's own built-in hosted tools (web_search_preview, file_search, computer_use_preview) are not supported. Web search is available via plugins: [{id:"web"}] — supported only on some model routes.
  • background: true is accepted but ignored — every request runs synchronously to completion.

Messages (Anthropic)

Anthropic-compatible Messages API for Claude SDK. Point the SDK at BazaarLink and use a BazaarLink API key; response fields and billing metadata follow the BazaarLink contract documented below.

POST/v1/messages
Note
Accepts Bearer token or x-api-key header (Anthropic SDK compatibility). Max body size: 10 MB.

Request Body

modelrequired
string
Use an id or alias listed by GET /v1/models, such as "claude-sonnet-4.6" or "anthropic/claude-sonnet-4.6".
max_tokensrequired
integer
Maximum number of tokens to generate (positive integer).
messagesrequired
Message[]
Array of conversation messages (non-empty).
system
string
Optional system prompt.
stream
boolean
If true, returns a Server-Sent Events stream. Default: false
temperature
number
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
top_p
number
Nucleus sampling probability mass. Default: 1
top_k
integer
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
stop_sequences
string[]
Custom stop sequence strings.
tools
Tool[]
List of tools (functions) the model may call
tool_choice
string | object
Controls tool use: "auto", "none", or specific tool

Example Request

from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.bazaarlink.ai/v1",
    api_key="sk-bl-YOUR_KEY"
)

response = client.messages.create(
    model="anthropic/claude-opus-4.6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}]
)
print(response.content[0].text)

Response

BazaarLink returns a standardized response and removes upstream-specific fields. The opaque BazaarLink request id in the response matches x-request-id; use it with GET /v1/generation. Non-streaming responses also return the same id in x-generation-id. usage.cost and usage.bz_cost contain the same USD amount charged to the customer. Stats may take a few seconds after completion; retry a 404 briefly.

{
  "id": "85e6f35c71a31f19-SJC",
  "type": "message",
  "role": "assistant",
  "model": "anthropic/claude-opus-4.6",
  "content": [
    { "type": "text", "text": "Hello! How can I help you today?" }
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 10,
    "output_tokens": 12,
    "cache_read_input_tokens": 0,
    "cache_creation_input_tokens": 0,
    "cost": 0.00042,
    "bz_cost": 0.00042
  }
}

Error envelopes vary by failure. Branch on HTTP status and error.type when present; error.code may be a string, a numeric HTTP status, or absent. See the Errors section for handling guidance.

Models

List all available models with pricing and capability information. No authentication required for this endpoint.

GET/v1/models
# Text models (default)
curl https://api.bazaarlink.ai/v1/models

# Complete catalog
curl "https://api.bazaarlink.ai/v1/models?output_modalities=all"

Response

{
  "data": [
    {
      "id": "openai/gpt-4o",
      "name": "GPT 4.1",
      "context_length": 1047576,
      "modality": "text+image+file->text",
      "pricing": {
        "prompt": "2.00",
        "completion": "8.00"
      }
    }
  ]
}
// /v1/models — Response Schema
type ModelsResponse = {
  data: Model[];
};

type Model = {
  id: string;                    // Model ID (e.g. "openai/gpt-4o")
  name: string;                  // Human-readable name
  context_length: number | null; // Max context window in tokens
  modality: string | null;       // e.g. "text->text", "text+image->text"
  architecture?: {
    input_modalities?: string[];
    output_modalities?: Array<
      "text" | "image" | "embeddings" | "audio" |
      "video" | "rerank" | "speech" | "transcription"
    >;
  };
  pricing: {
    prompt: string;              // Input price per 1M tokens (USD)
    completion: string;          // Output price per 1M tokens (USD)
    input_cache_read?: string;   // Cache read price per token (USD); only when the model publishes one
    input_cache_write?: string;  // Cache write price per token (USD); only when the model publishes one
    image_output?: string;       // Image output price per token (USD); only token-billed image models (display only)
  };
  description?: string | null;   // Model description
  top_provider?: {
    max_completion_tokens?: number;
  };
  supported_parameters?: string[]; // e.g. ["tools", "response_format", "reasoning"]
  pricing_tiers?: {               // Present only for models with input-length tiers
    above_prompt_tokens: number;  // Ascending; strict "greater than" threshold
    prompt: string;                // USD per token, override tier
    completion: string;            // USD per token, override tier
    input_cache_read?: string;     // USD per token, only when the tier overrides it
    input_cache_write?: string;    // USD per token, only when the tier overrides it
  }[];
};

Input-Length Tiered Pricing

Some models switch to a different price table once the prompt exceeds a token threshold — the whole table changes, not just the tokens above the threshold. Thresholds are strict: a prompt with exactly N tokens still bills at the tier below N; the higher tier applies only when input tokens are greater than N.

pricing_tiers is a sibling of pricing, present only when a model has override tiers beyond its base price. Entries are sorted by above_prompt_tokens ascending; prompt/completion are USD per token (same unit as pricing.prompt/pricing.completion). pricing.prompt and pricing.completion always remain the base (lowest) tier.

Most models have no tiers — for those, the pricing_tiers key is simply absent from the response.

{
  "id": "openai/gpt-4o",
  "name": "GPT 4.1",
  "pricing": {
    "prompt": "2.00",
    "completion": "8.00"
  },
  "pricing_tiers": [
    {
      "above_prompt_tokens": 128000,
      "prompt": "4.00",
      "completion": "16.00"
    }
  ]
}

Available Models (171)

These are the models currently available on BazaarLink, loaded dynamically from our database:

Qwen
qwen/qwen3.8-max1000K ctx · $1.32/$3.96text+image+video->text
qwen/qwen3.7-plus1000K ctx · $0.26/$1.02text+image->text
qwen/qwen3.7-max1000K ctx · $1.32/$3.96text->text
qwen/qwen3.7-flash1000K ctx · $0.03/$0.13text+image+video->text
qwen/qwen3-embedding-4b33K ctx · $0.02/$0.00text->embeddings
qwen/qwen3.5-35b-a3b262K ctx · $0.25/$1.00text+image+video->text
qwen/qwen3.5-27b262K ctx · $0.21/$2.08text+image+video->text
qwen/qwen3-next-80b-a3b-instruct262K ctx · $0.12/$0.46text->text
qwen/qwen3.5-397b-a17b262K ctx · $0.44/$2.80text+image+video->text
qwen/qwen3.6-35b-a3b262K ctx · $0.30/$1.80text+image+video->text
qwen/qwen3.6-plus1000K ctx · $0.26/$1.56text+image+video->text
qwen/qwen3-coder-flash1000K ctx · $0.24/$1.20text->text
qwen/qwen3.5-122b-a10b262K ctx · $0.32/$2.56text+image+video->text
qwen/qwen3-vl-235b-a22b-instruct262K ctx · $0.20/$0.88text+image->text
qwen/qwen3.8-27b1000K ctx · $0.40/$2.40text+image+video->text
qwen/qwen3.6-27b262K ctx · $0.48/$2.88text+image+video->text
qwen/qwen3.8-2.4t-a95b1049K ctx · $1.60/$4.80text->text
qwen/qwen3-embedding-8b33K ctx · $0.01/$0.00text->embeddings
qwen/qwen3-embedding-0.6b33K ctx · $0.01/$0.00text->embeddings
qwen/qwen3.5-flash-02-231000K ctx · $0.07/$0.26text+image+video->text
qwen/qwen3-coder-30b-a3b-instruct262K ctx · $0.07/$0.28text->text
qwen/qwen3-coder-next262K ctx · $0.12/$0.80text->text
qwen/qwen3-vl-30b-a3b-instruct262K ctx · $0.15/$0.60text+image->text
qwen/qwen3.5-plus-02-151000K ctx · $0.26/$1.56text+image+video->text
qwen/qwen3.5-plus-202604201000K ctx · $0.30/$1.80text+image+video->text
qwen/qwen3-coder262K ctx · $0.30/$1.00text->text
qwen/qwen-2.5-72b-instruct33K ctx · $0.36/$0.40text->text
qwen/qwen-image-3 · $0.00/$0.00text+image->image
qwen/qwen-image-3-pro · $0.00/$0.00text+image->image
Google
google/gemini-4-argon1049K ctx · $2.00/$10.00text+image+file+audio+video->text
google/gemini-3.1-flash-lite-preview1049K ctx · $0.25/$1.50text+image+file+audio+video->text
google/gemini-3.1-pro-preview1049K ctx · $1.80/$10.80text+image+file+audio+video->text
google/gemma-3-4b-it131K ctx · $0.05/$0.10text+image->text
google/gemini-3.1-flash-lite1049K ctx · $0.25/$1.50text+image+file+audio+video->text
google/gemini-3.5-flash-lite1049K ctx · $0.27/$2.25text+image+file+audio+video->text
google/gemini-3-flash-preview1049K ctx · $0.45/$2.70text+image+file+audio+video->text
google/gemini-3.8-flash1049K ctx · $0.75/$3.75text+image+file+audio+video->text
google/gemini-3.6-flash1049K ctx · $0.68/$3.38text+image+file+audio+video->text
google/gemini-3.7-flash1049K ctx · $0.75/$3.75text+image+file+audio+video->text
google/gemini-3.5-flash1049K ctx · $1.35/$8.10text+image+file+audio+video->text
google/text-multilingual-embedding-0022K ctx · $0.03/$0.00text->embeddings
google/text-embedding-0052K ctx · $0.03/$0.00text->embeddings
google/gemini-embedding-00120K ctx · $0.15/$0.00text->embeddings
google/gemma-4-26b-a4b-it262K ctx · $0.09/$0.34text+image+video->text
google/gemma-4-31b-it262K ctx · $0.09/$0.34text+image+video->text
google/gemini-2.5-flash-image33K ctx · $0.30/$2.50text+image->text+image
google/gemini-3.1-flash-image131K ctx · $0.50/$3.00text+image->text+image
google/gemini-3.1-flash-image-preview66K ctx · $0.50/$3.00text+image->text+image
google/gemini-3-pro-image131K ctx · $2.00/$12.00text+image->text+image
google/veo-3.1 · $0.00/$0.00text+image->video
google/gemini-embedding-2-preview8K ctx · $0.20/$0.00text+image+file+audio+video->embeddings
google/gemini-nano-banana-2.166K ctx · $1.50/$7.50text+image->text+image
OpenAI
openai/gpt-5.3-codex400K ctx · $1.75/$14.00text->text
openai/gpt-oss-120b131K ctx · $0.15/$0.60text->text
openai/gpt-oss-safeguard-20b131K ctx · $0.07/$0.30text->text
openai/gpt-6-luna1050K ctx · $0.09/$0.45text+image+file->text
openai/gpt-5.6-luna1050K ctx · $0.18/$1.08text+image+file->text
openai/gpt-5.4-mini400K ctx · $0.68/$4.05text+image+file->text
openai/gpt-5.6-terra1050K ctx · $1.80/$10.80text+image+file->text
openai/gpt-6.1-sol1050K ctx · $2.00/$10.00text+image+file->text
openai/gpt-6-sol1050K ctx · $1.80/$9.00text+image+file->text
openai/gpt-5.41050K ctx · $2.25/$13.50text+image+file->text
openai/gpt-4o128K ctx · $2.25/$9.00text+image+file->text
openai/gpt-5.6-sol1050K ctx · $4.50/$27.00text+image+file->text
openai/gpt-5.51050K ctx · $5.00/$30.00text+image+file->text
openai/gpt-6-astra1050K ctx · $9.00/$45.00text+image+file->text
openai/text-embedding-3-small8K ctx · $0.02/$0.00text->embeddings
openai/text-embedding-ada-0028K ctx · $0.10/$0.00text->embeddings
openai/text-embedding-3-large · $0.13/$0.00text->embeddings
openai/gpt-oss-20b131K ctx · $0.03/$0.13text->text
openai/gpt-image-1-mini400K ctx · $2.00/$8.00text+image->image
openai/gpt-image-2.5-sunburst400K ctx · $5.00/$30.00text+image->image
openai/gpt-5.4-image-2272K ctx · $8.00/$15.00text+image+file->text+image
openai/gpt-image-2.5-flare400K ctx · $5.00/$30.00text+image->image
openai/gpt-image-1400K ctx · $10.00/$10.00text+image->image
Anthropic
anthropic/claude-sonnet-4.61000K ctx · $3.00/$15.00text+image+file->text
anthropic/claude-opus-4.61000K ctx · $5.00/$25.00text+image+file->text
anthropic/claude-haiku-5.51000K ctx · $0.10/$0.50text+image+file->text
anthropic/claude-haiku-4.5200K ctx · $1.00/$5.00text+image+file->text
anthropic/claude-sonnet-51000K ctx · $2.00/$10.00text+image+file->text
anthropic/claude-sonnet-5.51000K ctx · $2.00/$10.00text+image+file->text
anthropic/claude-sonnet-4.51000K ctx · $3.00/$15.00text+image+file->text
anthropic/claude-opus-5.51000K ctx · $4.00/$20.00text+image+file->text
anthropic/claude-opus-51000K ctx · $5.00/$25.00text+image+file->text
anthropic/claude-opus-4.71000K ctx · $5.00/$25.00text+image+file->text
anthropic/claude-opus-4.5200K ctx · $5.00/$25.00text+image+file->text
anthropic/claude-opus-4.81000K ctx · $5.00/$25.00text+image+file->text
anthropic/claude-fable-51000K ctx · $10.00/$50.00text+image+file->text
anthropic/claude-fable-5.11000K ctx · $10.00/$50.00text+image+file->text
anthropic/claude-opus-4.1200K ctx · $15.00/$75.00text+image+file->text
Elevenlabs
elevenlabs/scribe-v2 · $0.00/$0.00audio->transcription
elevenlabs/eleven-v4 · $0.00/$0.00text->speech
elevenlabs/eleven-v3-conversational · $0.00/$0.00text->speech
elevenlabs/eleven-v3 · $0.00/$0.00text->speech
elevenlabs/eleven-turbo-v2.5 · $0.00/$0.00text->speech
elevenlabs/eleven-flash-v2.5 · $0.00/$0.00text->speech
elevenlabs/eleven-v4-turbo · $0.00/$0.00text->speech
elevenlabs/eleven-multilingual-v2 · $0.00/$0.00text->speech
elevenlabs/eleven-flash-v2 · $0.00/$0.00text->speech
elevenlabs/eleven-turbo-v2 · $0.00/$0.00text->speech
elevenlabs/scribe-v2-medical · $0.00/$0.00audio->transcription
DeepSeek
deepseek/deepseek-v4.1-flash-10171049K ctx · $0.21/$0.85text+image->text
deepseek/deepseek-v4-pro1049K ctx · $1.65/$3.30text->text
deepseek/deepseek-v4-flash1000K ctx · $0.14/$0.28text->text
deepseek/deepseek-v4.1-flash1049K ctx · $0.21/$0.85text+image->text
deepseek/deepseek-v4-flash-07311049K ctx · $0.44/$1.32text->text
deepseek/deepseek-chat-v3.1164K ctx · $0.21/$0.79text->text
deepseek/deepseek-chat164K ctx · $0.26/$0.38text->text
deepseek/deepseek-r164K ctx · $0.50/$2.15text->text
deepseek/deepseek-r1-0528164K ctx · $0.50/$2.15text->text
deepseek/deepseek-v4-pro-08131049K ctx · $0.89/$2.67text->text
deepseek/deepseek-v4-flash-0731free1049K ctx · $0.20/$0.40text->text
Zhipu AI
z-ai/glm-5v-turbo203K ctx · $1.20/$4.00text+image+video->text
z-ai/glm-4.7-flash200K ctx · $0.06/$0.40text->text
z-ai/glm-4.5-air131K ctx · $0.13/$0.85text->text
z-ai/glm-5.3-flash1049K ctx · $0.15/$0.50text+image+video->text
z-ai/glm-4.6205K ctx · $0.60/$2.20text->text
z-ai/glm-4.6v131K ctx · $0.30/$0.90text+image+video->text
z-ai/glm-5.3-flashx1049K ctx · $0.37/$1.25text+image+video->text
z-ai/glm-5.31049K ctx · $1.02/$3.96text->text
z-ai/glm-4.5131K ctx · $0.60/$2.20text->text
z-ai/glm-4.5v66K ctx · $0.60/$1.80text+image->text
Voyage
voyage/voyage-4-lite32K ctx · $0.02/$0.00text->embeddings
voyage/voyage-3.5-lite32K ctx · $0.02/$0.00text->embeddings
voyage/voyage-3.532K ctx · $0.06/$0.00text->embeddings
voyage/voyage-432K ctx · $0.06/$0.00text->embeddings
voyage/voyage-law-216K ctx · $0.12/$0.00text->embeddings
voyage/voyage-4-large32K ctx · $0.12/$0.00text->embeddings
voyage/voyage-finance-232K ctx · $0.12/$0.00text->embeddings
voyage/voyage-code-216K ctx · $0.12/$0.00text->embeddings
voyage/voyage-3-large32K ctx · $0.18/$0.00text->embeddings
voyage/voyage-code-332K ctx · $0.18/$0.00text->embeddings
xAI
x-ai/grok-build-0.1256K ctx · $0.90/$1.80text+image+file->text
x-ai/grok-4.31000K ctx · $1.13/$2.25text+image+file->text
x-ai/grok-4.5500K ctx · $1.80/$5.40text+image+file->text
x-ai/grok-4.6500K ctx · $1.80/$5.40text+image+file->text
x-ai/grok-4.7500K ctx · $2.00/$6.00text+image->text
x-ai/grok-4.202000K ctx · $1.25/$2.50text+image+file->text
x-ai/grok-imagine-video-1.5-lite · $0.00/$0.00text+image->video
x-ai/grok-imagine-image-2.0 · $0.00/$0.00text+image->image
ByteDance Seed
bytedance-seed/seed-1.6-flash262K ctx · $0.07/$0.30text+image+video->text
bytedance-seed/seed-2.0-mini262K ctx · $0.10/$0.40text+image+video->text
bytedance-seed/seed-1.6262K ctx · $0.25/$2.00text+image+video->text
bytedance-seed/seed-2.0-lite262K ctx · $0.25/$2.00text+image+video->text
ByteDance
bytedance/seedance-2.5 · $0.00/$0.00text+image+audio+video->video
bytedance/seedance-1-5-pro · $0.00/$0.00text+image->video
bytedance/seedance-2.0 · $0.00/$0.00text+image+audio+video->video
bytedance/seedance-2.0-fast · $0.00/$0.00text+image+audio+video->video
Moonshot AI
moonshotai/kimi-k31049K ctx · $2.83/$14.13text+image+video->text
moonshotai/kimi-k2-0905262K ctx · $0.60/$2.50text->text
moonshotai/kimi-k3.1-pro1049K ctx · $2.00/$10.00text+image+video->text
Cohere
cohere/rerank-4-fast33K ctx · $0.00/$0.00text->rerank
cohere/rerank-v3.54K ctx · $0.00/$0.00text->rerank
cohere/rerank-4-pro33K ctx · $0.00/$0.00text->rerank
MiniMax
minimax/minimax-m31049K ctx · $0.30/$1.20text+image+video->text
minimax/hailuo-3-max · $0.00/$0.00text+image->video
Xiaomi
xiaomi/mimo-v2.6-flash1049K ctx · $0.14/$0.28text+image+video+audio->text
xiaomi/mimo-v2.6-pro1049K ctx · $0.43/$0.87text+image+video+audio->text
Perplexity
perplexity/pplx-embed-v1-0.6b32K ctx · $0.00/$0.00text->embeddings
perplexity/pplx-embed-v1-4b32K ctx · $0.03/$0.00text->embeddings
Mistral
mistralai/mistral-embed-23128K ctx · $0.10/$0.00text->embeddings
mistralai/codestral-embed-25058K ctx · $0.15/$0.00text->embeddings
Cloudflare
cloudflare/clef-flash25K ctx · $0.09/$0.00text+image->decisions
cloudflare/clef66K ctx · $0.24/$0.00text+image->decisions
Reflection AI
reflection/beam-free1049K ctx · $0.00/$0.00text->text
Stealth
stealth/glyph-cluster256K ctx · $0.00/$0.00text->text
NVIDIA
nvidia/nemotron-3.5-lightning-free1000K ctx · $0.00/$0.00text->text
inclusionAI
inclusionai/ling-3.1-flash-10191000K ctx · $0.00/$0.00text->text
Meta
meta/muse-wonder1049K ctx · $4.00/$20.00text+image+file->text
Amazon
amazon/titan-embed-text-v28K ctx · $0.02/$0.00text->embeddings
Typesafe Ai
typesafe-ai/jev32K ctx · $0.04/$0.00text->decisions

Browse all models on the Models page.

Structured evaluations (Jev)

Jev evaluates a JSON state against named questions and returns answers typed as boolean, choice, or score. Use it when an application needs explicit criteria turned into machine-readable evaluation results.

Evaluation and judgement only
Jev does not support chat. Send Jev requests to POST /v1/evaluations; sending one to POST /v1/chat/completions returns HTTP 400.
POST/v1/evaluations

Call this endpoint with your BazaarLink API key. Full URL: https://api.bazaarlink.ai/v1/evaluations.

Field reference

model
string
Optional. Defaults to typesafe-ai/jev when omitted. Any other model ID returns HTTP 400 with code model_not_available.
state
string | object | array
Required. Provide a JSON string, object, or array as the state to evaluate. Serialized state and questions above 32,000 tokens return context_length_exceeded.
questions
record<string, Question>
Required. A non-empty object of named questions. Each question name becomes a key in the response answers object.
type
boolean | choice | score
Required for each question; one of boolean, choice, or score.
instructions
string | object | array
Required for each question. A JSON string, object, or array describing what to decide.
criteria
object | array
Depends on type: optional for boolean and, when supplied, an object whose keys are only true or false; required as a non-empty object for choice; required as an ordered array of at least two entries for score.

Answer types

  • boolean — criteria is optional; when supplied, only true and false keys are accepted. The answer contains a numeric probability.
  • choice — criteria must be a non-empty object mapping option names to JSON criteria. The answer contains a string choice; probabilities are keyed by option names.
  • score — criteria must be an ordered array with at least two entries. The numeric score is the expected value over the indices 0..N-1 in criteria order; probabilities are keyed by those indices as strings.

Request examples

curl --fail-with-body https://api.bazaarlink.ai/v1/evaluations \
  -H "Authorization: Bearer $BAZAARLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "typesafe-ai/jev",
  "state": "The support agent issued a full refund and apologised for the delay.",
  "questions": {
    "refunded": {
      "type": "boolean",
      "instructions": "Was a refund issued?"
    },
    "tone": {
      "type": "choice",
      "instructions": "Tone of the agent?",
      "criteria": {
        "apologetic": "The agent apologises",
        "neutral": "No emotional language",
        "hostile": "The agent is rude"
      }
    },
    "quality": {
      "type": "score",
      "instructions": "How complete was the resolution?",
      "criteria": [
        "poor",
        "partial",
        "complete"
      ]
    }
  }
}'

Successful response example

The sample response contains id, object, model, answers, usage, rounding, and warnings. answers is keyed by question name. usage contains input_tokens, output_tokens, and total_tokens; rounding shows probabilityDecimals and scoreDecimals. warnings is an array and is empty in this example. In answers, choice probabilities use option names as keys, while score probabilities use criteria indices as string keys.

{
  "id": "eval-a3d350e5-2705-4a9b-be7c-838fa4e4eb2b",
  "object": "evaluation",
  "model": "typesafe-ai/jev",
  "answers": {
    "refunded": {
      "type": "boolean",
      "probability": 0.99
    },
    "tone": {
      "type": "choice",
      "choice": "apologetic",
      "probabilities": {
        "apologetic": 1,
        "neutral": 0,
        "hostile": 0
      }
    },
    "quality": {
      "type": "score",
      "score": 1.98,
      "probabilities": {
        "0": 0,
        "1": 0.02,
        "2": 0.98
      }
    }
  },
  "usage": {
    "input_tokens": 394,
    "output_tokens": 74,
    "total_tokens": 468
  },
  "rounding": {
    "probabilityDecimals": 2,
    "scoreDecimals": 2
  },
  "warnings": []
}

Chat endpoint error response

Sending Jev to a chat endpoint returns HTTP 400. The fields and values below match the route handler; message is shown as a placeholder and its actual text is supplied by the server.

{
  "error": {
    "message": "<server-generated message>",
    "type": "invalid_request_error",
    "code": 400
  }
}

Streaming

Set stream: true to receive a Server-Sent Events (SSE) stream. Each event contains a chunk of the response.

from openai import OpenAI

client = OpenAI(
    base_url="https://api.bazaarlink.ai/v1",
    api_key="sk-bl-YOUR_API_KEY",
)

stream = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.6",
    messages=[{"role": "user", "content": "Count to 10 slowly."}],
    stream=True,
)

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

SSE Format

data: {"id":"85e6f35c71a31f19-SJC","choices":[{"delta":{"content":"Hello"},"index":0}]}

data: {"id":"85e6f35c71a31f19-SJC","choices":[{"delta":{"content":" world"},"index":0}]}

data: {"id":"85e6f35c71a31f19-SJC","choices":[{"delta":{},"finish_reason":"stop","index":0}],"usage":{"prompt_tokens":10,"completion_tokens":4,"total_tokens":14}}

data: [DONE]
Usage in streaming
When streaming, usage data is returned in the final chunk before the [DONE] message, alongside a choices array with an empty delta and finish_reason: "stop".

Keep-alive & final chunk

Streams may include SSE comment lines and unknown or ping events as keep-alives; ignore them and parse data frames. A final data frame may include usage before data: [DONE].

: keepalive          <- SSE comment line — ignore, do NOT JSON.parse

data: {"id":"85e6f35c71a31f19-SJC","choices":[{"delta":{"content":"Hi"},"index":0}]}

data: {"id":"85e6f35c71a31f19-SJC","choices":[{"delta":{},"finish_reason":"stop","index":0}],"usage":{...}}

data: [DONE]

Stream Cancellation

Streaming requests can be cancelled by closing the client connection — e.g. calling AbortController.abort() or closing the stream object. BazaarLink stops relaying further chunks and cancels the outbound provider request as soon as the cancellation is received.

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.bazaarlink.ai/v1",
  apiKey: "sk-bl-YOUR_API_KEY",
});

const controller = new AbortController();

const stream = await client.chat.completions.create(
  {
    model: "anthropic/claude-sonnet-4.6",
    messages: [{ role: "user", content: "Write a long story." }],
    stream: true,
  },
  { signal: controller.signal }
);

for await (const chunk of stream) {
  const content = chunk.choices[0]?.delta?.content;
  if (content) process.stdout.write(content);
}

// e.g. on a "Stop" button click:
controller.abort();
No charge for an interrupted stream
If a stream ends before the final usage chunk arrives — including a client-side cancellation — there is no token-count ground truth for that request, so BazaarLink refunds the full reservation. You are not billed for content already delivered to your client before the cancellation.
Not guaranteed to stop the provider instantly
Closing the connection stops BazaarLink from relaying and paying for further tokens right away, but whether the upstream provider halts generation on its own servers the instant the connection drops depends on that provider — some may keep computing briefly after disconnect.

Mid-Stream Errors

No choices field on error frames
If a failure happens after streaming has already started (e.g. the upstream connection drops), you get an SSE data frame shaped {error:{message,type,code}} instead of the usual {choices:[...]} — with no HTTP status change, since headers were already sent. Check for an error key before reading choices[0].delta, and bill only for tokens already streamed (partial billing applies).
data: {"id":"85e6f35c71a31f19-SJC","choices":[{"delta":{"content":"The capital of "},"index":0}]}

data: {"error":{"message":"The request could not be completed.","type":"upstream_error","code":502}}

data: [DONE]

Embeddings

Embeddings are numerical representations of text that capture semantic meaning — they convert text into vectors (arrays of numbers) usable for a range of machine learning tasks. BazaarLink provides a unified, OpenAI Embeddings API-compatible endpoint so you can call embedding models from multiple providers through one interface.

Embedding responses use an opaque BazaarLink request id that matches x-request-id; use it with GET /v1/generation. The non-streaming response also returns the same id in x-generation-id. usage.cost is the USD amount charged to the customer for this request. Stats may take a few seconds after completion; retry a 404 briefly.

What are Embeddings?

Embeddings transform text into high-dimensional vectors where semantically similar texts sit closer together in vector space — for example, "cat" and "kitten" would have similar embeddings, while "cat" and "airplane" would be far apart. These vector representations let machines understand relationships between pieces of text, making them foundational to many AI applications.

Common Use Cases

Use Case
Description
RAG (Retrieval-Augmented Generation)Build systems that retrieve relevant context from a knowledge base before generating an answer — embeddings find the most relevant documents to include in the LLM's context.
Semantic SearchConvert documents and queries into embeddings, then find the most relevant documents by vector similarity — this understands meaning rather than just matching keywords, giving better results than keyword search alone.
Recommendation SystemsGenerate embeddings for items (products, articles, videos) and user preferences to recommend similar items — vector comparison surfaces semantically related items even without shared keywords.
Clustering & ClassificationGroup similar documents or classify text by analyzing embedding patterns — documents with similar embeddings usually belong to the same topic or category.
Duplicate DetectionFind duplicate or near-duplicate content by comparing embedding similarity — this works even when the content has been paraphrased or reworded.
Anomaly DetectionSpot unusual or outlier content by identifying embeddings that deviate significantly from the dataset's typical pattern.
POST/v1/embeddings
Note
If an upstream source does not support the requested embeddings model, a request can try another available source for that model. If none is available, the request returns an error.

Parameters

modelrequired
string
The embedding model to use, e.g. "openai/text-embedding-3-small".
inputrequired
string | string[] | ContentItem[]
Text to embed — a single string, an array of strings for one batch call, or (for models that support it) an array of {content:[...]} items mixing text and image_url parts.
dimensions
integer
Requested output vector size. Only honored by models that support variable dimensions (e.g. the OpenAI text-embedding-3 family); forwarded to the upstream provider as-is and ignored by models that don't support it.
encoding_format
string
Requested embedding encoding, e.g. "float" or "base64". Forwarded to the upstream provider as-is — support depends on the model.
provider
object
Unsupported. The entire object is ignored and removed before forwarding; it cannot select an upstream or price tier.

Basic Request

from openai import OpenAI

client = OpenAI(
    base_url="https://api.bazaarlink.ai/v1",
    api_key="sk-bl-YOUR_API_KEY",
)

response = client.embeddings.create(
    model="openai/text-embedding-3-small",
    input="The quick brown fox jumps over the lazy dog",
)

print(response.data[0].embedding)  # 1536-dimensional vector

Batch Processing

Send an array of strings to embed multiple texts in a single request — cheaper and faster than one call per text.

response = client.embeddings.create(
    model="openai/text-embedding-3-small",
    input=[
        "Machine learning is a subset of artificial intelligence",
        "Deep learning uses neural networks with multiple layers",
        "Natural language processing enables computers to understand text",
    ],
)

for i, item in enumerate(response.data):
    print(f"Embedding {i}: {len(item.embedding)} dimensions")

Multimodal Input (Image + Text)

Models with image input support (output_modalities includes "embeddings" and inputModalities includes "image") accept input items shaped as {content:[{type:"text",...}, {type:"image_url",...}]}, letting you embed an image alone or jointly with text.

Model-dependent
Only some embedding models accept image input — check a model's supported modalities on the Models page before sending image_url content. Text-only models will reject this shape.
import requests

response = requests.post(
    "https://api.bazaarlink.ai/v1/embeddings",
    headers={
        "Authorization": "Bearer sk-bl-YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "model": "google/gemini-embedding-2-preview",
        "input": [{
            "content": [
                {"type": "text", "text": "A scenic boardwalk through a green meadow"},
                {"type": "image_url", "image_url": {"url": "https://example.com/boardwalk.jpg"}},
            ]
        }],
        "encoding_format": "float",
    },
)

embedding = response.json()["data"][0]["embedding"]
print(f"Embedding dimension: {len(embedding)}")

Finding Embedding Models

There's no dedicated embeddings model-listing endpoint — call GET /v1/models and filter client-side for entries whose output_modalities includes "embeddings", or browse the Models page.

Limitations

  • No streaming — embeddings are always returned as a complete response, unlike chat completions.
  • Each model has a maximum input length; text beyond that limit is truncated or rejected upstream.
  • Embeddings for identical input are deterministic — no temperature or randomness involved.

Best Practices

  • Choose a model for your speed/quality/cost tradeoff — smaller models (e.g. qwen/qwen3-embedding-4b) are cheaper and faster; larger ones (e.g. openai/text-embedding-3-large) generally embed with higher fidelity.
  • Batch multiple texts into one request instead of one call per text — fewer round trips, lower overhead.
  • Cache results — embeddings for the same input never change, so store them rather than regenerating.
  • Compare with cosine similarity, not Euclidean distance — it's scale-invariant and works better for high-dimensional vectors.
  • Watch each model's context length — long documents may need chunking before embedding.

Parameters

Sampling parameters shape the token generation process. BazaarLink passes supported parameters to the upstream provider; unsupported parameters are silently ignored.

Sampling Parameters

Pure passthrough — no local defaults
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
temperature
number
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
top_p
number
Nucleus sampling probability mass. Default: 1
top_k
integer
Sampling values are forwarded without range validation. The model may clamp or ignore values outside its supported range.
frequency_penalty
number
Penalize repeated tokens. Range: [-2, 2]. Default: 0
presence_penalty
number
Penalize tokens based on presence. Range: [-2, 2]. Default: 0
repetition_penalty
number
Reduce token repetition from input. Range: (0, 2]. Default: 1
min_p
number
Minimum probability relative to the top token. Range: [0, 1]. Default: 0
top_a
number
Dynamic top-P based on highest-probability token. Range: [0, 1]. Default: 0
seed
integer
Integer seed for deterministic sampling. Not guaranteed for all models
max_tokens
integer
Maximum number of tokens to generate
n
integer
Number of completions to generate. Default: 1
logit_bias
object
Map token IDs to bias values [-100, 100] added before sampling. OpenAI-family models only — see note below.
logprobs
boolean
Return log probabilities of each output token. OpenAI-family models only — see note below.
top_logprobs
integer
Number of most-likely tokens to return per position (requires logprobs: true). Range: 0–20. OpenAI-family models only — see note below.
response_format
object
Force structured JSON output. See Structured Output section
structured_outputs
boolean
Request strict JSON-schema-conformant output on providers that support it. Passed through as-is
reasoning
object
Provider-specific reasoning/thinking configuration. Passed through as-is
reasoning_effort
string
OpenAI o-series style reasoning effort: "low", "medium", or "high". Passed through as-is
include_reasoning
boolean
true returns the thinking text in message.reasoning (delta.reasoning when streaming); false turns thinking off for the models that think by default (see the reasoning section), so no thinking is billed. Without reasoning_effort, reasoning or thinking, only those models think and return it by default; other models return it only when asked
stop
string | string[]
Stop sequences — generation halts when encountered
tools
Tool[]
List of tools (functions) the model may call
tool_choice
string | object
Controls tool use: "auto", "none", or specific tool
parallel_tool_calls
boolean
Enable parallel function calling when tools are provided. Default: true. OpenAI-family models only — see note below.

Credits

Query current credit balance and lifetime API usage.

GET/v1/credits
Auth
Requires Bearer token (standard API key sk-bl-...).

Example Request

curl https://api.bazaarlink.ai/v1/credits \
  -H "Authorization: Bearer sk-bl-YOUR_KEY"

Response

{
  "data": {
    "total_credits": 100.00,
    "total_usage": 12.34
  }
}

Errors: 401 (missing/invalid key), 403 (suspended user).

Generation Details

Use the opaque BazaarLink request id from the response id or x-request-id with GET /v1/generation; non-streaming responses also return it in x-generation-id. Stats may take a few seconds after completion; retry a 404 briefly.

GET/v1/generation?id=<generation-id>
Auth
Requires Bearer token (standard API key). Required query param: id.

Example Request

curl "https://api.bazaarlink.ai/v1/generation?id=BAZAARLINK_REQUEST_ID" \
  -H "Authorization: Bearer sk-bl-YOUR_KEY"

Response

{
  "data": {
    "id": "85e6f35c71a31f19-SJC",
    "model": "openai/gpt-4o",
    "provider": "bazaarlink",
    "created_at": "2026-04-20T10:00:00.000Z",
    "app_name": "MyApp",
    "finish_reason": "stop",
    "status": 200,
    "duration_ms": 1234,
    "first_token_ms": 234,
    "throughput": 45.6,
    "usage": {
      "prompt_tokens": 100,
      "completion_tokens": 200,
      "total_tokens": 300,
      "prompt_tokens_details": { "cached_tokens": 50 },
      "completion_tokens_details": { "reasoning_tokens": 30 },
      "cost": 0.00123
    },
    "cost_breakdown": {
      "subtotal": 0.00123,
      "cache_discount": 0.00015,
      "total": 0.00108
    }
  }
}

Errors: 400 (missing id), 401 (auth), 404 (generation not found).

API Key Info

Query current API key's rate limit tier and aggregated usage counters (response format follows common industry key-info API conventions).

GET/v1/key
Auth
Requires Bearer token (standard API key).

Response

{
  "data": {
    "label": "Production Key",
    "limit": null,
    "limit_remaining": 100.00,
    "limit_reset": null,
    "expires_at": null,
    "is_free_tier": false,
    "is_management_key": false,
    "is_provisioning_key": false,
    "usage": 12.34,
    "usage_daily": 0.12,
    "usage_weekly": 0.45,
    "usage_monthly": 1.23,
    "requests": 1234,
    "requests_daily": 10,
    "requests_weekly": 50,
    "requests_monthly": 200,
    "rate_limit": { "requests": 600, "interval": "1m", "note": "Paid-tier rate limit." }
  }
}
Note
is_free_tier = true when credit balance < $10. Windows are in UTC: daily = current day, weekly = Mon–Sun, monthly = 1st–EOM. When the key has a per-key spend limit set (via the limit param on key creation/update), limit / limit_remaining / limit_reset reflect that limit and the matching period's usage; otherwise limit is null and limit_remaining falls back to your account credit balance. expires_at is the key's expiration time (null if none). is_management_key and is_provisioning_key are aliases for the same concept — both true for a management key.
BYOK
BazaarLink does not offer a bring-your-own-key (BYOK) program, so this endpoint does not return any byok_usage fields.

Errors: 401 (auth), 404 (user missing — rare).

Error Codes

Error response format

Error envelopes vary by endpoint. Use HTTP status and error.type when present; error.code may be a string or numeric HTTP status, and can be absent. Use error.message for display only.

{
  "error": {
    "message": "Insufficient credits. Please top up to continue.",
    "type": "invalid_request_error",
    "code": "insufficient_credits"
  }
}

HTTP status and error.code

Error envelopes vary by endpoint. Use HTTP status and error.type when present; error.code may be a string or numeric HTTP status, and can be absent. Use error.message for display only.

Code
Name
Description
424Failed DependencyYour BYOK key was rejected by the upstream provider; check or rotate it in BYOK settings. This appears only in strict mode
400Bad RequestMalformed request, empty messages array, or missing required fields
401UnauthorizedAPI key is missing, invalid, or disabled
402Payment RequiredInsufficient account credits, per-key spend limit reached, or monthly/weekly budget cap exceeded
403ForbiddenAccount is suspended or does not have permission
404Not FoundThe requested model, generation, key, or other resource does not exist
409ConflictThe resource is not in the required state, such as a video job that is not complete
410GoneThe requested model has been retired and must be replaced
413Payload Too LargeRequest body exceeds 10 MB; reduce content size or split the request
416Range Not SatisfiableThe requested byte range is invalid for generated video content
429Too Many RequestsRate limit exceeded; check Retry-After header before retrying
432Free Quota ExhaustedTavily-compatible endpoints (/v1/search, /v1/extract, /v1/crawl, /v1/map): the free search allowance is used up, or the balance is zero or below; add credits to continue. The body is Tavily-shaped: {"detail":{"error":"..."}}
500Server ErrorInternal BazaarLink error
502Bad GatewayAll upstream sources failed; no source could handle the request.
503Service UnavailableNo upstream provider is configured for this model; contact admin
504Gateway TimeoutThe upstream connection or stream stalled and timed out

Machine-readable billing codes

A 402 response can represent different controls. These stable codes let clients show the correct next action.

Code
Description
byok_key_invalidYour own BYOK key was rejected by the upstream provider; check or rotate it in BYOK settings. This appears only in strict mode.
budget_cap_reachedA weekly or monthly advisory budget cap was reached; raise or reset the cap.
credit_limit_exceededA monthly-billing organization's hard credit line was exhausted; contact billing.
insufficient_creditsA prepaid user or organization cannot reserve enough balance; add credits.
spend_limit_exceededThe API key reached its configured daily, weekly, or monthly spend limit.
capacity_unavailableThis model is temporarily at capacity. Retry shortly; the Retry-After header says how long to wait.

Detailed error codes

When an API request fails, error.code tells you the specific reason. Two errors can both use HTTP 400 but require different fixes: unknown_model means the model name is wrong, while image_too_large means the image must be reduced. Use the table below to find the cause and next action.

Model and endpoint
Model lookup, lifecycle, pricing, modality, and endpoint compatibility errors.
Code
HTTP status
unknown_model400
invalid_model_id400
model_not_found404
model_retired410
model_endpoint_mismatch400
embedding_on_chat_endpoint400
model_not_priced400
invalid_modality_for_model400
Request and safety
Invalid parameters, context, tools, schemas, and content-safety refusals.
Code
HTTP status
missing_required_field400
unsupported_param400
max_tokens_invalid400
context_too_long400
tool_use_unsupported400
malformed_tool_messages400
invalid_response_format_schema400
invalid_tools_definition400
content_moderation403
content_filter403
unknown_4xx400
Image generation and editing
Image input, multipart editing, output, and image-pipeline errors.
Code
HTTP status
invalid_image_url400
input_images_not_supported400
invalid_content_type400
mask_not_supported400
unsupported_response_format400
missing_prompt400
missing_image400
too_many_images400
invalid_image_type400
image_too_large400
invalid_n400
pipeline_error502
no_images502
Upstream routing
Sanitized provider connectivity, authentication, throttling, and availability errors.
Code
HTTP status
upstream_unreachable502
upstream_auth_failed502
upstream_rate_limited429
capacity_unavailable429
upstream_unavailable502/503
byok_key_invalid424

Content Moderation

All requests are automatically reviewed before being sent to the model. Requests that violate our usage policy are rejected before dispatch and return a 403 error.

Categories that get blocked

Moderation is based on our Acceptable Use Policy and primarily blocks the following categories of content:

  • Sexual exploitation content
  • Violent threats
  • Operational guidance for illegal acts
  • Biohazard-related content

403 response format

A request blocked by moderation returns an error in this shape:

{
  "error": {
    "message": "Your prompt was blocked by content moderation.",
    "type": "invalid_request_error",
    "code": "content_filter"
  }
}

Blocked requests are not billed — you are not charged for a rejected request.

Appealing a false positive

If you believe a request was blocked incorrectly, contact support with the request time and (if convenient) the request id, and we'll review it.

Privacy

Requests are automatically reviewed for content safety; the text and images of your latest input may be sent to a third-party content-safety scoring service.

For requests judged violating, suspected, near the threshold or randomly sampled, we may keep the full text or the leading portion for compliance evidence, dispute handling and auditing, with no fixed deletion period promised.

Other ordinary successful requests do not have their prompts or responses retained; requests made with ZDR keys are not subject to the first two points.

Rate limits, budgets, and emergency brakes

These controls can reject an otherwise valid request. They are separate from provider failures and require different recovery actions.

Control
HTTP status
How to identify it
Request rate limit429String code or numeric code 429; use Retry-After and X-RateLimit-* headers.
Rate-limit penalty block429Numeric code 429 and a temporary restriction message; use Retry-After.
Global spend emergency brake503Numeric code 503, global spend-limit message, and Retry-After of 30 or 300 seconds.
Org/team/member/user spend brake429Numeric code 429 and a spend circuit-breaker message naming the affected scope.
Billing and budget controls402Use the stable billing string codes listed above.

Error envelopes vary by endpoint. Use HTTP status and error.type when present; error.code may be a string or numeric HTTP status, and can be absent. Use error.message for display only.

Video and media resource state

Video validation commonly returns numeric code 400. A missing job returns 404, a retired model returns 410, unfinished video content returns 409, and an invalid video byte range returns 416. Poll until completion or correct the Range header before retrying.

Retry policy

Retry only failures that may recover without changing the request. When Retry-After is present, wait that long; otherwise use exponential backoff with jitter. Limit attempts and avoid layering manual retries on top of SDK automatic retries.

Retry with backoff
429, 502, 503, and 504. Respect Retry-After when provided. For generation requests, do not create a second job after an ambiguous network failure until you have checked the original job.
Fix before retrying
400, 401, 402, 403, 404, 409, 410, 413, and 416. Change the request, credentials, balance, permissions, resource state, or Range header first.

Handling Errors

import random
import time
from openai import OpenAI, APIStatusError

client = OpenAI(
    base_url="https://api.bazaarlink.ai/v1",
    api_key="sk-bl-YOUR_API_KEY",
    max_retries=0,  # Avoid double retries; this example handles them.
)

RETRYABLE = {429, 502, 503, 504}

for attempt in range(5):
    try:
        response = client.chat.completions.create(
            model="openai/gpt-4o",
            messages=[{"role": "user", "content": "Hello!"}],
        )
        break
    except APIStatusError as error:
        if error.status_code not in RETRYABLE or attempt == 4:
            raise
        retry_after = error.response.headers.get("Retry-After")
        delay = (
            float(retry_after)
            if retry_after
            else min(8, 0.5 * (2 ** attempt)) + random.uniform(0, 0.25)
        )
        time.sleep(delay)

Streaming Error Formats

Errors that occur before any tokens are streamed return a standard HTTP error response with a JSON body.

After a stream has started, the HTTP response is already 200. Parse every SSE data frame and treat a top-level error or choices[0].finish_reason === "error" as a failed, incomplete response.

If a stream fails after it starts, BazaarLink may emit a final SSE event with a top-level error object followed by data: [DONE]. Treat the response as incomplete; do not use partial output as a completed result.

data: {"error":{"message":"The request could not be completed.","type":"upstream_error","code":502}}

data: [DONE]

// Branch on error.code; error.type can vary by failure path.

Versioning

BazaarLink exposes a single stable API path, /v1 — there are no date-pinned versions or version headers to manage. The API evolves continuously rather than through numbered releases.

Non-Breaking Changes

These ship without prior notice:

  • New endpoints
  • New models added to the catalog
  • New optional request parameters
  • New response fields
  • New schemas with optional properties
  • Additional response status/error codes
Write clients defensively
Ignore response fields you don't recognize, and don't fail on unknown values in enum-like fields — new ones are added as the catalog and feature set grow.

Breaking Changes

These are rare, and cover:

  • Removing or renaming an endpoint, parameter, or response field
  • Changing a field's type
  • Making an optional parameter required

When they do happen, a breaking change applies to a specific endpoint rather than the whole /v1 surface — there's no single version bump that could break every integration at once. We don't yet publish a formal changelog with a Breaking-change tag (see Staying Current below); for anything integration-critical, reach out via Support before you rely on undocumented behavior.

Deprecation Policy

The one routine "breaking" event you should expect: individual models get retired as upstream providers deprecate them. Query a model's current status via GET /v1/models.

GET https://api.bazaarlink.ai/v1/models
Authorization: Bearer sk-bl-YOUR_API_KEY

# A model within 30 days of its deprecation date shows in the catalog
# with an "EOL" badge on the Models page. After the effective date it's
# dropped from the catalog and calls return:
# 410 { "error": { "type": "model_not_available", "code": "model_retired" } }

Staying Current

We don't yet publish a dedicated API changelog or RSS feed. For now, check this page directly, watch a model's status via GET /v1/models, or contact Support if you need advance notice for a critical integration.

Support
Support
Hi! How can we help you?
Send a message and we'll get back to you soon.