---
name: openrouter-debug-bundle
description: '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''.

  '
allowed-tools: Read, Write, Edit, Grep, Bash(curl:*), Bash(jq:*), Bash(python3:*), Bash(node:*)
version: 2.0.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- openrouter
- debugging
- troubleshooting
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
---
# 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

1. 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`.
2. 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.
3. 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`.
4. 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")`.
5. Match the symptoms against the Error Handling table (missing generation ID, 502/503, `model_not_found`, slow TTFT).
6. 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

```bash
# 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

```python
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

```bash
# 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:

```bash
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}'
```

```json
{
  "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](https://status.openrouter.ai)
