Sending telemetry
Send Argus one event per provider call your app makes, and the dashboard itemizes cost per request, live. The SDK does everything on this page automatically — read on if you'd rather send from another language, or just want to know what's on the wire.
1. Create a source
In Settings → Telemetry sources, create a source named after the app that will send — my-api, enrichment-worker. You get a source_id and a source_secret, shown once (what these are).
2. Describe each call as an event
An event is a small JSON object. No field is strictly required — send what you have, and Argus works with it. Three are recommended: id (makes retries free), and either provider or hostname (so the event lands under the right meter — without one, the event still counts but can't be attributed).
{
"provider": "openrouter",
"endpoint": "/v1/chat/completions",
"status": 200,
"latency_ms": 412,
"cost_usd": 0.0123,
"units": { "input_tokens": 900, "output_tokens": 120 }
}The full set of fields Argus understands:
idstringrecommendedYour unique id for the event (a ULID works well). Argus dedupes on it, which makes retries free. Omit it and the server derives one.
providerstringrecommendedCanonical provider id — openrouter, pdl, firecrawl. Don't know it? Send hostname instead.
hostnamestringrecommendedThe host you called, like api.openrouter.ai. Argus maps known hosts to providers; unknown hosts still get tracked under their hostname.
connection_idstringoptionalAttribute the event to a specific connection (its id from GET /connections). Use it to split telemetry across multiple accounts of one provider; omit it and the event lands on that provider's primary connection.
endpointstringoptionalThe path you called.
methodstringoptionalHTTP method.
statusnumberoptionalHTTP status. Powers the error-rate and 429 meters.
latency_msnumberoptionalHow long the call took.
cost_usdnumberoptionalWhat the call cost, if you know. If you don't, leave it off — see “How events get priced” below.
cost_provenancestringoptionalWhere your number came from: provider_reported (the provider told you, like OpenRouter's usage.cost) or app_reported (you computed it).
modelstringoptionalThe sku, like anthropic/claude-sonnet-4.5 — this is what lets Argus price an uncosted event from its catalog.
unitsobjectoptionalWhat the call consumed: any of input_tokens, output_tokens, cached_tokens, reasoning_tokens, requests, records, credits.
provider_request_idstringoptionalThe provider's own id for the call (an OpenRouter generation id, say). With it, Argus can fetch the exact cost from the provider after the fact.
credits_spent / credits_remainingnumberoptionalFor credit-metered providers: what this call used, and what's left if the response said.
rate_limitobjectoptional{ limit_type: "rpm" | "tpm" | "credits", limit, remaining, reset_at } — copy these from the provider's response headers. Use rate_limits (array) when a provider reports several windows.
metadataobjectoptionalYour own key/value strings — run ids, tenant, feature flags. Echoed back on the event in the ledger. Up to 2 KB serialized.
occurred_atnumberoptionalEpoch milliseconds. Defaults to arrival time, and is clamped into now ± 48 hours.
Two limits to know: a single event can be at most 8 KB serialized (oversized events are dropped, not truncated), and unknown fields are ignored.
3. Sign the batch and POST it
Wrap your events in { "events": [...] }, take the current time in epoch milliseconds, and sign timestamp.body with your source secret (HMAC-SHA256, hex). Batches signed more than five minutes ago are refused, which is what makes a stolen request useless to replay.
const body = JSON.stringify({ events });
const ts = Date.now().toString();
const key = await crypto.subtle.importKey("raw",
new TextEncoder().encode(process.env.ARGUS_SOURCE_SECRET),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const sig = [...new Uint8Array(await crypto.subtle.sign("HMAC", key,
new TextEncoder().encode(`${ts}.${body}`)))]
.map(b => b.toString(16).padStart(2, "0")).join("");
await fetch("https://api.argus.town/ingest", {
method: "POST",
headers: {
"content-type": "application/json",
"x-argus-source": process.env.ARGUS_SOURCE_ID,
"x-argus-timestamp": ts,
"x-argus-signature": sig,
},
body,
}); // → 202A 202 means accepted — events are usually processed within ~10 seconds, and the provider appears in your dashboard on its own if it wasn't connected yet.
How events get priced
You never have to send a cost for Argus to show one — but every number is honest about where it came from. In order of preference:
provider_reportedbestYou sent the provider's own number, or Argus fetched it afterward using provider_request_id.
app_reportedYou computed the cost yourself and sent it.
derivedYou sent model + units and Argus priced the event from its synced price catalog.
unpricedNot enough information to price — the call still counts toward request and error meters.
Daily totals are then reconciled against each provider's own billing endpoints, so drift between itemized costs and provider truth is measured, not assumed away.