OpenRouter Debug Bundle
Current State
!node --version 2>/dev/null || echo 'N/A'
!python3 --version 2>/dev/null || echo 'N/A'
Overview
When an OpenRouter request fails or returns unexpected results, you need a structured debug bundle: the exact request, response, headers, generation metadata, and environment info. The generation ID (gen-* prefix in response.id) is the key correlator -- it lets you look up exact cost, provider used, and latency via GET /api/v1/generation?id=.
Prerequisites
- An OpenRouter API key (
sk-or-v1-...) exported as OPENROUTER_API_KEY — see the openrouter-install-auth skill for setup
curl and jq for the quick-debug flow and the Common Debug Checks
- Python 3.8+ with the
openai and requests packages for the Debug Bundle Generator
- A failing or suspect request you can reproduce — its
gen-* generation ID is what everything else correlates on
Instructions
- Rule out environment problems first with the Common Debug Checks: verify the key via
/api/v1/auth/key, confirm the model exists in /api/v1/models, and check status.openrouter.ai.
- Reproduce the failure with the Quick Debug: curl command —
curl -v ... | tee /tmp/openrouter-debug.txt captures request headers, response headers, and body in one transcript.
- Extract the generation ID (
jq -r '.id') and query GET /api/v1/generation?id=$GEN_ID to get exact cost, token counts, generation_time, and provider_name.
- For failures inside an application, call
debug_request() from the Python Debug Bundle Generator to capture the same request/response/error/latency/environment data as a DebugBundle and save it with bundle.save("debug_bundle.json").
- Match the symptoms against the Error Handling table (missing generation ID, 502/503,
model_not_found, slow TTFT).
- Before sharing a bundle, redact API keys per Enterprise Considerations (
sk-or-v1-... -> sk-or-v1-[REDACTED]) and include the generation ID in any OpenRouter support request.
Quick Debug: curl
# Send a request and capture full response with headers
curl -v https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "HTTP-Referer: https://my-app.com" \
-H "X-Title: debug-test" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": "Say hello"}],
"max_tokens": 50
}' 2>&1 | tee /tmp/openrouter-debug.txt
# Extract generation ID from response
GEN_ID=$(jq -r '.id' /tmp/openrouter-debug.txt 2>/dev/null)
echo "Generation ID: $GEN_ID"
# Look up generation metadata (exact cost, provider, latency)
curl -s "https://openrouter.ai/api/v1/generation?id=$GEN_ID" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {
model: .model,
total_cost: .total_cost,
tokens_prompt: .tokens_prompt,
tokens_completion: .tokens_completion,
generation_time: .generation_time,
provider: .provider_name
}'
Python Debug Bundle Generator
import os, json, time, platform, sys
from datetime import datetime, timezone
from dataclasses import dataclass, asdict
from typing import Optional
from openai import OpenAI, APIError
import requests as http_requests
@dataclass
class DebugBundle:
timestamp: str
generation_id: Optional[str]
request_model: str
request_messages: list
request_params: dict
response_status: str
response_model: Optional[str]
response_content: Optional[str]
error_type: Optional[str]
error_message: Optional[str]
error_code: Optional[int]
latency_ms: float
generation_metadata: Optional[dict]
environment: dict
def to_json(self) -> str:
return json.dumps(asdict(self), indent=2)
def save(self, path: str = "debug_bundle.json"):
with open(path, "w") as f:
f.write(self.to_json())
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
)
def debug_request(
messages: list[dict],
model: str = "openai/gpt-4o-mini",
**kwargs,
) -> DebugBundle:
"""Execute a request and capture everything for debugging."""
env = {
"python": sys.version,
"platform": platform.platform(),
"openai_sdk": getattr(__import__("openai"), "__version__", "unknown"),
}
start = time.monotonic()
gen_id = None
response_model = None
content = None
error_type = None
error_msg = None
error_code = None
status = "success"
gen_meta = None
try:
response = client.chat.completions.create(
model=model, messages=messages, **kwargs
)
gen_id = response.id
response_model = response.model
content = response.choices[0].message.content
except APIError as e:
status = "error"
error_type = type(e).__name__
error_msg = str(e)
error_code = e.status_code
except Exception as e:
status = "error"
error_type = type(e).__name__
error_msg = str(e)
latency = (time.monotonic() - start) * 1000
# Fetch generation metadata if we have an ID
if gen_id:
try:
gen = http_requests.get(
f"https://openrouter.ai/api/v1/generation?id={gen_id}",
headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
timeout=5,
).json()
gen_meta = gen.get("data")
except Exception:
pass
return DebugBundle(
timestamp=datetime.now(timezone.utc).isoformat(),
generation_id=gen_id,
request_model=model,
request_messages=messages,
request_params={k: v for k, v in kwargs.items() if k != "messages"},
response_status=status,
response_model=response_model,
response_content=content,
error_type=error_type,
error_message=error_msg,
error_code=error_code,
latency_ms=round(latency, 1),
generation_metadata=gen_meta,
environment=env,
)
# Usage
bundle = debug_request(
[{"role": "user", "content": "Test"}],
model="anthropic/claude-3.5-sonnet",
max_tokens=100,
)
print(bundle.to_json())
bundle.save("debug_bundle.json")
Common Debug Checks
# 1. Verify API key is valid
curl -s https://openrouter.ai/api/v1/auth/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {label, usage, limit, is_free_tier}'
# 2. Check if model exists
MODEL="anthropic/claude-3.5-sonnet"
curl -s https://openrouter.ai/api/v1/models | jq --arg m "$MODEL" '.data[] | select(.id == $m) | {id, context_length}'
# 3. Check OpenRouter status
curl -s https://status.openrouter.ai/api/v2/status.json | jq '.status'
Output
Running these flows leaves you with concrete debug artifacts:
/tmp/openrouter-debug.txt — the full verbose curl transcript (request/response headers plus the completion JSON) from the quick-debug step
- A generation-metadata JSON from
/api/v1/generation: model, total_cost, tokens_prompt, tokens_completion, generation_time, and provider_name
debug_bundle.json — the serialized DebugBundle: timestamp, generation ID, request model/messages/params, response status and content, error type/message/code, latency_ms, generation metadata, and environment info (Python version, platform, SDK version)
- One-line JSON results from the three Common Debug Checks (key label/usage/limit, model existence, OpenRouter status)
Examples
Looking up a request you just sent by its generation ID:
curl -s "https://openrouter.ai/api/v1/generation?id=$GEN_ID" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {model, total_cost, generation_time, provider: .provider_name}'
{
"model": "openai/gpt-4o-mini",
"total_cost": 0.000021,
"generation_time": 412,
"provider": "OpenAI"
}
If the metadata comes back empty, wait 1-2 seconds and retry with the same key that made the request. More worked examples: references/examples.md.
Error Handling
| Error |
Cause |
Fix |
| No generation ID in response |
Request failed before reaching provider |
Check network, verify base URL is https://openrouter.ai/api/v1 |
| Generation metadata missing |
Fetched too soon or wrong key |
Wait 1-2s; use same API key that made the request |
| Intermittent 502/503 |
Upstream provider outage |
Check status.openrouter.ai; try different provider |
model_not_found |
Model ID typo or model removed |
Query /api/v1/models to verify model exists |
| Slow TTFT (>10s) |
Model cold start or overload |
Use streaming; try :floor variant for different provider |
Enterprise Considerations
- Always redact API keys from debug bundles before sharing (
sk-or-v1-... -> sk-or-v1-[REDACTED])
- Include the generation ID when contacting OpenRouter support -- it's the primary lookup key
- Log debug bundles to structured storage for post-incident analysis
- Set up automated debug bundle capture on 4xx/5xx responses in production
- Compare failing requests against a known-good baseline to isolate changes
References
- Examples | Errors
- Generation API | Status
Source: jeremylongshore/claude-code-plugins-plus-skills → skills/.curated/openrouter-debug-bundle/SKILL.md
Also appears in: jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/openrouter-pack/skills/openrouter-debug-bundle/SKILL.md
1---2name: openrouter-debug-bundle3description: "'Create debug bundles for troubleshooting OpenRouter API issues. Use when diagnosing failures, unexpected responses, or latency problems. Triggers: ''openrouter debug'', ''openrouter troubleshoot'', ''debug openrouter request'', ''openrouter issue''. '"4allowed-tools: "Read, Write, Edit, Grep, Bash(curl:*), Bash(jq:*), Bash(python3:*), Bash(node:*)"5category: engineering-core6source_repo: jeremylongshore/claude-code-plugins-plus-skills7source_path: "skills/.curated/openrouter-debug-bundle/SKILL.md"8source_url: https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/skills/.curated/openrouter-debug-bundle/SKILL.md9---1011# OpenRouter Debug Bundle1213## Current State1415!`node --version 2>/dev/null || echo 'N/A'`16!`python3 --version 2>/dev/null || echo 'N/A'`1718## Overview1920When an OpenRouter request fails or returns unexpected results, you need a structured debug bundle: the exact request, response, headers, generation metadata, and environment info. The generation ID (`gen-*` prefix in `response.id`) is the key correlator -- it lets you look up exact cost, provider used, and latency via `GET /api/v1/generation?id=`.2122## Prerequisites2324- An OpenRouter API key (`sk-or-v1-...`) exported as `OPENROUTER_API_KEY` — see the `openrouter-install-auth` skill for setup25- `curl` and `jq` for the quick-debug flow and the Common Debug Checks26- Python 3.8+ with the `openai` and `requests` packages for the Debug Bundle Generator27- A failing or suspect request you can reproduce — its `gen-*` generation ID is what everything else correlates on2829## Instructions30311. Rule out environment problems first with the Common Debug Checks: verify the key via `/api/v1/auth/key`, confirm the model exists in `/api/v1/models`, and check `status.openrouter.ai`.322. Reproduce the failure with the Quick Debug: curl command — `curl -v ... | tee /tmp/openrouter-debug.txt` captures request headers, response headers, and body in one transcript.333. Extract the generation ID (`jq -r '.id'`) and query `GET /api/v1/generation?id=$GEN_ID` to get exact cost, token counts, `generation_time`, and `provider_name`.344. For failures inside an application, call `debug_request()` from the Python Debug Bundle Generator to capture the same request/response/error/latency/environment data as a `DebugBundle` and save it with `bundle.save("debug_bundle.json")`.355. Match the symptoms against the Error Handling table (missing generation ID, 502/503, `model_not_found`, slow TTFT).366. Before sharing a bundle, redact API keys per Enterprise Considerations (`sk-or-v1-...` -> `sk-or-v1-[REDACTED]`) and include the generation ID in any OpenRouter support request.3738## Quick Debug: curl3940```bash41# Send a request and capture full response with headers42curl -v https://openrouter.ai/api/v1/chat/completions \43 -H "Authorization: Bearer $OPENROUTER_API_KEY" \44 -H "Content-Type: application/json" \45 -H "HTTP-Referer: https://my-app.com" \46 -H "X-Title: debug-test" \47 -d '{48 "model": "openai/gpt-4o-mini",49 "messages": [{"role": "user", "content": "Say hello"}],50 "max_tokens": 5051 }' 2>&1 | tee /tmp/openrouter-debug.txt5253# Extract generation ID from response54GEN_ID=$(jq -r '.id' /tmp/openrouter-debug.txt 2>/dev/null)55echo "Generation ID: $GEN_ID"5657# Look up generation metadata (exact cost, provider, latency)58curl -s "https://openrouter.ai/api/v1/generation?id=$GEN_ID" \59 -H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {60 model: .model,61 total_cost: .total_cost,62 tokens_prompt: .tokens_prompt,63 tokens_completion: .tokens_completion,64 generation_time: .generation_time,65 provider: .provider_name66 }'67```6869## Python Debug Bundle Generator7071```python72import os, json, time, platform, sys73from datetime import datetime, timezone74from dataclasses import dataclass, asdict75from typing import Optional76from openai import OpenAI, APIError77import requests as http_requests7879@dataclass80class DebugBundle:81 timestamp: str82 generation_id: Optional[str]83 request_model: str84 request_messages: list85 request_params: dict86 response_status: str87 response_model: Optional[str]88 response_content: Optional[str]89 error_type: Optional[str]90 error_message: Optional[str]91 error_code: Optional[int]92 latency_ms: float93 generation_metadata: Optional[dict]94 environment: dict9596 def to_json(self) -> str:97 return json.dumps(asdict(self), indent=2)9899 def save(self, path: str = "debug_bundle.json"):100 with open(path, "w") as f:101 f.write(self.to_json())102103client = OpenAI(104 base_url="https://openrouter.ai/api/v1",105 api_key=os.environ["OPENROUTER_API_KEY"],106 default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},107)108109def debug_request(110 messages: list[dict],111 model: str = "openai/gpt-4o-mini",112 **kwargs,113) -> DebugBundle:114 """Execute a request and capture everything for debugging."""115 env = {116 "python": sys.version,117 "platform": platform.platform(),118 "openai_sdk": getattr(__import__("openai"), "__version__", "unknown"),119 }120121 start = time.monotonic()122 gen_id = None123 response_model = None124 content = None125 error_type = None126 error_msg = None127 error_code = None128 status = "success"129 gen_meta = None130131 try:132 response = client.chat.completions.create(133 model=model, messages=messages, **kwargs134 )135 gen_id = response.id136 response_model = response.model137 content = response.choices[0].message.content138 except APIError as e:139 status = "error"140 error_type = type(e).__name__141 error_msg = str(e)142 error_code = e.status_code143 except Exception as e:144 status = "error"145 error_type = type(e).__name__146 error_msg = str(e)147148 latency = (time.monotonic() - start) * 1000149150 # Fetch generation metadata if we have an ID151 if gen_id:152 try:153 gen = http_requests.get(154 f"https://openrouter.ai/api/v1/generation?id={gen_id}",155 headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},156 timeout=5,157 ).json()158 gen_meta = gen.get("data")159 except Exception:160 pass161162 return DebugBundle(163 timestamp=datetime.now(timezone.utc).isoformat(),164 generation_id=gen_id,165 request_model=model,166 request_messages=messages,167 request_params={k: v for k, v in kwargs.items() if k != "messages"},168 response_status=status,169 response_model=response_model,170 response_content=content,171 error_type=error_type,172 error_message=error_msg,173 error_code=error_code,174 latency_ms=round(latency, 1),175 generation_metadata=gen_meta,176 environment=env,177 )178179# Usage180bundle = debug_request(181 [{"role": "user", "content": "Test"}],182 model="anthropic/claude-3.5-sonnet",183 max_tokens=100,184)185print(bundle.to_json())186bundle.save("debug_bundle.json")187```188189## Common Debug Checks190191```bash192# 1. Verify API key is valid193curl -s https://openrouter.ai/api/v1/auth/key \194 -H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {label, usage, limit, is_free_tier}'195196# 2. Check if model exists197MODEL="anthropic/claude-3.5-sonnet"198curl -s https://openrouter.ai/api/v1/models | jq --arg m "$MODEL" '.data[] | select(.id == $m) | {id, context_length}'199200# 3. Check OpenRouter status201curl -s https://status.openrouter.ai/api/v2/status.json | jq '.status'202```203204## Output205206Running these flows leaves you with concrete debug artifacts:207208- `/tmp/openrouter-debug.txt` — the full verbose curl transcript (request/response headers plus the completion JSON) from the quick-debug step209- A generation-metadata JSON from `/api/v1/generation`: `model`, `total_cost`, `tokens_prompt`, `tokens_completion`, `generation_time`, and `provider_name`210- `debug_bundle.json` — the serialized `DebugBundle`: timestamp, generation ID, request model/messages/params, response status and content, error type/message/code, `latency_ms`, generation metadata, and environment info (Python version, platform, SDK version)211- One-line JSON results from the three Common Debug Checks (key label/usage/limit, model existence, OpenRouter status)212213## Examples214215Looking up a request you just sent by its generation ID:216217```bash218curl -s "https://openrouter.ai/api/v1/generation?id=$GEN_ID" \219 -H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {model, total_cost, generation_time, provider: .provider_name}'220```221222```json223{224 "model": "openai/gpt-4o-mini",225 "total_cost": 0.000021,226 "generation_time": 412,227 "provider": "OpenAI"228}229```230231If the metadata comes back empty, wait 1-2 seconds and retry with the same key that made the request. More worked examples: `references/examples.md`.232233## Error Handling234235| Error | Cause | Fix |236|-------|-------|-----|237| No generation ID in response | Request failed before reaching provider | Check network, verify base URL is `https://openrouter.ai/api/v1` |238| Generation metadata missing | Fetched too soon or wrong key | Wait 1-2s; use same API key that made the request |239| Intermittent 502/503 | Upstream provider outage | Check status.openrouter.ai; try different provider |240| `model_not_found` | Model ID typo or model removed | Query `/api/v1/models` to verify model exists |241| Slow TTFT (>10s) | Model cold start or overload | Use streaming; try `:floor` variant for different provider |242243## Enterprise Considerations244245- Always redact API keys from debug bundles before sharing (`sk-or-v1-...` -> `sk-or-v1-[REDACTED]`)246- Include the generation ID when contacting OpenRouter support -- it's the primary lookup key247- Log debug bundles to structured storage for post-incident analysis248- Set up automated debug bundle capture on 4xx/5xx responses in production249- Compare failing requests against a known-good baseline to isolate changes250251## References252253- Examples | Errors254- Generation API | [Status](https://status.openrouter.ai)255256---257258**Source:** [`jeremylongshore/claude-code-plugins-plus-skills`](https://github.com/jeremylongshore/claude-code-plugins-plus-skills) → `skills/.curated/openrouter-debug-bundle/SKILL.md`259260**Also appears in:** `jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/openrouter-pack/skills/openrouter-debug-bundle/SKILL.md`