OpenRouter Integration
Use official OpenRouter docs as the source of truth for current endpoints, parameters, and capability metadata. Prefer openrouter.ai/docs, openrouter.ai/openapi.json, and the API reference pages under openrouter.ai/docs/api-reference.
Quick Snippets
Use these for fast copy-paste before reaching for the fuller references or templates.
Curl: list models
curl -s https://openrouter.ai/api/v1/models \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Accept: application/json"
Curl: list providers
curl -s https://openrouter.ai/api/v1/providers \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Accept: application/json"
Curl: fetch one generation and its cost
curl -s "https://openrouter.ai/api/v1/generation?id=$GENERATION_ID" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Accept: application/json"
Curl: inspect key and account credit headroom
curl -s https://openrouter.ai/api/v1/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Accept: application/json"
# Requires a management key. This is account-wide, unlike /api/v1/key.
curl -s https://openrouter.ai/api/v1/credits \
-H "Authorization: Bearer $OPENROUTER_MANAGEMENT_KEY" \
-H "Accept: application/json"
Fetch: free models from the catalog
const res = await fetch("https://openrouter.ai/api/v1/models", {
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
Accept: "application/json",
},
});
const json = await res.json();
const freeModels = (json?.data ?? []).filter((model: any) => {
const pricing = model?.pricing ?? {};
return ["prompt", "completion", "request", "image"].every((key) => {
const value = pricing[key];
return value == null || value === "0";
});
});
Curl: text-only chat call
curl -s https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "HTTP-Referer: ${OPENROUTER_SITE_URL:-http://localhost:3000}" \
-H "X-OpenRouter-Title: ${OPENROUTER_APP_NAME:-My App}" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "user", "content": "Write a one-line summary of invoice OCR."}
],
"temperature": 0
}'
Fetch: image input
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
"HTTP-Referer": process.env.OPENROUTER_SITE_URL || "http://localhost:3000",
"X-OpenRouter-Title": process.env.OPENROUTER_APP_NAME || "My App",
},
body: JSON.stringify({
model: "google/gemini-2.5-flash",
messages: [
{
role: "user",
content: [
{ type: "text", text: "Extract all visible text from this image." },
{
type: "image_url",
image_url: { url: imageDataUrl },
},
],
},
],
temperature: 0,
}),
});
const json = await res.json();
const content = json?.choices?.[0]?.message?.content;
Fetch: image generation
const res = await fetch("https://openrouter.ai/api/v1/images", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
"HTTP-Referer": process.env.OPENROUTER_SITE_URL || "http://localhost:3000",
"X-OpenRouter-Title": process.env.OPENROUTER_APP_NAME || "My App",
},
body: JSON.stringify({
model: "google/gemini-3.1-flash-image",
prompt: "Generate a clean product-style illustration of a glass teacup on a plain background.",
aspect_ratio: "1:1",
resolution: "1K",
output_format: "png",
}),
});
const json = await res.json();
const imageUrl = json?.data?.[0]?.b64_json
? `data:${json.data[0].media_type || "image/png"};base64,${json.data[0].b64_json}`
: null;
Fetch: PDF input with file-parser
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
"HTTP-Referer": process.env.OPENROUTER_SITE_URL || "http://localhost:3000",
"X-OpenRouter-Title": process.env.OPENROUTER_APP_NAME || "My App",
},
body: JSON.stringify({
model: "google/gemini-2.5-flash",
messages: [
{
role: "user",
content: [
{ type: "text", text: "Extract the invoice totals as JSON." },
{
type: "file",
file: {
filename: "invoice.pdf",
file_data: pdfDataUrl,
},
},
],
},
],
plugins: [
{
id: "file-parser",
pdf: { engine: "cloudflare-ai" },
},
],
response_format: { type: "json_object" },
temperature: 0,
}),
});
Workflow
Check the docs before making non-trivial changes.
- Run
scripts/check_openrouter_docs.py --quick when accuracy matters or the integration seems stale.
- If the script flags warnings, read
references/docs-check-workflow.md and browse only the flagged official pages.
- Reconcile templates, headers, and parameter usage with the current docs before coding.
Keep secrets server-side.
- Do not expose
OPENROUTER_API_KEY in browser code.
- Put a server route in front of OpenRouter for model discovery and chat calls.
- Set
HTTP-Referer and X-OpenRouter-Title headers when the app has a stable URL and title.
- Do not forward arbitrary user-supplied
http(s) asset URLs straight to OpenRouter. Fetch trusted assets server-side and convert them to data: URLs, or enforce an explicit host allowlist such as OPENROUTER_ALLOWED_REMOTE_ASSET_HOSTS.
Install a starter instead of retyping boilerplate.
- Use
scripts/install_template.sh with --template nextjs or --template express.
- Override base path and env var names at install time when the target project already has conventions.
- Copy shared helpers, streaming UI example, and test fixtures with the template.
Decide what you are integrating.
- Catalog, providers, free-model filters, key/credit diagnostics, or generation cost lookup: read
references/catalogs-and-costs.md.
- Promotions, discounted endpoints, workload cost comparisons, batch, or service tiers: read
references/discounts-and-cost-controls.md.
- Model catalog or picker: read
references/models-and-ui.md.
- Model selection, provider filters, or fallback policy that should be production-friendly: read
references/catalog-routing-best-practices.md.
- Text, image analysis, dedicated image generation, or PDF inference: read
references/requests-and-responses.md.
- End-to-end image asset workflows such as icons, OG images, preview, and storage: read
references/image-generation-best-practices.md.
- Tool calling or an agentic loop: read
references/tools-and-function-calling.md.
- Tool reliability or structured-output extraction that should survive production use: read
references/tool-calling-and-structured-output-best-practices.md.
- Routing and failover policy: read
references/routing-and-fallbacks.md.
- Logging, generation audit, and cost observability: read
references/operations-and-observability-best-practices.md.
- Common failures: read
references/troubleshooting.md.
Discover models before choosing one.
- Use
GET /api/v1/models for the full catalog.
- Use its filters, pagination, and server-side sorting for large or cost-sensitive catalogs; use
GET /api/v1/model/:author/:slug for one alias-aware lookup.
- Use
GET /api/v1/models/user when user or provider preferences matter.
- Use
GET /api/v1/providers when provider routing, privacy, or availability matter in the UI.
- Use
GET /api/v1/models/:author/:slug/endpoints when you need endpoint-level provider data, including promotional pricing.discount.
- Derive free-model lists by filtering zero-priced entries from the model catalog.
- Treat catalog and endpoint prices as already discounted. Never subtract the advertised percentage a second time.
- Treat promotions as temporary endpoint properties, not as a permanent model-quality or routing policy.
- Store model
id, not model name.
- Filter by
architecture.input_modalities and architecture.output_modalities first; use name heuristics only as fallback.
Build requests in OpenAI-compatible format.
- Send text-only prompts as normal chat
messages.
- Send images with
content arrays containing a text part and one or more image_url parts.
- Generate images by sending normal chat
messages plus modalities that include image; pass image_config when output settings matter.
- Discover generation models and endpoint-specific controls through
GET /api/v1/images/models and its per-model endpoint route.
- Use
POST /api/v1/images for new image integrations. Keep chat-completions image output only for legacy compatibility.
- Send PDFs with a
file content part and, when needed, the file-parser plugin.
- Default to
data: URLs for private uploads and for any untrusted remote asset. Use remote http(s) URLs only from explicit allowlisted hosts that you control or trust.
- Keep
tools in every tool-calling request, including follow-up calls that only send tool results.
- Preserve
reasoning_details unchanged across tool turns when a reasoning model returns it.
- Use
service_tier: "flex" only when lower price is worth higher latency and lower availability; record the served tier.
- Use the Batch API for non-interactive work that can finish within 24 hours. Do not assume a synchronous
:batch model id is a drop-in replacement.
Choose response handling deliberately.
- For plain prose, read
choices[0].message.content.
- For the Image API, read base64 assets from
data[*].b64_json and preserve media_type; for legacy chat image output, read choices[0].message.images.
- For structured data, prefer
response_format: { type: "json_schema", ... } when the model supports structured_outputs.
- Fall back to
response_format: { type: "json_object" } when you need JSON but not full schema enforcement.
- Use
assets/shared/parse-openrouter-response.ts for robust text, generated-image, and tool-call extraction.
- Use
assets/shared/stream-openrouter-sse.ts for streaming.
- Use
assets/shared/validate-structured-output.ts with zod for type-safe parsing.
- Use
assets/nextjs-template/components/openrouter-streaming-chat.tsx as the end-to-end streaming UI example.
Reuse parsed PDFs when iterating.
- If a PDF request returns assistant
annotations, pass them back on follow-up requests to avoid reparsing cost and latency.
- Preserve the original file message and append the annotated assistant message before the next user turn.
Verify the integration.
- Run
assets/tests/smoke-curl.sh for text, structured JSON, tools, image analysis, image generation, and PDF cases.
- Run
assets/tests/smoke-catalogs.sh for catalogs, endpoint discounts, key/credit diagnostics, and generation cost lookup.
- Check both successful responses and non-2xx OpenRouter errors.
- Log returned
usage, cost, finish reason, resolved model id, and generation id for debugging.
- Fetch
GET /api/v1/generation?id=... when exact post-hoc cost or token accounting matters.
- Compare candidate models on representative production inputs before switching for a temporary discount.
- For billing incidents, inspect both
/api/v1/key and /api/v1/credits; they report different scopes.
Resources
- Docs-check script:
scripts/check_openrouter_docs.py
- Installer script:
scripts/install_template.sh
- Next.js starter:
assets/nextjs-template/
- Express starter:
assets/express-template/
- Shared TypeScript helpers:
assets/shared/
- Smoke tests and fixtures:
assets/tests/
- Catalog and cost helper:
assets/shared/openrouter-catalog-and-cost.ts
- Image asset helper:
assets/shared/openrouter-generated-image-assets.ts
- Node image persistence helper:
assets/shared/openrouter-generated-image-assets-node.ts
- Catalogs, providers, free-model filters, and generation cost lookup:
references/catalogs-and-costs.md
- Discounts, workload comparisons, batch, service tiers, and credit guardrails:
references/discounts-and-cost-controls.md
- Catalog and routing production rules:
references/catalog-routing-best-practices.md
- Image generation usage, preview, storage, icons, and OG workflows:
references/image-generation-best-practices.md
- Tool calling and structured-output production rules:
references/tool-calling-and-structured-output-best-practices.md
- Operations, logging, and generation audit rules:
references/operations-and-observability-best-practices.md
Quality Rules
- Prefer a server proxy with caching for model lists.
- Keep model picker UIs searchable; plain
<select> breaks down on large catalogs.
- Use
architecture.input_modalities and architecture.output_modalities as the primary capability signals.
- Treat pricing fields as strings from the API; convert explicitly if you need numeric math.
- Persist generation ids anywhere later cost inspection matters.
- Prefer exact generation lookup over estimated UI-only price math when a completed request id exists.
- Include the organization prefix in model ids such as
openai/gpt-4o-mini.
- Expect
choices to always be an array.
- For streaming, expect SSE comment lines and ignore them.
- For PDFs, choose
cloudflare-ai for clean text PDFs, mistral-ocr for scanned or image-heavy PDFs, and native only when the selected model supports file input natively. pdf-text is deprecated.
- Do not assume every model supports
response_format, structured_outputs, tools, or every OpenAI parameter; check supported_parameters first.
- When a request depends on specific parameters such as tools or
response_format, prefer provider.require_parameters: true.
References
- Model discovery, caching, and picker UX:
references/models-and-ui.md
- Catalogs, providers, free-model filters, and generation cost lookup:
references/catalogs-and-costs.md
- Catalog and routing production rules:
references/catalog-routing-best-practices.md
- Image generation usage, preview, storage, icons, and OG workflows:
references/image-generation-best-practices.md
- Text, image analysis, image generation, and PDF request patterns plus response handling:
references/requests-and-responses.md
- Tool calling and agentic loops:
references/tools-and-function-calling.md
- Tool calling and structured-output production rules:
references/tool-calling-and-structured-output-best-practices.md
- Model routing, provider routing, and fallbacks:
references/routing-and-fallbacks.md
- Operations, logging, and generation audit rules:
references/operations-and-observability-best-practices.md
- Troubleshooting and failure diagnosis:
references/troubleshooting.md
- Docs-check workflow:
references/docs-check-workflow.md
1---2name: openrouter-integration3description: Connect apps to hundreds of AI models through OpenRouter — live model and discount discovery, batch and service-tier cost controls, multimodal chat, exact cost and credit diagnostics, provider routing, reasoning, tool calling, structured output validation, starter templates, production playbooks, and verification scripts. Use when an agent needs to add, compare, audit, or debug OpenRouter models, pricing, promotions, credits, routing, requests, or generated assets.4---56# OpenRouter Integration78Use official OpenRouter docs as the source of truth for current endpoints, parameters, and capability metadata. Prefer `openrouter.ai/docs`, `openrouter.ai/openapi.json`, and the API reference pages under `openrouter.ai/docs/api-reference`.910## Quick Snippets1112Use these for fast copy-paste before reaching for the fuller references or templates.1314### Curl: list models1516```bash17curl -s https://openrouter.ai/api/v1/models \18 -H "Authorization: Bearer $OPENROUTER_API_KEY" \19 -H "Accept: application/json"20```2122### Curl: list providers2324```bash25curl -s https://openrouter.ai/api/v1/providers \26 -H "Authorization: Bearer $OPENROUTER_API_KEY" \27 -H "Accept: application/json"28```2930### Curl: fetch one generation and its cost3132```bash33curl -s "https://openrouter.ai/api/v1/generation?id=$GENERATION_ID" \34 -H "Authorization: Bearer $OPENROUTER_API_KEY" \35 -H "Accept: application/json"36```3738### Curl: inspect key and account credit headroom3940```bash41curl -s https://openrouter.ai/api/v1/key \42 -H "Authorization: Bearer $OPENROUTER_API_KEY" \43 -H "Accept: application/json"4445# Requires a management key. This is account-wide, unlike /api/v1/key.46curl -s https://openrouter.ai/api/v1/credits \47 -H "Authorization: Bearer $OPENROUTER_MANAGEMENT_KEY" \48 -H "Accept: application/json"49```5051### Fetch: free models from the catalog5253```ts54const res = await fetch("https://openrouter.ai/api/v1/models", {55 headers: {56 Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,57 Accept: "application/json",58 },59});6061const json = await res.json();62const freeModels = (json?.data ?? []).filter((model: any) => {63 const pricing = model?.pricing ?? {};64 return ["prompt", "completion", "request", "image"].every((key) => {65 const value = pricing[key];66 return value == null || value === "0";67 });68});69```7071### Curl: text-only chat call7273```bash74curl -s https://openrouter.ai/api/v1/chat/completions \75 -H "Authorization: Bearer $OPENROUTER_API_KEY" \76 -H "Content-Type: application/json" \77 -H "HTTP-Referer: ${OPENROUTER_SITE_URL:-http://localhost:3000}" \78 -H "X-OpenRouter-Title: ${OPENROUTER_APP_NAME:-My App}" \79 -d '{80 "model": "openai/gpt-4o-mini",81 "messages": [82 {"role": "user", "content": "Write a one-line summary of invoice OCR."}83 ],84 "temperature": 085 }'86```8788### Fetch: image input8990```ts91const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {92 method: "POST",93 headers: {94 Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,95 "Content-Type": "application/json",96 "HTTP-Referer": process.env.OPENROUTER_SITE_URL || "http://localhost:3000",97 "X-OpenRouter-Title": process.env.OPENROUTER_APP_NAME || "My App",98 },99 body: JSON.stringify({100 model: "google/gemini-2.5-flash",101 messages: [102 {103 role: "user",104 content: [105 { type: "text", text: "Extract all visible text from this image." },106 {107 type: "image_url",108 image_url: { url: imageDataUrl },109 },110 ],111 },112 ],113 temperature: 0,114 }),115});116117const json = await res.json();118const content = json?.choices?.[0]?.message?.content;119```120121### Fetch: image generation122123```ts124const res = await fetch("https://openrouter.ai/api/v1/images", {125 method: "POST",126 headers: {127 Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,128 "Content-Type": "application/json",129 "HTTP-Referer": process.env.OPENROUTER_SITE_URL || "http://localhost:3000",130 "X-OpenRouter-Title": process.env.OPENROUTER_APP_NAME || "My App",131 },132 body: JSON.stringify({133 model: "google/gemini-3.1-flash-image",134 prompt: "Generate a clean product-style illustration of a glass teacup on a plain background.",135 aspect_ratio: "1:1",136 resolution: "1K",137 output_format: "png",138 }),139});140141const json = await res.json();142const imageUrl = json?.data?.[0]?.b64_json143 ? `data:${json.data[0].media_type || "image/png"};base64,${json.data[0].b64_json}`144 : null;145```146147### Fetch: PDF input with file-parser148149```ts150const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {151 method: "POST",152 headers: {153 Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,154 "Content-Type": "application/json",155 "HTTP-Referer": process.env.OPENROUTER_SITE_URL || "http://localhost:3000",156 "X-OpenRouter-Title": process.env.OPENROUTER_APP_NAME || "My App",157 },158 body: JSON.stringify({159 model: "google/gemini-2.5-flash",160 messages: [161 {162 role: "user",163 content: [164 { type: "text", text: "Extract the invoice totals as JSON." },165 {166 type: "file",167 file: {168 filename: "invoice.pdf",169 file_data: pdfDataUrl,170 },171 },172 ],173 },174 ],175 plugins: [176 {177 id: "file-parser",178 pdf: { engine: "cloudflare-ai" },179 },180 ],181 response_format: { type: "json_object" },182 temperature: 0,183 }),184});185```186187## Workflow1881891. Check the docs before making non-trivial changes.190 - Run `scripts/check_openrouter_docs.py --quick` when accuracy matters or the integration seems stale.191 - If the script flags warnings, read `references/docs-check-workflow.md` and browse only the flagged official pages.192 - Reconcile templates, headers, and parameter usage with the current docs before coding.1931942. Keep secrets server-side.195 - Do not expose `OPENROUTER_API_KEY` in browser code.196 - Put a server route in front of OpenRouter for model discovery and chat calls.197 - Set `HTTP-Referer` and `X-OpenRouter-Title` headers when the app has a stable URL and title.198 - Do not forward arbitrary user-supplied `http(s)` asset URLs straight to OpenRouter. Fetch trusted assets server-side and convert them to `data:` URLs, or enforce an explicit host allowlist such as `OPENROUTER_ALLOWED_REMOTE_ASSET_HOSTS`.1992003. Install a starter instead of retyping boilerplate.201 - Use `scripts/install_template.sh` with `--template nextjs` or `--template express`.202 - Override base path and env var names at install time when the target project already has conventions.203 - Copy shared helpers, streaming UI example, and test fixtures with the template.2042054. Decide what you are integrating.206 - Catalog, providers, free-model filters, key/credit diagnostics, or generation cost lookup: read `references/catalogs-and-costs.md`.207 - Promotions, discounted endpoints, workload cost comparisons, batch, or service tiers: read `references/discounts-and-cost-controls.md`.208 - Model catalog or picker: read `references/models-and-ui.md`.209 - Model selection, provider filters, or fallback policy that should be production-friendly: read `references/catalog-routing-best-practices.md`.210 - Text, image analysis, dedicated image generation, or PDF inference: read `references/requests-and-responses.md`.211 - End-to-end image asset workflows such as icons, OG images, preview, and storage: read `references/image-generation-best-practices.md`.212 - Tool calling or an agentic loop: read `references/tools-and-function-calling.md`.213 - Tool reliability or structured-output extraction that should survive production use: read `references/tool-calling-and-structured-output-best-practices.md`.214 - Routing and failover policy: read `references/routing-and-fallbacks.md`.215 - Logging, generation audit, and cost observability: read `references/operations-and-observability-best-practices.md`.216 - Common failures: read `references/troubleshooting.md`.2172185. Discover models before choosing one.219 - Use `GET /api/v1/models` for the full catalog.220 - Use its filters, pagination, and server-side sorting for large or cost-sensitive catalogs; use `GET /api/v1/model/:author/:slug` for one alias-aware lookup.221 - Use `GET /api/v1/models/user` when user or provider preferences matter.222 - Use `GET /api/v1/providers` when provider routing, privacy, or availability matter in the UI.223 - Use `GET /api/v1/models/:author/:slug/endpoints` when you need endpoint-level provider data, including promotional `pricing.discount`.224 - Derive free-model lists by filtering zero-priced entries from the model catalog.225 - Treat catalog and endpoint prices as already discounted. Never subtract the advertised percentage a second time.226 - Treat promotions as temporary endpoint properties, not as a permanent model-quality or routing policy.227 - Store model `id`, not model `name`.228 - Filter by `architecture.input_modalities` and `architecture.output_modalities` first; use name heuristics only as fallback.2292306. Build requests in OpenAI-compatible format.231 - Send text-only prompts as normal chat `messages`.232 - Send images with `content` arrays containing a `text` part and one or more `image_url` parts.233 - Generate images by sending normal chat `messages` plus `modalities` that include `image`; pass `image_config` when output settings matter.234 - Discover generation models and endpoint-specific controls through `GET /api/v1/images/models` and its per-model endpoint route.235 - Use `POST /api/v1/images` for new image integrations. Keep chat-completions image output only for legacy compatibility.236 - Send PDFs with a `file` content part and, when needed, the `file-parser` plugin.237 - Default to `data:` URLs for private uploads and for any untrusted remote asset. Use remote `http(s)` URLs only from explicit allowlisted hosts that you control or trust.238 - Keep `tools` in every tool-calling request, including follow-up calls that only send tool results.239 - Preserve `reasoning_details` unchanged across tool turns when a reasoning model returns it.240 - Use `service_tier: "flex"` only when lower price is worth higher latency and lower availability; record the served tier.241 - Use the Batch API for non-interactive work that can finish within 24 hours. Do not assume a synchronous `:batch` model id is a drop-in replacement.2422437. Choose response handling deliberately.244 - For plain prose, read `choices[0].message.content`.245 - For the Image API, read base64 assets from `data[*].b64_json` and preserve `media_type`; for legacy chat image output, read `choices[0].message.images`.246 - For structured data, prefer `response_format: { type: "json_schema", ... }` when the model supports `structured_outputs`.247 - Fall back to `response_format: { type: "json_object" }` when you need JSON but not full schema enforcement.248 - Use `assets/shared/parse-openrouter-response.ts` for robust text, generated-image, and tool-call extraction.249 - Use `assets/shared/stream-openrouter-sse.ts` for streaming.250 - Use `assets/shared/validate-structured-output.ts` with `zod` for type-safe parsing.251 - Use `assets/nextjs-template/components/openrouter-streaming-chat.tsx` as the end-to-end streaming UI example.2522538. Reuse parsed PDFs when iterating.254 - If a PDF request returns assistant `annotations`, pass them back on follow-up requests to avoid reparsing cost and latency.255 - Preserve the original file message and append the annotated assistant message before the next user turn.2562579. Verify the integration.258 - Run `assets/tests/smoke-curl.sh` for text, structured JSON, tools, image analysis, image generation, and PDF cases.259 - Run `assets/tests/smoke-catalogs.sh` for catalogs, endpoint discounts, key/credit diagnostics, and generation cost lookup.260 - Check both successful responses and non-2xx OpenRouter errors.261 - Log returned `usage`, `cost`, finish reason, resolved model id, and generation id for debugging.262 - Fetch `GET /api/v1/generation?id=...` when exact post-hoc cost or token accounting matters.263 - Compare candidate models on representative production inputs before switching for a temporary discount.264 - For billing incidents, inspect both `/api/v1/key` and `/api/v1/credits`; they report different scopes.265266## Resources267268- Docs-check script: `scripts/check_openrouter_docs.py`269- Installer script: `scripts/install_template.sh`270- Next.js starter: `assets/nextjs-template/`271- Express starter: `assets/express-template/`272- Shared TypeScript helpers: `assets/shared/`273- Smoke tests and fixtures: `assets/tests/`274- Catalog and cost helper: `assets/shared/openrouter-catalog-and-cost.ts`275 - Image asset helper: `assets/shared/openrouter-generated-image-assets.ts`276- Node image persistence helper: `assets/shared/openrouter-generated-image-assets-node.ts`277- Catalogs, providers, free-model filters, and generation cost lookup: `references/catalogs-and-costs.md`278- Discounts, workload comparisons, batch, service tiers, and credit guardrails: `references/discounts-and-cost-controls.md`279- Catalog and routing production rules: `references/catalog-routing-best-practices.md`280- Image generation usage, preview, storage, icons, and OG workflows: `references/image-generation-best-practices.md`281- Tool calling and structured-output production rules: `references/tool-calling-and-structured-output-best-practices.md`282- Operations, logging, and generation audit rules: `references/operations-and-observability-best-practices.md`283284## Quality Rules285286- Prefer a server proxy with caching for model lists.287- Keep model picker UIs searchable; plain `<select>` breaks down on large catalogs.288- Use `architecture.input_modalities` and `architecture.output_modalities` as the primary capability signals.289- Treat pricing fields as strings from the API; convert explicitly if you need numeric math.290- Persist generation ids anywhere later cost inspection matters.291- Prefer exact generation lookup over estimated UI-only price math when a completed request id exists.292- Include the organization prefix in model ids such as `openai/gpt-4o-mini`.293- Expect `choices` to always be an array.294- For streaming, expect SSE comment lines and ignore them.295- For PDFs, choose `cloudflare-ai` for clean text PDFs, `mistral-ocr` for scanned or image-heavy PDFs, and `native` only when the selected model supports file input natively. `pdf-text` is deprecated.296- Do not assume every model supports `response_format`, `structured_outputs`, `tools`, or every OpenAI parameter; check `supported_parameters` first.297- When a request depends on specific parameters such as tools or `response_format`, prefer `provider.require_parameters: true`.298299## References300301- Model discovery, caching, and picker UX: `references/models-and-ui.md`302- Catalogs, providers, free-model filters, and generation cost lookup: `references/catalogs-and-costs.md`303- Catalog and routing production rules: `references/catalog-routing-best-practices.md`304- Image generation usage, preview, storage, icons, and OG workflows: `references/image-generation-best-practices.md`305- Text, image analysis, image generation, and PDF request patterns plus response handling: `references/requests-and-responses.md`306- Tool calling and agentic loops: `references/tools-and-function-calling.md`307- Tool calling and structured-output production rules: `references/tool-calling-and-structured-output-best-practices.md`308- Model routing, provider routing, and fallbacks: `references/routing-and-fallbacks.md`309- Operations, logging, and generation audit rules: `references/operations-and-observability-best-practices.md`310- Troubleshooting and failure diagnosis: `references/troubleshooting.md`311- Docs-check workflow: `references/docs-check-workflow.md`