Providers
Quantized routes each request to a provider based on the endpoint and your configuration. You don’t need to manage separate API keys or accounts for each provider.
Supported providers
| Provider | Slug | Capabilities |
|---|---|---|
| OpenRouter | openrouter |
Chat completions, Responses, Models, Embeddings |
| OpenAI Direct | openai |
Chat completions, Responses, Embeddings, Image generation (gpt-image-*) |
| Anthropic | anthropic |
Chat completions, Models |
| AWS Bedrock | bedrock |
Chat completions, Responses, Bedrock-native embeddings |
| Google Gemini | gemini |
Gemini-native embeddings, Image generation (Gemini Flash Image, Imagen 4) |
| Exa | exa |
Web search, Content fetch |
| Tavily | tavily |
Web search, Content fetch |
| Consensus | consensus |
Academic web search (peer-reviewed papers) |
Default routing
Without an X-Quantized-Provider header, the router picks from the providers that can
serve the endpoint, preferring the one that carries the requested model in the catalog.
| Capability | Provider chosen |
|---|---|
| Chat completions | OpenRouter |
| Responses | OpenRouter |
| Models | OpenRouter |
Embeddings (/v1/embeddings) |
OpenRouter |
Bedrock-native embeddings (/v1/aws-bedrock/embeddings) |
AWS Bedrock (only) |
Gemini-native embeddings (/v1/gemini/embeddings) |
Google Gemini (only) |
Image generation (/v1/images/generations) |
Resolved per-model: OpenAI or Gemini |
| Web search | Exa or Tavily — or Consensus with type: "academic" |
| Content fetch | Exa or Tavily |
Web search, content fetch and image generation each have two eligible providers ranked
equally. When the model id does not select one for you, the choice between them is not
currently deterministic, and the two can differ in what they charge and how they word
their errors.
Pin X-Quantized-Provider whenever you need a repeatable provider, price or error
message. Requests that name a model the catalog resolves are unaffected: the model
selects its provider.
Consensus is not part of this. It is never picked by default on web search, so a
plain {"query": ...} search will not return academic papers.
Choosing a provider
Use the X-Quantized-Provider header to override the default:
# Use Anthropic directly instead of OpenRouter
curl -X POST https://api.quantized.us/v1/chat/completions \
-H "Authorization: Bearer sk-quantized-YOUR-KEY" \
-H "X-Quantized-Provider: anthropic" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4",
"messages": [{"role": "user", "content": "Hello!"}]
}'
# Use Tavily instead of Exa for web search
curl -X POST https://api.quantized.us/v1/web-search \
-H "Authorization: Bearer sk-quantized-YOUR-KEY" \
-H "X-Quantized-Provider: tavily" \
-H "Content-Type: application/json" \
-d '{"query": "latest AI news"}'
All models use the author/model format (e.g., openai/gpt-4.1-mini, anthropic/claude-sonnet-4). Use the Models endpoint to list available model IDs.
Capability matrix
| Endpoint | OpenAI | OpenRouter | Anthropic | Bedrock | Gemini | Exa | Tavily | Consensus |
|---|---|---|---|---|---|---|---|---|
POST /v1/chat/completions |
Yes | Yes (default) | Yes | Yes | — | — | — | — |
POST /v1/responses |
Yes | Yes (default) | — | Yes | — | — | — | — |
POST /v1/embeddings |
Yes | Yes (default) | — | — | — | — | — | — |
POST /v1/aws-bedrock/embeddings |
— | — | — | Yes (default, only) | — | — | — | — |
POST /v1/gemini/embeddings |
— | — | — | — | Yes (default, only) | — | — | — |
POST /v1/images/generations |
Yes | — | — | — | Yes | — | — | — |
GET /v1/models |
— | Yes (default) | Yes | — | — | — | — | — |
POST /v1/web-search |
— | — | — | — | — | Yes | Yes | Yes (pin only) |
POST /v1/fetch |
— | — | — | — | — | Yes | Yes | — |
Chat-completions modalities
Not every provider accepts every content part on POST /v1/chat/completions. Requests are additionally gated by the target model’s declared modalities — see the Models endpoint for per-model input_modality flags.
| Content part | OpenAI | OpenRouter | Anthropic | Bedrock |
|---|---|---|---|---|
text |
Yes | Yes | Yes | Yes |
image_url |
Yes | Yes | Yes | — |
input_audio |
— | Yes | — | — |
video_url |
— | Yes | — | — |
file (PDF) |
— | Yes (universal — works on all models via OpenRouter’s PDF parser) | — | — |
Sending an unsupported modality returns 400 with a descriptive error message (e.g. "No available provider for model 'openai/gpt-4.1-nano' supports audio input") before the request reaches the provider. The message names providers rather than the model because a model’s hosts can differ on what they accept: the router skips a provider that cannot serve the request and only fails when every reachable one refuses.
Anthropic-specific behavior
When routing through X-Quantized-Provider: anthropic, the router adapts OpenAI-shaped requests to Anthropic’s native /v1/messages format:
response_format — JSON output normalization
Anthropic’s API does not natively support the response_format parameter. The router emulates it by injecting a system-prompt instruction telling the model to return raw JSON. Some Claude models (notably Claude Haiku 4.5) still wrap their output in ```json ... ``` markdown fences despite this instruction.
To uphold the response_format contract — “callers asking for JSON get parseable JSON” — the router strips a single wrapping markdown fence from the response content when:
- the request specified
response_format: {"type": "json_object" | "json_schema"}, and - the response content is wrapped entirely in
```json ... ```or``` ... ```(a fence embedded inside prose is not stripped).
This stripping is only applied to the Anthropic provider path — OpenRouter responses are forwarded as-is because OpenRouter handles response_format server-side.
Out-of-scope content parts
Anthropic’s chat endpoint currently receives only text and image_url content parts from the router. Requests containing input_audio, video_url, or file parts are accepted by the router’s serializer but would fail upstream if routed to Anthropic. Use OpenRouter (the default for chat completions) for these modalities.
OpenAI Direct (Chat Completions)
OpenRouter is the default for POST /v1/chat/completions. Sending X-Quantized-Provider: openai routes the same request to OpenAI’s first-party API (https://api.openai.com/v1/chat/completions) with Quantized’s pooled OPENAI_API_KEY, bypassing OpenRouter’s margin. Clients don’t need their own OpenAI account — billing is unified through Quantized’s per-key credit balance.
# Call OpenAI directly instead of through OpenRouter
curl -X POST https://api.quantized.us/v1/chat/completions \
-H "Authorization: Bearer sk-quantized-YOUR-KEY" \
-H "X-Quantized-Provider: openai" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": "Hello!"}]
}'
The catalog id (openai/gpt-4o-mini) is rewritten to OpenAI’s bare model name (gpt-4o-mini) for the upstream call, then the public id is restored on the response. Without the header, the same id is served by OpenRouter.
Models in scope
Only OpenAI’s first-party chat models are routable through this header. The open-weight gpt-oss models are not served by OpenAI’s API and stay OpenRouter-only.
| Model id | Input rate | Output rate |
|---|---|---|
openai/gpt-4o |
$2.50 / 1M | $10.00 / 1M |
openai/gpt-4o-mini |
$0.15 / 1M | $0.60 / 1M |
openai/gpt-4.1-mini |
$0.40 / 1M | $1.60 / 1M |
openai/gpt-4.1-nano |
$0.10 / 1M | $0.40 / 1M |
openai/o4-mini |
$1.10 / 1M | $4.40 / 1M |
openai/gpt-5 |
$1.25 / 1M | $10.00 / 1M |
openai/gpt-5.1 |
$1.25 / 1M | $10.00 / 1M |
A model with X-Quantized-Provider: openai that has no openai row in the catalog is forwarded as-is to OpenAI, which rejects unknown ids with a 400/404.
Billing
OpenAI’s chat response does not include a per-call cost (unlike OpenRouter). credits_used is computed from the per-token rates above, multiplying prompt/completion tokens (and cached input tokens, when reported) by the model’s catalog rates. Streaming bills the same way once the final usage chunk arrives.
Request handling
The router forwards a standard OpenAI-compatible body, with two adjustments versus the OpenRouter path:
- Reasoning. Quantized accepts
reasoning: {"effort": "..."}or the flatreasoning_effort, normalizes both to areasoningobject, then converts it back to OpenAI’s top-levelreasoning_effortstring for the native call (OpenAI’s chat endpoint does not accept the nested object OpenRouter uses). repetition_penaltyis an OpenRouter extension. It is dropped on the native OpenAI route, since OpenAI’s API rejects unknown parameters.
OpenAI’s reasoning models (o4-mini, and the gpt-5 family for reasoning) reject max_tokens upstream. You do not have to care: the router sends max_completion_tokens on this route regardless, so either field works from a client’s point of view. Leave enough budget for the internal reasoning tokens reported under usage.completion_tokens_details.reasoning_tokens.
Responses API
X-Quantized-Provider: openai works on POST /v1/responses too, routing to OpenAI’s first-party /v1/responses. The same seven first-party models, alias rewriting, and local-rate billing apply. Two differences from the chat route:
- Reasoning is passed as an object, not translated. OpenAI’s Responses API takes
reasoning: {"effort": "..."}directly (the same shape OpenRouter uses), so there’s noreasoning_effortconversion here. Usemax_output_tokens(notmax_tokens) and leave room for reasoning tokens. - No
[DONE]sentinel when streaming. The responses stream is a sequence of typedevent: <type>/data: {...}pairs (response.created…response.completed); it ends onresponse.completedrather than adata: [DONE]line.
frequency_penalty / presence_penalty are not accepted by OpenAI’s Responses endpoint, so they are dropped on this route (they remain available via OpenRouter).
OpenAI Direct (Embeddings)
OpenRouter is the default for POST /v1/embeddings. OpenAI Direct serves the same models natively (it calls OpenAI’s /v1/embeddings with Quantized’s pooled OPENAI_API_KEY) and is opt-in via X-Quantized-Provider: openai, which avoids OpenRouter’s margin. Clients don’t need their own OpenAI account; billing is unified through Quantized’s per-key credit balance.
Models in scope
| Model id | Native dimension | Public list rate |
|---|---|---|
text-embedding-3-small |
1536 | $0.02 / 1M tokens |
text-embedding-3-large |
3072 | $0.13 / 1M tokens |
text-embedding-ada-002 |
1536 | $0.10 / 1M tokens |
Unknown OpenAI model ids fall back to a conservative default rate so the router never bills at $0 on a misconfigured request.
OpenRouter passthrough
X-Quantized-Provider: openrouter routes the same request through OpenRouter, which exposes OpenAI’s embedding models with an openai/ prefix. Because OpenRouter’s embedding response does not include a per-call cost, the router falls back to the OpenAI rate table after stripping the prefix.
Bedrock-specific behavior
When routing through X-Quantized-Provider: bedrock, the router calls AWS Bedrock’s Converse API on Quantized’s AWS account. Clients don’t need their own AWS credentials — billing is unified through Quantized’s per-key credit balance.
Model resolution
Models with a bedrock row in the catalog carry the full Bedrock model id in that row’s alias (e.g., amazon.nova-micro-v1:0); clients can use either that or the canonical Quantized id (e.g., amazon/nova-micro-v1:0).
A model with no Bedrock row is not rejected up front. The pin is honoured, the request goes to AWS as sent, and AWS answers:
{
"error": {
"message": "The provided model identifier is invalid."
}
}
That is a 400, not a 404 — see Who validates the model. GET /v1/models does not currently expose which provider serves a model, so there is no way to filter the list for Bedrock-eligible ids from the API alone.
Model access gates
Some Bedrock model families require AWS-side approval before they can be invoked, even when the catalog has a bedrock row for them:
| Family | Bedrock model id prefix | AWS-side approval |
|---|---|---|
| Amazon Nova | amazon.nova-* |
None — invokable immediately |
| Meta Llama, Mistral, Cohere | meta.*, mistral.*, cohere.* |
One-line click-through, instant |
| Anthropic Claude | anthropic.claude-* |
Use-case form (5 fields, manual approval) |
If the AWS account behind the router lacks access for a model, you’ll see a 404 with a message like "Model use case details have not been submitted for this account..." — that’s AWS, not the router. The fix is operator-side: enable the model in the AWS Console under Bedrock → Model access for the region. Until that’s done, route the same call to OpenRouter (the default) or pick a Nova model — Amazon’s own family has no gating.
Request translation
| OpenAI field | Bedrock Converse field |
|---|---|
messages (with role: "system") |
Split — system text becomes top-level system: [{"text": "..."}]; user/assistant stay in messages |
max_tokens / max_completion_tokens |
inferenceConfig.maxTokens |
temperature |
inferenceConfig.temperature |
top_p |
inferenceConfig.topP |
stop (string or array) |
inferenceConfig.stopSequences (always a list — must be non-whitespace, see below) |
tools + tool_choice |
toolConfig.tools (toolSpec) + toolConfig.toolChoice |
Assistant messages with tool_calls[] |
Assistant content with toolUse blocks |
role: "tool" (with tool_call_id) |
User content with a toolResult block |
Stop-sequence quirk
Bedrock rejects whitespace-only stop sequences with 400 The stop sequence value at inferenceConfig.stopSequences.0 is blank. Other providers (OpenRouter, Anthropic native) accept them. If you need a request body that works across all providers, use printable stop sequences such as "###", "END", or "---" instead of "\n\n".
Out-of-scope today
The following are accepted by the router but are not forwarded to Bedrock — they will produce unexpected behavior or no-ops on this provider path. Use OpenRouter (the default for chat completions) when you need them:
- Streaming (
stream: true) — Bedrock provider does not implement streaming; requests will hang or error - Vision / multimodal content parts (
image_url,input_audio,video_url,file) — even when the underlying model supports them response_format— JSON-mode emulation is not implemented for Bedrock; the parameter is silently dropped- Reasoning (
reasoning.effort) — Claude extended thinking via Bedrock is not yet wired through frequency_penalty/presence_penalty/repetition_penalty/seed/logprobs/top_logprobs/logit_bias— Bedrock’s Converse API doesn’t accept them; silently dropped
Bedrock-native Embeddings
POST /v1/aws-bedrock/embeddings is a native-shape passthrough — distinct from the OpenAI-compatible /v1/embeddings. It uses bedrock-runtime.invoke_model (NOT Converse — embedding models don’t speak Converse) and preserves Bedrock’s request/response shape byte-for-byte. See the full reference at AWS Bedrock Embeddings.
Models in scope
| Model id | Vendor | Native dimension | Public list rate |
|---|---|---|---|
amazon.titan-embed-text-v2:0 |
Amazon Titan | 256, 512, 1024 | $0.02 / 1M tokens |
cohere.embed-english-v3 |
Cohere | 1024 | $0.10 / 1M tokens |
cohere.embed-multilingual-v3 |
Cohere | 1024 | $0.10 / 1M tokens |
The endpoint accepts two distinct request bodies discriminated by the model prefix:
amazon.titan-*→{ model, inputText, dimensions?, normalize?, embeddingTypes? }cohere.*→{ model, texts, input_type, embedding_types?, truncate? }
Mismatching the body shape and the model prefix (e.g. Cohere fields on a Titan model id) is rejected with 422 before reaching upstream.
Token estimation for Cohere
Cohere’s response does not include a token count. The router estimates input tokens at ~4 chars per token (floored at 1) — conservative and rarely under-bills natural-language input. Titan returns inputTextTokenCount directly and is billed against the upstream count.
Google Gemini (Embeddings)
POST /v1/gemini/embeddings is a native-shape passthrough to Google’s generativelanguage.googleapis.com/v1beta endpoints. Clients don’t need their own Gemini API key — billing is unified through Quantized’s per-key credit balance. See the full reference at Gemini Embeddings.
Single vs batch routing
The router picks the upstream endpoint based on the cardinality of contents:
- 1 content →
:embedContent($0.15per 1M tokens) - N > 1 contents →
:batchEmbedContents($0.075per 1M tokens — half-priced)
The endpoint field in the response confirms which upstream URL was used.
Sending multi-part content.parts to :embedContent (Gemini’s single-content endpoint) makes Gemini silently concatenate the parts into one string and return ONE vector for the concatenation — no error, 200 OK, wrong shape. The router always dispatches multi-content requests to :batchEmbedContents to avoid this. Treat any unexpected endpoint value as a router bug.
Models in scope
| Model id | Native dimension | Truncatable to |
|---|---|---|
gemini-embedding-001 |
3072 | 768 |
Token estimation
Gemini’s embedding endpoints do not return token counts. Same heuristic as Cohere (~4 chars/token, floored at 1).
Image Generation
POST /v1/images/generations is a unified endpoint — there are no native passthroughs (no /v1/aws-bedrock/images/generations, no /v1/gemini/images/generations). All providers adapt to the same OpenAI-shape request/response.
Provider matrix
| Provider | Models | Native body shape | Transport |
|---|---|---|---|
| OpenAI Direct | gpt-image-2, gpt-image-1.5, gpt-image-1-mini, chatgpt-image-latest |
OpenAI /v1/images/generations |
REST |
| Google Gemini — Flash Image | gemini-3-pro-image, gemini-3.1-flash-image, gemini-3.1-flash-lite-image, gemini-2.5-flash-image |
:generateContent with responseModalities: [TEXT, IMAGE] |
Chat-style |
| Google Gemini — Imagen | imagen-4.0-fast-generate-001 |
:predict with instances + parameters |
REST |
Only these two providers carry the image_generation capability. AWS Bedrock does not, so X-Quantized-Provider: bedrock is not usable on this endpoint.
imagen-4.0-fast-generate-001 is in the catalog and routes correctly, but Gemini’s :predict endpoint is not available on a free-tier API key. Until the project is upgraded, requests for it come back 404 from Google. The Flash Image models are unaffected.
Output transport
Forced to b64_json for every provider. Read images from data[].b64_json. There is no data[].url field — a URL transport would expire and need a CDN rehost subsystem.
Provider-specific field handling
| Field | gpt-image-* | Gemini Flash Image | Imagen |
|---|---|---|---|
prompt |
Yes | Yes (chat-style) | Yes |
n |
1 only | 1 only | 1–4 |
size |
up to 3840px | (dropped — no aspect-ratio parameter) | mapped to aspectRatio + imageSize |
quality |
low, medium, high, auto |
— | — |
style |
(stripped) | (stripped) | (stripped) |
background |
transparent, opaque, auto |
(stripped) | (stripped) |
output_format |
png, jpeg, webp |
(stripped) | (stripped) |
seed |
(stripped) | (stripped) | (stripped) |
negative_prompt |
(stripped) | (stripped) | (stripped) |
size is accepted on every model, including the ones that have nowhere to put it, so one request body works across providers. It is dropped rather than rejected.
Pricing
| Model | Pricing model |
|---|---|
gpt-image-2 |
Token-priced — $5/M input + $30/M output tokens |
gpt-image-1.5, gpt-image-1-mini, chatgpt-image-latest |
Token-priced, computed by the OpenAI adapter |
gemini-3-pro-image |
Token-priced — $2/M input + $120/M output tokens |
gemini-3.1-flash-image |
Token-priced — $0.50/M input + $60/M output tokens |
gemini-3.1-flash-lite-image |
Token-priced — $0.25/M input + $30/M output tokens |
gemini-2.5-flash-image |
Token-priced — $0.30/M input + $30/M output tokens |
imagen-4.0-fast-generate-001 |
$0.020 per image (flat) |
Image pricing is a per-provider table rather than a single catalog rate, so the adapter computes the dollar cost per call and the response reports it as usage.credits_used. Read that field rather than deriving cost from the table above.
Watermarking
The unified response includes a watermark enum on each data[] entry:
c2pa— thegpt-image-*family (always)synthid— Gemini Flash Image and Imagen (always)provenance— reserved for Amazon Titan / Nova, not currently routablenone— no provenance metadata embedded
Consumers targeting education customers should consider rendering a disclosure when watermark != "none".
Content moderation
Both providers return a standard error response on a moderation block (400 with the upstream message), which the router maps to its normal error hierarchy.
The response schema also carries data[].flagged and allows usage.images: 0, for providers that answer a blocked generation with 200 and an empty image list instead of a 4xx. No currently routable provider does this, so flagged is always false today. It is kept because the field is part of the published response shape.
Consensus (academic search)
Consensus indexes peer-reviewed research papers rather than the general web. It serves
POST /v1/web-search only — it has no content-fetch capability, so
X-Quantized-Provider: consensus on POST /v1/fetch fails resolution with a 400
rather than reaching the provider.
Reaching it
Consensus is never a default. It answers only when the request pins it, either way:
# By intent — the router picks Consensus for you
curl -X POST https://api.quantized.us/v1/web-search \
-H "Authorization: Bearer sk-quantized-YOUR-KEY" \
-H "Content-Type: application/json" \
-d '{"query": "does caffeine improve memory", "type": "academic"}'
# By name — equivalent, and works without `type`
curl -X POST https://api.quantized.us/v1/web-search \
-H "Authorization: Bearer sk-quantized-YOUR-KEY" \
-H "X-Quantized-Provider: consensus" \
-H "Content-Type: application/json" \
-d '{"query": "does caffeine improve memory"}'
Prefer type: "academic". It says what you want rather than who should serve it, so it
keeps working if the academic provider changes.
Response differences
The response shape is identical to a general search, so the same client code reads both.
Five fields carry different content:
| Field | On academic |
|---|---|
results[].url |
The paper’s page on consensus.app, not the publisher or DOI link |
results[].id |
The paper’s DOI, or null when it has none |
results[].text |
The paper’s abstract, capped at max_characters. include_text changes nothing, since Consensus has no full text |
results[].score |
Always null — Consensus has no relevance score |
usage.search_time |
Always null — Consensus does not report one |
Academic metadata that Consensus returns but this endpoint does not expose today —
journal name, citation count, study type — is dropped to keep one response shape
across providers.
Consensus serves roughly one in-flight search per account. Searches issued in parallel
come back 503 Service temporarily unavailable; issued one after another they succeed.
Each search also takes several seconds, noticeably longer than a general web search.
If you are fanning out academic searches, run them sequentially or retry the 503.
Failed searches are not billed.
Provider errors
If the upstream provider fails (timeout, rate limit, authentication error), Quantized returns a 503 with a generic message:
{
"error": {
"message": "Service temporarily unavailable"
}
}
Internal provider errors are masked to avoid leaking infrastructure details. See Errors for the full error reference.