---
url: https://simpledatalogs.com/docs/integrations/openrouter.md
description: >-
  Send OpenRouter's Broadcast traces to simpledatalogs, so every LLM call
  becomes an llm_call event with model, provider, tokens, cost and latency.
---

# OpenRouter

OpenRouter's **Broadcast** sends a trace for every API request you make through OpenRouter. Point it at simpledatalogs and each LLM call becomes one `llm_call` event, with the model, provider, tokens, cost in USD and latency as properties. Your app's code doesn't change.

Prompts and completions are never stored.

## Set it up

1. In simpledatalogs, create a **secret** key (`sdl_sec_…`) on the deployment the calls should count under, such as a new deployment named `openrouter`.
2. In OpenRouter, open **Settings → Observability** and turn on **Enable Broadcast**. In an organization, you need the admin role.
3. Click the edit icon next to **Webhook** and fill in:

| Field | Value |
|---|---|
| URL | `https://edge.simpledatalogs.com/openrouter/v1/traces` |
| Method | `POST` |
| Headers | `{"Authorization": "Bearer sdl_sec_…"}` |

4. Under **Privacy**, turn on **Privacy Mode**. simpledatalogs drops prompts and completions either way, and with Privacy Mode they never leave OpenRouter.
   ::: warning Keep Privacy Mode on
   A trace over 512 KB is refused and lost. Long prompts and agent transcripts get there quickly; without them, a trace is a few KB.
   :::

5. Optional: under **Additional generation metadata**, turn on **Cost** to add the billed tier, BYOK and estimate flags. Costs and tokens come without it.

6. Click **Test Connection**, then save. OpenRouter saves only when the test passes.

7. Make one request through OpenRouter. An `llm_call` event shows up in Logs within a minute.

OpenRouter's test trace (a made-up GPT-4 Turbo call costing $0.02) arrives as `llm_call_test` instead, so it never adds to your `llm_call` spend.

::: tip OpenTelemetry Collector works too
OpenRouter's **OpenTelemetry Collector** destination sends the same traces. Use the same URL as its **Endpoint** and the same headers.
:::

To send only some keys' calls, pick them under the destination's **API keys**, or exclude the ones to leave out. Sampling under 1.0 sends fewer traces, so counts and spend drop with it.

## What's stored

One `llm_call` event per generation. Provider retries and fallbacks inside one call don't count twice.

| Property | What |
|---|---|
| `model` | The model that answered, such as `openai/gpt-4o-2024-08-06` |
| `requested_model` | The model you asked for, only when routing picked another |
| `provider`, `provider_slug` | Who served it, such as `OpenAI` and `openai` |
| `input_tokens`, `output_tokens`, `total_tokens` | Token counts |
| `cached_tokens`, `cache_write_tokens`, `reasoning_tokens` | When the provider reports them |
| `cost_usd` | What OpenRouter charged for the call, in USD, fees included |
| `input_cost_usd`, `output_cost_usd` | The prompt and completion parts of the cost |
| `input_unit_price_usd`, `output_unit_price_usd` | The price per token OpenRouter charged |
| `duration_ms`, `ttft_ms`, `inter_token_latency_ms` | Total time, time to first token, and the mean gap between tokens |
| `status`, `error`, `finish_reason` | `ok` or `error`, the error message, and why the answer ended (`stop`, `length` …) |
| `generation_id` | OpenRouter's generation id, to look the call up in OpenRouter |
| `api_key_name` | Which OpenRouter key made the call |
| `service_tier`, `is_byok`, `usage_is_estimated`, `cancelled` | With **Cost** on: the tier it was billed at, whether it used your own provider key, whether OpenRouter had to estimate usage, and whether the call was cancelled |
| `span_name`, `trace_id`, `span_id` | The trace's names and ids |
| `gateway` | Always `openrouter` |

* A failed call is an `error`-level event, so it shows in error counts and alerts.
* The request's `user` becomes the event's user, and `session_id` its session. An email as `user` is pseudonymized like any email user id.
* Flat values in the request's `trace` object (`"trace": {"feature": "support"}`) become properties too, up to 20.
* Prompts, completions, messages, tool definitions and the provider's raw usage are always dropped.
* A resent trace lands on the same event, so nothing counts twice.

## Chart your spend

Add a board with these widgets:

* **Number**, event `llm_call`, measure **sum** of `cost_usd`: what you spent in the range.
* **Stacked bars**, event `llm_call`, split by `model` (or `provider`): calls per model over time.
* **Number**, measure **avg** of `duration_ms`: how slow calls are.
* **Number**, measure **rate**, condition `status` is `error`: the share of calls that failed.

The [Spend](/guide/spend) tab prices calls by a fixed unit cost. OpenRouter already sends the real cost, so chart `cost_usd` on a board instead.

## Responses

* `200 {}` once the trace is stored, including for Test Connection's empty payload.
* `401` with a wrong or missing key.
* Other refusals are [OTLP's](/api/responses#v1-logs-opentelemetry), with `Retry-After`.

Each LLM call counts as one event.

## Next steps

* [Boards](/guide/boards): build the spend board.
* [Alerts](/guide/alerts): get told when `llm_call` errors spike.
