Copy every request, as a trace, to your own monitoring tool — add, change and remove destinations.
Every request can be copied, as a trace, to your own monitoring tool. You add a destination; each request is then sent there once it has been settled — its model, provider, tokens and timings and, unless privacy mode is on, its input and output. The guide, tool by tool, is Broadcast.
Every destination route is free and takes a management key: Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY.
| Method | Path | Does |
|---|---|---|
GET | /v1/observability/destinations | lists your destinations |
POST | /v1/observability/destinations | adds one |
GET | /v1/observability/destinations/{id} | reads one |
PATCH | /v1/observability/destinations/{id} | changes one |
DELETE | /v1/observability/destinations/{id} | removes one |
curl https://opendunes.com/v1/observability/destinations \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Souk Bot traces",
"type": "langfuse",
"config": {
"publicKey": "pk-lf-…",
"secretKey": "sk-lf-…",
"baseUrl": "https://cloud.langfuse.com"
},
"sampling_rate": 1,
"privacy_mode": false
}'| Field | Description |
|---|---|
name | Required, up to 200 characters |
type | Required. One of the types below; it cannot change afterwards |
config | Required. The type's settings, below |
enabled | true by default |
privacy_mode | true leaves the input and output out of every trace |
sampling_rate | The share of requests sent, from 0.0001 to 1 |
api_key_hashes | Only the requests of these keys, named by their hash |
filter_rules | Only the requests that pass — see below |
broadcast_generation_cost · broadcast_generation_identity · broadcast_generation_request_context | Extra detail on the trace: the cost in DA and token counts; the key's name and hash; the route, streaming and user agent. false by default |
regions | global (the default), europe, us |
At most five destinations of one type (409 beyond). Every address must be HTTPS on the public internet.
The answer is the destination, 201. Secrets and header values are masked on every read (abcd...wxyz); a masked value sent back in a PATCH leaves the stored one unchanged.
{
"data": {
"id": "a2d1a6b2-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
"name": "Souk Bot traces",
"type": "langfuse",
"config": {
"baseUrl": "https://cloud.langfuse.com",
"publicKey": "pk-l...b2c3",
"secretKey": "sk-l...d4e5"
},
"enabled": true,
"privacy_mode": false,
"sampling_rate": 1,
"api_key_hashes": null,
"filter_rules": null,
"broadcast_generation_cost": false,
"broadcast_generation_identity": false,
"broadcast_generation_request_context": false,
"regions": ["global"],
"created_at": "2026-09-24T09:12:03+00:00",
"updated_at": "2026-09-24T09:12:03+00:00"
}
}type | Required in config | Optional |
|---|---|---|
arize | apiKey, spaceKey, modelId | baseUrl |
braintrust | apiKey, projectId | baseUrl |
clickhouse | host, database, username, password | table |
opik | apiKey, workspace, projectName | — |
datadog | apiKey, mlApp | url |
bigquery | projectId, serviceAccountKey | dataset, table |
grafana | apiKey, instanceId | baseUrl |
langfuse | secretKey, publicKey | baseUrl |
langsmith | apiKey | endpoint, project, workspaceId |
newrelic | licenseKey | region (us or eu) |
otel-collector | endpoint | — |
posthog | apiKey | endpoint |
raindrop | writeKey | baseUrl |
ramp | apiKey | baseUrl |
s3 | bucketName, accessKeyId, secretAccessKey | endpoint, region, prefix, pathTemplate, sessionToken |
sentry | otlpEndpoint, dsn | — |
snowflake | account, token | database, schema, table, warehouse |
weave | apiKey, entity, project | baseUrl |
webhook | url | method (POST or PUT) |
A webhook and an OpenTelemetry collector receive the trace as OTLP JSON.
{
"filter_rules": {
"enabled": true,
"groups": [
{
"logic": "and",
"rules": [
{ "field": "model", "operator": "starts_with", "value": "anthropic/" },
{ "field": "total_cost", "operator": "gt", "value": 1 }
]
}
]
}
}A request is sent when it passes every group; inside a group, logic says whether every rule or any rule must hold. field is one of model, provider, session_id, user_id, api_key_name, finish_reason, input, output, total_cost (DA), total_tokens, prompt_tokens, completion_tokens; operator one of equals, not_equals, contains, not_contains, regex, starts_with, ends_with, gt, lt, gte, lte, exists, not_exists.
GET /v1/observability/destinations takes offset and limit (1 to 100, default 50) and answers { "data": [ … ], "total_count": n }. PATCH takes the fields above, each optional, except type. DELETE answers { "deleted": true }. A destination that is not yours is a 404.
Every request sends a trace: one answered from the response cache (its own id, 0 DA), one refused before a model ran and one whose model failed (both with an error status), and every stream.