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
Two endpoints have no fixed default

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"}'
Model naming

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 flat reasoning_effort, normalizes both to a reasoning object, then converts it back to OpenAI’s top-level reasoning_effort string for the native call (OpenAI’s chat endpoint does not accept the nested object OpenRouter uses).
  • repetition_penalty is an OpenRouter extension. It is dropped on the native OpenAI route, since OpenAI’s API rejects unknown parameters.
o-series token cap

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 no reasoning_effort conversion here. Use max_output_tokens (not max_tokens) and leave room for reasoning tokens.
  • No [DONE] sentinel when streaming. The responses stream is a sequence of typed event: <type> / data: {...} pairs (response.created … response.completed); it ends on response.completed rather than a data: [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.15 per 1M tokens)
  • N > 1 contents → :batchEmbedContents ($0.075 per 1M tokens — half-priced)

The endpoint field in the response confirms which upstream URL was used.

The silent-concatenation trap

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 needs a paid Google project

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 — the gpt-image-* family (always)
  • synthid — Gemini Flash Image and Imagen (always)
  • provenance — reserved for Amazon Titan / Nova, not currently routable
  • none — 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 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.

One search at a time

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.