AI platforms
OpenRouter Broadcast
Send the AI calls your app makes through OpenRouter to Otis with OpenRouter's Broadcast feature, without adding the Otis SDK to those calls.
OpenRouter is a gateway that routes your app's AI calls to many model providers. Its Broadcast feature sends a trace of every request your OpenRouter account makes to destinations that you configure. With Otis as a destination, Otis records each AI call that goes through OpenRouter, with its model, tokens, cost and messages. You don't need to wrap those calls with the Otis SDK.
When to use Broadcast
Use Broadcast when your AI calls go through OpenRouter and you would rather not change the code that makes them. You can still use the Otis SDK elsewhere in your app, for example for custom events, user identity or feedback.
If you already wrap your OpenRouter client with otis.wrap(), don't turn on Broadcast for the same calls. Each call would then reach Otis twice, and Otis would count its tokens and cost twice. Otis checks your instrumentation regularly, in a check called verification. Verification warns you when the same calls arrive both ways.
Setting up the destination
In OpenRouter, open Settings > Observability, turn on Enable Broadcast, and add an OpenTelemetry Collector destination for each Otis environment, production and development. Each Ingest Key belongs to one of those environments. Otis analyzes production data only. Customization describes Ingest Keys.
The OpenRouter Broadcast card, on the Ingestion tab of your project's settings in the Otis app, shows what to enter in each destination's two fields:
- Endpoint. Enter
https://ingest.runotis.com/v1/tracesin full, because OpenRouter sends to the URL exactly as written. - Headers. Enter
{"Authorization": "Bearer YOUR_INGEST_KEY"}, and replaceYOUR_INGEST_KEYwith an Ingest Key for the Otis environment that the destination feeds.
Then set two more options on each destination:
- Sampling rate. Set it to
1.0. A lower rate leaves whole sessions out of Otis, and Otis can't tell those calls from calls your app never made. - API Key Filtering. Include only the OpenRouter keys of the destination's environment: the keys your production app uses for the production destination, and the rest for the development one.
OpenRouter tests the destination with Test Connection before it saves it, and Otis accepts the test. Once the destination is saved, OpenRouter sends each request's trace after the request completes.
Privacy Mode
Privacy Mode is a setting on each OpenRouter destination that removes prompts and responses from the trace. With it on, Otis records each call's model, tokens, cost and timing, but no message text, so Otis can't read what your users asked or what your AI answered. Leave it off for a destination that sends to Otis, and rely on Otis's redaction to remove personal data instead.
Request fields Otis reads
Otis reads the identity of each call from fields that you already send to OpenRouter:
| Request field | Otis reads it as |
|---|---|
user | The user ID |
session_id, or the x-session-id header | The session ID |
trace.conversation_id, trace.chat_id or trace.thread_id | The chat ID |
trace.environment | The deployment environment, such as staging or preview |
trace.release, or else trace.version | The release |
Otis stores the deployment environment and the release with each call, so you can filter by them. They don't change the Otis environment: the Ingest Key on the destination decides whether a call counts as production or development data. A call sent without trace.release or trace.version has no release.
Every other key of the request's trace object is kept on the span as trace.metadata.<key>, so you can filter and segment by it as by any other span attribute. Otis scans these values for personal data before storing them, as it scans prompts.
Matching users with the Otis SDK
The Otis SDK hashes user and session IDs before they leave your app, and OpenRouter sends the raw IDs from your requests. Unless the IDs are matched, the same person appears in Otis twice: once from the SDK and once from OpenRouter. Either of two methods matches them:
- Let Otis hash raw IDs as the SDK would. The OpenRouter Broadcast card has a Match users with the Otis SDK switch for each Otis environment. A new project has it on already, and in an older project a project admin turns it on. With it on, Otis hashes each raw ID that the environment receives the way the SDK does. It can't do this for an SDK that sets its own
identifierHashKey, because Otis never receives that key. - Hash the IDs in your app. Pass each ID through the SDK's
processUserIdandprocessSessionIdbefore you put it in the request'suserandsession_id. This works with every SDK configuration, including one that sets its ownidentifierHashKey.
Choose a method before your app sends calls through Broadcast. If you change the switch later, each person whose ID arrives raw appears in Otis as two people, one before the change and one after. The card asks you to confirm before it changes the switch.
What Otis records
Each OpenRouter request becomes one AI call in Otis, with:
- the model that served the request, including when you asked for
openrouter/auto - input and output tokens, with reasoning tokens, cache reads and cache writes
- the cost OpenRouter billed
- the time to first token
- the user's last message and the AI's reply
A request that OpenRouter retried with another provider still counts as one AI call. Its attempts appear as separate spans under it, and their usage is not counted again.
Limits
Two limits can lose Broadcast calls without a warning in your app:
- Request rate. OpenRouter sends one request to Otis for each AI call, and Otis accepts a limited number of requests a minute for each Otis environment. OpenRouter retries a refused request once and then drops the trace. If your app makes many AI calls each second through OpenRouter, contact the Otis team before you turn on Broadcast, so that the limit for your project is set to fit.
- Request size. OpenRouter sends each call's content twice in one request. A conversation of several megabytes of text can exceed the size Otis accepts in one request, and that call is lost.
Checklist
- One Broadcast destination for each Otis environment, filtered to that environment's OpenRouter keys
- The endpoint and the headers from the OpenRouter Broadcast card, with an Ingest Key for that environment
- A sampling rate of
1.0, with Privacy Mode off -
userandsession_idon each request, and a conversation ID intracewhere your app has one - User IDs that match the Otis SDK's, by either method above
- No call that is also wrapped with
otis.wrap() - A request limit sized for your app, if it makes many AI calls each second