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:

idstringrecommended

Your unique id for the event (a ULID works well). Argus dedupes on it, which makes retries free. Omit it and the server derives one.

providerstringrecommended

Canonical provider id — openrouter, pdl, firecrawl. Don't know it? Send hostname instead.

hostnamestringrecommended

The host you called, like api.openrouter.ai. Argus maps known hosts to providers; unknown hosts still get tracked under their hostname.

connection_idstringoptional

Attribute 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.

endpointstringoptional

The path you called.

methodstringoptional

HTTP method.

statusnumberoptional

HTTP status. Powers the error-rate and 429 meters.

latency_msnumberoptional

How long the call took.

cost_usdnumberoptional

What the call cost, if you know. If you don't, leave it off — see “How events get priced” below.

cost_provenancestringoptional

Where your number came from: provider_reported (the provider told you, like OpenRouter's usage.cost) or app_reported (you computed it).

modelstringoptional

The sku, like anthropic/claude-sonnet-4.5 — this is what lets Argus price an uncosted event from its catalog.

unitsobjectoptional

What the call consumed: any of input_tokens, output_tokens, cached_tokens, reasoning_tokens, requests, records, credits.

provider_request_idstringoptional

The 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_remainingnumberoptional

For 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.

metadataobjectoptional

Your own key/value strings — run ids, tenant, feature flags. Echoed back on the event in the ledger. Up to 2 KB serialized.

occurred_atnumberoptional

Epoch 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,
});  // → 202

A 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_reportedbest

You sent the provider's own number, or Argus fetched it afterward using provider_request_id.

app_reported

You computed the cost yourself and sent it.

derived

You sent model + units and Argus priced the event from its synced price catalog.

unpriced

Not 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.