After every request — chat completions, embeddings, images, audio, reranking, video and batch submissions — a record of it can go to destinations you configure: a signed webhook, an OpenTelemetry collector, an S3-compatible bucket, Langfuse or Datadog. Up to ten per organisation, each with its own sample rate, set under Observability in your workspace or with the API below.
What a destination receives
{
"type": "request.completed",
"request": {
"id": "gen-...",
"created_at": "2026-11-01T14:03:11.412Z",
"finished_at": "2026-11-01T14:03:13.907Z",
"requested_model": "anthropic/claude-sonnet-5",
"served_model": "anthropic/claude-sonnet-5",
"provider": "anthropic",
"status": "completed",
"finish_reason": "stop",
"error_code": null,
"streamed": true,
"is_byok": true,
"cache_hit": false,
"usage": {"prompt_tokens": 812, "completion_tokens": 240, "cached_tokens": 0},
"cost_usd": 0.00604,
"latency_ms": {"ttft": 410, "generation": 2495, "routing_overhead": 18},
"key_name": "production",
"workspace_id": null,
"agent": "claude-code",
"session_id": null,
"run_id": null,
"app": null
}
}output.destination_id has each finished run’s result delivered there as an agent.run.completed envelope.The record is read back a moment after the response ends, so it carries what settlement wrote: the served model, the cost, and cache_hit when the response cache answered. Nothing here is awaited by a request; a slow or failing destination costs its own deliveries and nothing else.
Webhooks
A POST of the JSON above to your https URL, with X-Autonomous-Relay-Event: request.completed and, when the destination was created with a signing secret (every webhook is), X-Autonomous-Relay-Signature:
t=1793000000,v1=5f1d... # HMAC-SHA256 over "<t>.<raw body>", hex, keyed by the signing secret
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) {
const t = /(?:^|,)t=(\d+)/.exec(header)?.[1];
const v1 = /(?:^|,)v1=([0-9a-f]+)/.exec(header)?.[1];
if (!t || !v1) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}- The signing secret is returned once, in the response that created the destination, and never again.
- Both headers are also sent under their former names,
X-AgentsRouter-EventandX-AgentsRouter-Signature, with the same values. header_nameandheader_valueadd one header of your own, such as a bearer token; the value is sealed on arrival and the API shows its last four characters.- A delivery the network, a 429 or a 5xx fails is tried again, at most three times in all, after a short jittered wait; any other refusal is final. The destination’s
last_attempt_at,last_success_at,last_http_statusandlast_errorreport the final outcome.
OpenTelemetry
An otlp destination receives one span per request as OTLP/HTTP JSON at the URL you give, named chat <model> and attributed after the GenAI semantic conventions (gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.finish_reasons), with the rest of the record as autonomousrelay.* attributes and session.id. The trace id is derived from the request id, so a retried delivery is the same span, not a twin. A failed request is a span with error status.
S3-compatible storage
An s3 destination writes newline-delimited JSON objects — each line the record a webhook receives — to AWS S3, Cloudflare R2, Backblaze B2, MinIO or Google Cloud Storage’s XML API. Records are batched per destination and written when 500 have accumulated, or 4 MB, or 60 seconds after the first. url is the storage endpoint (e.g. https://s3.us-east-1.amazonaws.com), with s3_region, s3_bucket, an optional s3_prefix, s3_path_style where the endpoint needs it, and the access key pair (s3_access_key_id, s3_secret_access_key, sealed). Uploads are signed with SigV4.
Langfuse and Datadog
- Langfuse:
langfuse_public_key(pk-lf-…) andlangfuse_secret_key(sk-lf-…), andurlas the host — Langfuse’s EU cloud when left out, orus.,jp.,hipaa.cloud.langfuse.com, or your own. Each request becomes a trace with one generation, sent to the OpenTelemetry endpoint Langfuse documents; the generation has model, usage, cost, level and time to first token, and no input or output. - Datadog:
datadog_site(datadoghq.com,us3.,us5.,datadoghq.eu,ap1.,ap2.,uk1.or the US government sites) anddatadog_api_key. Each request becomes a log,service:autonomousrelay, tagged by model, provider and status, with the record as its attributes.
The API
curl -X POST https://autonomousrelay.com/v1/broadcast \
-H "Authorization: Bearer $AUTONOMOUSRELAY_MANAGEMENT_KEY" \
-H 'content-type: application/json' \
-d '{"kind": "webhook", "url": "https://hooks.example.com/relay", "name": "warehouse", "sample_rate": 1}'kind is webhook, otlp, s3, langfuse or datadog; sample_rate (above 0, at most 1) sends that share of requests. PATCH changes disabled, sample_rate and name; everything else is a new destination. /test sends one synthetic record now and answers with {"delivered", "http_status", "error"}. Any role reads the list; creating, changing and removing destinations needs config:write, as they decide where request metadata leaves the gateway for.
Input and output logging
Separate from broadcast, and off: storing the prompts and completions of chosen keys for your own later reading (GET /v1/generation/:id/content). Its settings — the keys, a retention of at least 90 days, a lock — can be prepared now, but enabled: true is refused in this release pending a privacy review, so no content is stored anywhere today. Management keys only: an inference key cannot change what is recorded about its own traffic.