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:
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.
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
1
2
3
4
5
6
7
8
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.
finish_reason uses BazaarLink-normalized values such as stop, length, tool_calls, content_filter, and error.
JSON
1
2
3
{"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.
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.
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.
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.
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.
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
typeResponse = {
id: string;
object: "chat.completion" | "chat.completion.chunk";
created: number; // Unix timestampmodel: string;
choices: (NonStreamingChoice | StreamingChoice)[];
usage?: ResponseUsage;
};
typeNonStreamingChoice = {
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 sectionreasoning_details?: Array<{ type: string; text?: string }>;
};
};
typeStreamingChoice = {
index: number;
finish_reason: string | null;
delta: {
role?: string;
content?: string | null;
tool_calls?: ToolCall[];
reasoning?: string;
reasoning_details?: Array<{ type: string; text?: string }>;
};
};
typeResponseUsage = {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
cost: number; // USD amount charged to the customer for this requestprompt_tokens_details?: {
cached_tokens: number; // Tokens served from prompt cache (reduced cost)cache_write_tokens?: number; // Tokens written to cache in this requestaudio_tokens?: number;
};
completion_tokens_details?: {
reasoning_tokens?: number; // Thinking/reasoning tokens (e.g. o3, Qwen3, DeepSeek R1)image_tokens?: number;
};
};
typeToolCall = {
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.
1
2
3
4
5
6
7
8
9
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.
1
2
3
4
5
6
7
8
9
10
11
12
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.
1
2
3
4
5
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.
1
2
3
4
5
6
7
8
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)
1
2
3
4
5
6
# 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.
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.
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}.
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 ID
Modality
Tasks
x-ai/grok-imagine-video-1.5-lite
text+image->video
t2v, i2v
bytedance/seedance-2.5
text+image+audio+video->video
—
google/veo-3.1
text+image->video
t2v, i2v
bytedance/seedance-1-5-pro
text+image->video
t2v, i2v
bytedance/seedance-2.0
text+image+audio+video->video
t2v, i2v
bytedance/seedance-2.0-fast
text+image+audio+video->video
t2v, i2v
minimax/hailuo-3-max
text+image->video
t2v, 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.
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.
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
1
2
3
4
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.
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
Parameter
Type
Required
Description
model
string
Yes
Speech model id, e.g. elevenlabs/eleven-flash-v2.5.
input
string
Yes
Text 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.
voice
string
Yes
Voice to use. Its format depends on the model — see Voice rules below.
response_format
string
No
mp3 (default) or pcm. opus, aac, flac and wav are accepted by the API but currently rejected by ElevenLabs models with 400.
speed
number
No
Speaking speed from 0.25 to 4.0. Not every model applies it.
instructions
string
No
Style instructions for the voice. Not every model applies it.
stability
number
No
Voice 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_boost
number
No
Similarity to the original voice, 0–1 (default 0.75). Not used by Eleven v3.
style
number
No
Style exaggeration, 0–1 (default 0). Above 0 adds latency. Not used by Eleven v4.
use_speaker_boost
boolean
No
Boosts similarity to the original speaker (default true); adds a little latency.
clone_strength
number
No
Clone strength, 0–1 (0 is recommended for most voices). Only for your own professional clone voices (voice_…) trained for Eleven v4.
language_code
string
No
Two-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_effects
object
No
Optional 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.
Voice
Description
Preview
george
Male, middle-aged, British accent. Mature and steady, suited to narration and storytelling.
sarah
Female, young adult, American accent. Professional and clear, suited to broadcast-style reading.
adam
Male, middle-aged, American accent. Everyday and approachable, suited to short social videos.
alice
Female, middle-aged, British accent. Professional and informative, suited to explainers and teaching.
bella
Female, middle-aged, American accent. Professional and informative, suited to explainers and teaching.
bill
Male, senior, American accent. Crisp and direct, suited to advertising voice-overs.
brian
Male, middle-aged, American accent. Polished and refined, suited to social and brand content.
callum
Male, middle-aged, American accent. Expressive, suited to characters and animation.
charlie
Male, young adult, Australian accent. Energetic and lively, suited to conversation.
chris
Male, middle-aged, American accent. Relaxed and casual, suited to conversation.
daniel
Male, middle-aged, British accent. Formal and measured, suited to news-style and educational reading.
eric
Male, middle-aged, American accent. Polished and warm, suited to conversation.
harry
Male, young adult, American accent. Rough and gritty, suited to character voices.
jessica
Female, young adult, American accent. Bright and cute, suited to conversation.
laura
Female, young adult, American accent. Playful with attitude, suited to social content.
liam
Male, young adult, American accent. Confident and upbeat, suited to social content.
lily
Female, middle-aged, British accent. Confident and clear, suited to explainers and teaching.
matilda
Female, middle-aged, American accent. Upbeat and friendly, suited to explainers and teaching.
river
Neutral voice, middle-aged, American accent. Calm and even, suited to conversation.
roger
Male, middle-aged, American accent. Polished and confident, suited to conversation.
will
Male, 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.
1
2
3
4
5
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.
Invalid 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).
401
Missing or invalid API key. Send Authorization: Bearer with your BazaarLink key.
402
Insufficient credits. Top up and retry.
403
A management key cannot call models, or the account is suspended. Use a standard API key.
429
Rate limit reached. Wait for the Retry-After header and retry.
502
The model service is temporarily unavailable or the audio transfer was interrupted. Retry; failed requests are not charged.
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
Parameter
Type
Required
Description
model
string
Yes
Transcription model id, e.g. elevenlabs/scribe-v2.
file
file
Yes
Audio 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_format
string
No
json (default) returns {text}; verbose_json adds language, duration and segments/words.
language
string
No
Optional language hint such as en or zh. Leave empty to auto-detect.
prompt
string
No
Optional context text to help with names and terms. Not every model applies it.
timestamp_granularities[]
string[]
No
word 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.
Invalid 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.
401
Missing or invalid API key.
402
Insufficient credits. Top up and retry.
413
audio_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.
429
Rate limit reached. Wait for the Retry-After header and retry.
502
The model service is temporarily unavailable or the audio transfer was interrupted. Retry; failed requests are not charged.
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
Field
Type
Required
Description
name
string
Yes
Voice name, 1–60 characters.
files
file[]
Yes
1 to 5 audio recordings of the voice (mp3, wav, m4a, ogg, flac, webm).
consent
boolean
Yes
Must be true: you confirm you have the right to clone and use this voice (see Consent).
consent_statement_version
string
Yes
The consent statement version you confirmed. Currently "v1".
description
string
No
Optional, up to 200 characters.
remove_background_noise
boolean
No
Optional 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.
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.
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
Status
Code
Meaning
400
bad_request
Malformed request: not multipart/form-data, no audio file, description too long, or limit outside 1–100.
400
consent_required
consent=true is missing.
400
consent_version_mismatch
The consent statement version is out of date; confirm again with the current version.
400
invalid_voice_name
The name is empty, longer than 60 characters, or not allowed.
400
too_many_files
More than 5 audio files.
401
invalid_api_key
Missing or invalid API key.
402
insufficient_balance
Balance too low (the creation fee or the minimum balance).
403
email_unverified
Verify your e-mail address first.
403
voice_cloning_not_enabled
Your account is not enabled for custom voices yet; contact customer support to apply.
403
voice_cloning_disabled
Custom voices are not enabled for this service right now.
404
voice_not_found
No such voice (or it is not yours).
404
not_found
Not found, including every /v1/voices request while the feature is not enabled for your account.
405
method_not_allowed
That method is not available on this path.
409
duplicate_request
The same Idempotency-Key is still being processed; wait and retry.
413
file_too_large
A file is over 10 MB or the files together are over 25 MB.
415
unsupported_file_type
Audio type not supported; use mp3, wav, m4a, ogg, flac, webm.
422
voice_rejected
The 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.
429
voice_create_rate_limited
Daily creation limit reached; try again tomorrow (UTC).
429
voice_limit_reached
You have reached the maximum number of custom voices.
502
upstream_failed
The voice could not be created; you were not charged. Retry with a new Idempotency-Key.
503
voice_capacity_full
Custom voice capacity is full for now; you were not charged. Try again later.
504
upstream_timeout
The 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
Code
Type
Required
Meaning
platform
string
Yes
Platform id from the catalog, such as twitter or instagram.
action
string
Yes
Action supported by that platform; see the catalog for the available pairs.
params
object
No
Action-specific inputs. Use required_params and optional_params in the catalog.
mode
sync | async
No
Optional. async returns a job to poll; async-only actions return a job even when omitted.
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.
{"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.
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.
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
1
2
3
4
5
6
7
8
9
10
11
12
13
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?"},
],
}],
)
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,...`)
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.
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.
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{"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.
// /v1/models — Response SchematypeModelsResponse = {
data: Model[];
};
typeModel = {
id: string; // Model ID (e.g. "openai/gpt-4o")name: string; // Human-readable namecontext_length: number | null; // Max context window in tokensmodality: 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 oneinput_cache_write?: string; // Cache write price per token (USD); only when the model publishes oneimage_output?: string; // Image output price per token (USD); only token-billed image models (display only)
};
description?: string | null; // Model descriptiontop_provider?: {
max_completion_tokens?: number;
};
supported_parameters?: string[]; // e.g. ["tools", "response_format", "reasoning"]pricing_tiers?: { // Present only for models with input-length tiersabove_prompt_tokens: number; // Ascending; strict "greater than" thresholdprompt: string; // USD per token, override tiercompletion: string; // USD per token, override tierinput_cache_read?: string; // USD per token, only when the tier overrides itinput_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.
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.
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.
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].
1
2
3
4
5
6
7
: 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
importOpenAIfrom"openai";
const client = newOpenAI({
baseURL: "https://api.bazaarlink.ai/v1",
apiKey: "sk-bl-YOUR_API_KEY",
});
const controller = newAbortController();
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 }
);
forawait (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).
1
2
3
4
5
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
1
2
3
4
5
6
7
8
9
10
11
12
13
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.
1
2
3
4
5
6
7
8
9
10
11
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 inenumerate(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.
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.
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-...).
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.
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.
1
2
3
4
5
6
7
{"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:
1
2
3
4
5
6
7
{"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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 inrange(5):
try:
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
breakexcept APIStatusError as error:
if error.status_code notin RETRYABLE or attempt == 4:
raise
retry_after = error.response.headers.get("Retry-After")
delay = (
float(retry_after)
if retry_after
elsemin(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.
1
2
3
4
5
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.
1
2
3
4
5
6
7
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.