API reference
Version 1 of the Argus API: what you spend, where, and what is about to run out, plus the writes to connect providers, send events, set alerts and record negotiated pricing. Base URL https://api.argus.town. This page is generated from the same contract as the OpenAPI document, so it cannot describe an endpoint that does not exist.
Start
Create an api key in Settings (or have an agent request one through the device login), then:
curl -s https://api.argus.town/v1/project -H "Authorization: Bearer $ARGUS_API_KEY"
Responses are bare resources with an object tag; lists are {"object": "list", "data", "has_more", "next_cursor"} and take limit and cursor. Unknown query parameters and body fields are rejected with the field named. Within v1 changes only add fields: ignore the ones you do not recognise.
Ids
Ids carry their kind: prj_ project, conn_ connection, key_ key, sync_ sync, plan_ plan, src_ source, lnk_ connect link, alr_ alert rule, fire_ alert firing. Input also accepts the bare form.
Idempotency
Operations marked idempotent accept an Idempotency-Key header of 1 to 255 printable ASCII characters. A retry with the same key and body returns the first response with Idempotent-Replayed: true for 24 hours; the same key with a different body, or while the first request is still running, is a 409 (idempotency_key_reused, idempotency_key_in_progress). Keys are scoped to the project.
Rate limits
600 a minute per key, and 6,000 a minute events per source on POST /v1/events. Over the limit is a 429 with Retry-After and a RateLimit-Policy header. More than 30 failed key checks a minute from one address are refused with too_many_auth_failures.
Errors
Every failure has a real HTTP status and one shape. code is stable; param names the offending field; next_step, when present, says what to do. The full catalogue is on the errors page and at GET /v1/errors.
{
"error": {
"type": "invalid_request",
"code": "invalid_parameter",
"message": "Invalid value for \"limit\". Expected an integer from 1 to 200.",
"param": "limit",
"doc_url": "https://argus.town/docs/errors#invalid_parameter",
"request_id": "…"
}
}MCP
The same operations are MCP tools at POST https://mcp.argus.town, named by their operation id and taking the same arguments, errors and idempotency. An API key can call every tool; an MCP key keeps what it has always had: reads, connecting a provider, connect links and refreshes. Session-only operations, the device login and event sending are never tools. See MCP.
Start
/v1No keyAPI index. Self-describing entry point: where the contract, error catalog and MCP endpoint live, how to authenticate, and the first authenticated call.
curl -s "https://api.argus.town/v1"
# index → 200
{
"object": "api",
"version": "v1",
"status": "ok",
"links": {
"documentation": "https://argus.town/docs/api",
"openapi": "/v1/openapi.json",
"errors": "/v1/errors",
"providers": "/v1/providers",
"mcp": "https://mcp.argus.town"
},
"authentication": {
"scheme": "bearer",
"key_type": "api",
"create_key_url": "https://argus.town/settings"
},
"first_call": {
"method": "GET",
"href": "/v1/project",
"curl": "curl -s https://api.argus.town/v1/project -H \"Authorization: Bearer $ARGUS_API_KEY\""
},
"next_steps": [
{
"rel": "list_providers",
"method": "GET",
"href": "/v1/providers",
"why": "See which providers Argus covers, no key needed."
}
]
}/v1/device/codeNo keyStart device login. For an agent with no credentials: ask for access, show the human `verification_uri_complete`, and poll `POST /v1/device/token`. The human signs in, picks the project and approves the scopes listed; nothing is issued before that. `api` (alias `read`) is an API key, which can read spend and also create and update connections, alert rules and negotiated pricing in the approved project; `ingest` is a telemetry source; `mcp` is an MCP key. Without `scopes`: `api` and `ingest`. Rate limited per address.
Optional client name and scopes.
client_namestringoptionala string at most 60 character(s) long
scopes"api" | "read" | "ingest" | "mcp"[]optional`api` (alias `read`): an API key; `ingest`: a telemetry source; `mcp`: an MCP key. Default: `api` and `ingest`.
Errors: invalid_request_body, invalid_parameter, payload_too_large
curl -s -X POST "https://api.argus.town/v1/device/code" -H "content-type: application/json" -d '{"client_name":"my-agent","scopes":["api","ingest"]}'# api and ingest → 201
{
"object": "device_code",
"device_code": "argus_dev_3b9f0c5e1a7d4826b0c9e3f5a1d7b2c84e6f0a9d3c5b7e1f",
"user_code": "BCDF-GHJK",
"verification_uri": "https://argus.town/device",
"verification_uri_complete": "https://argus.town/device?code=BCDF-GHJK",
"expires_in": 900,
"interval": 7
}# unknown scope → 400
{
"error": {
"type": "invalid_request",
"code": "invalid_parameter",
"message": "Invalid value for \"scopes[0]\": Expected \"api\" | \"read\" | \"ingest\" | \"mcp\", actual \"admin\"",
"param": "scopes[0]",
"doc_url": "https://argus.town/docs/errors#invalid_parameter",
"request_id": null
}
}/v1/device/tokenNo keyPoll device login. Poll with the `device_code` until `status` leaves `pending`. On `approved` the credentials are returned once (`api_key`, `source.secret`, `mcp_key` as requested) together with the project; later polls say `consumed`. `denied`, `expired`, `consumed` and `invalid` are final: start again. Rate limited per code and, for unknown codes, per address.
The device code from the first step.
device_codestringrequireda string at most 200 character(s) long
Errors: invalid_request_body, invalid_parameter, missing_parameter, payload_too_large
curl -s -X POST "https://api.argus.town/v1/device/token" -H "content-type: application/json" -d '{"device_code":"{device_code.device_code}"}'# pending → 200
{
"object": "device_token",
"status": "pending",
"next_steps": [
{
"rel": "poll",
"method": "POST",
"href": "/v1/device/token",
"why": "Not approved yet. Poll again in 7 seconds."
}
]
}/v1/openapi.jsonNo keyOpenAPI document. This contract as OpenAPI 3.1, generated from the operation table.
/v1/errorsNo keyError catalog. Every error code the API returns, with its type and HTTP status.
limitquery · integeroptionalPage size. Default 25.
cursorquery · stringoptionalOpaque cursor from a previous page's `next_cursor`.
Errors: invalid_parameter, invalid_cursor
curl -s "https://api.argus.town/v1/errors?limit=1"
# first code → 200
{
"object": "list",
"data": [
{
"object": "error_code",
"code": "connection_not_found",
"type": "not_found",
"status": 404,
"description": "No connection with this id in the key's project.",
"doc_url": "https://argus.town/docs/errors#connection_not_found"
}
],
"has_more": true,
"next_cursor": "WyJjb25uZWN0aW9uX25vdF9mb3VuZCJd"
}Providers
/v1/providersNo keyMCP tool providers_listList providers. The provider catalog: pricing model and whether Argus can poll the provider with a credential. Public, no key.
limitquery · integeroptionalPage size. Default 25.
cursorquery · stringoptionalOpaque cursor from a previous page's `next_cursor`.
connectablequery · booleanoptionalOnly providers Argus can poll (true) or only telemetry-only ones (false).
Errors: invalid_parameter, invalid_cursor
curl -s "https://api.argus.town/v1/providers?connectable=true&limit=1"
# connectable → 200
{
"object": "list",
"data": [
{
"object": "provider",
"id": "openrouter",
"pricing_model": "usage_drawdown",
"summary": "Prepaid credits drawn down per request across many model providers.",
"connectable": true,
"credential_types": [
"api_key"
]
}
],
"has_more": true,
"next_cursor": "WyJvcGVucm91dGVyIl0"
}/v1/providers/{slug}No keyMCP tool providers_getGet a provider. One provider with its full offering: plans, rate limits and how spend is observed.
slugpathrequiredProvider slug, for example `openrouter`.
Errors: provider_not_found
curl -s "https://api.argus.town/v1/providers/openrouter"
# openrouter → 200
{
"object": "provider",
"id": "openrouter",
"pricing_model": "usage_drawdown",
"summary": "Prepaid credits drawn down per request across many model providers.",
"connectable": true,
"credential_types": [
"api_key"
],
"offering": {
"provider": "openrouter"
}
}# unknown → 404
{
"error": {
"type": "not_found",
"code": "provider_not_found",
"message": "No provider has the slug nope.",
"param": null,
"doc_url": "https://argus.town/docs/errors#provider_not_found",
"next_step": {
"hint": "List provider slugs.",
"method": "GET",
"href": "/v1/providers"
},
"request_id": null
}
}Project
/v1/projectAPI keyMCP tool project_getGet the key's project. Who am I: the project this key belongs to, the key itself, what it may call, the plan, and what to do next.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed
curl -s "https://api.argus.town/v1/project" -H "Authorization: Bearer $ARGUS_API_KEY"
# empty project → 200
{
"object": "project",
"id": "prj_org_3kQ9vXw2",
"name": "Acme",
"created_at": "2026-10-01T09:30:00.000Z",
"key": {
"object": "key",
"id": "key_7d2e91a0",
"type": "api",
"name": "ci",
"prefix": "argus_api_ab12"
},
"permissions": [
"project_get",
"summary_get",
"connections_list",
"connections_get"
],
"plan": {
"id": "free",
"name": "Free"
},
"next_steps": [
{
"rel": "list_providers",
"method": "GET",
"href": "/v1/providers?connectable=true",
"why": "No connections yet. See which providers Argus can poll."
}
]
}Spend
/v1/summaryAPI keyMCP tool summary_getSpend summary. What the project is spending and what is about to run out: month-to-date and projected totals, every connection with its metrics, and advisories.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed
curl -s "https://api.argus.town/v1/summary" -H "Authorization: Bearer $ARGUS_API_KEY"
# one connection → 200
{
"object": "summary",
"totals": {
"mtd_spend_usd": 18.4,
"projected_month_end_usd": 55.2
},
"connections": [
{
"object": "connection",
"id": "conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11",
"provider": "openrouter",
"origin": "poll",
"label": "OpenRouter (production)",
"credential_type": "api_key",
"status": "active",
"status_detail": null,
"last_synced_at": "2026-10-01T09:30:00.000Z",
"created_at": "2026-10-01T09:30:00.000Z",
"updated_at": "2026-10-01T09:30:00.000Z",
"metrics": {
"balance_usd": 42.5,
"burn_per_day": 3.1,
"days_to_zero": 13.7,
"mtd_spend_usd": 18.4,
"spend_basis": "reported",
"observed_since": null,
"balance_scope": "account",
"projected_month_end_usd": 55.2,
"wow_change_pct": 4.2,
"rate_limit_headroom_pct": 80,
"error_rate_pct": 0.4,
"accuracy": "exact",
"cost_drift_pct": 0.3,
"billing_mode": "usage_drawdown",
"utilization_pct": null,
"used_amount": null,
"included_amount": null,
"included_meter_id": null,
"subscription_fee_usd": null,
"effective_usd_per_unit": null,
"days_to_reset": null,
"recommended_action": null,
"recommended_tier": null,
"recommended_delta_usd": null
},
"metered": true
}
],
"advisories": [],
"next_steps": []
}/v1/spendAPI keyMCP tool spend_getSpend over time. Daily spend for the project: totals and a day-by-day series, optionally split by `provider`, `connection` or `model`. Totals are the sum of the daily rollups. `model` groups use provider-reported breakdowns where a connection has them and itemized events otherwise, so they can sum to less than the day's spend. Defaults to the last 30 days; at most 366.
sincequery · stringoptionalFirst day, inclusive (rounded down to 00:00 UTC).
untilquery · stringoptionalLast day, exclusive (rounded up to 00:00 UTC). Default: tomorrow.
granularityquery · stringoptionalBucket size. Only `day` for now.
group_byquery · stringoptionalSplit each day by this dimension.
providerquery · stringoptionalOnly this provider slug.
connectionquery · stringoptionalOnly this connection (`conn_...`).
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, too_many_auth_failures, invalid_parameter
curl -s "https://api.argus.town/v1/spend?group_by=provider" -H "Authorization: Bearer $ARGUS_API_KEY"
# by provider → 200
{
"object": "spend",
"since": "2026-09-21T00:00:00.000Z",
"until": "2026-10-22T00:00:00.000Z",
"granularity": "day",
"group_by": "provider",
"totals": {
"spend_usd": 18.4,
"requests": 1200,
"tokens_input": 900000,
"tokens_output": 120000,
"errors": 3
},
"series": [
{
"period_start": "2026-10-01T00:00:00.000Z",
"spend_usd": 18.4,
"requests": 1200,
"groups": [
{
"key": "openai",
"spend_usd": 18.4,
"requests": 1200
}
]
}
],
"next_steps": []
}Connections
/v1/connectionsAPI keyMCP tool connections_listList connections. Providers the project tracks, polled, pushed or both, newest first. Credentials are never returned.
limitquery · integeroptionalPage size. Default 25.
cursorquery · stringoptionalOpaque cursor from a previous page's `next_cursor`.
providerquery · stringoptionalOnly connections to this provider slug.
statusquery · stringoptionalOnly connections in this status.
originquery · stringoptionalOnly connections fed this way.
expand[]query · string arrayoptionalFields to include. One or more of: metrics, advisories.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, invalid_parameter, invalid_cursor
curl -s "https://api.argus.town/v1/connections?limit=10&expand[]=metrics" -H "Authorization: Bearer $ARGUS_API_KEY"
# with metrics → 200
{
"object": "list",
"data": [
{
"object": "connection",
"id": "conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11",
"provider": "openrouter",
"origin": "poll",
"label": "OpenRouter (production)",
"credential_type": "api_key",
"status": "active",
"status_detail": null,
"last_synced_at": "2026-10-01T09:30:00.000Z",
"created_at": "2026-10-01T09:30:00.000Z",
"updated_at": "2026-10-01T09:30:00.000Z",
"metrics": {
"balance_usd": 42.5,
"burn_per_day": 3.1,
"days_to_zero": 13.7,
"mtd_spend_usd": 18.4,
"spend_basis": "reported",
"observed_since": null,
"balance_scope": "account",
"projected_month_end_usd": 55.2,
"wow_change_pct": 4.2,
"rate_limit_headroom_pct": 80,
"error_rate_pct": 0.4,
"accuracy": "exact",
"cost_drift_pct": 0.3,
"billing_mode": "usage_drawdown",
"utilization_pct": null,
"used_amount": null,
"included_amount": null,
"included_meter_id": null,
"subscription_fee_usd": null,
"effective_usd_per_unit": null,
"days_to_reset": null,
"recommended_action": null,
"recommended_tier": null,
"recommended_delta_usd": null
},
"metered": true
}
],
"has_more": false,
"next_cursor": null
}/v1/connections/{id}API keyMCP tool connections_getGet a connection. One connection. Expand `usage`, `breakdown`, `rate_limits`, `sync_runs` and `plan` for the detail view; expansions only add fields.
idpathrequiredConnection id, with or without the `conn_` prefix.
expand[]query · string arrayoptionalFields to include. One or more of: metrics, advisories, usage, breakdown, rate_limits, sync_runs, plan.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, connection_not_found, invalid_parameter
curl -s "https://api.argus.town/v1/connections/$ID?expand[]=plan" -H "Authorization: Bearer $ARGUS_API_KEY"
# with plan → 200
{
"object": "connection",
"id": "conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11",
"provider": "openrouter",
"origin": "poll",
"label": "OpenRouter (production)",
"credential_type": "api_key",
"status": "active",
"status_detail": null,
"last_synced_at": "2026-10-01T09:30:00.000Z",
"created_at": "2026-10-01T09:30:00.000Z",
"updated_at": "2026-10-01T09:30:00.000Z",
"plan": null
}/v1/connectionsAPI keyMCP tool connections_createConnect a provider. Poll a provider with a credential. Argus checks the credential with the provider before storing it (encrypted; it is never returned) and queues the first sync. A repeated `label` for the same provider is a 409 `label_conflict`: rotate the credential with `PATCH /v1/connections/{id}` instead. Without a `label` an extra account gets a distinct default one. A provider the project already tracks by telemetry only (no label given) is upgraded in place (`origin: both`). At most 100 polled connections per project (telemetry-only ones are capped separately).
The provider, its credential and optionally a label.
providerstringrequiredProvider slug from `GET /v1/providers?connectable=true`.
credential_typestringoptionalDefaults to the provider's first supported credential type.
secretstringrequiredThe provider credential. Checked with the provider, stored encrypted, never returned.
labelstringoptionala string at most 80 character(s) long
metaobjectoptional
Send an Idempotency-Key header to make retries safe.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, invalid_request_body, invalid_parameter, missing_parameter, invalid_idempotency_key, idempotency_key_reused, payload_too_large, provider_not_connectable, credential_invalid, provider_unreachable, label_conflict, quota_exceeded
curl -s -X POST "https://api.argus.town/v1/connections" -H "Authorization: Bearer $ARGUS_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" -d '{"provider":"millionverifier","secret":"mv-example-key","label":"Verifier"}'# connect → 201
{
"object": "connection",
"id": "conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11",
"provider": "millionverifier",
"origin": "poll",
"label": "Verifier",
"credential_type": "api_key",
"status": "active",
"status_detail": null,
"last_synced_at": null,
"created_at": "2026-10-01T09:30:00.000Z",
"updated_at": "2026-10-01T09:30:00.000Z"
}# repeated label → 409
{
"error": {
"type": "conflict",
"code": "label_conflict",
"message": "Another millionverifier connection is named \"Verifier\".",
"param": "label",
"doc_url": "https://argus.town/docs/errors#label_conflict",
"request_id": null
}
}/v1/connections/{id}API keyMCP tool connections_updateUpdate a connection. Relabel a connection or rotate its credential. Send `secret` to rotate: it is checked with the provider, replaces the stored credential and re-queues a sync. `credential_type` and `meta` are part of the credential, so they are accepted only together with `secret` (non-secret fields you leave out of `meta` keep their stored values). A connection an operator suspended stays suspended. Credentials of AI-built connectors rotate in the app. Nothing secret is ever returned.
idpathrequiredConnection id, with or without the `conn_` prefix.
At least one of `label` or `secret`.
labelstringoptionala string at most 80 character(s) long
secretstringoptionalA replacement credential. Checked with the provider before it is stored. `credential_type` and `meta` change only together with it.
credential_typestringoptional
metaobjectoptional
Send an Idempotency-Key header to make retries safe.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, connection_not_found, invalid_request_body, invalid_parameter, invalid_idempotency_key, idempotency_key_reused, payload_too_large, credential_invalid, provider_unreachable, label_conflict
curl -s -X PATCH "https://api.argus.town/v1/connections/$ID" -H "Authorization: Bearer $ARGUS_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" -d '{"label":"OpenAI (production)"}'# relabel → 200
{
"object": "connection",
"id": "conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11",
"provider": "openai",
"origin": "poll",
"label": "OpenAI (production)",
"credential_type": "api_key",
"status": "active",
"status_detail": null,
"last_synced_at": "2026-10-01T09:30:00.000Z",
"created_at": "2026-10-01T09:30:00.000Z",
"updated_at": "2026-10-01T09:30:00.000Z"
}# meta without secret → 400
{
"error": {
"type": "invalid_request",
"code": "invalid_parameter",
"message": "\"meta\" can only change together with \"secret\": it is validated and stored with the credential.",
"param": "meta",
"doc_url": "https://argus.town/docs/errors#invalid_parameter",
"request_id": null
}
}/v1/connections/{id}Signed-in app onlyDelete a connection. Stop tracking a provider and delete its stored credential and history. Session only: an API key is refused with `session_required`. Unknown ids are a 404.
idpathrequiredConnection id, with or without the `conn_` prefix.
Errors: session_required, connection_not_found
# delete → 204 (no content)
# api key refused → 403
{
"error": {
"type": "permission_error",
"code": "session_required",
"message": "This action needs a signed-in session in the Argus app. API keys cannot perform it.",
"param": null,
"doc_url": "https://argus.town/docs/errors#session_required",
"next_step": {
"hint": "Do this from the Argus app, signed in as a member of the project.",
"href": "https://argus.town/settings"
},
"request_id": null
}
}Events
/v1/eventsSource secretSend events. Send one event or a batch of up to 1,000. Authenticate with a source secret (`Authorization: Bearer argus_src_...` plus `Argus-Source: <source id>`) or HMAC-sign the body as `POST /ingest` does. A source credential can only write events; it cannot read anything. Argus prices events without a `cost_usd` from `model` and `units`. Bad events are rejected one by one in `rejected`; the rest are queued. Re-sending an event `id` is a no-op, and an `Idempotency-Key` makes the whole request safe to retry for 24 hours. Bearer mode has no replay protection: a captured request can be replayed, so retry with the same `Idempotency-Key` (or give events ids). HMAC mode rejects timestamps more than 5 minutes off, and a replayed signed body without event ids derives the same ids and stores once. After a timeout, resend the identical signed request within 5 minutes or use explicit event ids: a re-signed retry derives new ids and can double-count if the first attempt died before its response was stored. Rate limits per source: 6,000 events and 600 requests a minute. Telemetry may create at most 10 new connections per request and 100 per project. `accepted` counts events queued: the queue consumer prices events without a `cost_usd` afterwards, and drops (and logs) an event whose derived cost exceeds 1,000,000 USD, which cannot be known at request time.
backfillquery · booleanoptionalAllow `occurred_at` up to 29 days back instead of 48 hours.
An event, or `{events: [...], backfill?: true}`. `backfill=true` as a query parameter works too.
Send an Idempotency-Key header to make retries safe.
Errors: missing_source_credentials, invalid_source_credentials, timestamp_stale, key_type_not_allowed, too_many_auth_failures, invalid_request_body, invalid_idempotency_key, payload_too_large, too_many_events, no_valid_events, idempotency_key_reused, invalid_parameter
curl -s -X POST "https://api.argus.town/v1/events" -H "Authorization: Bearer $ARGUS_SOURCE_SECRET" -H "Argus-Source: $ARGUS_SOURCE_ID" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" -d '{"provider":"openai","model":"gpt-4o","units":{"input_tokens":900,"output_tokens":120},"metadata":{"run":"nightly"}}'# one event → 202
{
"object": "event_batch",
"accepted": 1,
"rejected": [],
"rejected_count": 0,
"warnings": [],
"warnings_count": 0,
"events": [
{
"id": "evt_6f1d2c4a9b8e47d3a05c1e7b9d2f4a68"
}
],
"connections": [
{
"id": "conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11",
"provider": "openai",
"created": false
}
],
"next_steps": [
{
"rel": "read_events",
"method": "GET",
"href": "/v1/events?id=evt_6f1d2c4a9b8e47d3a05c1e7b9d2f4a68&since=2026-10-01T09%3A29%3A00.000Z&until=2026-10-01T09%3A31%3A00.000Z",
"why": "Read the event back once the queue has stored it, usually within seconds."
}
]
}# every event rejected → 422
{
"error": {
"type": "invalid_request",
"code": "no_valid_events",
"message": "None of the 1 events were valid. First: events[0]: event must be an object.",
"param": "events[0]",
"doc_url": "https://argus.town/docs/errors#no_valid_events",
"next_step": {
"hint": "Validate the batch to see every rejection.",
"method": "POST",
"href": "/v1/events/validate"
},
"request_id": null
}
}/v1/events/validateSource secretValidate events. Runs the exact pipeline of `POST /v1/events` and stores nothing: which events would be accepted, which rejected and why, and the normalized events. Same credentials and body limits; counts against the same events rate limit.
backfillquery · booleanoptionalValidate as a backfill batch (`occurred_at` up to 29 days back).
The same body as `POST /v1/events`.
Errors: missing_source_credentials, invalid_source_credentials, timestamp_stale, key_type_not_allowed, too_many_auth_failures, invalid_request_body, payload_too_large, too_many_events, invalid_parameter
curl -s -X POST "https://api.argus.town/v1/events/validate" -H "Authorization: Bearer $ARGUS_SOURCE_SECRET" -H "Argus-Source: $ARGUS_SOURCE_ID" -H "content-type: application/json" -d '{"events":[{"provider":"openai","model":"gpt-4o","colour":"red"},{"provider":"openai","status":"ok"}]}'# one bad, one good → 200
{
"object": "event_validation",
"valid": false,
"accepted": 1,
"rejected": [
{
"index": 1,
"id": null,
"code": "invalid_event",
"message": "status must be a finite number"
}
],
"rejected_count": 1,
"warnings": [
{
"index": 0,
"code": "unknown_field",
"field": "colour",
"message": "`colour` is not an event field and was ignored."
}
],
"warnings_count": 1,
"normalized_events": [
{
"provider": "openai",
"model": "gpt-4o"
}
],
"next_steps": []
}/v1/eventsAPI keyMCP tool events_listList events. The project's call ledger, newest first, paged with a keyset cursor so events sharing a timestamp are never skipped. Time-bounded: `since` defaults to 31 days before `until` (default: 48 hours ahead of now) and the window is at most 31 days. `id` and `metadata[key]=value` filters need an explicit `since` and a window of at most 7 days. By default only events with a cost or a status of 400 or above are listed; `view=all` also lists cost-less events, and filtering by `id` or `cost_provenance` always does. Every accepted event counts toward `events_this_month` on `GET /billing`, listed or not.
limitquery · integeroptionalPage size. Default 25, at most 500.
cursorquery · stringoptionalOpaque cursor from a previous page's `next_cursor`.
connectionquery · stringoptionalOnly events of this connection (`conn_...`).
providerquery · stringoptionalOnly events of this provider slug.
modelquery · stringoptionalOnly events of this model.
idquery · stringoptionalOnly the event with this id (any source of the project). Needs `since`, window of at most 7 days.
statusquery · integeroptionalOnly events with this HTTP status.
cost_provenancequery · stringoptionalOnly events whose cost was obtained this way.
sincequery · stringoptionalOldest `occurred_at`, inclusive.
untilquery · stringoptionalNewest `occurred_at`, exclusive.
viewquery · stringoptional`all` lists cost-less events with a status below 400 too.
metadataquery · string mapoptionalFilter on metadata, for example `metadata[run]=nightly`. Up to 5; needs `since` (window of at most 7 days).
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, too_many_auth_failures, invalid_parameter, invalid_cursor
curl -s "https://api.argus.town/v1/events?limit=1&view=all" -H "Authorization: Bearer $ARGUS_API_KEY"
# recent → 200
{
"object": "list",
"data": [
{
"object": "event",
"id": "01JAX7Q2M5N8R4T6V9W3Y1Z0BC",
"source": "src_5b1c0e9a",
"connection": "conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11",
"provider": "openai",
"hostname": "api.openai.com",
"endpoint": "/v1/chat/completions",
"method": "POST",
"model": "gpt-4o",
"status": 200,
"latency_ms": 812,
"units": {
"input_tokens": 900,
"output_tokens": 120
},
"metadata": {
"run": "nightly"
},
"cost_usd": 0.00345,
"cost_micros": 3450,
"cost_provenance": "derived",
"price_match": "exact",
"provider_request_id": null,
"credits_spent": null,
"credits_remaining": null,
"occurred_at": "2026-10-01T09:30:00.000Z",
"received_at": "2026-10-01T09:30:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}Syncs
/v1/connections/{id}/syncsAPI keyMCP tool syncs_createRefresh a connection. Queue an immediate refresh of balance, usage and plan, and get a sync to poll with `GET /v1/syncs/{id}`. At most one refresh is queued per connection a minute, whatever state the last one is in: a request inside that window returns the latest sync instead of queueing another. Telemetry-only and suspended connections have nothing to poll: 409 `connection_not_pollable`.
idpathrequiredConnection id, with or without the `conn_` prefix.
Send an Idempotency-Key header to make retries safe.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, connection_not_found, connection_not_pollable, invalid_idempotency_key, idempotency_key_reused
curl -s -X POST "https://api.argus.town/v1/connections/$CONNECTION/syncs" -H "Authorization: Bearer $ARGUS_API_KEY" -H "Idempotency-Key: $(uuidgen)"
# queued → 202
{
"object": "sync",
"id": "sync_5d0c7a21-8e4b-4f6a-9c13-2b7e1a90d4f8",
"kind": "refresh",
"status": "queued",
"started_at": "2026-10-01T09:30:00.000Z",
"finished_at": null,
"records_written": 0,
"error": null
}# telemetry only → 409
{
"error": {
"type": "conflict",
"code": "connection_not_pollable",
"message": "This connection only receives telemetry, so there is nothing to refresh.",
"param": null,
"doc_url": "https://argus.town/docs/errors#connection_not_pollable",
"request_id": null
}
}/v1/connections/{id}/syncsAPI keyList refreshes. Refreshes requested for the connection, newest first. A sync is `queued`, `running` once some of its runs have reported, then `success`, `partial` or `error`. A transient provider error reads `partial` until the retry lands; a sync the queue never finished is closed after 30 minutes with what it has.
idpathrequiredConnection id, with or without the `conn_` prefix.
limitquery · integeroptionalPage size. Default 25.
cursorquery · stringoptionalOpaque cursor from a previous page's `next_cursor`.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, connection_not_found, invalid_parameter, invalid_cursor
curl -s "https://api.argus.town/v1/connections/$CONNECTION/syncs?limit=5" -H "Authorization: Bearer $ARGUS_API_KEY"
# history → 200
{
"object": "list",
"data": [
{
"object": "sync",
"id": "sync_5d0c7a21-8e4b-4f6a-9c13-2b7e1a90d4f8",
"kind": "refresh",
"status": "queued",
"started_at": "2026-10-01T09:30:00.000Z",
"finished_at": null,
"records_written": 0,
"error": null
}
],
"has_more": false,
"next_cursor": null
}/v1/syncs/{id}API keyMCP tool syncs_getGet a sync. One refresh, or one scheduled run as listed under a connection's `sync_runs` expansion.
idpathrequiredSync id, with or without the `sync_` prefix.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, sync_not_found
curl -s "https://api.argus.town/v1/syncs/$SYNC" -H "Authorization: Bearer $ARGUS_API_KEY"
# queued → 200
{
"object": "sync",
"id": "sync_5d0c7a21-8e4b-4f6a-9c13-2b7e1a90d4f8",
"kind": "refresh",
"status": "queued",
"started_at": "2026-10-01T09:30:00.000Z",
"finished_at": null,
"records_written": 0,
"error": null
}Connect links
/v1/connect_linksAPI keyMCP tool connect_links_createCreate a connect link. A single-use URL, valid for an hour, where a human pastes the provider credential so it never passes through the agent. The `url` is returned only now and cannot be read back; a replay with the same `Idempotency-Key` returns it from an encrypted store. Poll `GET /v1/connect_links/{id}` until `status` is `done`. A `label` another connection to the provider already has is a 409 `label_conflict`: a link never rotates an existing connection's credential. At most 10 unused links per project.
The provider to connect, optionally pinning the credential type and a label.
providerstringrequired
credential_typestringoptional
labelstringoptionala string at most 60 character(s) long
Send an Idempotency-Key header to make retries safe.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, invalid_request_body, invalid_parameter, missing_parameter, invalid_idempotency_key, idempotency_key_reused, payload_too_large, provider_not_connectable, label_conflict, quota_exceeded
curl -s -X POST "https://api.argus.town/v1/connect_links" -H "Authorization: Bearer $ARGUS_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" -d '{"provider":"millionverifier"}'# millionverifier → 201
{
"object": "connect_link",
"id": "lnk_9a3f6c1e-2b84-4d7a-8e05-1c6b9d2f7a40",
"provider": "millionverifier",
"credential_type": null,
"label": null,
"status": "pending",
"connection": null,
"expires_at": "2026-10-01T10:30:00.000Z",
"next_steps": [
{
"rel": "poll",
"method": "GET",
"href": "/v1/connect_links/lnk_9a3f6c1e-2b84-4d7a-8e05-1c6b9d2f7a40",
"why": "Poll until the status is done."
}
],
"url": "https://argus.town/connect/argus_cl_3f1d9b7a5c2e4086a1b3d5f70e9c2a48b6d1f3e5a7c90214"
}# not connectable → 422
{
"error": {
"type": "invalid_request",
"code": "provider_not_connectable",
"message": "Argus cannot poll \"nope\".",
"param": "provider",
"doc_url": "https://argus.town/docs/errors#provider_not_connectable",
"next_step": {
"hint": "List providers Argus can poll.",
"method": "GET",
"href": "/v1/providers?connectable=true"
},
"request_id": null
}
}/v1/connect_links/{id}API keyMCP tool connect_links_getGet a connect link. Poll a link: `pending`, `submitting`, `done`, `expired` or `locked` (too many failed attempts). When `done`, `connection` is the new connection's id, or the object with `expand[]=connection`. The URL is not returned again.
idpathrequiredLink id, with or without the `lnk_` prefix.
expand[]query · string arrayoptionalFields to include. One or more of: connection.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, connect_link_not_found, invalid_parameter
curl -s "https://api.argus.town/v1/connect_links/$CONNECT_LINK" -H "Authorization: Bearer $ARGUS_API_KEY"
# waiting → 200
{
"object": "connect_link",
"id": "lnk_9a3f6c1e-2b84-4d7a-8e05-1c6b9d2f7a40",
"provider": "millionverifier",
"credential_type": null,
"label": null,
"status": "pending",
"connection": null,
"expires_at": "2026-10-01T10:30:00.000Z",
"next_steps": [
{
"rel": "poll",
"method": "GET",
"href": "/v1/connect_links/lnk_9a3f6c1e-2b84-4d7a-8e05-1c6b9d2f7a40",
"why": "Poll until the status is done."
}
]
}Alerts
/v1/alert_rulesAPI keyMCP tool alert_rules_createCreate an alert rule. Email the project's members when a connection crosses a threshold. `threshold` is USD, days or percent depending on `type`. `connections` lists the connections to watch; omit it to watch all of them. An id that is not a connection of the project is a 422, never a rule that silently watches everything. At most 100 rules per project.
The condition and what it watches.
type"low_balance" | "days_to_zero" | "spend_spike" | "projected_overage" | "rate_limit_headroom" | "error_rate" | "cost_drift"required
thresholdnumberrequiredUSD, days or percent, depending on `type`.
connectionsstring[]optionalConnection ids the rule watches. Omit to watch every connection. An unknown id is an error.
enabledbooleanoptional
Send an Idempotency-Key header to make retries safe.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, invalid_request_body, invalid_parameter, missing_parameter, invalid_idempotency_key, idempotency_key_reused, payload_too_large, connection_not_found, quota_exceeded
curl -s -X POST "https://api.argus.town/v1/alert_rules" -H "Authorization: Bearer $ARGUS_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" -d '{"type":"low_balance","threshold":10,"connections":["{id}"]}'# low balance → 201
{
"object": "alert_rule",
"id": "alr_2c8e5b13-7d4a-4f90-b6a1-3e9d0c7f5a82",
"type": "low_balance",
"threshold": 10,
"connections": [
"conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11"
],
"enabled": true,
"channel": "email",
"last_fired_at": null,
"created_at": "2026-10-01T09:30:00.000Z"
}# unknown connection → 422
{
"error": {
"type": "not_found",
"code": "connection_not_found",
"message": "No connection has the id conn_00000000-0000-0000-0000-000000000000 in this project.",
"param": "connections[0]",
"doc_url": "https://argus.town/docs/errors#connection_not_found",
"next_step": {
"hint": "List this project's connections.",
"method": "GET",
"href": "/v1/connections"
},
"request_id": null
}
}/v1/alert_rulesAPI keyMCP tool alert_rules_listList alert rules. The project's alert rules, newest first.
limitquery · integeroptionalPage size. Default 25.
cursorquery · stringoptionalOpaque cursor from a previous page's `next_cursor`.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, invalid_parameter, invalid_cursor
curl -s "https://api.argus.town/v1/alert_rules?limit=10" -H "Authorization: Bearer $ARGUS_API_KEY"
# rules → 200
{
"object": "list",
"data": [
{
"object": "alert_rule",
"id": "alr_2c8e5b13-7d4a-4f90-b6a1-3e9d0c7f5a82",
"type": "low_balance",
"threshold": 10,
"connections": [
"conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11"
],
"enabled": true,
"channel": "email",
"last_fired_at": null,
"created_at": "2026-10-01T09:30:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}/v1/alert_rules/{id}API keyMCP tool alert_rules_updateUpdate an alert rule. Change any of `type`, `threshold`, `connections` (`null` watches every connection) and `enabled`. Connection ids are checked like on creation.
idpathrequiredRule id, with or without the `alr_` prefix.
At least one field to change.
type"low_balance" | "days_to_zero" | "spend_spike" | "projected_overage" | "rate_limit_headroom" | "error_rate" | "cost_drift"optional
thresholdnumberoptionala number between 0 and 1000000000000
connectionsstring[] | nulloptional`null` makes the rule watch every connection.
enabledbooleanoptional
Send an Idempotency-Key header to make retries safe.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, alert_rule_not_found, invalid_request_body, invalid_parameter, invalid_idempotency_key, idempotency_key_reused, payload_too_large, connection_not_found
curl -s -X PATCH "https://api.argus.town/v1/alert_rules/$ALERT_RULE" -H "Authorization: Bearer $ARGUS_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" -d '{"threshold":25}'# raise the threshold → 200
{
"object": "alert_rule",
"id": "alr_2c8e5b13-7d4a-4f90-b6a1-3e9d0c7f5a82",
"type": "low_balance",
"threshold": 25,
"connections": [
"conn_0b6f2c1e-6c1d-4a39-9f43-5b0d2f7a8c11"
],
"enabled": true,
"channel": "email",
"last_fired_at": null,
"created_at": "2026-10-01T09:30:00.000Z"
}# empty connections → 400
{
"error": {
"type": "invalid_request",
"code": "invalid_parameter",
"message": "Invalid value for \"connections\": Expected an array of at least 1 item(s), actual []",
"param": "connections",
"doc_url": "https://argus.town/docs/errors#invalid_parameter",
"request_id": null
}
}/v1/alert_firingsAPI keyMCP tool alert_firings_listList alert firings. When rules fired, newest first. Filter with `rule` or `connection` (the same ids the objects carry).
limitquery · integeroptionalPage size. Default 25.
cursorquery · stringoptionalOpaque cursor from a previous page's `next_cursor`.
rulequery · stringoptionalOnly firings of this rule (`alr_...`).
connectionquery · stringoptionalOnly firings for this connection (`conn_...`).
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, invalid_parameter, invalid_cursor
curl -s "https://api.argus.town/v1/alert_firings?limit=10" -H "Authorization: Bearer $ARGUS_API_KEY"
# none yet → 200
{
"object": "list",
"data": [],
"has_more": false,
"next_cursor": null
}/v1/alert_rules/{id}Signed-in app onlyDelete an alert rule. Session only: an API key is refused with `session_required`. Unknown ids are a 404.
idpathrequiredRule id, with or without the `alr_` prefix.
Errors: session_required, alert_rule_not_found
# delete → 204 (no content)
# api key refused → 403
{
"error": {
"type": "permission_error",
"code": "session_required",
"message": "This action needs a signed-in session in the Argus app. API keys cannot perform it.",
"param": null,
"doc_url": "https://argus.town/docs/errors#session_required",
"next_step": {
"hint": "Do this from the Argus app, signed in as a member of the project.",
"href": "https://argus.town/settings"
},
"request_id": null
}
}Negotiated pricing
/v1/negotiated_pricing/{provider}API keyMCP tool negotiated_pricing_putSet negotiated pricing. Record what your deal with a provider costs, so spend is priced at your rates instead of list price. `rates` are set one per `sku` and `unit` (rates you do not list are kept); `plan` replaces the negotiated subscription, and fields you omit from it are cleared. Every entry is validated and one bad entry rejects the request with `param` naming it: nothing is skipped silently. Events from the last 7 days that were left unpriced are repriced. `provider` is any provider slug your events use, catalog or not. At most 500 rates and 100 plans per project. List rates are read-only (`GET /v1/negotiated_pricing/{provider}?expand[]=list_rates`); resetting to list price is done in the app.
providerpathrequiredProvider slug, for example `openrouter`.
Rates, a plan, or both.
ratesobject[]optionalRates to set, one per `sku` and `unit`; rates already set and not listed are kept. One bad entry rejects the whole request.
planobjectoptionalThe negotiated subscription. Fields you omit are cleared.
Send an Idempotency-Key header to make retries safe.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, provider_not_found, invalid_request_body, invalid_parameter, invalid_idempotency_key, idempotency_key_reused, payload_too_large, quota_exceeded
curl -s -X PUT "https://api.argus.town/v1/negotiated_pricing/openrouter" -H "Authorization: Bearer $ARGUS_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "content-type: application/json" -d '{"rates":[{"sku":"gpt-4o","unit":"input_tokens","nano_usd_per_unit":2000}]}'# one rate → 200
{
"object": "negotiated_pricing",
"provider": "openrouter",
"rates": [
{
"sku": "gpt-4o",
"unit": "input_tokens",
"nano_usd_per_unit": 2000
}
],
"plan": null,
"updated_at": "2026-10-01T09:30:00.000Z"
}# malformed rate → 400
{
"error": {
"type": "invalid_request",
"code": "invalid_parameter",
"message": "Invalid value for \"rates[0].nano_usd_per_unit\": Expected a number between 0 and 1000000000000, actual -1",
"param": "rates[0].nano_usd_per_unit",
"doc_url": "https://argus.town/docs/errors#invalid_parameter",
"request_id": null
}
}/v1/negotiated_pricing/{provider}API keyMCP tool negotiated_pricing_getGet negotiated pricing. The project's own rates and subscription for a provider; empty `rates` and a null `plan` when nothing is negotiated. `expand[]=list_rates` adds Argus's list rates, which negotiated rates replace one for one.
providerpathrequiredProvider slug, for example `openrouter`.
expand[]query · string arrayoptionalFields to include. One or more of: list_rates.
Errors: missing_credentials, invalid_api_key, key_type_not_allowed, provider_not_found, invalid_parameter
curl -s "https://api.argus.town/v1/negotiated_pricing/openrouter" -H "Authorization: Bearer $ARGUS_API_KEY"
# negotiated → 200
{
"object": "negotiated_pricing",
"provider": "openrouter",
"rates": [
{
"sku": "gpt-4o",
"unit": "input_tokens",
"nano_usd_per_unit": 2000
}
],
"plan": null,
"updated_at": "2026-10-01T09:30:00.000Z"
}/v1/negotiated_pricing/{provider}Signed-in app onlyReset to list pricing. Remove every negotiated rate and the negotiated plan for a provider, or with `sku` and `unit` just that one rate (the plan stays). Session only: an API key is refused with `session_required`. A project with nothing negotiated, or no rate for that `sku` and `unit`, gets a 404.
providerpathrequiredProvider slug, for example `openrouter`.
skuquery · stringoptionalReset only this rate; needs `unit`.
unitquery · stringoptionalThe unit of the rate to reset; needs `sku`.
Errors: session_required, provider_not_found, negotiated_pricing_not_found, invalid_parameter
# reset → 204 (no content)
# api key refused → 403
{
"error": {
"type": "permission_error",
"code": "session_required",
"message": "This action needs a signed-in session in the Argus app. API keys cannot perform it.",
"param": null,
"doc_url": "https://argus.town/docs/errors#session_required",
"next_step": {
"hint": "Do this from the Argus app, signed in as a member of the project.",
"href": "https://argus.town/settings"
},
"request_id": null
}
}