Send usage, errors and billing corrections to your OpenTelemetry collector
Administrator·5 minutes to read
What this does for you
The activity export sends your organisation's model usage to the OpenTelemetry collector you already run, so your own dashboards can show who used which model, how many tokens it took, what it is estimated to cost, which runs failed and which charges were corrected. Only an owner or an administrator can see or change it. On the hosted product the export is sent by Botseon's worker; on the home edition it leaves from the owner's own machine.
No message text is ever part of these records: not the prompt, not the reply, not a provider's error message. Conversation content has its own two switches on the same screen and is off unless both are on.
Before you start
- A collector that takes OTLP over HTTP with JSON bodies, at a public https address. Logs are
posted to
/v1/logsand metrics to/v1/metricsunder the address you enter. - If the collector sits behind a firewall, read the activity export's addresses first, and add both of its addresses (IPv4 and IPv6) to the collector's allowlist. The Outbound addresses part of the Activity export screen shows the same pair. A home installation sends from its own machine instead.
Steps
- Open Activity export (
/telemetry), or Organisation → Audit and scroll to it. - Enter the Collector endpoint and any Headers your collector needs (for example
x-api-key). Headers are stored encrypted and never shown again. - Press Save and turn on, then Send a test event to check the collector accepts it.
- Under What is sent, turn off any kind of event you do not want. Everything is on until you turn it off, and a kind added in a later release is sent from the first minute after it ships.
The export runs every 30 seconds and reads a call once it is five seconds old, so new activity reaches the collector within a minute and a call still being written is never sent half done.
What each switch sends
| Switch | Signal | What arrives |
|---|---|---|
| Model calls | log, event.name = model.call | one record per metered model call: run, bot, member (enduser.id), model, route, credential kind, tokens by type, latency, finish reason, and botseon.cost.estimate_eur |
| Run errors | log, event.name = run.error | one record per run that failed: its failure kind only (a fixed word, or unknown), never the message |
| Billing corrections | log, event.name = billing.correction | one record per credit-ledger row that corrects a charge after the fact (refund, adjustment, overshoot_credit, overshoot_recovered): its ledger id, kind, source, signed botseon.billing.amount_eur, reason word and the usage it corrects |
| Token usage by type | metric botseon.usage.tokens ({token}) | a delta sum per model, credential kind, bot, member and botseon.token.type (input, cached_input, cache_write, output, reasoning) |
| Cost estimate | metric botseon.usage.cost_estimate (EUR) | a delta sum per model, credential kind, bot and member |
Every metric point carries the organisation as the resource attribute botseon.org.id.
How the two metrics add up
Both metrics are delta sums (aggregationTemporality = 1, monotonic): each post carries only
what happened in its own window, from startTimeUnixNano to timeUnixNano, and the next window
starts exactly where the last one ended. Add the points up over any period to get the total for
that period; do not take the latest value as a running total.
- Each call is counted once. A window with no new calls sends nothing rather than a zero.
- A retry is the same post. If the collector is down or answers 429 or 5xx, the window is kept and posted again, unchanged, on the next pass, before any newer window. Your collector may see the same window twice only if it stored it and still answered with an error; drop a repeat by its start and end time.
- A refused window is not resent. A 4xx other than 429 means the collector will never take it, so it is counted under dropped on the screen and the export moves on.
- Windows waiting for the collector are held by the sending worker, up to 32 of them. If the collector stays down past that, the oldest waiting window is dropped and counted under dropped; a restart of the worker while a window waits also loses that window. The model call log records and the billing ledger stay the record to reconcile against.
The cost estimate is not an invoice
botseon.usage.cost_estimate and botseon.cost.estimate_eur are the cost of each call at the
price pinned when it ran, before credits, allowances and corrections. Your invoice is what the
billing pages say. A correction made later arrives as a billing correction record; it does
not change a cost estimate already sent.
If something goes wrong
- Nothing arrives. Press Send a test event; the screen says whether the address was refused, the collector answered with an error, or it could not be reached.
- The dropped count grows. The collector is refusing or cannot keep up. Fix it, and new activity flows again on the next pass; what was dropped is not sent later.
- A kind is missing. Check its switch under What is sent. Turning a switch back on sends activity from that moment; what happened while it was off is not sent afterwards.
Last verified against build a60904f.