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
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'.4license: MIT5---6# OpenRouter Debug Bundle
7
8## Current State
9
10!`node --version 2>/dev/null || echo 'N/A'`
11!`python3 --version 2>/dev/null || echo 'N/A'`
12
13## Overview
14
15When 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=`.
16
17## Prerequisites
18
19- An OpenRouter API key (`sk-or-v1-...`) exported as `OPENROUTER_API_KEY` — see the `openrouter-install-auth` skill for setup
20- `curl` and `jq` for the quick-debug flow and the Common Debug Checks
21- Python 3.8+ with the `openai` and `requests` packages for the Debug Bundle Generator
22- A failing or suspect request you can reproduce — its `gen-*` generation ID is what everything else correlates on
23
24## Instructions
25
261. 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`.
272. 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.
283. 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`.
294. 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")`.
305. Match the symptoms against the Error Handling table (missing generation ID, 502/503, `model_not_found`, slow TTFT).
316. 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.
32
33## Quick Debug: curl
34
35```bash
36# Send a request and capture full response with headers
37curl -v https://openrouter.ai/api/v1/chat/completions \
38 -H "Authorization: Bearer $OPENROUTER_API_KEY" \
39 -H "Content-Type: application/json" \
40 -H "HTTP-Referer: https://my-app.com" \
41 -H "X-Title: debug-test" \
42 -d '{
43 "model": "openai/gpt-4o-mini",
44 "messages": [{"role": "user", "content": "Say hello"}],
45 "max_tokens": 50
46 }' 2>&1 | tee /tmp/openrouter-debug.txt
47
48# Extract generation ID from response
49GEN_ID=$(jq -r '.id' /tmp/openrouter-debug.txt 2>/dev/null)
50echo "Generation ID: $GEN_ID"
51
52# Look up generation metadata (exact cost, provider, latency)
53curl -s "https://openrouter.ai/api/v1/generation?id=$GEN_ID" \
54 -H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {
55 model: .model,
56 total_cost: .total_cost,
57 tokens_prompt: .tokens_prompt,
58 tokens_completion: .tokens_completion,
59 generation_time: .generation_time,
60 provider: .provider_name
61 }'
62```
63
64## Python Debug Bundle Generator
65
66```python
67import os, json, time, platform, sys
68from datetime import datetime, timezone
69from dataclasses import dataclass, asdict
70from typing import Optional
71from openai import OpenAI, APIError
72import requests as http_requests
73
74@dataclass
75class DebugBundle:
76 timestamp: str
77 generation_id: Optional[str]
78 request_model: str
79 request_messages: list
80 request_params: dict
81 response_status: str
82 response_model: Optional[str]
83 response_content: Optional[str]
84 error_type: Optional[str]
85 error_message: Optional[str]
86 error_code: Optional[int]
87 latency_ms: float
88 generation_metadata: Optional[dict]
89 environment: dict
90
91 def to_json(self) -> str:
92 return json.dumps(asdict(self), indent=2)
93
94 def save(self, path: str = "debug_bundle.json"):
95 with open(path, "w") as f:
96 f.write(self.to_json())
97
98client = OpenAI(
99 base_url="https://openrouter.ai/api/v1",
100 api_key=os.environ["OPENROUTER_API_KEY"],
101 default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
102)
103
104def debug_request(
105 messages: list[dict],
106 model: str = "openai/gpt-4o-mini",
107 **kwargs,
108) -> DebugBundle:
109 """Execute a request and capture everything for debugging."""
110 env = {
111 "python": sys.version,
112 "platform": platform.platform(),
113 "openai_sdk": getattr(__import__("openai"), "__version__", "unknown"),
114 }
115
116 start = time.monotonic()
117 gen_id = None
118 response_model = None
119 content = None
120 error_type = None
121 error_msg = None
122 error_code = None
123 status = "success"
124 gen_meta = None
125
126 try:
127 response = client.chat.completions.create(
128 model=model, messages=messages, **kwargs
129 )
130 gen_id = response.id
131 response_model = response.model
132 content = response.choices[0].message.content
133 except APIError as e:
134 status = "error"
135 error_type = type(e).__name__
136 error_msg = str(e)
137 error_code = e.status_code
138 except Exception as e:
139 status = "error"
140 error_type = type(e).__name__
141 error_msg = str(e)
142
143 latency = (time.monotonic() - start) * 1000
144
145 # Fetch generation metadata if we have an ID
146 if gen_id:
147 try:
148 gen = http_requests.get(
149 f"https://openrouter.ai/api/v1/generation?id={gen_id}",
150 headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
151 timeout=5,
152 ).json()
153 gen_meta = gen.get("data")
154 except Exception:
155 pass
156
157 return DebugBundle(
158 timestamp=datetime.now(timezone.utc).isoformat(),
159 generation_id=gen_id,
160 request_model=model,
161 request_messages=messages,
162 request_params={k: v for k, v in kwargs.items() if k != "messages"},
163 response_status=status,
164 response_model=response_model,
165 response_content=content,
166 error_type=error_type,
167 error_message=error_msg,
168 error_code=error_code,
169 latency_ms=round(latency, 1),
170 generation_metadata=gen_meta,
171 environment=env,
172 )
173
174# Usage
175bundle = debug_request(
176 [{"role": "user", "content": "Test"}],
177 model="anthropic/claude-3.5-sonnet",
178 max_tokens=100,
179)
180print(bundle.to_json())
181bundle.save("debug_bundle.json")
182```
183
184## Common Debug Checks
185
186```bash
187# 1. Verify API key is valid
188curl -s https://openrouter.ai/api/v1/auth/key \
189 -H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {label, usage, limit, is_free_tier}'
190
191# 2. Check if model exists
192MODEL="anthropic/claude-3.5-sonnet"
193curl -s https://openrouter.ai/api/v1/models | jq --arg m "$MODEL" '.data[] | select(.id == $m) | {id, context_length}'
194
195# 3. Check OpenRouter status
196curl -s https://status.openrouter.ai/api/v2/status.json | jq '.status'
197```
198
199## Output
200
201Running these flows leaves you with concrete debug artifacts:
202
203- `/tmp/openrouter-debug.txt` — the full verbose curl transcript (request/response headers plus the completion JSON) from the quick-debug step
204- A generation-metadata JSON from `/api/v1/generation`: `model`, `total_cost`, `tokens_prompt`, `tokens_completion`, `generation_time`, and `provider_name`
205- `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)
206- One-line JSON results from the three Common Debug Checks (key label/usage/limit, model existence, OpenRouter status)
207
208## Examples
209
210Looking up a request you just sent by its generation ID:
211
212```bash
213curl -s "https://openrouter.ai/api/v1/generation?id=$GEN_ID" \
214 -H "Authorization: Bearer $OPENROUTER_API_KEY" | jq '.data | {model, total_cost, generation_time, provider: .provider_name}'
215```
216
217```json
218{
219 "model": "openai/gpt-4o-mini",
220 "total_cost": 0.000021,
221 "generation_time": 412,
222 "provider": "OpenAI"
223}
224```
225
226If 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`.
227
228## Error Handling
229
230| Error | Cause | Fix |
231|-------|-------|-----|
232| No generation ID in response | Request failed before reaching provider | Check network, verify base URL is `https://openrouter.ai/api/v1` |
233| Generation metadata missing | Fetched too soon or wrong key | Wait 1-2s; use same API key that made the request |
234| Intermittent 502/503 | Upstream provider outage | Check status.openrouter.ai; try different provider |
235| `model_not_found` | Model ID typo or model removed | Query `/api/v1/models` to verify model exists |
236| Slow TTFT (>10s) | Model cold start or overload | Use streaming; try `:floor` variant for different provider |
237
238## Enterprise Considerations
239
240- Always redact API keys from debug bundles before sharing (`sk-or-v1-...` -> `sk-or-v1-[REDACTED]`)
241- Include the generation ID when contacting OpenRouter support -- it's the primary lookup key
242- Log debug bundles to structured storage for post-incident analysis
243- Set up automated debug bundle capture on 4xx/5xx responses in production
244- Compare failing requests against a known-good baseline to isolate changes
245
246## References
247
248- Examples | Errors
249- Generation API | [Status](https://status.openrouter.ai)