# BazaarLink Full Agent Context > Consolidated public product and API documentation for AI assistants and offline retrieval. Canonical pages and machine-readable specifications remain authoritative. Source index: https://bazaarlink.ai/llms.txt OpenAPI: https://bazaarlink.ai/docs/openapi.yaml Blog Markdown index: https://bazaarlink.ai/blog.md Research brief: https://bazaarlink.ai/research/llm-resale-substitution-2026.md # BazaarLink — Free LLM API Gateway > Free OpenAI-compatible AI API gateway with 170+ models (GPT-4o, Claude, Gemini, DeepSeek, Llama). Free API key on signup — no credit card. TWD billing and Taiwan unified invoices available for paid plans. ## Quick facts - Base URL: `https://api.bazaarlink.ai/v1` - Auth: `Authorization: Bearer sk-bl-...` - Compatibility: OpenAI SDK drop-in - Free tier: trial credits on signup, plus `auto:free` model for zero-cost inference - Keys: create them at https://bazaarlink.ai/keys, or mint more with a management key via `POST /v1/keys` ## Get an API key 1. Sign up at https://bazaarlink.ai/login (no credit card required). 2. Open https://bazaarlink.ai/keys and create a key. 3. Hand the `sk-bl-...` value to your agent as `OPENAI_API_KEY`. Need one key per agent? Create a management key once, then call `POST /v1/keys` to mint standard keys programmatically, each with its own spend cap. ## Call a model ```python from openai import OpenAI client = OpenAI( base_url="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_KEY", ) response = client.chat.completions.create( model="auto:free", # or e.g. "openai/gpt-4o", "anthropic/claude-sonnet-4.6" messages=[{"role": "user", "content": "Hello!"}], ) ``` Model IDs are **provider-prefixed** (e.g. `openai/gpt-4o`). Always prefer the full form — common families (`gpt-*`, `claude-*`) are auto-prefixed and chat/completions resolves unambiguous bare names, but unresolvable names return HTTP 400. ## Core endpoints | Endpoint | Purpose | |---|---| | `POST /v1/chat/completions` | OpenAI-compatible chat | | `POST /v1/messages` | Anthropic-compatible messages | | `POST /v1/responses` | OpenAI Responses API | | `POST /v1/embeddings` | 20+ embedding models | | `POST /v1/images/generations` | Image generation | | `POST /v1/videos` | Video generation | | `GET /v1/models` | Public model catalog with pricing | | `GET /v1/key` | Key status + credit balance | | `GET /v1/usage` | Usage analytics for current key | | `POST /v1/keys` | Mint a key per agent (management key) | ## Discovery surfaces for agents - API catalog (RFC 9727): https://bazaarlink.ai/.well-known/api-catalog - Agent skills index: https://bazaarlink.ai/.well-known/agent-skills/index.json - LLMs text: https://bazaarlink.ai/llms.txt - Agent skill guide: https://bazaarlink.ai/skill.md ## Pricing & billing - Pay-as-you-go: same list price as upstream providers, no markup. - Subscription plans: daily AI points, tiered rate limits, per-model access. See `/v1/plans`. - Taiwan companies: TWD billing and unified invoices (統一發票) on paid plans. ## More - Blog: https://bazaarlink.ai/blog - Docs: https://bazaarlink.ai/docs - Status: https://bazaarlink.ai/api/health --- # BazaarLink Documentation > Markdown mirror of https://bazaarlink.ai/docs — regenerated at most every 10 minutes. > OpenAPI spec: https://bazaarlink.ai/docs/openapi.yaml · Index: https://bazaarlink.ai/llms.txt [Docs](/en/docs)[API Reference](/en/docs/api)[SDK Reference](/en/docs/sdk)[Agentic Usage](/en/docs/agentic)[AI Skills](/en/docs/skills) # BazaarLink Documentation BazaarLink is a unified AI API gateway for Taiwan — providing access to hundreds of models from OpenAI, Anthropic, Google, Meta, and more through a single, OpenAI-compatible API endpoint. AI Agent Skill File Load our skill file into your AI assistant (Claude, Cursor, Copilot…) to give it full knowledge of the BazaarLink API:1 ``` Read https://bazaarlink.ai/skill.md and follow the instructions to integrate BazaarLink into your app. ``` Free models & rate limits Looking for information about free models, per-minute request caps, and free credits? See [Rate limits](#limits) · [FAQ](#faq) ## Pricing BazaarLink prices model usage at zero markup (identical to each provider's official list price). Platform fees are charged at top-up (deposit): a 10% transaction fee, plus 5% Taiwan VAT on TWD channels. Billed in USD with TWD quotes and electronic unified invoices. Self-serve pay-as-you-go top-ups; enterprises may arrange monthly billing (Net-30, negotiable). ### How it works - Consumption (debit): each API call is billed by actual token usage at the provider's official USD list price, deducted from your balance — zero markup, no extra fee on consumption. - Top-up (deposit): TWD is converted to USD at the real-time selling rate and added to your balance; a 10% transaction fee is charged at top-up. - • Payment options and any channel-specific fees are shown at live checkout. - • Eligible TWD channels: applicable VAT and Taiwan e-invoice terms are shown before payment. - • Bank wire transfer: for larger or enterprise top-ups, contact us to arrange a wire transfer and custom invoicing. - Invoicing: OEN (credit card) top-ups issue a Taiwan e-invoice automatically and email it to you, with an optional tax ID. Companies needing procurement or monthly billing should contact sales (Net-30 negotiable). Example For a US$10.00 credit purchase, the selected live payment option shows the final charge, applicable fees or tax, and receipt or invoice eligibility before confirmation. The US$10.00 balance is then spent at official list prices with no further model markup. ### About the exchange rate Foreign-exchange conversion uses the real-time rate; monthly billing uses the rate at billing (statement) time, while prepaid top-ups convert at the top-up-time rate. The rate and timestamp are retained with billing records. ## Failed-request billing protection If an upstream request fails without billable usage, BazaarLink automatically returns the entire reserved amount. You pay $0 for that attempt even if the stream had already started before it failed. When you are not charged No setting is required. This rule is enabled automatically for public inference and media APIs. BazaarLink may absorb a cost already charged by the upstream provider instead of passing that failed-request cost on to you. - The upstream cannot connect, rejects the request, or returns no usable response - A stream stops before a final usage record is received, even after partial content - The response has no usage data, or only an empty all-zero usage object ### 0 output tokens does not always mean free usage.cost is the USD amount charged for the request. A response with 0 output tokens can still incur input-token charges; check usage.cost or the Activity record for the final amount. ## Quick Start ### Three Ways to Integrate Approach Best for Start Raw APIAny language, zero dependencies, full request control[→](#quickstart) OpenAI / Anthropic SDKAlready on an official SDK — swap base URL and key only[→](/en/docs/sdk#openai-sdk) Agent frameworksLangChain, Vercel AI SDK, CrewAI and other agent apps[→](/en/docs/agentic#agentic) All requests are automatically reviewed before being sent to the model; requests that violate our usage policy are rejected and return a 403. [Learn more about content moderation](#content-moderation) Get started in under 5 minutes. BazaarLink is fully compatible with the OpenAI SDK — just change the 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. ### Base URL 1 ``` https://api.bazaarlink.ai/v1 ``` api.bazaarlink.ai is an API-only entry point, deployed separately from the website. The previous base URL https://bazaarlink.ai/api/v1 remains fully supported — existing integrations do not need to change. ### Using the OpenAI SDK BazaarLink is fully compatible with the OpenAI SDK. Just change the base URL and API key — all other code stays the same. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` from openai import OpenAI client = OpenAI( base_url="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_API_KEY", ) completion = client.chat.completions.create( model="openai/gpt-4o", messages=[ {"role": "user", "content": "What is the meaning of life?"} ], ) print(completion.choices[0].message.content) ``` Need an API key? Get your API key from the [API Keys page](/keys). All keys start with `sk-bl-`. Calling from the browser (CORS) The public API answers every /v1 request with Access-Control-Allow-Origin: *, so you can call it directly from browser JavaScript with fetch or the OpenAI SDK. Do not ship your API key in front-end code — anyone who loads the page can read it; use a revocable, spend-capped key or proxy the call through your own backend. Model ID Format — use provider/model-name GET /v1/models lists ids such as "claude-sonnet-4.6" and canonical aliases such as "anthropic/claude-sonnet-4.6". Requests accept either form; use a listed id or alias.✓ claude-sonnet-4.6   anthropic/claude-sonnet-4.6 ### Bring Your Own Key (BYOK) Attach an upstream API key to your account or organization. Eligible requests use your key first. After an upstream error, a request can try another available source; if none is available, it returns an error. Manage personal keys on the keys page and organization keys in organization settings. [Go to BYOK settings →](/keys/byok) ### Content filtering Two-way content protection for your API traffic: requests carrying prompt-injection attempts are blocked (400), and sensitive data in prompts or responses (API keys, card numbers, national IDs, and more) is automatically redacted. Rules and an exemption allowlist are customizable, with usage statistics included. [Go to content filter settings →](/content-filter) ## Web search (:online) Searches the web before the model answers and provides the results as reference material. This is useful for questions that need current information, such as news, prices, recently published policies, or products. ### Supported endpoints - POST /api/v1/chat/completions - POST /api/v1/messages - POST /api/v1/responses Both streaming (`stream: true`) and non-streaming requests are supported. ### Usage **Method 1: append `:online` to the model name** 1 2 3 4 5 6 7 ``` curl https://bazaarlink.ai/api/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.4-mini:online", "messages": [{ "role": "user", "content": "What AI news is there in Taiwan today?" }] }' ``` **Method 2: use the `plugins` parameter** 1 2 3 4 5 6 7 8 9 10 11 ``` { "model": "gpt-5.4-mini", "messages": [{ "role": "user", "content": "..." }], "plugins": [{ "id": "web", "max_results": 5, "query_source": "last_user", "country": "taiwan", "time_range": "week" }] } ``` **Method 3: use `web_search_options`** 1 2 3 4 5 ``` { "model": "gpt-5.4-mini", "messages": [{ "role": "user", "content": "..." }], "web_search_options": { "search_context_size": "high", "query_source": "conversation" } } ``` You can use any one of these methods, or combine them. ### Search parameters The following parameters can be placed in the web item of `plugins` or in `web_search_options`; when both specify different values, `plugins` takes precedence. Parameter Accepted values Default Description `search_prompt`Any text—Directly sets the search query. When provided, it takes priority over `query_source`. `query_source``last_user`, `conversation``last_user`The source of the search query when `search_prompt` is absent. `last_user` uses only the latest user message; `conversation` uses the whole conversation, excluding system messages. `country`Lowercase English country name (for example, `taiwan`, `japan`, `united states`)All regionsPrioritizes sources from the specified country. If omitted, the search is not limited by region. When `country` is set, the search type is fixed to general search (`general`); specifying `topic: "news"` at the same time returns 400. `topic``general`, `news``general`The search type. If omitted, general search (`general`) is used; set `topic: "news"` explicitly when you need news results. `search_queries`String array, 1–5 items—Specifies multiple search queries at once. **Each query runs once and incurs one search charge**; results are merged and duplicate URLs are removed before they are provided to the model. Cannot be used with `search_prompt`. If some queries fail, the successful results are used and only successful queries are charged; all failures return 503 without a charge. `time_range``day`, `week`, `month`, `year` (also accepts common forms such as `d`/`w`/`m`/`y`, `past_day`, `24h`, `7d`, `30d`, `365d`; the parameter name may also be `recency` or `search_recency_filter`)No limitSearches only content from the specified period. `language`ISO 639-1 language code (such as `zh`, `en`, `ja`), single value or arrayNo limitPrioritizes content in the specified language. When an array is provided, only the first language is currently used. `include_domains`String array of domains, with wildcards (for example, `*.gov.tw`)—Searches only these domains, up to 300 domains with a maximum of 253 characters per domain. `exclude_domains`String array of domains, with wildcards—Excludes these domains, up to 150 domains. `max_results`1–10By levelThe number of reference sources returned. Can also be written as `num_results`. `engine``basic`, `fast`, `ultra-fast`, `advanced`By levelDirectly selects search depth and has the highest priority. Other values, including search engine names from other platforms, use the default depth and do not return an error. (`plugins` only) `mode``turbo`, `instant`, `fast`, `basic`, `advanced`, `deep`—Another way to select depth: `turbo`/`instant` → fastest, `fast` → fast, `basic` → standard, and `advanced`/`deep` → advanced. Unrecognized values are ignored. `search_context_size``low`, `medium`, `high`, `fast``medium`Search level; see the table below. (`web_search_options` only) Depth priority: `engine` (the four values above) > `mode` > `search_context_size` > default `medium`. The parameter format of `plugins` and `web_search_options` is compatible with OpenRouter, so existing requests can be used directly. ### Search tool syntax also compatible with Vercel AI Gateway Chat Completions `tools` also accepts the following search tool format. It is treated as one web search and handled by this platform's web search service, with the same billing and citation format: 1 2 3 4 5 6 7 8 ``` { "model": "gpt-5.4-mini", "messages": [{ "role": "user", "content": "..." }], "tools": [{ "type": "vercel:perplexity_search", "config": { "query": "2026 Taiwan semiconductor exports", "max_results": 5 } }] } ``` - Accepted type values: vercel:perplexity_search, vercel:exa_search, vercel:parallel_search, and vercel:tako_search. These type values behave and cost the same. - config.query is treated as search_prompt; config.max_results (or num_results) and config.country map to the same-named parameters above. Unrecognized fields are ignored. - These search tools are not forwarded to the model. - When one request contains multiple search tools, only the first is used. ### `web_search_preview` tool is also accepted In `tools` for Responses and other endpoints, `{ "type": "web_search_preview" }` is likewise treated as one web search and handled by this platform's web search service, with the same billing and citation format. This tool is not forwarded to the model. ### When multiple formats are used together `:online`, `plugins`, `web_search_options`, and search tools (including `web_search_preview`) can appear together, but each request performs only one search and incurs one search charge; `search_queries` is charged per query. Search-query priority is `search_queries` > the search tool's `config.query` > `search_prompt` > `query_source`. Other parameters are merged according to the parameter priorities above. A 400 `invalid_web_search_options` is returned only for parameter type errors, such as passing a number to `engine` or a non-string array to `include_domains`, or for values outside the allowed range. ### Search levels Level Search depth Reference sources (default) Description `low`StandardUp to 5More concise `medium`StandardUp to 10**Default level** `high`AdvancedUp to 10Deeper search, higher unit price `fast`FastUp to 10Faster response `medium` is used when no level is specified. ### Citations in the response Search results are returned in `annotations`, using the industry-standard `url_citation` format: 1 2 3 4 5 6 7 8 9 10 11 12 ``` "annotations": [ { "type": "url_citation", "url_citation": { "url": "https://example.com/article", "title": "Article title", "content": "Source summary content…", "start_index": 0, "end_index": 0 } } ] ``` - Each reference source has one citation entry. - When the model marks a corresponding source in its answer (for example, [source-1]), start_index and end_index identify the marker's position in the answer text; both are 0 when no marker is present. Locations by endpoint: - Chat Completions: non-streaming responses use choices[].message.annotations; streaming responses send an additional block containing only delta.annotations before [DONE]. - Responses: in annotations within output_text; streaming responses send them with completion events such as response.output_text.done. - Messages: attached to the text content block in annotations; streaming responses send them in the text_delta event before the end. ### Billing - Cost per request = model cost + web search cost. - Web search is charged per search, at the platform's listed price; advanced depth (high, or advanced depth selected with engine/mode) has a higher unit price. - Free models also incur web search charges; the model itself remains free. - Each request performs one search by default. With search_queries, the search count equals the number of queries and charges are multiplied accordingly. Backend route retries do not repeat the search or charge. - The following cases do not incur a web search charge:The search itself fails (returns 503; see below). - Individual failures when using search_queries (only successful queries are charged). - The search succeeds but the model ultimately fails to produce a response. - The search cost is included when the balance is reserved. The actual deduction is reflected in response usage.cost and the billing record. ### Errors and limits HTTP status Error code Description Charged? 503`web_search_unavailable`The web search service is paused. Try again later.No charge; the model is not called 402`insufficient_credits`The balance cannot cover this request, including search cost; the request is blocked before search.No charge 429`web_search_rate_limited`Web search requests are too frequent. Try again later.No charge 400`invalid_web_search_options`Invalid search parameters, such as an unsupported `country`, a non-string-array `include_domains`, or specifying `search_queries` and `search_prompt` together.No charge - Rate limits, model permissions, content review, and other checks for a normal request complete before search; requests blocked by these checks do not search. - Search parameters and tools (:online suffix, the web item in plugins, web_search_options, the search tools above, and web_search_preview) are handled only by this platform and are not forwarded to the model. ## Social data (:social) Append `:social` to a model name when your question is about a social platform. Before the model answers, BazaarLink fetches matching posts, profiles or comments from that platform and provides them as reference material. If the platform data cannot be fetched in time, the request falls back to the normal web search, so you still get an answer. ### Example 1 2 3 4 5 6 7 8 ``` curl https://bazaarlink.ai/api/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ --max-time 60 \ -d '{ "model": "gpt-5.4-mini:social", "messages": [{ "role": "user", "content": "Recent posts on X about GPT-6" }] }' ``` ### What to know - Wait time: social data is fetched live, so a :social request can take 20 to 40 seconds before the answer starts. After 40 seconds it falls back to the web search. Set your client timeout to at least 60 seconds. :online stays fast because it is the normal web search. - Price: queries answered with social data use the same daily free search allowance as :online (5 per day without a balance, 10 with a balance or a subscription, reset at UTC midnight). Beyond the allowance, each query is billed from the Data API price table (see GET /v1/data/catalog), on top of the model's own token charge. A query that falls back to the web search is billed like :online. - Coverage: :social reads only some social platforms, and the list depends on what is enabled; the current list is on /solutions/data-api#social-suffix. A platform that is in the Data API catalog but not on that list is not read by :social, so a query about it, or about no platform, is answered by the web search. - Compatibility: works on POST /api/v1/chat/completions, /api/v1/messages and /api/v1/responses, streaming and non-streaming, with the same citations as :online. :online and :social cannot be combined in one model name (400 invalid_web_search_options). ## Migrate from OpenRouter BazaarLink's API is OpenRouter-compatible — most integrations switch by changing two values: the base URL to https://api.bazaarlink.ai/v1 and the API key to a BazaarLink key starting with sk-bl-. - Base URL: https://openrouter.ai/api/v1 → https://api.bazaarlink.ai/v1 - API key: sk-or-... → sk-bl-... (create one at /keys) - Model IDs: same provider/model format (e.g. anthropic/claude-sonnet-4.6); full catalog at GET /v1/models - models[] fallbacks, streaming, tool calling, and structured outputs use the same request shape 1 2 3 4 5 6 7 8 ``` from openai import OpenAI client = OpenAI( - base_url="https://openrouter.ai/api/v1", - api_key="sk-or-...", + base_url="https://api.bazaarlink.ai/v1", + api_key="sk-bl-...", ) ``` Note Billing is in USD with Taiwan e-invoices available. Upstream-specific routing controls such as :nitro are ignored; see the API Reference for BazaarLink's supported routing behavior. ## Authentication All API requests require an Authorization header with your API key. 1 ``` Authorization: Bearer sk-bl-YOUR_API_KEY ``` Get your API key from the [dashboard](/keys). Keep your key secure — do not expose it in client-side code. Security Note Never expose API keys in client-side JavaScript. Always proxy requests through your backend server. ### Optional Headers `HTTP-Referer` string Your site URL, for usage tracking and analytics (optional) `X-Title` string Your app name, shown in dashboards (optional) ## Principles BazaarLink is designed around three core principles: ### 1. Unified Interface One API, one SDK, hundreds of models. Switch between OpenAI, Anthropic, Google Gemini, Meta Llama, and others without changing your code — just change the model ID. ### 2. Price Optimization BazaarLink automatically routes to the most cost-effective provider for your chosen model. You pay only for what you use, billed in USD with full invoicing support. ### 3. Request Handling When an upstream source has a problem, a request can try another available source. If none is available, the request returns an error. ## Multimodal BazaarLink supports multimodal inputs — send images, audio, and files alongside text to models that support them. Content is passed through to the upstream provider. ### Supported Modalities Input Description Example Models TextStandard text messagesAll models ImagesURL or base64 data URI — PNG, JPEG, WebP, GIF`openai/gpt-6-luna``anthropic/claude-sonnet-4.6``google/gemini-3.1-flash-lite-preview`[+97 more](/en/models) Files / PDFsDocument via base64 data URI (`data:application/pdf;base64,...`)`openai/gpt-6-luna``anthropic/claude-sonnet-4.6``google/gemini-3.1-flash-lite-preview`[+41 more](/en/models) AudioRaw base64 — no URL support. Requires `format` field`google/gemini-3.1-flash-lite-preview``xiaomi/mimo-v2.6-flash``google/gemini-3.1-pro-preview`[+15 more](/en/models) VideoURL (CDN) or base64 data URI`google/gemini-3.1-flash-lite-preview``qwen/qwen3.8-max``z-ai/glm-5v-turbo`[+38 more](/en/models) 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 32 33 34 35 ``` # Image — URL or base64 data URI curl https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"google/gemini-3.5-flash","messages":[{"role":"user","content":[ {"type":"text","text":"What is in this?"}, {"type":"image_url","image_url":{"url":"https://example.com/photo.jpg"}} ]}]}' # File / PDF — base64 data URI only, no URL curl https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"google/gemini-3.5-flash","messages":[{"role":"user","content":[ {"type":"file","file":{"filename":"doc.pdf","file_data":"data:application/pdf;base64,JVBER..."}}, {"type":"text","text":"Summarize this."} ]}]}' # Audio — raw base64, no URL. "format" is required curl https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"google/gemini-3.5-flash","messages":[{"role":"user","content":[ {"type":"text","text":"Transcribe this."}, {"type":"input_audio","input_audio":{"data":"UklGRi...","format":"wav"}} ]}]}' # Video — URL (CDN) or base64 data URI curl https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"google/gemini-3.5-flash","messages":[{"role":"user","content":[ {"type":"text","text":"Describe this video."}, {"type":"video_url","video_url":{"url":"https://example.com/clip.mp4"}} ]}]}' ``` ### Sending Images Use the content array format with image_url parts. Supported formats: PNG, JPEG, WebP, and GIF (including animated). You can include multiple images in a single message — each as a separate image_url part: 1 2 3 4 5 6 7 8 9 10 ``` curl https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer sk-bl-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4o", "messages": [{"role":"user","content":[ {"type":"text","text":"What is in this image?"}, {"type":"image_url","image_url":{"url":"https://example.com/photo.jpg","detail":"auto"}} ]}] }' ``` Sending Images Always include a text part alongside images. Text-first ordering (text part before image parts) is recommended for best compatibility across all providers. Sending Images Check the Models page for each model's supported input modalities. The modality column shows which inputs each model accepts. ## Limits BazaarLink enforces two independent limits: a rate limit on requests per minute, and a credit limit on account spending. Exceeding the rate limit returns HTTP 429; running out of credits returns HTTP 402. ### Rate Limits Rate limits are per user (not per key), measured in requests per minute (RPM). There is no daily cap. Tier is determined automatically by your account credit balance. Tier RPM Daily Usage Notes Free (< $5 credits)20 RPMUnlimitedDevelopment & testing Paid (≥ $5 credits)200 RPMUnlimitedProduction workloads When a rate limit is exceeded you receive a 429 response with a Retry-After header. Implement exponential backoff when retrying requests. ### Response Headers Responses can include x-request-id; x-generation-id on non-streaming responses; x-ratelimit-*; retry-after when applicable; and x-auto-resolved-model for auto or auto:free routing. 1 2 3 4 5 6 7 ``` x-request-id: 85e6f35c71a31f19-SJC x-generation-id: 85e6f35c71a31f19-SJC x-ratelimit-limit: 200 x-ratelimit-remaining: 198 x-ratelimit-reset: 1740000060 retry-after: 5 x-auto-resolved-model: openai/gpt-4o ``` ### Credit Limits A 402 response means your account balance or a key's spending cap has hit zero — not that you're sending requests too fast. These responses carry no rate limit headers, and if the limit is hit mid-stream you'll receive an SSE error event instead of an HTTP status change. 402 Insufficient Credits When your balance reaches $0, the API returns HTTP 402 with message "Insufficient credits. Please top up to continue." — monitor usage.cost in responses to track spending in real time. ## IP Allowlist Each API key can have its own IP allowlist, so only requests from the specified source IPs can use that key. ### When to use it This is best for production services running on servers with a fixed egress IP. If your application runs from a home network, mobile network, or another changing address, confirm that its egress IP is stable first; otherwise you may lock yourself out. ### How to set it up Go to [/keys](/keys) and open the key’s “…” menu. Choose “IP Allowlist”, enter the entries, and save. You can also use PATCH /api/v1/keys/:id with a signed-in session or a Management API key: 1 2 3 4 5 6 7 8 9 10 11 12 13 ``` PATCH https://api.bazaarlink.ai/v1/keys/KEY_ID Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY Content-Type: application/json { "allowedCidrs": [ "203.0.113.7", "198.51.100.0/24" ] } # Clear the allowlist and return to unrestricted access {"allowedCidrs": []} ``` ### Entry format Enter one value per line in the UI. In the API, put the same values in the `allowedCidrs` array. Accepted values are a bare IPv4 address, an IPv4 CIDR, or an IPv6 literal (exact match only). 1 2 3 ``` 203.0.113.7 198.51.100.0/24 2001:db8::1 ``` ### Behavior Leave it empty (send an empty [] array) to allow all source IPs. This is the default, so existing keys keep working as before. The source IP is read from the `cf-connecting-ip` header. If the list is non-empty but the source IP cannot be resolved, the request is rejected (fail-closed). ⚠️ Important: rejected requests return a generic 401 A request from outside the list—or one whose source IP cannot be resolved—returns exactly the same generic HTTP 401 as an invalid key, with no dedicated error message. This is intentional security design so someone who obtained the key cannot learn that an allowlist exists. The trade-off is that a misconfigured list looks exactly like an invalid key and is very difficult to debug. Before saving, confirm that your server’s egress IP is already in the list. ⚠️ Important: IPv6 uses exact string matching only IPv6 does not support subnet calculation; the address must match the string exactly. If your IPv6 address changes, you can be locked out. IPv4 supports /0–/32 CIDR ranges. When changes take effect After PATCH or UI save succeeds, the updating instance clears its cache immediately. Under normal conditions, other instances synchronize within about 15 seconds at most. If shared Redis is temporarily unavailable, cross-instance invalidation cannot propagate immediately and may fall back to the one-hour cache TTL. ## Personal Circuit Breaker A fixed 1-minute and 1-hour USD spend limit applied to all your API keys. New requests get HTTP 429 once a window threshold is hit; the window auto-resets on the clock boundary. `cbEnabled` boolean Enabled `cbMinuteUsd` number | null Per-minute USD limit · Use default `cbHourlyUsd` number | null Per-hour USD limit · Use default (inheriting defaults) Values must be at least 0.01 (or empty for default) [Personal Circuit Breaker · Adjust →](/keys) ## Account Security Beyond your password, BazaarLink also supports two-step verification for an extra layer of protection at sign-in. ### Enable two-step verification (TOTP) - Go to the Settings page and find the "Two-step verification (TOTP)" section, then click "Enable two-step verification." - Scan the QR code with an authenticator app (such as Google Authenticator), or enter the secret shown below manually. - Enter the 6-digit code your authenticator generates and confirm to finish enabling it. Once enabled, you'll see 8 backup codes shown only once — each is 10 characters and works only once. Save or copy them somewhere safe other than your phone; use a backup code to sign in if you lose your phone or can't reach your authenticator. Signing in after enabling From then on, every sign-in requires a code from your authenticator app (or a backup code) in addition to your password (or Google sign-in). ### Other ways to protect your account Combine these with two-step verification to further limit the damage from a leaked or misused key: - IP allowlist — restrict an API key to specific source IPs. - Personal circuit breaker — set per-minute and per-hour spend caps that pause automatically once exceeded. - Per-key spend limit — set a daily, weekly, or monthly spend cap when creating or updating an API key. ## Image Generation Generate images via /v1/chat/completions with modalities:["image"], or the OpenAI DALL·E-compatible /v1/images/generations. 1 2 3 4 ``` curl -N https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_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}' ``` [Full flow (streaming, image edits, SSE protocol, model list) →](/en/docs/api#image-generation) ## Video Generation Async 3-step flow (submit → poll → content). Video generation takes 30 seconds to 5 minutes. 1 2 3 4 5 ``` curl https://api.bazaarlink.ai/v1/videos \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"bytedance/seedance-2.0","prompt":"a bird flying over mountains","duration":3}' # → 202 { "id": "vjob_xxx", "status": "pending" } ``` [Full flow (polling, download, task types, caveats) →](/en/docs/api#video-generation) ## 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ``` 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."}, ], }], ) ``` ## 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)``MPEG``MOV``WebM` 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?"}, ], }], ) ``` [Full API reference →](/en/docs/api#chat) ## Management API Keys Management keys are designed for programmatic key management. They can create, list, update, disable, and delete standard API keys — but cannot make AI model calls. Note Management keys cannot call AI models (chat/completions/messages/embeddings). Use a standard API key for model access. ### Creating a Management Key Go to the [Management API Keys page](/keys/management) and click "Create" — it's a separate page from your standard API Keys, not a type selector on the same page. ### List Keys 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ``` GET https://api.bazaarlink.ai/v1/keys Authorization: Bearer sk-bl-YOUR_MGMT_KEY # Response { "keys": [ { "id": "clxyz123...", "name": "Production Key", "keyType": "standard", "keyPrefix": "sk-bl-abc1", "keySuffix": "XyZ9", "enabled": true, "spendLimitUsd": 10.00, "spendLimitPeriod": "month", "expiresAt": null, "createdAt": "2026-01-01T00:00:00.000Z", "lastUsed": "2026-03-01T12:34:56.000Z", "requestCount": 1234, "totalTokens": 5678901 } ] } ``` ### Create Sub-Key 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 ``` POST https://api.bazaarlink.ai/v1/keys Authorization: Bearer sk-bl-YOUR_MGMT_KEY Content-Type: application/json { "name": "Agent Key", "limit": 10.00, "limit_reset": "monthly", "expires_at": "2026-12-31T23:59:59Z" } # limit_reset: daily | weekly | monthly # expires_at: ISO 8601 datetime (optional) # Response — save the key value, it won't be shown again { "id": "clxyz789...", "name": "Agent Key", "key": "sk-bl-xyz789abcdef...", "keyType": "standard", "spendLimitUsd": 10.00, "spendLimitPeriod": "month", "expiresAt": "2026-12-31T23:59:59.000Z", "enabled": true, "createdAt": "2026-03-01T00:00:00.000Z" } ``` ### Update Key 1 2 3 4 5 6 7 8 ``` PATCH https://api.bazaarlink.ai/v1/keys/:id Authorization: Bearer sk-bl-YOUR_MGMT_KEY Content-Type: application/json {"enabled": false} # disable key {"spendLimitUsd": 5, "spendLimitPeriod": "week"} # set spend limit {"spendLimitUsd": null} # remove spend limit # Response: {"updated": true} ``` ### Revoke Key 1 2 3 4 ``` DELETE https://api.bazaarlink.ai/v1/keys/:id Authorization: Bearer sk-bl-YOUR_MGMT_KEY # Returns 204 No Content on success ``` ### Query Balance 1 2 3 4 5 6 7 8 9 10 ``` GET https://api.bazaarlink.ai/v1/credits Authorization: Bearer sk-bl-YOUR_MGMT_KEY # Response { "data": { "total_credits": 12.345, "total_usage": 3.210 } } ``` ### Query Usage 1 2 3 4 ``` GET https://api.bazaarlink.ai/v1/usage?period=month Authorization: Bearer sk-bl-YOUR_MGMT_KEY # period: day | week | month | year ``` ## App Attribution Identify your application in request headers to enable usage tracking, dashboard visibility, and fine-grained analytics. Note These headers are entirely optional and do not affect API functionality. However, setting them is recommended for debugging and usage attribution. ### Available Headers HeaderDescription HTTP-RefererYour site URL, for usage tracking and analytics (optional) X-TitleYour app name, shown in dashboards (optional) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` from openai import OpenAI client = OpenAI( base_url="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_KEY", default_headers={ "HTTP-Referer": "https://yourapp.com", # Optional: your site URL "X-Title": "My Application", # Optional: your app name }, ) response = client.chat.completions.create( model="openai/gpt-4o", messages=[{"role": "user", "content": "Hello!"}], ) ``` ## 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 `424`Failed DependencyYour BYOK key was rejected by the upstream provider; check or rotate it in BYOK settings. This appears only in strict mode `400`Bad RequestMalformed request, empty messages array, or missing required fields `401`UnauthorizedAPI key is missing, invalid, or disabled `402`Payment RequiredInsufficient account credits, per-key spend limit reached, or monthly/weekly budget cap exceeded `403`ForbiddenAccount is suspended or does not have permission `404`Not FoundThe requested model, generation, key, or other resource does not exist `409`ConflictThe resource is not in the required state, such as a video job that is not complete `410`GoneThe requested model has been retired and must be replaced `413`Payload Too LargeRequest body exceeds 10 MB; reduce content size or split the request `416`Range Not SatisfiableThe requested byte range is invalid for generated video content `429`Too Many RequestsRate limit exceeded; check Retry-After header before retrying `500`Server ErrorInternal BazaarLink error `502`Bad GatewayAll upstream sources failed; no source could handle the request. `503`Service UnavailableNo upstream provider is configured for this model; contact admin `504`Gateway 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_invalid`Your own BYOK key was rejected by the upstream provider; check or rotate it in BYOK settings. This appears only in strict mode. `budget_cap_reached`A weekly or monthly advisory budget cap was reached; raise or reset the cap. `credit_limit_exceeded`A monthly-billing organization's hard credit line was exhausted; contact billing. `insufficient_credits`A prepaid user or organization cannot reserve enough balance; add credits. `spend_limit_exceeded`The API key reached its configured daily, weekly, or monthly spend limit. ### 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_model``400` `invalid_model_id``400` `model_not_found``404` `model_retired``410` `model_endpoint_mismatch``400` `embedding_on_chat_endpoint``400` `model_not_priced``400` `invalid_modality_for_model``400` Request and safety Invalid parameters, context, tools, schemas, and content-safety refusals. Code HTTP status `missing_required_field``400` `unsupported_param``400` `max_tokens_invalid``400` `context_too_long``400` `tool_use_unsupported``400` `malformed_tool_messages``400` `invalid_response_format_schema``400` `invalid_tools_definition``400` `content_moderation``403` `content_filter``403` `unknown_4xx``400` Image generation and editing Image input, multipart editing, output, and image-pipeline errors. Code HTTP status `invalid_image_url``400` `input_images_not_supported``400` `invalid_content_type``400` `mask_not_supported``400` `unsupported_response_format``400` `missing_prompt``400` `missing_image``400` `too_many_images``400` `invalid_image_type``400` `image_too_large``400` `invalid_n``400` `pipeline_error``502` `no_images``502` Upstream routing Sanitized provider connectivity, authentication, throttling, and availability errors. Code HTTP status `upstream_unreachable``502` `upstream_auth_failed``502` `upstream_rate_limited``429` `upstream_unavailable``502/503` `byok_key_invalid``424` ### 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 Every request is automatically reviewed for content policy compliance. Requests found to violate policy have their content retained as compliance evidence. Other requests do not have their full prompt content retained. ### 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 limit`429`String code or numeric code 429; use Retry-After and X-RateLimit-* headers. Rate-limit penalty block`429`Numeric code 429 and a temporary restriction message; use Retry-After. Global spend emergency brake`503`Numeric code 503, global spend-limit message, and Retry-After of 30 or 300 seconds. Org/team/member/user spend brake`429`Numeric code 429 and a spend circuit-breaker message naming the affected scope. Billing and budget controls`402`Use 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 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. 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. ``` ## Tool Calling Tool calling (also known as function calling) lets models invoke external functions you define. The model decides when to call a tool and generates structured arguments — your code executes the function and returns results to continue the conversation. ### Supported Models Most frontier models support tool calling. Here are some popular choices: ### Defining Tools Each tool is a JSON object describing a function the model can call. The parameters field uses JSON Schema. `name`required string Function name (a-z, A-Z, 0-9, underscores, dashes) `description`required string Clear description of when and how the function should be used `parameters`required object JSON Schema object defining function parameters ### tool_choice Options Value Behavior `"auto"`Model decides whether to call a tool (default) `"none"`Model will not call any tool `"required"`Model must call at least one tool `{"type": "function", "function": {"name": "get_weather"}}`Model must call the specified function ### Complete Flow Tool calling is a multi-turn process: (1) send the request with tools → (2) model returns tool_calls → (3) execute functions → (4) send results back → (5) model generates final response. 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 ``` 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":"user","content":"What is the weather in Taipei?"}], "tools": [{ "type": "function", "function": { "name": "get_weather", "description": "Get current weather for a city", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "City name"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} }, "required": ["city"] } } }], "tool_choice": "auto" }' # Response carries tool_calls — run get_weather() yourself, then send the # result back with role:"tool" (same shape as the Python/TS steps 3-5) to # get the model's final answer. ``` ### Parallel Tool Calls Some models can call multiple tools in a single response. Handle each tool call and return all results: 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 ``` # Model may return multiple tool_calls if message.tool_calls: messages = [ {"role": "user", "content": "Weather and time in Tokyo?"}, message, ] for tool_call in message.tool_calls: # Execute each function if tool_call.function.name == "get_weather": result = {"temperature": 22, "condition": "Clear"} elif tool_call.function.name == "get_time": result = {"time": "2026-02-23T15:30:00+09:00"} messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result), }) # Send all results back at once final = client.chat.completions.create( model="openai/gpt-4o", messages=messages, tools=tools, ) print(final.choices[0].message.content) ``` ### Streaming Tool Calls When streaming, tool calls arrive as partial deltas indexed by position — accumulate each delta's argument string by index until finish_reason becomes "tool_calls", which signals the call is complete. 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 ``` # Streaming: tool_calls arrive as partial deltas indexed by position — # accumulate function.arguments per index until finish_reason == "tool_calls". stream = client.chat.completions.create( model="openai/gpt-4o", messages=[{"role": "user", "content": "What's the weather in Taipei?"}], tools=tools, tool_choice="auto", stream=True, ) tool_calls = {} for chunk in stream: delta = chunk.choices[0].delta if delta.tool_calls: for tc in delta.tool_calls: entry = tool_calls.setdefault(tc.index, {"id": "", "name": "", "arguments": ""}) if tc.id: entry["id"] = tc.id if tc.function.name: entry["name"] = tc.function.name if tc.function.arguments: entry["arguments"] += tc.function.arguments if chunk.choices[0].finish_reason == "tool_calls": for call in tool_calls.values(): print(call["name"], json.loads(call["arguments"])) ``` ### Simple Agentic Loop A generic pattern that keeps calling the model while it keeps requesting tools, and stops once it returns a final answer — use max_iterations to guard against infinite loops. 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 ``` # Generic loop: keep calling the model while it keeps requesting tools, # stop once it returns a plain answer. max_iterations guards against loops. messages = [{"role": "user", "content": "What's the weather in Taipei, and what time is it there?"}] max_iterations = 10 for _ in range(max_iterations): response = client.chat.completions.create( model="openai/gpt-4o", messages=messages, tools=tools, ) message = response.choices[0].message messages.append(message) if not message.tool_calls: break # model gave a final answer for tool_call in message.tool_calls: args = json.loads(tool_call.function.arguments) result = TOOL_MAPPING[tool_call.function.name](**args) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result), }) else: print("Warning: max_iterations reached without a final answer") print(messages[-1].content) ``` ### Function Definition Best Practices - Use specific, descriptive names — get_weather_forecast rather than just weather. - Write a clear description of what the function does and when to use it — the model relies solely on this text to decide whether to call it. - Constrain values with enum where possible and include an example in the description to reduce malformed arguments. - Only mark fields required that are truly required — optional fields should be genuinely omittable. ## Structured Output Force the model to return valid JSON matching a schema. This is essential for building reliable applications that parse model outputs programmatically. ### Method 1: response_format (JSON Schema) to enforce strict JSON Schema compliance: `type`required string Must be "json_schema" `json_schema.name`required string A name for the schema (used for caching) `json_schema.strict` boolean When true, guarantees exact schema compliance `json_schema.schema`required object The JSON Schema definition 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 ``` 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":"user","content":"Review the movie Inception"}], "response_format": { "type": "json_schema", "json_schema": { "name": "movie_review", "strict": true, "schema": { "type": "object", "properties": { "title": {"type": "string"}, "rating": {"type": "integer", "description": "Rating 1-10"}, "summary": {"type": "string"}, "pros": {"type": "array", "items": {"type": "string"}}, "cons": {"type": "array", "items": {"type": "string"}} }, "required": ["title", "rating", "summary", "pros", "cons"], "additionalProperties": false } } } }' ``` ### Tips - Use clear, descriptive property names — the model uses them as context. - Add descriptions to schema properties to guide the model. - Set strict: true for guaranteed schema compliance (may increase latency slightly). - Keep schemas simple — deeply nested schemas may reduce output quality. - Test with different models — some handle complex schemas better than others. ## Assistant Prefill Add a partial assistant message as the last item to request continuation on compatible model routes. 1 2 3 4 5 6 7 8 9 10 11 ``` curl https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-4.6", "messages": [ {"role":"user","content":"What is the capital of France?"}, {"role":"assistant","content":"The capital of France is"} ] }' # Model continues: " Paris, known for the Eiffel Tower..." ``` 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. ## Message Transforms The client-supplied `transforms` field is ignored and removed before an upstream request. It cannot enable or disable message transformations. BazaarLink may apply context handling under its own server-side rules; shorten the input or select a model with a larger context window when needed. Unsupported request field Do not rely on `transforms` values such as `middle-out`; they do not change how this request is routed or processed. ## Zero Data Retention BazaarLink does not store your message content by default. This page describes how your data is handled. Suitable for applications processing sensitive data. ### Current Data Handling - Message content: not stored by default, discarded from memory after processing - Billing metadata: token counts, timestamps, model IDs - Usage logs: request statistics only, no message content - Upstream forwarding: messages forwarded to upstream providers — subject to their privacy policies ## Prompt Caching Prompt caching reuses previously computed prompt tokens, significantly reducing cost and latency — especially for applications with large, repeated system prompts. Note BazaarLink automatically tracks cache savings and reflects them in billing. The `cached_tokens` field in the response shows actual cache hits; `cacheDiscount` shows the amount saved on that request. ### How It Works Whether caching needs configuration depends on the provider. OpenAI-family models cache long, repeated prompt prefixes automatically — no request changes needed. Claude (Anthropic) models only cache when the request carries an explicit cache_control breakpoint; BazaarLink does not add this for you, so a Claude request with no marker is never cached. BazaarLink forwards whatever cache markers you send as-is and reports the resulting cache read/write tokens in the usage response. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ``` # OpenAI-family models: nothing to add, long repeated prefixes cache automatically. response = client.chat.completions.create( model="openai/gpt-4o", messages=[ {"role": "system", "content": "You are an expert..."}, # cached automatically if long/repeated {"role": "user", "content": "Question here"}, ], ) # Check cache savings in the response usage usage = response.usage print(f"Prompt tokens: {usage.prompt_tokens}") print(f"Cached tokens: {usage.prompt_tokens_details.cached_tokens}") print(f"Cache savings: {usage.prompt_tokens_details.cached_tokens / usage.prompt_tokens * 100:.1f}%") ``` Claude needs an explicit cache_control marker Add cache_control: {"type": "ephemeral"} to the content block you want cached, as shown below. Anthropic also enforces its own minimum prompt length before it will cache at all — short prompts silently skip caching even with the marker present. Check cached_tokens (OpenAI-shape) or cache_read_input_tokens / cache_creation_input_tokens (Anthropic-shape) in the response to confirm a hit. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ``` # Claude models: you must mark the block to cache yourself. response = client.chat.completions.create( model="anthropic/claude-sonnet-4.6", messages=[ { "role": "system", "content": [ {"type": "text", "text": "You are an expert...", "cache_control": {"type": "ephemeral"}} ], }, # BazaarLink does not add cache_control on your behalf {"role": "user", "content": "Question here"}, ], ) usage = response.usage print(f"Cache read tokens: {getattr(usage, 'cache_read_input_tokens', 0)}") print(f"Cache write tokens: {getattr(usage, 'cache_creation_input_tokens', 0)}") ``` ## Reasoning Tokens Reasoning models (e.g., DeepSeek R1, o1 series) think internally before producing their final answer. These internal tokens are called reasoning tokens and are billed separately. Note BazaarLink reports reasoning tokens in `usage.completion_tokens_details.reasoning_tokens` and shows them separately in billing. ### Reading Reasoning Tokens from Responses 1 2 3 4 5 6 7 8 9 10 11 12 ``` response = client.chat.completions.create( model="deepseek/deepseek-r1", messages=[{"role": "user", "content": "Solve: if f(x) = x^2 + 3x, what is f(5)?"}], ) # Read reasoning tokens from usage usage = response.usage print(f"Completion tokens: {usage.completion_tokens}") if hasattr(usage, "completion_tokens_details"): details = usage.completion_tokens_details print(f"Reasoning tokens: {details.reasoning_tokens}") print(f"Output tokens: {details.accepted_prediction_tokens}") ``` 1 2 3 4 5 6 7 8 9 ``` const response = await client.chat.completions.create({ model: "openai/gpt-5.5", messages: [{ role: "user", content: "Prove that sqrt(2) is irrational." }], // @ts-ignore - BazaarLink extension reasoning_effort: "high", // low | medium | high }); const usage = response.usage; console.log("Reasoning tokens:", usage?.completion_tokens_details?.reasoning_tokens); ``` ### Thinking Mode Control Some models support toggling their "thinking" mode. Thinking mode generates internal reasoning tokens before producing the final answer, improving quality at the cost of more tokens. Model FamilyParameterDefault qwen3-*enable_thinking: booleanfalse (platform default) openai/gpt-5.5, gpt-5.6-sol, gpt-5.4reasoning_effort: "low" | "medium" | "high"medium deepseek/deepseek-r1—Always enabled (cannot disable) 1 2 3 4 5 6 7 8 ``` # Qwen3: explicitly enable thinking mode response = client.chat.completions.create( model="qwen/qwen3.7-flash", messages=[{"role": "user", "content": "Prove the Pythagorean theorem"}], extra_body={"enable_thinking": True}, # opt-in to thinking ) # usage.completion_tokens_details.reasoning_tokens shows thinking token count ``` ### Unified reasoning Object (New Format) BazaarLink also supports the unified reasoning object, which works across all model families with a single consistent API: FieldValuesApplies to reasoning.effort"xhigh" | "high" | "medium" | "low" | "none"OpenAI o-series, Grok reasoning.max_tokensintegerAnthropic Claude, Gemini reasoning.excludebooleanHide thinking from response (model still reasons) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ``` // Claude extended thinking — specify thinking budget in tokens const response = await client.chat.completions.create({ model: "anthropic/claude-sonnet-4.5", messages: [{ role: "user", content: "Prove the Pythagorean theorem" }], // @ts-ignore - BazaarLink extension reasoning: { max_tokens: 5000 }, }); // OpenAI GPT-5.6 Sol — specify effort level const response2 = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Solve this math problem..." }], // @ts-ignore - BazaarLink extension reasoning: { effort: "high" }, }); // Hide thinking content from response (model still thinks) const response3 = await client.chat.completions.create({ model: "anthropic/claude-sonnet-4.5", messages: [{ role: "user", content: "What is 2+2?" }], // @ts-ignore - BazaarLink extension reasoning: { max_tokens: 2000, exclude: true }, }); ``` Pricing Thinking tokens are billed as completion tokens. Some providers charge a higher rate for thinking mode — Qwen3 costs 2x standard pricing when thinking is active. BazaarLink defaults Qwen3 to enable_thinking=false to avoid unexpected costs. ## Latency & Performance Optimizing AI API response latency is critical for user experience. Below are the key factors that affect latency in the BazaarLink architecture and best practices for optimization. Note BazaarLink records `duration_ms` (end-to-end latency) and `throughput` (tokens/sec) for every request — look them up via GET /v1/generation?id=..., or in the Activity Export CSV. ### Factors That Affect Latency - Model size: larger models (70B+) are generally slower to generate - Provider load: varies across providers and time of day - Token count: higher max_tokens means longer completion time - Streaming vs. non-streaming: stream: true delivers the first token faster - Context length: very long contexts increase pre-processing time ### Optimization Tips - Prefer streaming (stream: true) to improve perceived latency - Choose smaller models (flash/mini/haiku) for latency-sensitive scenarios - Enable prompt caching to reduce latency for repeated requests 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ``` import time # Measure time to first token with streaming start = time.time() first_token_time = None stream = client.chat.completions.create( model="google/gemini-3.5-flash", # Fast model messages=[{"role": "user", "content": "Hello!"}], stream=True, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content and not first_token_time: first_token_time = time.time() - start print(f"Time to first token: {first_token_time:.3f}s") ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` # Look up per-request latency and throughput after the fact, using the # generation ID from the response (or the final streamed chunk). curl "https://api.bazaarlink.ai/v1/generation?id=BAZAARLINK_REQUEST_ID" \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" # Response { "data": { "id": "85e6f35c71a31f19-SJC", "model": "google/gemini-3.5-flash", "duration_ms": 842, "throughput": 61.2, "usage": { "prompt_tokens": 12, "completion_tokens": 48, "total_tokens": 60 } } } ``` ## Uptime Optimization After an upstream error, a request can try another available source; if none can handle it, the request returns an error. Circuit breakers limit repeated attempts, and provider status is monitored. Note BazaarLink tracks availability for all upstream providers. When a provider's error rate exceeds a threshold, the circuit breaker automatically triggers and routes requests to the next available provider. ### Availability Mechanisms - Circuit breaker: auto-detects and isolates failing providers - When an upstream source has a problem, a request can try another available source. If none is available, the request returns an error. - Provider health monitoring: continuously tracks error rates and latency per provider - Retry logic: transient errors (5xx) are automatically retried ### Circuit Breaker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ``` # If an upstream source returns an error, the request can try another available source. # Requests handled by a platform model are billed at that model's catalog price. response = client.chat.completions.create( model="openai/gpt-4o", # Primary model messages=[{"role": "user", "content": "Hello!"}], extra_body={ "models": [ # Local fallback chain, tried in order "openai/gpt-4o", "anthropic/claude-sonnet-4.6", "google/gemini-3.5-flash", ], }, ) # Check whether another source handled the request (in usage logs) # "is_failover": true indicates the primary provider was bypassed ``` Provider health monitoring is an internal ops view GET /api/admin/provider-health is an internal endpoint for the ops dashboard, gated by admin auth. It returns a full operational payload (per-provider request volume, error rates, latency percentiles, failover stats, and more) — it's not a public customer API, so we don't reproduce the exact fields here. ## Guardrails Add content-safety mechanisms to your API requests to filter harmful content and enforce compliance policies. BazaarLink currently offers customizable content-filter guardrails only at the organization level; personal (non-org) API keys have no equivalent setting — content safety relies entirely on each upstream model provider's own built-in safety systems. Current Scope Personal API keys have no built-in custom guardrails — content safety relies entirely on the upstream provider's own safety systems. If you need customizable content-filter rules (block/redact/flag, keyword and regex rules, built-in PII templates), create an organization and use an organization API key — configured under "Content Filter Guardrails." ### Planned Features (not yet available for either personal or org keys) Guardrail Description PII detectionDetect and redact personally identifiable information Topic restrictionRestrict model responses to approved topics only Output validationValidate model outputs against custom rules before returning ### Current Behavior Personal API keys: all upstream providers have their own content safety systems — model responses that trigger content filters return with finish_reason: "content_filter", and BazaarLink applies no additional filtering. Organization API keys: org_admin can configure custom rules (block/redact/flag) under "Content Filter Guardrails," applied before text reaches the model. ## Cursor IDE Integration Use BazaarLink as Cursor's OpenAI Override URL. Drop-in setup with auto-conversion of Responses API bodies, tool-format normalization, and the bz- prefix convention for Claude models. ### Quick setup In Cursor, open Settings → Models, then: - Set Override OpenAI Base URL to https://api.bazaarlink.ai/v1 - Set Override OpenAI API Key to your sk-bl-... BazaarLink key - Add the model name you want — see below for Claude (the bz- prefix). Backward compatibility The older /cursor compatibility endpoint still works, on both hosts (https://api.bazaarlink.ai/v1/cursor and https://bazaarlink.ai/api/v1/cursor). It is a thin re-export of /v1/chat/completions — new setups should point at the base URL above instead. ### The bz- prefix (for Claude models) Cursor's client-side validation reroutes any model name starting with claude- through Cursor's own Anthropic integration, bypassing your Override URL. To force Cursor to send the request to BazaarLink, prefix the model name with bz-. The server strips the prefix and resolves the rest via the alias map. Type in CursorResolves to bz-claude-sonnet-4.6anthropic/claude-sonnet-4.6 bz-claude-opus-4.7anthropic/claude-opus-4.7 gpt-4oopenai/gpt-4o gemini-3.5-flashgoogle/gemini-3.5-flash The dot-vs-hyphen variants are normalized: bz-claude-sonnet-4.6 and bz-claude-sonnet-4-6 both resolve to the same model. ### CURSOR_MODEL_MAP env var (operator override) For self-hosted BazaarLink deployments, set this env var to remap arbitrary Cursor-side model names to catalog canonical ids: 1 ``` CURSOR_MODEL_MAP=gpt-claude-sonnet:anthropic/claude-sonnet-4.6,gpt-opus:anthropic/claude-opus-4.7 ``` Now gpt-claude-sonnet typed in Cursor maps to anthropic/claude-sonnet-4.6 server-side. Useful when you want Cursor to think a model is GPT-family (so it routes through the Override URL) while you actually serve Claude. ### What happens automatically When a request hits /v1/chat/completions, BazaarLink applies these compatibility transforms transparently — you don't need to do anything client-side: - Auto-detects Responses API bodies — if the body has input instead of messages, it's converted to Chat Completions shape (Cursor sends Responses API format for GPT-family models). - Wraps flat tool definitions — Cursor Agent sends { name, description, parameters } without a function wrapper. We wrap it so Anthropic doesn't reject as Tool '' not found in provided tools. - Coerces malformed tool_choice — Cursor sends { type: "auto" } (object form, no function). The OpenAI spec requires the string form for auto/none/required, so we coerce. - Strips OpenAI-only fields when routing to non-OpenAI providers — parallel_tool_calls, logprobs, top_logprobs, logit_bias, service_tier, user are removed before forwarding (Anthropic returns 400 otherwise). - Maps max_output_tokens → max_tokens and strips Responses-API-only fields (previous_response_id, truncation, background, store). The reasoning field is preserved for Chat-Completions-native bodies. ### Cursor Agent mode Tool calling works through the standard Chat Completions tool-call flow. Cursor sends tools (Shell, Read, Write, Grep, etc.) with tool_choice: "auto"; BazaarLink forwards to your chosen provider, which decides whether to call a tool. Tool calls return as standard OpenAI tool_calls deltas; Cursor executes locally and continues the conversation. Works the same regardless of whether you pick gpt-4o (native OpenAI) or bz-claude-sonnet-4.6. Debugging upstream rejects If you see provider 4xx errors, check the admin Provider Health panel. Every 4xx response is persisted with the full upstream error body and a summary of the request body we forwarded — click any 🔴 row to expand the JSON. ## Model Routing BazaarLink uses the provider/model-name format to route requests to the correct upstream provider. This gives you access to all major models through a single API endpoint. ### Model ID Format 1 2 3 4 5 6 7 ``` {provider}/{model-name} # Examples openai/gpt-5.4-mini anthropic/claude-sonnet-4.6 google/gemini-3-flash-preview deepseek/deepseek-v4.1-flash ``` ### Routing Priority When you send a request, BazaarLink resolves the upstream provider in this order: - Exact match — looks for a model route matching the full model ID - Provider wildcard — falls back to provider/* routes (e.g. openai/*) - Global wildcard — falls back to * wildcard routes - Default provider key — for a known catalog model only, uses enabled keys marked as default Browse all available models on the [Models page](/en/models). ## Auto Router Auto Router v3 scores the request into one of 14 task tiers, then uses the current primary and fallback chain configured for that tier. The paid and free tables are managed separately in the admin console. - auto — paid routing table. The successful resolved model is billed at its published price. - auto:free — free routing table. Calls within the free quota cost $0; after the quota, funded accounts may switch to paid auto routing unless paid fallback is disabled. ### How to Use Set the model to "auto" (paid) or "auto:free" (free) to enable automatic routing: 1 2 3 4 ``` curl https://api.bazaarlink.ai/v1/chat/completions \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"auto","messages":[{"role":"user","content":"Review this TypeScript function"}]}' ``` ### How v3 selects a tier General tiers are simple, standard, complex and reasoning. Specialized tiers are coding, vision, image, video, data, search, social, email, calendar and trading. Low-confidence boundary results are promoted one level instead of guessing downward. - Tier scoring: messages, tools, length, keywords and structural signals select one of 14 tiers - Hard overrides: vision, formal reasoning and specialized tasks can select a tier directly - Route lookup: the selected mode reads that tier's current primary and up to five fallbacks; a disabled tier returns 503 - Execution: BazaarLink tries the primary, then the configured fallback chain in order - Response tracking: the resolved model is returned in the response body and X-Auto-Resolved-Model header ### Current model tables The tables below are read from the same live configuration used by inference and the admin console. Administrators can change each tier's primary model, fallback order and enabled state without a deployment. ### auto Tier Primary Fallbacks State `simple``openai/gpt-5.4-nano``google/gemini-3.1-flash-lite-preview``anthropic/claude-haiku-4.5` enabled `standard``google/gemini-3-flash-preview``openai/gpt-5.4-mini``anthropic/claude-haiku-4.5` enabled `complex``google/gemini-3.1-pro-preview``anthropic/claude-sonnet-4.6``openai/gpt-5.4-pro` enabled `reasoning``anthropic/claude-opus-4.7``openai/gpt-5.4-pro``google/gemini-3.1-pro-preview` enabled `coding``openai/gpt-5.3-codex``anthropic/claude-sonnet-4.6``openai/gpt-5.4-pro` enabled `vision``openai/gpt-5.4-image-2`— enabled `image``openai/gpt-5.4-image-2`— enabled `video``bytedance/seedance-2.0-fast``bytedance/seedance-2.0``anthropic/claude-sonnet-4.6` enabled `data``openai/gpt-5.4-pro``anthropic/claude-sonnet-4.6``google/gemini-3.1-pro-preview` enabled `search``perplexity/sonar-pro``perplexity/sonar-reasoning-pro``openai/gpt-5.4-pro` enabled `social``openai/gpt-5.4-nano``google/gemini-3.1-flash-lite-preview``anthropic/claude-haiku-4.5` enabled `email``openai/gpt-5.4-nano``google/gemini-3.1-flash-lite-preview``anthropic/claude-sonnet-4.6` enabled `calendar``openai/gpt-5.4-nano``google/gemini-3.1-flash-lite-preview` enabled `trading``anthropic/claude-opus-4.7``openai/gpt-5.4-pro``google/gemini-3.1-pro-preview` enabled ### auto:free Tier Primary Fallbacks State `simple``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `standard``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `complex``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `reasoning``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `coding``deepseek/deepseek-v4-flash-0731free``reflection/beam``nvidia/nemotron-3.5-lightning-free` enabled `vision``openai/gpt-5.4-image-2`— disabled `image``openai/gpt-5.4-image-2`— disabled `video``google/gemini-2.5-flash-lite`— disabled `data``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `search``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `social``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `email``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `calendar``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled `trading``deepseek/deepseek-v4-flash-0731free``nvidia/nemotron-3.5-lightning-free``reflection/beam` enabled Selected models offer a rate-limited free tier. Free eligibility is granted per model by the platform — call the model by its regular ID; the :free suffix is an optional alias (adding it to a paid model does not make it free). After the free quota runs out After the free quota is used, requests can continue at the listed paid model rate if the account has balance. To receive 429 instead, send `X-Free-Fallback: false` or disable the paid-tier transition in key settings. Without balance, over-quota requests return 429. When a request is actually degraded this way, the response carries an x-bl-free-fallback: daily or x-bl-free-fallback: rpm response header naming which cap fired, so you can detect the switch at request time instead of only from your bill. The header is absent from any request that was not degraded. X-Auto-Resolved-Model The actual selected model is returned in the X-Auto-Resolved-Model response header and in the response body's model field. ## Model Variants 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. ### Independent Model IDs Catalog variants such as :free, :extended, and :thinking can identify entries with distinct capabilities or catalog pricing. BazaarLink's catalog and server-side routing rules determine the upstream and customer price. 1 2 3 ``` :free :extended :thinking ``` 1 2 3 ``` :nitro :floor :exacto ``` 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. 1 ``` :online ``` ## Free Models Selected models offer a rate-limited free tier. Free eligibility is granted per model by the platform — call the model by its regular ID; the :free suffix is an optional alias (adding it to a paid model does not make it free). - Call the regular model ID (e.g. deepseek/deepseek-v4-flash). Requests are automatically served free of charge while within the free quota. - Free usage is limited per user by requests-per-minute and a daily cap. Limits scale with account tier (no credit / credited). - When you exceed the free quota and have credits, requests automatically continue on the paid tier at the listed price. Send X-Free-Fallback: false to disable automatic fallback and receive 429 instead. Without credits, over-quota requests return 429. - GET /v1/models lists a :free entry for every model with a free tier; auto:free always routes to a free model. ### Models with a free tier right now Call these model IDs directly to use the free quota. The list changes as models are added or retired — query the API for the live set. 1 2 3 4 5 6 ``` qwen/qwen3.7-flash reflection/beam nvidia/nemotron-3.5-lightning-free reflection/beam-free stealth/glyph-cluster deepseek/deepseek-v4-flash-0731free ``` ### Free quota limits Item Value Requests per minute (RPM)`10 / min` Daily request budget`60 / day` Account tier multiplier — no credit`× 1` Account tier multiplier — funded`× 2` Your daily budget = the daily request budget above × your account tier multiplier, counted separately per free model. auto:free additionally enforces a parallel per-IP cap. Individual models may carry tighter or looser limits set by the platform — the model page's "Free tier" block always shows the effective values. Free quota is measured in units, not raw request counts: the units consumed by a single request grow with the prompt's context length. Under the same daily quota, long-context requests therefore yield noticeably fewer usable requests than short prompts. ### After the free quota runs out After the free quota is used, requests can continue at the listed paid model rate if the account has balance. To receive 429 instead, send `X-Free-Fallback: false` or disable the paid-tier transition in key settings. Without balance, over-quota requests return 429. When a request is actually degraded this way, the response carries an x-bl-free-fallback: daily or x-bl-free-fallback: rpm response header naming which cap fired, so you can detect the switch at request time instead of only from your bill. The header is absent from any request that was not degraded. 1 2 ``` # Return 429 instead of switching to paid routing -H "X-Free-Fallback: false" ``` ## Organization Management When several people share BazaarLink, create an organization to group members into teams and manage API keys, allowed models, budgets, and billing in one place. The organization owns the shared balance, while teams and members can have their own monthly limits. Manage your organization To add teams, invite members, or change organization settings, [open Settings and choose an organization](/settings) ### Three-Tier Budget System Every API request checks member, team, and organization limits. Monthly budget or balance failures return HTTP 402; a spend emergency brake returns HTTP 429. - Member monthly budget (OrgMember.monthlyBudget) - Team monthly budget (Team.monthlyBudget) - Org credits balance (Organization.credits) ### Usage Reports Open an organization and select Reports to view monthly costs in four ways: - Overview: total spend, margin rate, daily trend chart - By Team: per-team spend, share %, model breakdown, budget utilization - By Model: per-model spend, avg price ($/1M tokens) - By Member: per-member spend — org_admin only All views support CSV export with BOM prefix for direct Excel compatibility. ### Create & Manage Organizations - Open Settings and create an organization in the Organizations section - Select the organization name to open its management area - Create teams and optionally assign cost-center codes and monthly budgets - Invite members by email, assign a role and Team - Issue API keys for members — usage is automatically tagged to the correct Team / member - View the Reports page for monthly spend broken down by Team, Model, or Member ### Member Roles `org_admin`Organization administrator. Manages all teams, members, API keys, allowed models, budgets, billing, reports, settings, and emergency brakes. `billing_viewer`Finance viewer. Can view the overview, billing, API-key list, and reports, but cannot change settings or view per-member spend. `team_admin`Team administrator. Manages only their own team's members, invites, API keys, budgets, and emergency brakes. `member`Regular member. Uses assigned organization API keys and is subject to member, team, organization, and model limits. ### What else can an organization manage? Beyond members and teams, the organization management area provides: - API keys: create keys for an organization, team, or member and restrict their allowed models - Content filtering: block, redact, or log sensitive text before it reaches a model - Allowed Models: restrict models for the organization, a team, a member, or an individual API key - Budgets and emergency brakes: set monthly limits and minute/hour spend protection - Reports and billing: review spend, model usage, team allocation, balance, credit limits, and payments - Change and security logs: track settings changes, content-filter hits, and security events ### Content filtering These are organization-owned rules that inspect text before it is sent to a model through an organization API key. An org_admin can enable, edit, and test rules under Settings → Content filtering. - block: reject the whole request with HTTP 403 before it reaches the model - redact: replace matched text with [REDACTED], then send the cleaned text - flag: send the request unchanged and record the match in the organization audit log - Use built-in sensitive-data and prompt-injection templates, or create keyword and regular-expression rules - Up to 100 rules; regular expressions are safety-checked and rules can be previewed with test text Currently limited to text input Filtering currently covers Chat Completions text, string input on Responses, and text content on Messages. Images, audio, video, some structured or multimodal content, and model output are not inspected. Do not treat it as complete data-loss prevention or output moderation. ### Management API (v1) Use the Management API to list organizations, create teams, add members, or update budgets without clicking through the web UI. `/v1/orgs` handles organization and member management; cost reports use the separate `/api/orgs/:orgId/reports/*` paths below. Authentication GET /v1/orgs lists organizations belonging to the key owner and accepts a valid API key or signed-in session. Requests that read or change a specific organization, team, or member require a Bearer management key owned by an org_admin, or that org_admin's signed-in session. Create a management key from the organization's API Keys page. ### Organizations GET`/v1/orgs` List all organizations the caller belongs to, with role and joinedAt. GET`/v1/orgs/:orgId` Get org detail including team and member counts. 1 2 ``` curl https://api.bazaarlink.ai/v1/orgs \ -H "Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY" ``` ### Teams GET`/v1/orgs/:orgId/teams` List teams with member counts, ordered by name. POST`/v1/orgs/:orgId/teams` `name`required string Team display name (must be unique within the org) `costCenterCode` string Accounting cost center code `monthlyBudget` number | null Team monthly spend cap in USD PATCH`/v1/orgs/:orgId/teams/:teamId` Partial update — include only the fields to change. DELETE`/v1/orgs/:orgId/teams/:teamId` 1 2 3 4 5 6 ``` # Create a team curl https://api.bazaarlink.ai/v1/orgs/{orgId}/teams \ -X POST \ -H "Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Engineering", "costCenterCode": "ENG-001", "monthlyBudget": 500}' ``` ### Members GET`/v1/orgs/:orgId/members` List all members with nested user (id/name/email) and team info. POST`/v1/orgs/:orgId/members` `email`required string Email of an existing BazaarLink user `role` string org_admin | billing_viewer | team_admin | member (default: member) `teamId` string Assign to a team (required if role is team_admin) `monthlyBudget` number | null Per-member monthly spend cap in USD 404 if the email address has no BazaarLink account. 409 if already a member. Default role: member. PATCH`/v1/orgs/:orgId/members/:memberId` Partial update of role, teamId, or monthlyBudget. DELETE`/v1/orgs/:orgId/members/:memberId` Returns 400 if the target is the last org_admin. 1 2 3 4 5 6 7 8 9 10 11 ``` # Add a member curl https://api.bazaarlink.ai/v1/orgs/{orgId}/members \ -X POST \ -H "Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "alice@example.com", "role": "member", "monthlyBudget": 50}' # Remove a member curl https://api.bazaarlink.ai/v1/orgs/{orgId}/members/{memberId} \ -X DELETE \ -H "Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY" ``` ### Reports API The Reports API powers the organization Reports page and can also feed internal dashboards, monthly reconciliation, or automated CSV downloads. org_admin can access every report; billing_viewer can access overview, team, and model reports, but not per-member spend. It accepts a signed-in session or Bearer management key. Pass both year and month (1–12). overview and by-team default to the current month when omitted, while by-model, by-member, and export require both values. Supplying both every time avoids endpoint-specific surprises. Endpoint Description `GET /api/orgs/:orgId/reports/overview`Total spend, margin rate, daily trend `GET /api/orgs/:orgId/reports/by-team`Per-team spend, share %, model breakdown, budget utilization `GET /api/orgs/:orgId/reports/by-model`Per-model spend, avg price ($/1M tokens) `GET /api/orgs/:orgId/reports/by-member`Per-member spend — org_admin only `GET /api/orgs/:orgId/reports/export`CSV download; add ?view=overview|by-team|by-model|by-member 1 2 3 4 5 6 7 8 9 10 11 12 ``` # Monthly overview via management key curl "https://bazaarlink.ai/api/orgs/{orgId}/reports/overview?year=2026&month=3" \ -H "Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY" # By-team breakdown curl "https://bazaarlink.ai/api/orgs/{orgId}/reports/by-team?year=2026&month=3" \ -H "Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY" # Export CSV (downloads file) curl "https://bazaarlink.ai/api/orgs/{orgId}/reports/export?year=2026&month=3&view=by-team" \ -H "Authorization: Bearer sk-bl-YOUR_MANAGEMENT_KEY" \ -o report.csv ``` ### Error response reference `401`API key invalid or revoked `403`RBAC denial (insufficient role) or model not in Allowed Models whitelist `402`Monthly budget, credit limit, or available balance is insufficient `429`Rate limit or scoped spend emergency brake; follow Retry-After when present `503`Platform-wide spend emergency brake or temporary service outage ## Allowed Models (Whitelist) Restrict which models your organization, teams, or individual members can call. Useful for blocking expensive or unvetted models, enforcing model standards, or scoping a team to a single provider. ### How it works - Three independent layers — Organization, Team, Member — each holds its own list (String[] in the database). - When all three layers are empty, every model is allowed (default behavior). - When one or more layers are non-empty, the effective list is the intersection of the non-empty layers — a model must be allowed at every restricted layer to pass. - Changes take effect within a few seconds (60s in-memory + 5min Redis cache; both are cleared on update). ### Pattern format - Exact match — e.g. openai/gpt-4o (this exact model only). - Provider wildcard — e.g. openai/* (any model under the openai/ prefix). - Lowercase only. Max 200 entries per list, 100 chars per entry. ### Where to manage Org Portal → Allowed Models. org_admin can edit org / team / member lists; team_admin can edit their own team and members within it. ### Error response when blocked Calls to a disallowed model return HTTP 403 with this body: 1 2 3 4 5 6 7 8 9 ``` HTTP/1.1 403 Forbidden Content-Type: application/json { "error": { "message": "Model is not allowed for this account", "code": "model_not_allowed" } } ``` ### Management API All endpoints accept Web Session or Bearer Management Key (sk-bl-...). PATCH replaces the entire list; pass [] to clear. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ``` # Org-level list GET /api/orgs/:orgId/allowed-models PATCH /api/orgs/:orgId/allowed-models # Team-level list GET /api/orgs/:orgId/teams/:teamId/allowed-models PATCH /api/orgs/:orgId/teams/:teamId/allowed-models # Member-level list GET /api/orgs/:orgId/members/:memberId/allowed-models PATCH /api/orgs/:orgId/members/:memberId/allowed-models # Example: restrict an org to OpenAI + a specific Anthropic model curl -X PATCH https://bazaarlink.ai/api/orgs/$ORG_ID/allowed-models \ -H "Authorization: Bearer sk-bl-..." \ -H "Content-Type: application/json" \ -d '{"allowedModels": ["openai/*", "anthropic/claude-sonnet-4.6"]}' ``` ## Circuit Breaker (Spend Kill Switch) A dual-window spend cap that blocks further requests when upstream cost spikes. Designed to contain runaway scripts, infinite loops, or stolen-key abuse before they cost real money. ### How it works - Two fixed windows are tracked in Redis per scope: 1-minute and 1-hour upstream cost (USD). - If either window's spend reaches its threshold, all subsequent requests in that scope are rejected until the window resets. - Defaults: $5 / minute, $20 / hour. Disabled by default — opt in to turn it on. - Counters live in Redis with TTL — recovery is automatic, no manual reset needed for org/team/member trips. ### Scopes (member overrides team overrides org) Each layer can set its own thresholds. Resolution order is member → team → org → platform default — the first non-null value wins per field (cbEnabled, cbMinuteUsd, cbHourlyUsd). - Org level — applies to all keys under the organization. Set in Org Portal → Circuit Breaker. - Team level — applies to all keys tagged to that team. Overrides org for those keys. - Member level — applies only to keys tagged to that member. Overrides team and org. ### Trip behavior When tripped, requests fail fast (no upstream call is made). The response is HTTP 429 with this body: 1 2 3 4 5 6 7 8 ``` HTTP/1.1 429 Too Many Requests Content-Type: application/json { "error": { "message": "Spend circuit breaker tripped at member scope (minute window: $5.2341 ≥ $5.00). Try again later or contact your organization owner." } } ``` Global vs scoped A separate global platform-wide circuit breaker (operator-controlled, not visible in the org portal) returns HTTP 503 with a Retry-After header. Operators set it to defend the platform against multi-tenant abuse — it cannot be overridden from your org settings. ### Audit log Every trip event and every configuration change is recorded: - Trip events — actions org.cb.tripped / team.cb.tripped / org_member.cb.tripped. Deduplicated to one entry per scope+window per hour, so a sustained trip doesn't flood the log. - Config changes — actions org.cb.update / team.cb.update / org_member.cb.update. Capture before/after values plus the actor. ### Management API Org admins can read and update settings via API. All endpoints accept Web Session or Bearer Management Key (sk-bl-...). Send any subset of fields in the PATCH body; null clears a field and falls back to the parent layer. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ``` # Org-level config GET /api/orgs/:orgId/circuit-breaker PATCH /api/orgs/:orgId/circuit-breaker # Team-level config GET /api/orgs/:orgId/teams/:teamId/circuit-breaker PATCH /api/orgs/:orgId/teams/:teamId/circuit-breaker # Member-level config GET /api/orgs/:orgId/members/:memberId/circuit-breaker PATCH /api/orgs/:orgId/members/:memberId/circuit-breaker # Example: tighten the org-level cap to $2/min, $10/hr curl -X PATCH https://bazaarlink.ai/api/orgs/$ORG_ID/circuit-breaker \ -H "Authorization: Bearer sk-bl-..." \ -H "Content-Type: application/json" \ -d '{"cbMinuteUsd": 2, "cbHourlyUsd": 10, "cbEnabled": true}' # GET response (org scope) { "settings": { "cbEnabled": true, "cbMinuteUsd": 2, "cbHourlyUsd": 10 }, "resolvedSettings": { "cbEnabled": true, "cbMinuteUsd": 2, "cbHourlyUsd": 10 }, "liveSpend": { "minuteSpend": 0.4123, "hourSpend": 3.8721 } } ``` ## API Key Rotation Rotating API keys is a security practice. Create a replacement key, update and verify your integration, then revoke the old key. We do not guarantee zero downtime during this process. Note API keys can be revoked at any time from the dashboard or via the management API. Revocation is immediate — all requests using that key will fail instantly. ### Rotation Steps - Create a new API key - Update your application or environment variables to use the new key - Verify the new key is working correctly - Disable or delete the old key 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ``` # Key CRUD via Bearer auth requires a MANAGEMENT key (keyType: "management"). # Standard keys get 403 on /v1/keys — create a management key first, # or rotate keys from the dashboard UI instead. # Step 1: Create new key (management key auth) POST https://api.bazaarlink.ai/v1/keys Authorization: Bearer $BL_MANAGEMENT_KEY {"name": "Production v2"} # → saves new key: sk-bl-NEW_KEY_VALUE # Step 2: Update your application # export BAZAARLINK_API_KEY=sk-bl-NEW_KEY_VALUE # Step 3: Verify new key works curl https://api.bazaarlink.ai/v1/models \ -H "Authorization: Bearer sk-bl-NEW_KEY_VALUE" # Step 4: Revoke old key (management key auth again) DELETE https://api.bazaarlink.ai/v1/keys/:old_key_id Authorization: Bearer $BL_MANAGEMENT_KEY ``` ## Activity Export Download your complete API usage history as CSV for financial audits, cost analysis, or compliance reporting. ### CSV Export Log in and go to the Logs page. Click the Export CSV button in the top-right corner to download your full history as a CSV file. No API call required. ### CSV Columns Column Description `date`ISO 8601 timestamp (UTC) `model`Model ID (e.g. openai/gpt-4o) `prompt_tokens`Input token count `completion_tokens`Output token count `total_tokens`Total tokens (prompt + completion) `reasoning_tokens`Reasoning tokens (o-series / thinking models) `cached_tokens`Prompt cache hit tokens `cost_ntd`Charged amount in New Taiwan dollars `duration_ms`End-to-end latency in milliseconds `finish_reason`stop / length / content_filter / error `status`BazaarLink response status code `app_name`X-Title header value (app attribution) ### JSON Usage API For programmatic access, query aggregated stats grouped by period, model, or key: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ``` # Query usage data (grouped / aggregated) GET https://api.bazaarlink.ai/v1/usage Authorization: Bearer sk-bl-YOUR_KEY # With period filtering (day | week | month | year) GET https://api.bazaarlink.ai/v1/usage?period=month # Response { "period": "month", "since": "2025-01-01T00:00:00.000Z", "credits": 10.5000, "totals": { "spend": 0.1812, "requests": 309, "tokens": 161200, "promptTokens": 95000, "completionTokens": 66200 }, "byModel": [{ "model": "openai/gpt-4o", "spend": 0.0028, "tokens": 1200, "requests": 5 }], "byKey": [{ "keyName": "My Agent", "spend": 0.0028, "tokens": 1200, "requests": 5 }], "byApp": [{ "appName": "MyApp", "spend": 0.0015, "tokens": 600, "requests": 3 }], "timeSeries": [{ "date": "2025-01-15", "model": "openai/gpt-4o", "cost": 0.0012, "tokens": 500, "requests": 2 }] } ``` ## Usage Accounting Query detailed usage statistics via API, including token consumption, cost analysis, and request history. Note Usage data is billed in USD. Individual request records are available on the Logs page or via CSV export. Aggregated stats (by period, model, or key) are available via the `/v1/usage` endpoint with Bearer token auth. ### Response Field Reference FieldTypeDescription modelstringModel ID used (e.g., openai/gpt-4o) providerstringFixed BazaarLink value: bazaarlink prompt_tokensnumberInput tokens consumed completion_tokensnumberOutput tokens generated total_tokensnumberTotal tokens (prompt + completion) reasoning_tokensnumberReasoning tokens (for thinking models) cached_tokensnumberPrompt tokens served from cache costnumberUSD amount charged to the customer for this request duration_msnumberEnd-to-end latency in milliseconds throughputnumberGeneration speed in tokens/sec finish_reasonstringstop | length | content_filter | error statusnumberBazaarLink response status code app_namestring | nullApplication name (X-Title header) key_namestringAPI key name used for the request 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ``` import httpx # Aggregated stats (Bearer token — period: day | week | month | year) response = httpx.get( "https://api.bazaarlink.ai/v1/usage", headers={"Authorization": "Bearer sk-bl-YOUR_KEY"}, params={"period": "month"}, ) data = response.json() totals = data["totals"] print("This month: US$%.4f (%d requests)" % (totals["spend"], totals["requests"])) # Cost breakdown by model for m in data["byModel"]: print(" %s: US$%.4f (%d reqs, %d tokens)" % (m["model"], m["spend"], m["requests"], m["tokens"])) ``` ## Report Feedback Help us improve BazaarLink by reporting issues, bugs, or suggestions. We actively monitor all feedback channels. ### How to Report Channel Best For Response Time Contact PageGeneral feedback, feature requests1-2 business days EmailBug reports, technical issuesWithin 24 hours API Response HeadersAuto-reported errors and metricsAutomatic ### What to Include - Request ID (from the response id field) - Model used and parameters sent - Expected vs actual behavior - Timestamps and frequency of issue - Error messages or HTTP status codes [Visit our Contact page to submit feedback.](/en/contact) ## FAQ How is BazaarLink different from calling OpenAI directly? BazaarLink provides USD billing with NTD-quoted pricing, unified invoices, Chinese support, and a single API across all major models. You can access OpenAI, Anthropic, Google, and more with the same code. Do I need to change my existing code? Just change the base URL and API key. All other settings (except model IDs) remain unchanged. Does BazaarLink store my messages? By default, we do not store message content. We only log token counts and timestamps for billing purposes. How do I get a unified invoice (統一發票)? A Taiwan unified e-invoice is available only through a currently offered payment channel marked invoice-eligible or through a confirmed enterprise billing arrangement. Check live checkout before paying, or contact sales first if you need a company tax ID, quotation, or monthly billing. What payment methods are supported? All major credit cards are accepted (Visa, Mastercard, American Express). Which OpenAI SDK features are supported? Chat completions, streaming, tool calling, structured output (response_format), and assistant prefill all work. Features are passed through to the upstream provider. Can I use BazaarLink with agent frameworks like LangChain or CrewAI? Yes! Any framework that supports the OpenAI API works with BazaarLink. Just set the base URL and use your BazaarLink API key. See the Agentic Usage section for examples. --- # BazaarLink API Reference > Markdown mirror of https://bazaarlink.ai/docs/api — regenerated at most every 10 minutes. > OpenAPI spec: https://bazaarlink.ai/docs/openapi.yaml · Index: https://bazaarlink.ai/llms.txt [Docs](/en/docs)[API Reference](/en/docs/api)[SDK Reference](/en/docs/sdk)[Agentic Usage](/en/docs/agentic)[AI Skills](/en/docs/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: - YAML: https://bazaarlink.ai/docs/openapi.yaml - JSON: https://bazaarlink.ai/docs/openapi.json 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](#parameters)。 Request Schema 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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 ``` // 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; 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; input_audio?: InputAudio; // Provider-specific reasoning controls reasoning_effort?: "low" | "medium" | "high"; 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 1 2 3 4 5 6 7 8 9 ``` { "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 1 2 3 4 5 6 7 8 9 10 11 12 13 ``` await fetch("https://api.bazaarlink.ai/v1/chat/completions", { method: "POST", headers: { "Authorization": "Bearer ", "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 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. Response Schema 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 ``` type Response = { id: string; object: "chat.completion" | "chat.completion.chunk"; created: number; model: string; choices: Array; 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[]; }; }; type StreamingChoice = { index: number; finish_reason: string | null; delta: { role?: string; content?: string | null; tool_calls?: ToolCall[]; }; }; ``` ### Finish Reason 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. TypeScript 1 2 3 4 5 6 7 8 ``` const generation = await fetch( "https://api.bazaarlink.ai/v1/generation?id=", { headers: { Authorization: "Bearer " } } ).then((response) => response.json()); ``` ## Agent registration When enabled, agents can create an account through an unpaid or x402-paid registration door. POST`https://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. POST`https://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. POST`https://api.bazaarlink.ai/api/x402/topup` ## Chat Completions The primary endpoint. Compatible with the OpenAI Chat Completions API. POST`/v1/chat/completions` ### Request Body `model`required string Use an id or alias listed by GET /v1/models, such as "claude-sonnet-4.6" or "anthropic/claude-sonnet-4.6". `messages`required 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 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) 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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 ``` // 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; 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; input_audio?: InputAudio; // Provider-specific reasoning controls reasoning_effort?: "low" | "medium" | "high"; 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 1 2 3 4 5 6 7 8 9 10 11 12 ``` 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 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 ``` { "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. 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 ``` 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[]; }; }; type StreamingChoice = { index: number; finish_reason: string | null; delta: { role?: string; content?: string | null; tool_calls?: ToolCall[]; }; }; 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. 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" }' ``` `model`required string Model id, e.g. google/gemini-2.5-flash-image `prompt`required 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 ``` : keep-alive event: image data: {"index": 0, "url": "https://api.bazaarlink.ai/v1/images/proxy/"} event: image-cached # bazaarlink Redis-backed proxy URL (1 hr TTL) data: {"index": 0, "url": "https://api.bazaarlink.ai/v1/images/proxy/"} 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` `model`required string Model id, e.g. bytedance/seedance-2.0 `prompt`required 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}. 1 2 3 4 5 6 7 8 9 10 11 ``` 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}` 1 2 ``` 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` 1 2 3 ``` 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— bytedance/seedance-1-5-protext+image->videot2v, i2v google/veo-3.1text+image->videot2v, i2v minimax/hailuo-3-maxtext+image->videot2v, i2v bytedance/seedance-2.0-fasttext+image+audio+video->videot2v, i2v bytedance/seedance-2.0text+image+audio+video->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` 1 2 3 4 5 ``` curl https://api.bazaarlink.ai/v1/audio/speech \ -H "$BAZAARLINK_AUTH_HEADER" \ -H "Content-Type: application/json" \ -d '{"model":"","input":"Hello","voice":"alloy","response_format":"mp3"}' \ --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` 1 2 3 4 5 6 7 8 9 10 ``` curl https://api.bazaarlink.ai/v1/audio/transcriptions \ -H "$BAZAARLINK_AUTH_HEADER" \ -F model="" \ -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":"","input_audio":{"data":"","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` 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. 1 2 3 4 ``` curl https://api.bazaarlink.ai/v1/chat/completions \ -H "$BAZAARLINK_AUTH_HEADER" \ -H "Content-Type: application/json" \ -d '{"model":"","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. ### Voice rules ElevenLabs models (elevenlabs/…) take an ElevenLabs voice ID (20 letters/digits), for example JBFqnCBsd6RMkjVDRZzb (George) or 21m00Tcm4TlvDq8ikWAM (Rachel), or 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). Copy more voice IDs from the ElevenLabs voice library. 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. Voice cloning is not offered: you cannot upload voice samples or create custom voices through the API. Choose one of the 21 preset names above or any voice ID from the public ElevenLabs voice library. 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. ### 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. ### Examples 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-flash-v2.5","input":"Hello from BazaarLink.","voice":"21m00Tcm4TlvDq8ikWAM","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](/en/docs/api/reference/audio/audio-speech) ## 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 1 2 3 4 5 6 ``` 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](/en/docs/api/reference/audio/audio-transcriptions) ## 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 `platform``string`YesPlatform id from the catalog, such as twitter or instagram. `action``string`YesAction supported by that platform; see the catalog for the available pairs. `params``object`NoAction-specific inputs. Use required_params and optional_params in the catalog. `mode``sync | async`NoOptional. 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 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 ``` { "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. 1 2 3 4 5 6 7 8 ``` { "platform": "instagram", "action": "discover", "params": { "keyword": "coffee" }, "mode": "async" } ``` HTTP 202 1 2 3 4 5 6 7 ``` { "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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ``` { "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 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 ``` { "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":"","message":""}} CodeHTTPMeaning `unauthorized`401API key missing or invalid. `forbidden`403Request is not allowed. `invalid_request`400Invalid request body. `invalid_params`400Action parameters were rejected. `unsupported_capability`400Platform or action is unavailable. `insufficient_credits`402Insufficient credits. `budget_cap_reached`402 or policy statusAccount spending limit reached. `body_too_large`413Request body exceeds the size limit. `free_quota_exhausted`432Daily free allowance exhausted. `rate_limited`429Rate limit reached; respect Retry-After. `data_unavailable`503Data service is temporarily unavailable. `timeout`504Request timed out. `bad_upstream_response`502Data service returned an invalid response. `job_expired`410Job expired after 15 minutes. `not_found`404Job unknown or owned by another account. `job_failed`503Async job failed. `service_paused`503Service is not enabled yet or is paused. ### Examples ### curl 1 2 3 4 ``` 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) 1 2 3 4 5 6 7 8 9 10 11 ``` 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) 1 2 3 4 5 6 7 8 9 10 ``` 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](/en/docs/api/reference/decisions/submit-a-decisions-request) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` 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)``MPEG``MOV``WebM` 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?"}, ], }], ) ``` [Full API reference →](/en/docs/api#chat) ## 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ``` 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 `model`required string Use an id or alias listed by GET /v1/models, such as "claude-sonnet-4.6" or "anthropic/claude-sonnet-4.6". `input`required 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) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ``` 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 1 2 3 4 5 6 7 8 ``` 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. 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 ``` // 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. 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 `model`required string Use an id or alias listed by GET /v1/models, such as "claude-sonnet-4.6" or "anthropic/claude-sonnet-4.6". `max_tokens`required integer Maximum number of tokens to generate (positive integer). `messages`required 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 1 2 3 4 5 6 7 8 9 10 11 12 13 ``` 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. 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. GET`/v1/models` 1 2 3 4 5 ``` # Text models (default) curl https://api.bazaarlink.ai/v1/models # Complete catalog curl "https://api.bazaarlink.ai/v1/models?output_modalities=all" ``` ### Response 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ``` { "data": [ { "id": "openai/gpt-4o", "name": "GPT 4.1", "context_length": 1047576, "modality": "text+image+file->text", "pricing": { "prompt": "2.00", "completion": "8.00" } } ] } ``` 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 ``` // /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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` { "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-max`1000K ctx · $2.00/$6.00text+image+video->text `qwen/qwen3.7-plus`1000K ctx · $0.32/$1.28text+image->text `qwen/qwen3.7-max`1000K ctx · $1.48/$4.42text->text `qwen/qwen3.7-flash`1000K ctx · $0.03/$0.13text+image+video->text `qwen/qwen3.5-35b-a3b`262K ctx · $0.31/$1.25text+image+video->text `qwen/qwen3.5-27b`262K ctx · $0.26/$2.60text+image+video->text `qwen/qwen3-next-80b-a3b-instruct`262K ctx · $0.10/$1.10text->text `qwen/qwen3.5-397b-a17b`262K ctx · $0.55/$3.50text+image+video->text `qwen/qwen3.6-plus`1000K ctx · $0.33/$1.95text+image+video->text `qwen/qwen3-coder-flash`1000K ctx · $0.20/$0.97text->text `qwen/qwen3.5-122b-a10b`262K ctx · $0.29/$2.40text+image+video->text `qwen/qwen3-vl-235b-a22b-instruct`262K ctx · $0.21/$1.90text+image->text `qwen/qwen3.8-27b`1000K ctx · $0.55/$3.20text+image+video->text `qwen/qwen3.8-2.4t-a95b`1049K ctx · $2.00/$6.00text->text `qwen/qwen3-embedding-0.6b`33K ctx · $0.01/$0.00text->embeddings `qwen/qwen3-embedding-8b`33K ctx · $0.01/$0.00text->embeddings `qwen/qwen3-embedding-4b`33K ctx · $0.02/$0.00text->embeddings `qwen/qwen3.5-flash-02-23`1000K ctx · $0.07/$0.26text+image+video->text `qwen/qwen3-coder-30b-a3b-instruct`262K ctx · $0.07/$0.28text->text `qwen/qwen3.6-35b-a3b`262K ctx · $0.15/$1.00text+image+video->text `qwen/qwen3-coder-next`262K ctx · $0.12/$0.80text->text `qwen/qwen3-vl-30b-a3b-instruct`262K ctx · $0.15/$0.60text+image->text `qwen/qwen3.5-plus-02-15`1000K ctx · $0.26/$1.56text+image+video->text `qwen/qwen3.6-27b`262K ctx · $0.29/$2.40text+image+video->text `qwen/qwen3-coder`262K ctx · $0.30/$1.00text->text `qwen/qwen3.5-plus-20260420`1000K ctx · $0.30/$1.80text+image+video->text `qwen/qwen-2.5-72b-instruct`33K ctx · $0.36/$0.40text->text `qwen/qwen-image-3-pro` · $0.00/$0.00text+image->image `qwen/qwen-image-3` · $0.00/$0.00text+image->image OpenAI `openai/gpt-5.3-codex`400K ctx · $1.75/$14.00text->text `openai/gpt-6-luna`1050K ctx · $0.10/$0.50text+image+file->text `openai/gpt-5.6-luna`1050K ctx · $0.20/$1.20text+image+file->text `openai/gpt-5.4-mini`400K ctx · $0.75/$4.50text+image+file->text `openai/gpt-6-sol`1050K ctx · $2.00/$10.00text+image+file->text `openai/gpt-6.1-sol`1050K ctx · $2.00/$10.00text+image+file->text `openai/gpt-5.4`1050K ctx · $2.50/$15.00text+image+file->text `openai/gpt-5.5`1050K ctx · $5.00/$30.00text+image+file->text `openai/gpt-6-astra`1050K ctx · $10.00/$50.00text+image+file->text `openai/gpt-5.6-terra`1050K ctx · $2.00/$12.00text+image+file->text `openai/text-embedding-3-small`8K ctx · $0.02/$0.00text->embeddings `openai/gpt-oss-safeguard-20b`128K ctx · $0.07/$0.30text->text `openai/gpt-oss-120b`131K ctx · $0.15/$0.60text->text `openai/text-embedding-ada-002`8K ctx · $0.10/$0.00text->embeddings `openai/text-embedding-3-large` · $0.13/$0.00text->embeddings `openai/gpt-5.6-sol`1050K ctx · $4.00/$20.00text+image+file->text `openai/gpt-oss-20b`131K ctx · $0.03/$0.13text->text `openai/gpt-3.5-turbo`16K ctx · $0.50/$1.50text->text `openai/gpt-image-1-mini`400K ctx · $2.50/$2.50text+image->image `openai/gpt-4o`128K ctx · $2.50/$10.00text+image+file->text `openai/gpt-image-2.5-flare`400K ctx · $8.00/$8.00text+image->image `openai/gpt-image-2.5-sunburst`400K ctx · $8.00/$8.00text+image->image `openai/gpt-5.4-image-2`272K ctx · $8.00/$15.00text+image+file->text+image `openai/gpt-image-1`400K ctx · $10.00/$10.00text+image->image Google `google/gemini-3.1-flash-lite-preview`1049K ctx · $0.25/$1.50text+image+file+audio+video->text `google/gemini-3.1-pro-preview`1049K ctx · $2.00/$12.00text+image+file+audio+video->text `google/gemma-3-4b-it`131K ctx · $0.05/$0.10text+image->text `google/gemini-3.1-flash-lite`1049K ctx · $0.25/$1.50text+image+file+audio+video->text `google/gemini-3.5-flash-lite`1049K ctx · $0.30/$2.50text+image+file+audio+video->text `google/gemini-3-flash-preview`1049K ctx · $0.50/$3.00text+image+file+audio+video->text `google/gemini-3.7-flash`1049K ctx · $0.75/$3.75text+image+file+audio+video->text `google/gemini-3.8-flash`1049K ctx · $0.75/$3.75text+image+file+audio+video->text `google/gemini-3.6-flash`1049K ctx · $1.35/$6.75text+image+file+audio+video->text `google/gemini-3.5-flash`1049K ctx · $1.50/$9.00text+image+file+audio+video->text `google/gemini-4-argon`1000K ctx · $2.00/$10.00text+image+file+audio+video->text `google/text-multilingual-embedding-002`2K ctx · $0.03/$0.00text->embeddings `google/text-embedding-005`2K ctx · $0.03/$0.00text->embeddings `google/gemini-embedding-001`20K ctx · $0.15/$0.00text->embeddings `google/gemma-4-26b-a4b-it`262K ctx · $0.09/$0.34text+image+video->text `google/gemma-4-31b-it`262K ctx · $0.09/$0.34text+image+video->text `google/gemini-2.5-flash-image`33K ctx · $0.30/$2.50text+image->text+image `google/gemini-3.1-flash-image`131K ctx · $0.50/$3.00text+image->text+image `google/gemini-3.1-flash-image-preview`66K ctx · $0.50/$3.00text+image->text+image `google/gemini-3-pro-image`131K ctx · $2.00/$12.00text+image->text+image `google/veo-3.1` · $0.00/$0.00text+image->video `google/gemini-embedding-2-preview`8K ctx · $0.20/$0.00text+image+file+audio+video->embeddings `google/gemini-nano-banana-2.1`66K ctx · $1.50/$7.50text+image->text+image Anthropic `anthropic/claude-sonnet-4.6`1000K ctx · $3.00/$15.00text+image+file->text `anthropic/claude-opus-4.6`1000K ctx · $5.00/$25.00text+image+file->text `anthropic/claude-haiku-5.5`1000K ctx · $0.10/$0.50text+image+file->text `anthropic/claude-haiku-4.5`200K ctx · $1.00/$5.00text+image+file->text `anthropic/claude-sonnet-5`1000K ctx · $2.00/$10.00text+image+file->text `anthropic/claude-sonnet-5.5`1000K ctx · $2.00/$10.00text+image+file->text `anthropic/claude-sonnet-4.5`1000K ctx · $3.00/$15.00text+image+file->text `anthropic/claude-opus-5.5`1000K ctx · $4.00/$20.00text+image+file->text `anthropic/claude-opus-4.7`1000K ctx · $5.00/$25.00text+image+file->text `anthropic/claude-opus-5`1000K ctx · $5.00/$25.00text+image+file->text `anthropic/claude-opus-4.8`1000K ctx · $5.00/$25.00text+image+file->text `anthropic/claude-opus-4.5`200K ctx · $5.00/$25.00text+image+file->text `anthropic/claude-fable-5`1000K ctx · $10.00/$50.00text+image+file->text `anthropic/claude-fable-5.1`1000K ctx · $10.00/$50.00text+image+file->text `anthropic/claude-opus-4.1`200K ctx · $15.00/$75.00text+image+file->text DeepSeek `deepseek/deepseek-v4.1-flash-1017`1049K ctx · $0.21/$0.84text+image->text `deepseek/deepseek-v4-flash`1049K ctx · $0.20/$0.40text->text `deepseek/deepseek-v4-pro`1049K ctx · $2.40/$4.80text->text `deepseek/deepseek-v4.1-flash`1049K ctx · $0.21/$0.84text+image->text `deepseek/deepseek-chat-v3.1`164K ctx · $0.25/$0.95text->text `deepseek/deepseek-chat`164K ctx · $0.40/$1.30text->text `deepseek/deepseek-r1`64K ctx · $0.70/$2.50text->text `deepseek/deepseek-v4-pro-0813`1049K ctx · $0.89/$2.67text->text `deepseek/deepseek-v4-flash-0731`1000K ctx · $0.44/$1.32text->text `deepseek/deepseek-r1-0528`164K ctx · $0.50/$2.15text->text `deepseek/deepseek-v4-flash-0731free`1049K ctx · $0.20/$0.40text->text elevenlabs `elevenlabs/eleven-turbo-v2.5` · $0.00/$0.00text->speech `elevenlabs/eleven-flash-v2` · $0.00/$0.00text->speech `elevenlabs/eleven-multilingual-v2` · $0.00/$0.00text->speech `elevenlabs/eleven-v3-conversational` · $0.00/$0.00text->speech `elevenlabs/eleven-flash-v2.5` · $0.00/$0.00text->speech `elevenlabs/eleven-v4` · $0.00/$0.00text->speech `elevenlabs/eleven-v3` · $0.00/$0.00text->speech `elevenlabs/eleven-turbo-v2` · $0.00/$0.00text->speech `elevenlabs/eleven-v4-turbo` · $0.00/$0.00text->speech `elevenlabs/scribe-v2-medical` · $0.00/$0.00audio->transcription `elevenlabs/scribe-v2` · $0.00/$0.00audio->transcription Zhipu AI `z-ai/glm-5v-turbo`203K ctx · $1.20/$4.00text+image+video->text `z-ai/glm-5.3-flash`1000K ctx · $0.15/$0.50text+image+video->text `z-ai/glm-4.6`205K ctx · $0.43/$1.75text->text `z-ai/glm-5.3-flashx`1049K ctx · $0.37/$1.25text+image+video->text `z-ai/glm-5.3`1049K ctx · $1.40/$4.40text->text `z-ai/glm-4.7-flash`200K ctx · $0.06/$0.40text->text `z-ai/glm-4.5-air`131K ctx · $0.13/$0.85text->text `z-ai/glm-4.6v`131K ctx · $0.30/$0.90text+image+video->text `z-ai/glm-4.5v`66K ctx · $0.60/$1.80text+image->text `z-ai/glm-4.5`131K ctx · $0.60/$2.20text->text voyage `voyage/voyage-3.5-lite`32K ctx · $0.02/$0.00text->embeddings `voyage/voyage-4-lite`32K ctx · $0.02/$0.00text->embeddings `voyage/voyage-4`32K ctx · $0.06/$0.00text->embeddings `voyage/voyage-3.5`32K ctx · $0.06/$0.00text->embeddings `voyage/voyage-finance-2`32K ctx · $0.12/$0.00text->embeddings `voyage/voyage-code-2`16K ctx · $0.12/$0.00text->embeddings `voyage/voyage-law-2`16K ctx · $0.12/$0.00text->embeddings `voyage/voyage-4-large`32K ctx · $0.12/$0.00text->embeddings `voyage/voyage-code-3`32K ctx · $0.18/$0.00text->embeddings `voyage/voyage-3-large`32K ctx · $0.18/$0.00text->embeddings xAI `x-ai/grok-build-0.1`256K ctx · $1.00/$2.00text+image+file->text `x-ai/grok-4.3`1000K ctx · $1.25/$2.50text+image+file->text `x-ai/grok-4.5`500K ctx · $2.00/$6.00text+image+file->text `x-ai/grok-4.6`500K ctx · $2.00/$6.00text+image+file->text `x-ai/grok-4.7`500K ctx · $2.00/$6.00text+image->text `x-ai/grok-4.20`2000K 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-flash`262K ctx · $0.07/$0.30text+image+video->text `bytedance-seed/seed-2.0-mini`262K ctx · $0.10/$0.40text+image+video->text `bytedance-seed/seed-1.6`262K ctx · $0.25/$2.00text+image+video->text `bytedance-seed/seed-2.0-lite`262K 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-fast` · $0.00/$0.00text+image+audio+video->video `bytedance/seedance-2.0` · $0.00/$0.00text+image+audio+video->video Cohere `cohere/rerank-4-pro`33K ctx · $0.00/$0.00text->rerank `cohere/rerank-4-fast`33K ctx · $0.00/$0.00text->rerank `cohere/rerank-v3.5`4K ctx · $0.00/$0.00text->rerank Moonshot AI `moonshotai/kimi-k3`1049K ctx · $3.00/$15.00text+image+video->text `moonshotai/kimi-k2-0905`262K ctx · $0.60/$2.50text->text MiniMax `minimax/minimax-m3`1049K ctx · $0.30/$1.20text+image+video->text `minimax/hailuo-3-max` · $0.00/$0.00text+image->video xiaomi `xiaomi/mimo-v2.6-flash`1049K ctx · $0.14/$0.28text+image+video+audio->text `xiaomi/mimo-v2.6-pro`1049K ctx · $0.43/$0.87text+image+video+audio->text reflection `reflection/beam`1049K ctx · $0.00/$0.00text->text `reflection/beam-free`1049K ctx · $0.00/$0.00text->text Perplexity `perplexity/pplx-embed-v1-0.6b`32K ctx · $0.00/$0.00text->embeddings `perplexity/pplx-embed-v1-4b`32K ctx · $0.03/$0.00text->embeddings Mistral `mistralai/mistral-embed-2312`8K ctx · $0.10/$0.00text->embeddings `mistralai/codestral-embed-2505`8K ctx · $0.15/$0.00text->embeddings cloudflare `cloudflare/clef-flash`25K ctx · $0.09/$0.00text+image->decisions `cloudflare/clef`66K ctx · $0.24/$0.00text+image->decisions NVIDIA `nvidia/nemotron-3.5-lightning-free`1000K ctx · $0.00/$0.00text->text stealth `stealth/glyph-cluster`256K ctx · $0.00/$0.00text->text Meta `meta/muse-wonder`1049K ctx · $4.00/$20.00text+image+file->text amazon `amazon/titan-embed-text-v2`8K ctx · $0.02/$0.00text->embeddings typesafe-ai `typesafe-ai/jev`32K ctx · $0.04/$0.00text->decisions Browse all models on the [Models page](/en/models). ## 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 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. 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 ``` { "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. 1 2 3 4 5 6 7 ``` { "error": { "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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ``` 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 1 2 3 4 5 6 7 ``` 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]. 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 ``` 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). 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 `model`required string The embedding model to use, e.g. "openai/text-embedding-3-small". `input`required 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 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ``` 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 `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 1 2 ``` curl https://api.bazaarlink.ai/v1/credits \ -H "Authorization: Bearer sk-bl-YOUR_KEY" ``` ### Response 1 2 3 4 5 6 ``` { "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=` Auth Requires Bearer token (standard API key). Required query param: id. ### Example Request 1 2 ``` curl "https://api.bazaarlink.ai/v1/generation?id=BAZAARLINK_REQUEST_ID" \ -H "Authorization: Bearer sk-bl-YOUR_KEY" ``` ### Response 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 ``` { "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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ``` { "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. 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 `424`Failed DependencyYour BYOK key was rejected by the upstream provider; check or rotate it in BYOK settings. This appears only in strict mode `400`Bad RequestMalformed request, empty messages array, or missing required fields `401`UnauthorizedAPI key is missing, invalid, or disabled `402`Payment RequiredInsufficient account credits, per-key spend limit reached, or monthly/weekly budget cap exceeded `403`ForbiddenAccount is suspended or does not have permission `404`Not FoundThe requested model, generation, key, or other resource does not exist `409`ConflictThe resource is not in the required state, such as a video job that is not complete `410`GoneThe requested model has been retired and must be replaced `413`Payload Too LargeRequest body exceeds 10 MB; reduce content size or split the request `416`Range Not SatisfiableThe requested byte range is invalid for generated video content `429`Too Many RequestsRate limit exceeded; check Retry-After header before retrying `500`Server ErrorInternal BazaarLink error `502`Bad GatewayAll upstream sources failed; no source could handle the request. `503`Service UnavailableNo upstream provider is configured for this model; contact admin `504`Gateway 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_invalid`Your own BYOK key was rejected by the upstream provider; check or rotate it in BYOK settings. This appears only in strict mode. `budget_cap_reached`A weekly or monthly advisory budget cap was reached; raise or reset the cap. `credit_limit_exceeded`A monthly-billing organization's hard credit line was exhausted; contact billing. `insufficient_credits`A prepaid user or organization cannot reserve enough balance; add credits. `spend_limit_exceeded`The API key reached its configured daily, weekly, or monthly spend limit. ### 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_model``400` `invalid_model_id``400` `model_not_found``404` `model_retired``410` `model_endpoint_mismatch``400` `embedding_on_chat_endpoint``400` `model_not_priced``400` `invalid_modality_for_model``400` Request and safety Invalid parameters, context, tools, schemas, and content-safety refusals. Code HTTP status `missing_required_field``400` `unsupported_param``400` `max_tokens_invalid``400` `context_too_long``400` `tool_use_unsupported``400` `malformed_tool_messages``400` `invalid_response_format_schema``400` `invalid_tools_definition``400` `content_moderation``403` `content_filter``403` `unknown_4xx``400` Image generation and editing Image input, multipart editing, output, and image-pipeline errors. Code HTTP status `invalid_image_url``400` `input_images_not_supported``400` `invalid_content_type``400` `mask_not_supported``400` `unsupported_response_format``400` `missing_prompt``400` `missing_image``400` `too_many_images``400` `invalid_image_type``400` `image_too_large``400` `invalid_n``400` `pipeline_error``502` `no_images``502` Upstream routing Sanitized provider connectivity, authentication, throttling, and availability errors. Code HTTP status `upstream_unreachable``502` `upstream_auth_failed``502` `upstream_rate_limited``429` `upstream_unavailable``502/503` `byok_key_invalid``424` ### 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 Every request is automatically reviewed for content policy compliance. Requests found to violate policy have their content retained as compliance evidence. Other requests do not have their full prompt content retained. ### 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 limit`429`String code or numeric code 429; use Retry-After and X-RateLimit-* headers. Rate-limit penalty block`429`Numeric code 429 and a temporary restriction message; use Retry-After. Global spend emergency brake`503`Numeric code 503, global spend-limit message, and Retry-After of 30 or 300 seconds. Org/team/member/user spend brake`429`Numeric code 429 and a spend circuit-breaker message naming the affected scope. Billing and budget controls`402`Use 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 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. 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. --- # BazaarLink SDK Reference > Markdown mirror of https://bazaarlink.ai/docs/sdk — regenerated at most every 10 minutes. > OpenAPI spec: https://bazaarlink.ai/docs/openapi.yaml · Index: https://bazaarlink.ai/llms.txt [Docs](/en/docs)[API Reference](/en/docs/api)[SDK Reference](/en/docs/sdk)[Agentic Usage](/en/docs/agentic)[AI Skills](/en/docs/skills) # SDK Reference ## OpenAI SDK Compatibility BazaarLink is fully compatible with the OpenAI Python and Node.js SDKs. Only two changes needed: Before (OpenAI direct) 1 2 3 4 5 6 7 8 9 10 ``` from openai import OpenAI client = OpenAI( api_key="sk-..." ) response = client.chat.completions.create( model="gpt-4o", messages=[...] ) ``` After (BazaarLink) 1 2 3 4 5 6 7 8 9 10 11 ``` from openai import OpenAI client = OpenAI( base_url="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_API_KEY" ) response = client.chat.completions.create( model="openai/gpt-4o", # add provider/ messages=[...] ) ``` That's it! All other features — streaming, tool calling, structured output, async — work exactly the same. The only change is base_url, api_key, and adding the provider prefix to model IDs. ## Python ### Installation 1 ``` pip install openai ``` ### Synchronous 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.chat.completions.create( model="openai/gpt-4o", messages=[{"role": "user", "content": "Hello!"}], ) print(response.choices[0].message.content) ``` ### Async 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ``` import asyncio from openai import AsyncOpenAI client = AsyncOpenAI( base_url="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_API_KEY", ) async def main(): response = await client.chat.completions.create( model="anthropic/claude-sonnet-4.6", messages=[{"role": "user", "content": "Hello async world!"}], ) print(response.choices[0].message.content) asyncio.run(main()) ``` ### Streaming with async 1 2 3 4 5 6 7 8 9 10 11 12 ``` async def stream_chat(): stream = await client.chat.completions.create( model="deepseek/deepseek-chat", messages=[{"role": "user", "content": "Tell me a story"}], stream=True, ) async for chunk in stream: content = chunk.choices[0].delta.content if content: print(content, end="", flush=True) asyncio.run(stream_chat()) ``` ## TypeScript / Node.js ### Installation 1 ``` npm install openai ``` ### Basic Usage 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ``` import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.bazaarlink.ai/v1", apiKey: process.env.BAZAARLINK_API_KEY, }); async function chat(message: string): Promise { const response = await client.chat.completions.create({ model: "openai/gpt-4o", messages: [{ role: "user", content: message }], }); return response.choices[0].message.content ?? ""; } ``` ### Streaming 1 2 3 4 5 6 7 8 9 ``` const stream = await client.chat.completions.create({ model: "openai/gpt-4o", messages: [{ role: "user", content: "Tell me a story" }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); } ``` ## LangChain Use BazaarLink with LangChain by configuring the OpenAI-compatible base URL. 1 2 3 4 5 6 7 8 9 10 ``` from langchain_openai import ChatOpenAI llm = ChatOpenAI( base_url="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_API_KEY", model="anthropic/claude-sonnet-4.6", ) response = llm.invoke("Explain BazaarLink in one sentence.") print(response.content) ``` ## LlamaIndex Use BazaarLink in LlamaIndex RAG pipelines. LlamaIndex supports any OpenAI-compatible endpoint — just change two parameters. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ``` from llama_index.llms.openai import OpenAI from llama_index.core import Settings Settings.llm = OpenAI( model="openai/gpt-4o", api_base="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_API_KEY", ) # Now use LlamaIndex normally — it calls BazaarLink from llama_index.core import VectorStoreIndex, SimpleDirectoryReader documents = SimpleDirectoryReader("./docs").load_data() index = VectorStoreIndex.from_documents(documents) query_engine = index.as_query_engine() response = query_engine.query("What is this documentation about?") print(response) ``` ## Vercel AI SDK Integrate the Vercel AI SDK with BazaarLink in your Next.js app. Supports streaming, tool calling, and structured output. ### Installation 1 ``` npm install ai @ai-sdk/openai ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ``` import { createOpenAI } from "@ai-sdk/openai"; import { generateText } from "ai"; const bazaarlink = createOpenAI({ baseURL: "https://api.bazaarlink.ai/v1", apiKey: "sk-bl-YOUR_API_KEY", }); const { text } = await generateText({ model: bazaarlink("openai/gpt-4o"), prompt: "Write a haiku about programming", }); console.log(text); ``` --- # BazaarLink Agentic Usage > Markdown mirror of https://bazaarlink.ai/docs/agentic — regenerated at most every 10 minutes. > OpenAPI spec: https://bazaarlink.ai/docs/openapi.yaml · Index: https://bazaarlink.ai/llms.txt [Docs](/en/docs)[API Reference](/en/docs/api)[SDK Reference](/en/docs/sdk)[Agentic Usage](/en/docs/agentic)[AI Skills](/en/docs/skills) # Agentic Usage ## Agent Frameworks Use BazaarLink with popular agent frameworks. Since BazaarLink is OpenAI-compatible, most frameworks work out of the box — just set the base URL and API key. ### Vercel AI SDK 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ``` import { createOpenAI } from "@ai-sdk/openai"; import { generateText } from "ai"; const bazaarlink = createOpenAI({ baseURL: "https://api.bazaarlink.ai/v1", apiKey: "sk-bl-YOUR_API_KEY", }); const { text } = await generateText({ model: bazaarlink("openai/gpt-4o"), prompt: "Write a haiku about programming", }); console.log(text); ``` ### CrewAI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ``` from crewai import Agent, Task, Crew from langchain_openai import ChatOpenAI llm = ChatOpenAI( base_url="https://api.bazaarlink.ai/v1", api_key="sk-bl-YOUR_API_KEY", model="anthropic/claude-sonnet-4.6", ) researcher = Agent( role="Researcher", goal="Research and summarize topics", llm=llm, ) task = Task( description="Research the latest trends in AI agents", agent=researcher, ) crew = Crew(agents=[researcher], tasks=[task]) result = crew.kickoff() print(result) ``` ### AutoGen 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ``` from autogen import AssistantAgent, UserProxyAgent config_list = [{ "model": "openai/gpt-4o", "base_url": "https://api.bazaarlink.ai/v1", "api_key": "sk-bl-YOUR_API_KEY", }] assistant = AssistantAgent( "assistant", llm_config={"config_list": config_list}, ) user_proxy = UserProxyAgent( "user_proxy", human_input_mode="NEVER", code_execution_config={"work_dir": "coding"}, ) user_proxy.initiate_chat( assistant, message="Write a Python function to calculate fibonacci numbers", ) ``` ### Environment Variables Most agent frameworks support configuration via environment variables: 1 2 3 ``` # .env OPENAI_API_KEY=sk-bl-YOUR_API_KEY OPENAI_BASE_URL=https://api.bazaarlink.ai/v1 ``` Environment Variables Any framework that supports the OpenAI API or custom base URLs can work with BazaarLink. Just set the base URL and use a BazaarLink API key. ## Agent registration When enabled, agents can create an account through an unpaid or x402-paid registration door. ### Unpaid registration 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. ### x402 registration and top-ups 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. --- # BazaarLink AI Skills > Markdown mirror of https://bazaarlink.ai/docs/skills — regenerated at most every 10 minutes. > OpenAPI spec: https://bazaarlink.ai/docs/openapi.yaml · Index: https://bazaarlink.ai/llms.txt [Docs](/en/docs)[API Reference](/en/docs/api)[SDK Reference](/en/docs/sdk)[Agentic Usage](/en/docs/agentic)[AI Skills](/en/docs/skills) # AI Skill Files Paste any skill URL into your AI assistant (Claude, Cursor, Copilot, ChatGPT…) to instantly give it full knowledge of that BazaarLink topic. No setup required — the assistant reads the file and knows what to do. How to use a skill - Copy the skill URL below - Open your AI assistant - Tell it: "Load this skill: " - The assistant now knows everything about that topic ## What Are Skills? Skills are self-contained Markdown files that package the full context for a topic — authentication, endpoints, error handling — so an AI assistant can load one file and immediately understand how to work with it, without hunting through docs or trial and error. BazaarLink hosts a growing library of these files, covering the API, organization management, and the Probe system. Each file is a public, static Markdown document — loading and using it happens entirely on your AI assistant's side. BazaarLink does not execute skills or call models on your behalf. ## Skills vs. Models Models are the raw intelligence behind every request — they generate text, code, and structured data when you call the API. Skills are reference material, not a separate execution layer: each file explains how to use BazaarLink (or one specific feature of it) so your AI assistant writes correct requests on the first try. Loading a skill doesn't call any model or spend any credits — it just adds context to your assistant's conversation. Actual model calls still go through the normal API endpoints described under Core Endpoints, using whichever model you choose. ## Skills BazaarLink API Complete BazaarLink API guide: Jev evaluations, authentication, model IDs, chat and Responses APIs, embeddings, streaming, tool calling, routing, and key management. `skill.md`[Open ↗](https://bazaarlink.ai/skill.md) Organization Management API Programmatic management of organizations, teams, and members via Bearer management key. Covers all /v1/orgs/ endpoints with curl, Python, and TypeScript examples. `orgskill.md`[Open ↗](https://bazaarlink.ai/orgskill.md) Probe API Complete guide to the Probe identity and capability API: detect model substitution, downgrades, or impersonation on any OpenAI-compatible endpoint, with the full probing workflow and API examples. `probe-api-skill.md`[Open ↗](https://bazaarlink.ai/probe-api-skill.md) --- # BazaarLink Documented Endpoint Reference > Registry-backed English Markdown for all 63 public endpoints with dedicated BazaarLink reference pages. Internal, legacy, alias, and documentation-hold routes are intentionally not promoted as public API. Canonical: https://bazaarlink.ai/docs/api-reference OpenAPI JSON: https://bazaarlink.ai/docs/openapi.json OpenAPI YAML: https://bazaarlink.ai/docs/openapi.yaml Registry entries: 63 # Decisions ## POST /v1/alpha/decisions **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. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | An available decisions-output model id from /v1/models?output_modalities=decisions. Example: "cloudflare/clef-flash" | | `state` | `JSON string | object | array` | yes | The evidence to evaluate, supplied as a string, JSON object, or array. Structured content parts are preserved. Image interpretation depends on the selected model's supported state format; input_tokens includes image tokens when they are counted. | | `images` | `string[]` | no | Optional inline images, up to 4 per request: PNG, JPEG or WebP as a data URI or raw base64, each up to 4 MiB and 8 MiB in total. Remote URLs are not accepted. Images are added to the state for the model; input_tokens includes the image tokens. A request with an unsupported or invalid image is rejected with 400 invalid_images. | | `questions` | `record` | yes | Named questions. Each answer uses the same question id as its key. | | `questions.type` | `string` | yes | noul returns a number for a yes/no decision; choice selects an option; score returns a numeric score. Values: noul, choice, score | | `questions.instructions` | `JSON string | object | array` | yes | Instructions explaining the decision to make. | | `questions.criteria` | `record | JSON[]` | no | Optional true/false guidance for noul. Required option-to-guidance map for choice, with null allowed as option guidance. Required ordered guidance array with at least one item for score. | | `stream` | `boolean` | no | Omit this field or set false. Streaming requests are rejected with HTTP 400. Values: false | ### cURL ```bash 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?" } } }' ``` ### Response 200 Success. answers contains the typed result for each question. Optional confidence, probabilities and score legend fields depend on the model. usage.input_tokens includes every counted input token; usage.output_tokens reports output tokens. usage.cost is the USD amount charged to the customer. id is an opaque BazaarLink request id and matches x-request-id and x-generation-id. Use this id with GET /v1/generation?id=ID to read back persisted customer usage; retry a 404 briefly while the usage log is written. ```json { "id": "85e6f35c71a31f19-SJC", "model": "cloudflare/clef-flash", "answers": { "refunded": { "type": "noul", "noul": 0.99 } }, "usage": { "input_tokens": 64, "output_tokens": 1, "cost": 0.000007 } } ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "'model' is required.", "type": "invalid_request_error", "code": 400 } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable. Please retry shortly. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` --- # Speech synthesis ## POST /v1/audio/speech **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. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | Model id or alias from /v1/models. | | `input` | `string` | yes | Text to synthesize. 1–4096 characters (10,000 for elevenlabs/eleven-v4 and eleven-v4-turbo) | | `voice` | `string` | no | Voice supported by the selected model. ElevenLabs models (elevenlabs/*) take an ElevenLabs voice id (20 letters/digits, e.g. 21m00Tcm4TlvDq8ikWAM) or one of these voice names (george, sarah, adam, alice, bella, bill, brian, callum, charlie, chris, daniel, eric, harry, jessica, laura, liam, lily, matilda, river, roger, will), or omit it (default george); any other value returns 400 invalid_voice (OpenAI: alloy). Other models require voice. | | `response_format` | `string` | no | Output audio format; support depends on the model. Native WAV routes currently only produce WAV without transcoding; even mp3 or opus requests return audio/wav. Save the file according to the response Content-Type. Values: mp3, opus, aac, flac, wav, pcm | | `speed` | `number` | no | Speech speed from 0.25 to 4.0; support depends on the model. 0.25–4.0 | | `instructions` | `string` | no | Speech generation instructions; support depends on the model. | ### cURL ```bash curl https://api.bazaarlink.ai/v1/audio/speech \ -H "$BAZAARLINK_AUTH_HEADER" \ -H "Content-Type: application/json" \ -d '{"model":"","input":"Hello","voice":"alloy","response_format":"mp3"}' \ --output speech.mp3 ``` ### Response 200 Success. ```text ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "'model' is required.", "type": "invalid_request_error", "code": 400 } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` --- ## POST /v1/audio/transcriptions **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. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | Transcription model id, e.g. elevenlabs/scribe-v2. GET /v1/models?output_modalities=transcription | | `file` | `file` | no | 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. multipart/form-data: required; decoded <= 25 MiB | | `input_audio` | `object` | no | JSON alternative to file: data contains raw base64 audio and format names the type, such as wav or mp3. Request body maximum: 50 MiB; decoded audio maximum: 25 MiB. application/json: required; data: raw base64; format: wav \| mp3 \| mp4 \| m4a \| ogg \| flac \| webm \| aac | | `response_format` | `string` | no | json (default) returns {text}; verbose_json adds language, duration and segments/words. Values: json, verbose_json default: json | | `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. Values: word, segment response_format=verbose_json; multipart: timestamp_granularities[] | | `language` | `string` | no | Optional language hint such as en or zh. Leave empty to auto-detect. ISO-639-1 | | `prompt` | `string` | no | Optional context text to help with names and terms. Not every model applies it. | | `temperature` | `number` | no | Optional sampling temperature; not every model applies it. 0 <= temperature <= 1 | | `stream` | `boolean` | no | Streaming is not supported; stream=true returns 400 unsupported_parameter. Unsupported: 400 unsupported_parameter | | `provider` | `object` | no | Optional routing field; ignored. Ignored | ### cURL ```bash curl https://api.bazaarlink.ai/v1/audio/transcriptions \ -H "$BAZAARLINK_AUTH_HEADER" \ -F model="" \ -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":"","input_audio":{"data":"","format":"wav"},"response_format":"verbose_json","timestamp_granularities":["word","segment"]}' ``` ### Response 200 Audio transcription: json / verbose_json — Example charges are illustrative, not model prices. 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. ```json {"text":"Hello","usage":{"seconds":1,"cost":0.00001}} ``` verbose_json ```json {"text":"Hello","duration":1,"segments":[{"id":0,"start":0,"end":1,"text":"Hello"}],"words":[{"word":"Hello","start":0,"end":1}],"usage":{"seconds":1,"cost":0.00001}} ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` ### Response 400 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. ```json {"error": {"message": "stream is not supported", "type": "invalid_request_error", "code": "unsupported_parameter"}} ``` ### Response 413 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. ```json {"error": {"message": "Decoded audio exceeds 25 MiB", "type": "invalid_request_error", "code": "audio_too_large"}} ``` --- # Document reranking ## POST /v1/rerank **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. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `return_documents` | `boolean` | no | When true, return document text; when content filtering is enabled, return the filtered text. | | `model` | `string` | yes | Canonical vendor-prefixed model ID listed by /v1/models?output_modalities=rerank; aliases are not supported. | | `query` | `string` | yes | Search query text. | | `documents` | `(string | {text:string})[]` | yes | Text documents only: an array of strings or {text} objects; multimodal documents are not supported. | | `top_n` | `integer` | no | Maximum results, as a positive integer; values above documents.length are clamped to documents.length. positive integer | | `max_tokens_per_doc` | `integer` | no | Cohere/Jina compatibility field: accepted and ignored; not sent to the model service and does not change reranking. | | `max_chunks_per_doc` | `integer` | no | Cohere/Jina compatibility field: accepted and ignored; not sent to the model service and does not change reranking. | | `priority` | `integer` | no | Cohere/Jina compatibility field: accepted and ignored; not sent to the model service and does not change reranking. | | `rank_fields` | `string[]` | no | Cohere/Jina compatibility field: accepted and ignored; not sent to the model service and does not change reranking. | | `truncate` | `string` | no | Cohere/Jina compatibility field: accepted and ignored; not sent to the model service and does not change reranking. | ### cURL ```bash 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}' ``` ### Response 200 Success. ```json {"id":"rerank-example","model":"your-rerank-model-id","meta":{"billed_units":{"search_units":1}},"results":[{"index":0,"relevance_score":0.98}],"usage":{"search_units":1,"total_tokens":0}} ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "'model' is required.", "type": "invalid_request_error", "code": 400 } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` --- # Chat ## POST /v1/chat/completions **Create a chat** BazaarLink returns a standardized response and removes upstream-specific fields. usage.cost is the USD amount charged to the customer for this request. 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. Long non-streaming Chat, Messages, and Responses requests (about 12 seconds or longer) may start with whitespace keep-alive padding before the JSON body. Standard JSON parsers accept leading whitespace. Audio chat uses this endpoint; request audio output with modalities:["text","audio"] and audio:{voice,format}. GET /v1/models?output_modalities=audio filters audio output only, not all audio-input models; audio-input models producing text remain in the text list. Capability metadata alone does not guarantee an available route; select a listed model and its supported parameters. Audio-only output, TTS, STT, and other non-token generation modes are outside this contract. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | The model to use, provider-qualified, e.g. "openai/gpt-4o". GET /v1/models lists ids such as "claude-sonnet-4.6" and canonical aliases such as "anthropic/claude-sonnet-4.6". Requests accept either form; use a listed id or alias. minLength: 1 Example: "openai/gpt-4o" | | `messages` | `Message[]` | yes | Array of conversation messages, at least one. Each has a role (system/user/assistant/tool) and content. minLength: 1 | | `messages.content` | `string | ContentPart[]` | no | Put audio input in messages[].content[]: {"type":"input_audio","input_audio":{"data":"","format":"wav"}}. Use raw base64, not a data URI; formats depend on the model. Do not use top-level input_audio: it has no dedicated validation or credit-reservation contract. Example: [{"type":"input_audio","input_audio":{"data":"","format":"wav"}}] | | `models` | `string[]` | no | Fallback model list — tried in order, only used once the primary model's own candidate group is exhausted. | | `stream` | `boolean` | no | true streams the response as SSE; omitted or false returns a single JSON response. Audio streams return data (base64 audio) and transcript fragments in choices[].delta.audio; non-streaming responses use choices[].message.audio. The model may also provide id and expires_at. Accumulate fragments and decode using audio.format; streaming formats depend on the model. | | `temperature` | `number` | no | Sampling temperature — higher is more random, lower is more deterministic. Sampling values are forwarded without range validation; the model may clamp or ignore values outside its supported range. | | `max_tokens` | `integer` | no | Maximum number of tokens the response may generate. | | `max_completion_tokens` | `integer` | no | Alias of max_tokens with identical semantics. | | `top_p` | `number` | no | Nucleus sampling threshold. | | `top_k` | `integer` | no | Limits sampling to the K highest-probability tokens. | | `frequency_penalty` | `number` | no | Penalizes tokens based on how often they've already appeared. | | `presence_penalty` | `number` | no | Penalizes tokens based on whether they've appeared at all, regardless of count. | | `repetition_penalty` | `number` | no | Repetition penalty coefficient — a separate mechanism from frequency/presence penalty. | | `min_p` | `number` | no | Minimum probability threshold relative to the most likely token. | | `top_a` | `number` | no | Another dynamic-cutoff sampling mechanism, scaling the threshold by the square of the top probability. | | `seed` | `integer` | no | Deterministic sampling seed; not all providers guarantee reproducibility. | | `n` | `integer` | no | Number of completions to generate. | | `stop` | `string | string[]` | no | Sequences where generation stops, up to 4. | | `tools` | `Tool[]` | no | List of tool/function definitions the model may call. | | `tool_choice` | `string | object` | no | Controls whether/how the model is forced to call a tool. | | `parallel_tool_calls` | `boolean` | no | Whether to allow parallel tool calls when tools are provided. Default true. OpenAI-family models only — silently stripped when the target model isn't OpenAI's own, not an error. | | `response_format` | `object` | no | Constrains the output format, e.g. {type:"json_object"} or {type:"json_schema"} with a JSON Schema. | | `logit_bias` | `object` | no | Maps token IDs to bias values [-100, 100] added before sampling. OpenAI-family models only — silently stripped when the target model isn't OpenAI's own, not an error. | | `logprobs` | `boolean` | no | Return log probabilities of each output token. OpenAI-family models only — silently stripped when the target model isn't OpenAI's own, not an error. | | `top_logprobs` | `integer` | no | Number of most-likely tokens to return per position (requires logprobs: true). OpenAI-family models only — silently stripped when the target model isn't OpenAI's own, not an error. 0–20 | | `user` | `string` | no | End-user identifier for monitoring and abuse detection. Has no effect on billing. OpenAI-family models only — silently stripped when the target model isn't OpenAI's own, not an error. | | `reasoning` | `object` | no | Reasoning-model thinking control object (e.g. {effort, max_tokens}). | | `reasoning_effort` | `string` | no | Flat shorthand for reasoning.effort. Values: low, medium, high | | `image_config` | `object` | no | Image generation/edit options (used when modalities includes image). | | `modalities` | `string[]` | no | Requested output modalities. Including image routes through the image-generation dispatch path. Audio chat uses ["text","audio"] with an audio object and requires a model supporting text and audio output. Values: text, image, audio Example: ["text","audio"] | | `audio` | `object` | no | Audio output options with modalities:["text","audio"]. Supported voice and format values depend on the model. Example: {"voice":"alloy","format":"wav"} | | `audio.voice` | `string` | no | A voice supported by the model, for example alloy. Example: "alloy" | | `audio.format` | `string` | no | An output format supported by the model, for example wav; streaming commonly uses pcm16, subject to model support. Example: "wav" | | `plugins` | `object[]` | no | Array of plugins (e.g. {id:"web"} to enable web search). Supported only on some model routes, with no effect elsewhere; :online enables web search on supported routes. Web search requested through plugins, :online, or web_search_options may incur an upstream add-on charge on the key used: BazaarLink's platform key or your BYOK key. | ### cURL ```bash 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": "user", "content": "Explain quantum computing in one paragraph."}] }' ``` ### Response 200 Success. BazaarLink returns only fields from its public response allowlist, such as its own response ID, model, choices, and usage. The model field follows BazaarLink's public reporting rule: after alias resolution it may retain the name you requested, while auto-routed requests show the model that served them. The provider field and other upstream-only metadata are removed. usage.cost is always BazaarLink's settled amount. ```json { "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.000648 } } ``` ### Response 400 Invalid request. Common causes: malformed body, missing model/messages, or unsupported_modality — the messages include image input but every route for this model explicitly lacks image support (this check exists only on this endpoint; when the catalog has no modality data the request is passed through for the upstream to judge, so not every text-only model is guaranteed to return 400). ```json { "error": { "message": "Model 'deepseek/deepseek-v4-flash' does not support image input.", "type": "invalid_request_error", "code": "unsupported_modality", "param": "messages" } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` ### Response 410 The model has been retired from the catalog — switch to a newer model ID. ```json { "error": { "message": "Model openai/gpt-3.5-turbo has been retired", "type": "model_not_available", "code": "model_retired" } } ``` --- # Evaluations ## POST /v1/evaluations **Run a typed evaluation** Jev evaluates a JSON state against named questions and returns typed boolean, choice, or score answers. Call POST /v1/evaluations with Bearer authentication; the model has a 32K-token context window. Discover Jev with /v1/models?output_modalities=decisions or /v1/models?output_modalities=all; the default text-only model list omits decision-only models. Jev is available only through the evaluations endpoint; chat-shaped endpoints return HTTP 400. Current list price: $0.042 per 1M input tokens and $0 per 1M output tokens. Sufficient credits are required. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | no | The optional evaluation model in the request body. Omit it to use the default typesafe-ai/jev; any other value is rejected as model_not_available. optional; defaults to typesafe-ai/jev Example: "typesafe-ai/jev" | | `state` | `JSON string | object | array` | yes | The state or evidence to evaluate. It may be a string, object, or array. JSON value Example: "The support agent issued a full refund." | | `questions` | `record` | yes | One or more named questions evaluated in parallel. | | `questions.type` | `boolean | choice | score` | yes | The typed answer requested for this question. | | `questions.instructions` | `JSON string | object | array` | yes | Instructions describing what this question should decide. | | `questions.criteria` | `record | JSON[]` | no | Required for choice questions as an option-to-description record; required for score questions as an ordered array of at least two criteria; optional for boolean questions. | ### cURL ```bash curl 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"] } } }' ``` ### Response 200 Success. The response includes id, object, model, answers, usage, rounding, and warnings. answers is keyed by question name; choice probabilities use option names as keys. For score questions, score is the expected value over indices 0 through N-1, and probabilities keys are the string indices in criteria order. usage reports input_tokens, output_tokens, and total_tokens; rounding reports probabilityDecimals and scoreDecimals. ```json { "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": [] } ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "'model' is required.", "type": "invalid_request_error", "code": 400 } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` ### Response 402 A paid evaluation is rejected before upstream dispatch when the API key's user or organization does not have enough available credits. ```json { "error": { "message": "Insufficient credits. Please top up to continue.", "type": "invalid_request_error", "code": 402 } } ``` ### Response 403 The evaluation content was blocked by moderation. ```json { "error": { "message": "Content policy violation", "type": "content_filter", "code": 403 } } ``` --- # Messages (Anthropic) ## POST /v1/messages **Create a message** Requests a model response using Anthropic's native /v1/messages shape; BazaarLink routes it to any supported model, not just Anthropic's own. Point the official Anthropic SDK's base_url at BazaarLink and existing code works unchanged. Accepts either Authorization: Bearer or the Anthropic SDK's conventional x-api-key header. If the resolved model isn't native Anthropic, BazaarLink transparently translates the request/response, preserving tool_use blocks and stop_reason semantics where possible. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | The model to use, provider-qualified, e.g. "anthropic/claude-sonnet-4.6". minLength: 1 Example: "anthropic/claude-sonnet-4.6" | | `messages` | `Message[]` | yes | Array of conversation messages, at least one. Each has a role (user/assistant) and content (a string, or an array of content blocks such as text/image/tool_use/tool_result). minLength: 1 | | `max_tokens` | `integer` | yes | Maximum number of tokens the response may generate. Unlike the OpenAI-compatible endpoints, this field is required in the Anthropic shape. positive integer Example: 1024 | | `system` | `string | ContentBlock[]` | no | System prompt — a separate top-level field, not a message inside the messages array (one of the main shape differences from the OpenAI format). | | `stream` | `boolean` | no | true streams the response as SSE (message_start / content_block_delta / message_delta / message_stop events); omitted or false returns a single JSON response. | | `temperature` | `number` | no | Sampling temperature — higher is more random, lower is more deterministic. Sampling values are forwarded without range validation; the model may clamp or ignore values outside its supported range. Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | | `top_p` | `number` | no | Nucleus sampling threshold. Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | | `top_k` | `integer` | no | Limits sampling to the K highest-probability tokens. Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | | `stop_sequences` | `string[]` | no | Array of strings where generation stops. Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | | `tools` | `Tool[]` | no | List of tool definitions the model may call, Anthropic-shaped (name, description, input_schema). Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | | `tool_choice` | `object` | no | Controls whether/how the model is forced to call a tool, e.g. {type:"auto"}, {type:"any"}, {type:"tool", name:"..."}. Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | | `thinking` | `object` | no | Extended-thinking control object, e.g. {type:"enabled", budget_tokens:2048}. Only meaningful on reasoning models. Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | | `metadata` | `object` | no | Request metadata, e.g. {user_id:"..."}. Used for monitoring and abuse detection; has no effect on billing. Not validated by BazaarLink — forwarded as-is to the upstream. If the resolved model isn't native Anthropic, how this field is handled depends on BazaarLink's translation layer. | ### cURL ```bash curl https://api.bazaarlink.ai/v1/messages \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-4.6", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}] }' ``` ### Response 200 BazaarLink returns a standardized response and removes upstream-specific fields. usage.cost is the USD amount charged to the customer for this request. 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. usage.cost and usage.bz_cost contain the same USD customer charge. Long non-streaming Chat, Messages, and Responses requests (about 12 seconds or longer) may start with whitespace keep-alive padding before the JSON body. Standard JSON parsers accept leading whitespace. ```json { "id": "85e6f35c71a31f19-SJC", "type": "message", "role": "assistant", "model": "anthropic/claude-sonnet-4.6", "content": [ { "type": "text", "text": "Hello! How can I help you today?" } ], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 10, "output_tokens": 12, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cost": 0.000186, "bz_cost": 0.000186 } } ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "type": "error", "error": { "type": "invalid_request_error", "message": "'max_tokens' is required and must be a positive integer." } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "type": "error", "error": { "type": "authentication_error", "message": "Invalid API key" } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "type": "error", "error": { "type": "invalid_request_error", "message": "Insufficient credits" } } ``` ### Response 410 The model has been retired from the catalog — switch to a newer model ID. ```json { "error": { "message": "Model anthropic/claude-2 has been retired", "type": "model_not_available", "code": "model_retired", "param": "model" } } ``` ### Response 413 Request body too large (50 MB limit). ```json { "type": "error", "error": { "type": "invalid_request_error", "message": "Request body too large (max 10 MB)." } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "type": "error", "error": { "type": "rate_limit_error", "message": "Rate limit exceeded (free tier)." } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "type": "error", "error": { "type": "overloaded_error", "message": "Service temporarily unavailable — global spend limit reached. Try again shortly." } } ``` --- # Responses API ## POST /v1/responses **Create a response** Requests a model response using the OpenAI Responses API request shape (input/instructions); internally converted to Chat Completions format and routed to any supported model — this is a translation layer, not a separate inference path. This is a stateless endpoint: passing store: true or a non-null previous_response_id returns 400 (see the 400 entry below for its distinct error shape) — every call must carry the full conversation in input. When streaming, reasoning is surfaced via response.reasoning_text.delta events and a type:"reasoning" output item. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | The model to use, provider-qualified, e.g. "openai/gpt-5.4". minLength: 1 Example: "openai/gpt-5.4" | | `input` | `string | InputItem[]` | yes | Conversation input — either a single string, or a structured array of items (message/function_call/function_call_output). Messages may use plain text or a content-block array including input_image. | | `instructions` | `string` | no | System-level instructions, inserted as the first system message at the front of the converted messages array. | | `stream` | `boolean` | no | true streams the response as Responses-API SSE events (response.created, response.output_item.added, response.output_text.delta, response.reasoning_text.delta, response.completed, and others); omitted or false returns a single JSON response. | | `max_output_tokens` | `integer` | no | Maximum number of tokens the response may generate (converted to the upstream Chat Completions max_tokens). | | `tools` | `Tool[]` | no | List of tool/function definitions the model may call. Accepts the Responses-spec flat shape {type:"function", name, description, parameters} (recommended), and also the Chat Completions nested shape {type:"function", function:{...}} (accepted for back-compat, passed through as-is). The response object's tools field echoes whatever was actually sent here. | | `tool_choice` | `string | object` | no | Controls whether/how the model is forced to call a tool, e.g. {type:"function", name:"..."} (flat shape, recommended, auto-converted for upstream) or "auto". The response object's tool_choice field echoes whatever was sent here, defaulting to "auto" when omitted. | | `parallel_tool_calls` | `boolean` | no | Whether to allow parallel tool calls when tools are provided. Defaults to true when omitted; the response object's parallel_tool_calls field echoes whatever was sent here. | | `plugins` | `object[]` | no | Array of plugins (e.g. {id:"web"} to enable web search). Supported only on some model routes, with no effect elsewhere; :online enables web search on supported routes. Web search requested through plugins, :online, or web_search_options may incur an upstream add-on charge on the key used: BazaarLink's platform key or your BYOK key. | | `store` | `boolean` | no | ⚠️ This endpoint is stateless — passing true returns 400 immediately, it is never accepted or silently ignored. Omitting it or passing false is the only valid usage. | | `previous_response_id` | `string` | no | ⚠️ This endpoint is stateless — passing any non-null value returns 400 immediately, it is never accepted or silently ignored. To continue a conversation, send the full history in the input array instead. | | `models` | `string[]` | no | Fallback model list — tried in order, only used once the primary model's own candidate group is exhausted. Each fallback is resolved through BazaarLink routing and billed at BazaarLink's customer price for that model; the list is not forwarded upstream. | | `reasoning` | `object` | no | Reasoning-model thinking control object (e.g. {effort, max_tokens}), forwarded as-is upstream — no defaults are injected. | | `reasoning_effort` | `string` | no | Flat shorthand for reasoning.effort. Values: low, medium, high | | `thinking` | `object` | no | Claude extended-thinking's additive token budget (e.g. {type:"enabled", budget_tokens:2048}), on top of max_output_tokens — forwarded as-is, no defaults injected. | | `enable_thinking` | `boolean` | no | Thinking on/off toggle for Qwen3/GLM-family models, forwarded as-is. | ### cURL ```bash curl https://api.bazaarlink.ai/v1/responses \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "input": "What is the capital of France?" }' ``` ### Response 200 BazaarLink returns a standardized response and removes upstream-specific fields. usage.cost and usage.bz_cost are the same USD amount charged to the customer for this request. 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. Long non-streaming Chat, Messages, and Responses requests (about 12 seconds or longer) may start with whitespace keep-alive padding before the JSON body. Standard JSON parsers accept leading whitespace. ```json { "id": "85e6f35c71a31f19-SJC", "object": "response", "created_at": 1753500000, "completed_at": 1753500002, "status": "completed", "model": "openai/gpt-5.4", "output": [ { "type": "reasoning", "id": "bazaarlink-reasoning-item-id", "status": "completed", "summary": [], "content": [ { "type": "reasoning_text", "text": "The user is asking a simple geography fact..." } ] }, { "type": "message", "id": "bazaarlink-message-item-id", "role": "assistant", "status": "completed", "content": [ { "type": "output_text", "text": "The capital of France is Paris.", "annotations": [] } ] } ], "usage": { "input_tokens": 14, "output_tokens": 32, "total_tokens": 46, "cost": 0.00021, "bz_cost": 0.00021, "output_tokens_details": { "reasoning_tokens": 18 } }, "error": null, "incomplete_details": null, "tool_choice": "auto", "tools": [], "truncation": "auto", "parallel_tool_calls": true, "metadata": {}, "store": false } ``` ### Response 400 Invalid request. Most causes (malformed JSON, missing required fields, input converted to zero messages, context exceeding the model's limit) use the standard {error:{message,type,code}} shape. ⚠️ One exception: passing store: true or a non-null previous_response_id uses {error:{code:"invalid_prompt",message,type},metadata:null} instead — a STRING code, not a number, plus an extra top-level metadata field, as shown in the example below. ```json { "error": { "code": "invalid_prompt", "message": "This API is stateless: 'previous_response_id' is not supported. Pass the full conversation history in the 'input' array instead.", "type": "invalid_request_error" }, "metadata": null } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 410 The model has been retired from the catalog — switch to a newer model ID. ```json { "error": { "message": "Model openai/gpt-4-vision has been retired", "type": "model_not_available", "code": "model_retired", "param": "model" } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded (free tier). Please slow down.", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or this tier was disabled by an admin. Retry later. ```json { "error": { "message": "Service temporarily unavailable. Please retry shortly.", "type": "api_error", "code": 502 } } ``` --- # Images ## POST /v1/images/generations **Generate an image** Generates an image from a text prompt. Returns OpenAI-compatible sync JSON by default (works with client.images.generate()); pass stream: true to get an SSE event stream instead. Image-to-image is supported via inputImages / input_references. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | The image generation model to use, provider-qualified, e.g. "openai/gpt-image-1". minLength: 1 Example: "openai/gpt-image-1" | | `prompt` | `string` | yes | Text description of the desired image. minLength: 1 Example: "a red cat on a sofa" | | `n` | `integer` | no | Number of images to generate, 1–10. 1–10 Example: 1 | | `size` | `string` | no | Image size — explicit pixels ("2048x2048") or a tier ("2K", "4K"). Supported sizes vary by model; see GET /api/v1/images/models for each model's supported_image_sizes. Example: "1024x1024" | | `stream` | `boolean` | no | Selects the response shape: omitted or false = OpenAI-compatible sync JSON (the default), true = SSE event stream. | | `input_references` | `string[]` | no | Source images for image-to-image (data: or https: URLs), up to 8. Alias of inputImages (industry-conventional field name); both pass content moderation first. Returns 400 — not a warning — when the model's route does not support i2i. ≤ 8 images | | `aspect_ratio` | `enum` | no | Aspect ratio of the generated image. Natively supported on some model routes — check supported_parameters in the GET /v1/images/models response for each model; unsupported routes ignore the field and note it in the response's warnings[] (the request still succeeds). Values: 1:1, 1:2, 1:4, 1:8, 2:1, 2:3, 3:2, 3:4, 4:1, 4:3, 4:5, 5:4, 8:1, 9:16, 16:9, 9:19.5, 19.5:9, 9:20, 20:9, 9:21, 21:9, auto Example: "16:9" | | `resolution` | `enum` | no | Resolution tier; the maximum and supported values depend on the model. See GET /api/v1/images/models for each model's supported_image_sizes. Natively supported on some model routes — check supported_parameters in the GET /v1/images/models response for each model; unsupported routes ignore the field and note it in the response's warnings[] (the request still succeeds). Values: 0.5K, 1K, 2K, 4K Example: "2K" | | `quality` | `enum` | no | Rendering quality. Natively supported on some model routes — check supported_parameters in the GET /v1/images/models response for each model; unsupported routes ignore the field and note it in the response's warnings[] (the request still succeeds). Values: auto, low, medium, high | | `background` | `enum` | no | Background treatment; transparent requires an alpha-capable output_format (png/webp). Natively supported on some model routes — check supported_parameters in the GET /v1/images/models response for each model; unsupported routes ignore the field and note it in the response's warnings[] (the request still succeeds). Values: auto, transparent, opaque | | `output_format` | `enum` | no | Encoding of the returned image bytes. Natively supported on some model routes — check supported_parameters in the GET /v1/images/models response for each model; unsupported routes ignore the field and note it in the response's warnings[] (the request still succeeds). Values: png, jpeg, webp, svg Example: "png" | | `output_compression` | `integer` | no | Compression level (0–100) for webp/jpeg output; ignored for png. Natively supported on some model routes — check supported_parameters in the GET /v1/images/models response for each model; unsupported routes ignore the field and note it in the response's warnings[] (the request still succeeds). 0–100 Example: 100 | | `seed` | `integer` | no | Deterministic sampling seed; identical seed + parameters should reproduce the result, though not all providers guarantee it. Natively supported on some model routes — check supported_parameters in the GET /v1/images/models response for each model; unsupported routes ignore the field and note it in the response's warnings[] (the request still succeeds). | ### cURL ```bash curl https://api.bazaarlink.ai/v1/images/generations \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-image-1", "prompt": "a red cat on a sofa", "size": "1024x1024" }' ``` ### Response 200 Success. By default (or stream: false) returns the sync JSON below; with stream: true, returns an SSE event stream (see the deep guide for the event protocol). Parameters ignored by the route appear in top-level warnings[] (informational only). usage.cost is USD amount charged to the customer for this request. ```json { "created": 1753500000, "data": [ { "url": "https://api.bazaarlink.ai/v1/images/proxy/eyJhbGciOi..." } ], "warnings": [ { "param": "aspect_ratio", "action": "dropped", "reason": "'aspect_ratio' is not supported for this model and was ignored; the image was still generated using the model's default." } ] } ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "'model' is required.", "type": "invalid_request_error", "code": 400 } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` ### Response 410 The model has been retired from the catalog — switch to a newer model ID. ```json { "error": { "message": "Model openai/dall-e-2 has been retired", "type": "model_not_available", "code": "model_retired" } } ``` ### Response 413 Request body too large (50 MB limit). ```json { "error": { "message": "Request body too large (max 10 MB).", "type": "invalid_request_error", "code": 413 } } ``` --- ## POST /v1/images/edits **Edit an image** Uploads source images as multipart/form-data and edits them with a text prompt — compatible with OpenAI client.images.edit(). Reuses the image-to-image pipeline internally; the response is always sync JSON. mask is not supported yet, and response_format only supports URLs (b64_json is rejected). Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `image` | `file` | yes | Source image file(s) to edit. Repeat the image field (or use image[]) for multiple files — up to 8, 10 MB each, MIME must be image/*. ≤ 8 files, ≤ 10 MB each, image/* | | `prompt` | `string` | yes | Text description of the edit to apply. minLength: 1 Example: "change the background to a night city" | | `model` | `string` | no | The image edit model. When omitted, the server default applies (currently gpt-5.4-image-2). Example: "openai/gpt-5.4-image-2" | | `n` | `integer` | no | Number of results to generate, 1–10. 1–10 Example: 1 | | `size` | `string` | no | Output size, must be a "x" pixel string (e.g. 1024x1024); values in any other format are silently ignored. format: x Example: "1024x1024" | ### cURL ```bash curl https://api.bazaarlink.ai/v1/images/edits \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -F model="openai/gpt-5.4-image-2" \ -F image=@cat.png \ -F prompt="change the background to a night city" ``` ### Response 200 Success. Always returns sync JSON (the SSE stream is drained internally) — compatible with client.images.edit(). ```json { "created": 1753500000, "data": [ { "url": "https://api.bazaarlink.ai/v1/images/proxy/eyJhbGciOi..." } ] } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "mask is not supported yet", "type": "invalid_request_error", "code": "mask_not_supported" } } ``` ### Response 410 The model has been retired from the catalog — switch to a newer model ID. ```json { "error": { "message": "Model openai/dall-e-2 has been retired", "type": "model_not_available", "code": "model_retired" } } ``` ### Response 413 Request body too large (50 MB limit). ```json { "error": { "message": "Request body too large (max 10 MB).", "type": "invalid_request_error", "code": 413 } } ``` --- ## GET /v1/images/proxy/:token **Fetch a cached image** Returns the raw bytes of a generated image. The image-cached events and data[].url values in generation responses point here; the token itself is the access credential (no API key needed) and expires after 1 hour, after which the endpoint returns 404. Authentication: Public ### Path parameters | Name | Type | Required | Description | |---|---|---:|---| | `token` | `string` | yes | The image token issued in the generation response. Only UUID v4 is accepted; anything else returns 404. UUID v4 | ### cURL ```bash curl -o result.png \ https://api.bazaarlink.ai/v1/images/proxy/9f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f ``` ### Response 200 Success. Returns the raw image bytes (not JSON) with the image's MIME as Content-Type (SVG is neutralized to application/octet-stream to prevent same-origin script execution) and a 1-hour immutable cache header. ```text HTTP/1.1 200 OK Content-Type: image/png Content-Length: 428381 Cache-Control: public, max-age=3600, immutable X-Content-Type-Options: nosniff Content-Security-Policy: sandbox ``` ### Response 404 The token is not a UUID v4, or the image has expired (1-hour TTL) or never existed. The response body is plain text, not JSON. ```text Image not found ``` ### Response 503 The cache storage is temporarily unavailable. The response body is plain text, not JSON. ```text Storage unavailable ``` --- ## GET /v1/images/models **List image generation models** Lists every available image generation model, each with supported_parameters — the authoritative list of what that model's route actually accepts (e.g. whether aspect_ratio is honored natively, whether input_references enables image-to-image). No auth required; with an organization API key, the response is filtered by the org's model allowlist. Authentication: Public ### cURL ```bash curl https://api.bazaarlink.ai/v1/images/models ``` ### Response 200 Success. Returns the model list; cached for 5 minutes (private, uncached with an organization key). supported_parameters is the authoritative per-route support table. ```json { "object": "list", "data": [ { "id": "openai/gpt-image-1", "object": "model", "created": 1753500000, "owned_by": "openai", "name": "GPT Image 2", "description": "…", "pricing": { "prompt": "0.000005", "completion": "0.00004", "image": "0.04" }, "architecture": { "modality": "text+image->image", "input_modalities": [ "text", "image" ], "output_modalities": [ "image" ] }, "supported_parameters": [ "model", "prompt", "n", "size", "stream", "aspect_ratio", "resolution", "quality", "background", "output_format", "output_compression", "seed", "input_references" ] } ] } ``` ### Response 500 The server temporarily failed to fetch the model list. Retry later. ```json { "error": { "message": "Failed to fetch image models", "type": "server_error", "code": 500 } } ``` --- # Embeddings ## POST /v1/embeddings **Create embeddings** Converts text (or, for models that support it, multimodal content) into vector embeddings. Request and response shapes match OpenAI's embeddings API, so it works directly with client.embeddings.create(). input accepts a single string or an array of strings. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | The embeddings model to use, provider-qualified, e.g. "openai/text-embedding-3-small". minLength: 1 Example: "openai/text-embedding-3-small" | | `input` | `string | string[]` | yes | The text to embed. Accepts a single string, or an array of strings to embed multiple inputs in one request. Some models additionally accept structured multimodal content items, forwarded to the upstream as-is; actual support depends on the model. Example: "The quick brown fox" | | `encoding_format` | `string` | no | Encoding format, forwarded to the upstream as-is with no local validation; supported values depend on the upstream. Example: "float" | | `dimensions` | `integer` | no | The number of dimensions for the output vector, forwarded to the upstream as-is with no local validation; only some models support custom dimensions, and the upstream decides the behavior (typically ignore or error) when unsupported. | | `provider` | `object` | no | Unsupported. The entire object is ignored and removed before forwarding; it cannot select an upstream or price tier. | ### cURL ```bash curl https://api.bazaarlink.ai/v1/embeddings \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/text-embedding-3-small", "input": "The quick brown fox" }' ``` ### Response 200 Success. Returns the OpenAI-compatible shape; data[] corresponds to each input in order. usage.cost is the USD amount charged to the customer for this request. The opaque BazaarLink request id in the response matches x-request-id; use it with GET /v1/generation. Stats may take a few seconds after completion; retry a 404 briefly. The non-streaming response also returns the same id in x-generation-id. ```json { "id": "85e6f35c71a31f19-SJC", "object": "list", "data": [ { "object": "embedding", "index": 0, "embedding": [ 0.0023064255, -0.009327292, 0.015797347 ] } ], "model": "openai/text-embedding-3-small", "usage": { "prompt_tokens": 5, "total_tokens": 5, "cost": 1e-7 } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "No route found for model 'unknown/model-name'.", "type": "invalid_request_error", "code": "unknown_model", "param": "model" } } ``` ### Response 403 A Management API Key was used to call this endpoint, or the account is suspended/not found. Management Keys can only call key-management endpoints, not model requests. ```json { "error": { "message": "Management keys cannot make model calls. Use a standard API key.", "type": "invalid_request_error", "code": 403 } } ``` ### Response 404 The model exists in the catalog but currently has no configured, priced route available (a zero-price model). ```json { "error": { "message": "Model has no priced route configured", "type": "model_not_available", "code": "model_not_configured" } } ``` ### Response 410 The model has been retired from the catalog — switch to a newer model ID. ```json { "error": { "message": "Model openai/text-embedding-ada-002 has been retired", "type": "model_not_available", "code": "model_retired" } } ``` ### Response 502 All available upstream providers failed, or the upstream response could not be parsed. Retry shortly. ```json { "error": { "message": "Service temporarily unavailable. Please retry shortly.", "type": "api_error", "code": 502 } } ``` --- ## GET /v1/models **List embedding models** Returns available models, filterable via output_modalities. This is not a dedicated embeddings endpoint — it is the shared model-listing endpoint (GET /v1/models); passing output_modalities=embeddings narrows it to embedding models only. When the parameter is omitted, the default returns text models only, not everything. Authentication: Public ### Query parameters | Name | Type | Required | Description | |---|---|---:|---| | `output_modalities` | `string` | no | A single comma-separated string (not repeated same-name query keys), e.g. embeddings or embeddings,image. Pass all to get every modality. Defaults to text only when omitted. Unrecognized values return 400. comma-separated, single value Example: "embeddings" | ### cURL ```bash curl "https://api.bazaarlink.ai/v1/models?output_modalities=embeddings" ``` ### Response 200 Success. data[] contains only models matching the filter; each entry includes pricing (USD per million tokens), context_length, architecture.output_modalities, and related fields. ```json { "object": "list", "data": [ { "id": "text-embedding-3-small", "object": "model", "owned_by": "openai", "name": "OpenAI: text-embedding-3-small", "context_length": 8191, "aliases": [ "openai/text-embedding-3-small" ], "pricing": { "prompt": "0.00000002", "completion": "0" }, "architecture": { "modality": "text->embedding", "input_modalities": [ "text" ], "output_modalities": [ "embeddings" ] } } ] } ``` ### Response 400 Malformed output_modalities — not a single comma-separated string, contains an unrecognized modality value, or exceeds the length limit. ```json { "error": { "message": "output_modalities must be a single comma-separated string", "type": "invalid_request_error", "code": "invalid_output_modalities" } } ``` ### Response 500 An unexpected error occurred while fetching the model catalog. ```json { "error": { "message": "Internal error fetching models", "code": 500 } } ``` --- # Videos ## POST /v1/videos **Submit a video generation request** Submits an asynchronous video generation job and immediately returns 202 with a job id. Poll GET /v1/videos/{id} until completed or failed; when complete, download from unsigned_urls. Credits are reserved at submit based on duration and resolution, using a worst-case estimate when needed. On completion, usage.cost is the USD amount charged to the customer based on delivered seconds. Authentication: Bearer API key required ### Request body | Name | Type | Required | Description | |---|---|---:|---| | `model` | `string` | yes | The video generation model ID (provider-qualified). Example: "bytedance/seedance-2.0" | | `prompt` | `string` | yes | The text prompt describing the video to generate. | | `duration` | `integer` | no | Video length in seconds. Defaults to 2. Each model has tighter bounds — e.g. reference-to-video (r2v) allows 2–10s; out-of-range values return 400 before any credits are reserved. 1–600 (per-model bounds are tighter; reference-to-video: 2–10) Example: 5 | | `resolution` | `string` | no | Output quality tier, default "720p". Supported tiers vary per model; an unsupported tier returns 400 before any credits are reserved, and the error lists the tiers the model does support. Values: 480p, 720p, 1080p Example: "720p" | | `aspect_ratio` | `string` | no | Aspect ratio. Common values: "16:9", "9:16", "1:1", "4:3", "3:4"; the exact set depends on the model (some also take wide ratios like "21:9"). Unsupported combinations return 400 before credits are reserved; modes that take an input frame derive the ratio from the frame and ignore this field. Values: 16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3, 21:9, 9:21 Example: "16:9" | | `frame_images` | `array` | no | First/last keyframe images (image-to-video, keyframe-to-video). Accepts bare URL strings, or objects { type: "image", image_url: { url }, frame_type: "first" \| "last" }. | | `input_references` | `object[] | string[]` | no | Reference assets (reference-to-video): guide generation with reference items — prefer object form { type: "image", image_url: { url } } (bare URL strings are also accepted and treated as image references). Image references are the most widely supported; audio/video reference objects are forwarded as-is and only take effect on models that support them — other models use the image references and ignore the rest. Only models with an r2v mode accept this field. | | `input_video` | `string | object` | no | Source video (video-edit models only; a BazaarLink extension field). Accepts a URL string, { video_url: { url } }, or { url }. Ignored by non-video-edit models. Editing bills on source seconds + delivered output seconds. | | `generate_audio` | `boolean` | no | Whether to also generate audio. Only effective on audio-capable models; audio seconds bill at a higher per-second rate. | | `watermark` | `boolean` | no | Whether to watermark the output. Defaults to false — BazaarLink actively disables upstream default watermarks unless you explicitly pass true. | | `seed` | `number` | no | Sampling seed. Reproducibility guarantees vary by model. | | `callback_url` | `string` | no | A webhook URL called on the job's terminal state, so you don't have to poll. HTTPS only, and SSRF-checked (private/internal addresses rejected) before any credits are reserved. Fires exactly once, only from the request that wins the terminal-state determination; delivery is retried on failure (2 attempts, 2s/8s backoff), and exhausting retries never affects billing or the job's real status. No signature in v1 — treat the callback as a hint, and confirm status/amount by calling GET /v1/videos/{id} with your API key before acting on it. ⚠️ unsigned_urls in the webhook payload are absolute URLs, unlike the relative paths returned by the poll endpoint. https:// only, ≤2048 chars | ### cURL ```bash curl https://api.bazaarlink.ai/v1/videos \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "bytedance/seedance-2.0", "prompt": "a corgi surfing a wave at sunset", "duration": 5, "resolution": "720p", "aspect_ratio": "16:9" }' ``` ### Response 202 Job accepted (asynchronous). Poll the returned polling_url (i.e. GET /v1/videos/{id}) for status; a ≥5s interval is recommended. ```json { "id": "vjob_Xk3fJ9aQ2mB7cD1eF5gH6iJk", "status": "pending", "polling_url": "/v1/videos/vjob_Xk3fJ9aQ2mB7cD1eF5gH6iJk" } ``` ### Response 400 Invalid request — malformed JSON, missing required fields, or failed validation. ```json { "error": { "message": "'model' is required.", "type": "invalid_request_error", "code": 400 } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": 401 } } ``` ### Response 402 Insufficient credits, or your weekly/monthly budget cap has been reached. ```json { "error": { "message": "Insufficient credits", "type": "invalid_request_error", "code": 402 } } ``` ### Response 429 Rate limit exceeded (RPM depends on your account tier), or an account-level spend brake has tripped. Retry later. ```json { "error": { "message": "Rate limit exceeded", "type": "rate_limit_error", "code": 429 } } ``` ### Response 503 Service temporarily unavailable — all upstream providers are circuit-broken, the global spend limit tripped, or the service is draining for restart. Retry later. ```json { "error": { "message": "Service temporarily unavailable — global spend limit reached. Try again shortly.", "type": "api_error", "code": 503 } } ``` ### Response 404 The requested resource was not found — the ID does not exist or does not belong to this key. ```json { "error": { "message": "Unknown model: alibaba/wan99-t2v", "type": "invalid_request_error", "code": 404 } } ``` ### Response 410 The model has been retired from the catalog — switch to a newer model ID. ```json { "error": { "message": "Model retired.", "type": "model_not_available", "code": 410 } } ``` --- ## GET /v1/videos/:id **Get video generation status** Poll a video job until completed or failed. In-progress status is pending or processing. On completion, unsigned_urls contains downloadable paths and usage.cost is the USD amount charged to the customer, based on delivered seconds. Poll at intervals of at least 5 seconds. Authentication: Bearer API key required ### Path parameters | Name | Type | Required | Description | |---|---|---:|---| | `id` | `string` | yes | The job id returned at submit (prefixed vjob_). Only jobs owned by the same account are visible. Example: "vjob_Xk3fJ9aQ2mB7cD1eF5gH6iJk" | ### cURL ```bash curl https://api.bazaarlink.ai/v1/videos/vjob_Xk3fJ9aQ2mB7cD1eF5gH6iJk \ -H "Authorization: Bearer $BAZAARLINK_API_KEY" ``` ### Response 200 Success. The example shows a completed job; pending / processing responses omit unsigned_urls and usage, and failed adds an error field. unsigned_urls are relative paths — GET them with the same API key to download. ```json { "id": "vjob_Xk3fJ9aQ2mB7cD1eF5gH6iJk", "status": "completed", "polling_url": "/v1/videos/vjob_Xk3fJ9aQ2mB7cD1eF5gH6iJk", "unsigned_urls": [ "/v1/videos/vjob_Xk3fJ9aQ2mB7cD1eF5gH6iJk/content?index=0" ], "usage": { "cost": 0.5, "is_byok": false } } ``` ### Response 401 Missing Authorization header, or the API key is invalid or disabled. ```json { "error": { "message": "Invalid or disabled API key.", "type": "invalid_request_error", "code": 401 } } ``` ### Response 404 The requested resource was not found — the ID does not exist or does not belong to this key. ```json { "error": { "message": "Job not found.", "type": "invalid_request_error", "code": 404 } } ``` --- ## GET /v1/videos/:id/content **Download the generated video** Streams the generated video bytes (typically video/mp4). The unsigned_urls in the poll response point here. Supports HTTP Range (Accept-Ranges: bytes, 206 partial content) — mobile-browser