REST API Reference
Bypass the dashboard and operate Argosvix data directly over HTTP. Usable from any framework or curl.
Base URL
https://ingest.argosvix.com
All endpoints live under this origin (same target as the SDK). Distinct from the dashboard origin (dashboard.argosvix.com). CORS is explicitly allow-listed as a rule, with one exception: POST /v1/ingest responds with Access-Control-Allow-Origin: * (authentication is via the Bearer key, so you can ingest directly from a browser without pre-registering your domain).
Authentication
Every endpoint accepts one of:
- Bearer token — Same
argk_...API key the SDK uses, sent asAuthorization: Bearer argk_.... Standard for server-to-server. - Session cookie — When called from a logged-in
dashboard.argosvix.combrowser session. Mutating methods enforce Origin / Referer CSRF checks.
Status codes:
| Code | Meaning |
|---|---|
| 200 | OK |
| 201 | Created |
| 204 | OK, no body (delete etc.) |
| 400 | Validation failure |
| 401 | Missing / invalid Bearer or session |
| 402 | Consent endpoints (plaintext storage / AI helper consent) and Team invitations / membership management only. All other plan gates return 403 |
| 403 | CSRF reject, permission denied, plan gate (reason: "plan_required" — most Pro+-only features return this) |
| 404 | Resource not found, or hidden by ownership scope |
| 409 | Duplicate — the resource already exists |
| 429 | Rate limit |
| 500 | Server-side transient error (retry) |
| 503 | Service temporarily unavailable — retry shortly; if it persists, contact support |
API key scopes (least privilege)
API keys can carry permission scopes. There are three:
read— read-only (GET endpoints and/v1/query/*)write:records— record ingestion (/v1/ingest,/v1/traces, and other records writes)write— everything else (creating / modifying alerts, prompts, evals, gates, etc.)
Keys issued by npx @argosvix/cli init default to read + write:records — least privilege, so a key embedded in your app cannot change alert settings or perform destructive operations even if it leaks. If you need operational access (e.g. running operations via MCP), issue a separate key with broader scopes from the dashboard. Keys without explicit scopes keep full access for backward compatibility.
Quotas and retention:
- Record quota is 50,000 records/month on Free and 1,000,000/month on Pro / Team (a flat per-account cap on Team too — it does not scale with seats). Once exceeded, ingestion returns 429 (with a
Retry-Afterheader) until the start of the next month. Read endpoints keep working. - Retention is 30 days on Free and 90 days on Pro / Team. Older records are deleted automatically.
- Endpoints with their own rate limits (webhook test, proposal endpoints) note them in their sections.
Ingesting records
POST /v1/ingest — send call records
Send LLM call records over plain HTTP without the SDK. Use this from languages the SDKs don't cover (Go / Ruby / Rust, etc.). Up to 100 records per request; body capped at 1 MiB (413 beyond that).
Required fields: id (unique per call; a duplicate of an existing id is silently skipped rather than overwritten — it does not appear in rejected and is counted in accepted, but only the first record is stored and quota is not double-charged), provider (openai / anthropic / gemini / mistral), model, timestamp (ISO 8601), and non-negative numbers for promptTokens / completionTokens / totalTokens / costUsd / latencyMs.
Optional fields: tags (Record<string, string>), error, errorDetails, traceId / spanId / parentSpanId (for trace linking), sessionId.
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/ingest \
-d '{
"records": [{
"id": "call_20260706_0001",
"provider": "openai",
"model": "gpt-5-mini",
"timestamp": "2026-07-06T00:00:00.000Z",
"promptTokens": 42,
"completionTokens": 18,
"totalTokens": 60,
"costUsd": 0.0012,
"latencyMs": 234,
"tags": {"service": "checkout"}
}]
}'
Responses use partial success: records that fail validation come back in rejected with a reason, and the rest are ingested:
{"accepted": 1, "rejected": [{"id": "call_x", "reason": "missing id"}]}
OpenTelemetry trace ingestion (OTLP/HTTP)
POST /v1/traces — Ingest OTLP/HTTP spans
Send traces over OTLP/HTTP (JSON encoding) and Argosvix ingests the spans that follow the GenAI semantic conventions as call records. Point any OTel instrumentation — LangGraph, OpenLLMetry, Traceloop — straight at this endpoint without rewriting your code.
Authentication is the same Bearer API key as every other endpoint. Both http/json and http/protobuf encodings are accepted, so exporters work with their default settings.
Example exporter configuration:
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://ingest.argosvix.com/v1/traces
OTEL_EXPORTER_OTLP_TRACES_HEADERS=Authorization=Bearer%20argk_xxx
Span mapping
Spans that represent an LLM call (those carrying gen_ai.* attributes) are ingested as call records. Both the newer and older attribute names are accepted. Spans without gen_ai.* attributes are handled separately (they are ingested as typed observations rather than rejected — see below).
| Ingested value | Attributes (first match wins) |
|---|---|
| Provider | gen_ai.provider.name or gen_ai.system |
| Model | gen_ai.request.model or gen_ai.response.model |
| Input tokens | gen_ai.usage.input_tokens or gen_ai.usage.prompt_tokens |
| Output tokens | gen_ai.usage.output_tokens or gen_ai.usage.completion_tokens |
| Cost | gen_ai.usage.cost or gen_ai.usage.cost_usd (otherwise derived from per-model pricing) |
The provider is normalized to one of openai / anthropic / gemini / mistral. Spans that carry gen_ai.* attributes but don't map to one of these are counted as rejected. Spans without gen_ai.* attributes (non-LLM spans such as retrieval / tool / agent / chain steps), however, are not rejected — they are ingested as typed observations, with the type inferred from the span name, and shown nested alongside LLM spans in the trace detail view. Note that each observation consumes one unit of your monthly record quota, the same as a call record — sending large volumes of unrelated spans (HTTP, DB, etc.) burns quota, so we recommend filtering the exporter to LLM-adjacent spans. traceId and spanId are preserved, so you can follow the nested execution in the trace detail view after ingestion. Re-sending the same span is deduplicated by traceId-spanId, so retries are not double-counted (quota for duplicates is released too).
Response contract
Because OTel exporters retry on any non-2xx response, business-level rejections are still returned with HTTP 200.
| Situation | Status | Body |
|---|---|---|
| All spans accepted | 200 | {} |
| Some rejected | 200 | {"partialSuccess":{"rejectedSpans":"2","errorMessage":"..."}} |
| Monthly quota exceeded | 200 | every span counted in rejectedSpans (nothing ingested until next month) |
| Malformed protobuf | 400 | — |
| Malformed JSON | 400 | — |
| Body too large (over 1 MiB) | 413 | — |
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/traces \
-d '{
"resourceSpans": [{
"scopeSpans": [{
"spans": [{
"traceId": "5b8aa5a2d2c872e8321cf37308d69df2",
"spanId": "051581bf3cb55c13",
"name": "chat",
"startTimeUnixNano": "1700000000000000000",
"endTimeUnixNano": "1700000002400000000",
"attributes": [
{"key": "gen_ai.system", "value": {"stringValue": "openai"}},
{"key": "gen_ai.request.model", "value": {"stringValue": "gpt-5.5"}},
{"key": "gen_ai.usage.input_tokens", "value": {"intValue": "100"}},
{"key": "gen_ai.usage.output_tokens", "value": {"intValue": "50"}}
]
}]
}]
}]
}'
Observability (call records)
POST /v1/query/calls — Recent calls
Filter by time range / provider / model / count.
Request body:
| Field | Type | Description |
|---|---|---|
startTime | ISO 8601 | Window start (UTC) |
endTime | ISO 8601 | Window end (UTC) |
provider | string | openai / anthropic / gemini / mistral |
model | string | Partial match |
limit | number | 1–200, default 50 |
sortBy | string | timestamp / provider / model / total_tokens / cost_usd / latency_ms / error |
sortOrder | string | asc / desc (default desc) |
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/query/calls \
-d '{"provider":"anthropic","limit":10}'
Response (a records array; every record has the same shape, absent values are null):
{
"records": [{
"id": "call_20260706_0001",
"provider": "openai",
"model": "gpt-5-mini",
"promptTokens": 42,
"completionTokens": 18,
"totalTokens": 60,
"costUsd": 0.0012,
"latencyMs": 234,
"timestamp": "2026-07-06T00:00:00.000Z",
"tags": {"service": "checkout"},
"error": null,
"errorDetails": null,
"traceId": null,
"spanId": null,
"parentSpanId": null,
"sessionId": null,
"toolCalls": null,
"piiRedacted": false,
"redactionMetadata": null,
"requestMeta": null
}]
}
limit caps at 200. For larger pulls, page through time windows with startTime / endTime, or use POST /v1/query/export below (or the MCP export_calls tool).
POST /v1/query/export — Bulk export
Accepts the same filter and sort body as /v1/query/calls, but returns up to the per-plan cap in one call (Free 1,000 / Pro and Team 50,000 records). The default response is the same JSON as /calls (a records array); add the ?format=csv query parameter or an Accept: text/csv header to get server-side CSV (UTF-8 BOM, Content-Disposition: attachment).
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
"https://ingest.argosvix.com/v1/query/export?format=csv" \
-d '{"provider":"openai","startTime":"2026-06-01T00:00:00Z"}' \
-o argosvix-calls.csv
POST /v1/query/aggregate — Aggregate
Supports 7 groupBy axes — provider / model / day / hour / minute / tag / error — and 12 metrics: cost / latency / tokens / count / error_rate / input_tokens / output_tokens / cached_tokens / cache_savings / reasoning_tokens / audio_tokens / ttft. hour mode covers up to 168 hours, minute up to 60 minutes, and day up to 366 days (tag mode requires a tagKey).
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/query/aggregate \
-d '{"groupBy":"model","metric":"cost","startTime":"2026-06-01T00:00:00Z"}'
Response:
{
"groups": [
{"key": "gpt-4o", "value": 1.7031, "count": 68, "lastSeen": "2026-07-02T04:45:16.000Z"},
{"key": "gpt-5-mini", "value": 0.0214, "count": 312, "lastSeen": "2026-07-05T22:10:03.000Z"}
],
"total": {"value": 1.7245, "count": 380}
}
POST /v1/query/percentiles — Percentiles
Returns p50 / p95 / p99 for latency or cost in one call.
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/query/percentiles \
-d '{"metric":"latency"}'
Response:
{"metric": "latency", "p50": 950, "p95": 2400, "p99": 4200, "p999": 6005, "max": 6005, "count": 246}
GET /v1/query/calls/:id — Single call
Returns one record by call id. Other accounts' ids return 404.
GET /v1/query/trace/:id — Trace detail
Returns the spans for a trace (capped at 500, ascending by timestamp). Each span includes errorDetails, requestMeta, and toolCalls. The response also carries nodes — a merged tree of LLM spans and non-LLM observations (retrieval / tool etc.).
Account & budget
GET /v1/account — Identity snapshot
Returns plan, this month's usage, retention — non-sensitive identity. No subscription details. Bearer-only.
curl -H "Authorization: Bearer $ARGOSVIX_API_KEY" \
https://ingest.argosvix.com/v1/account
GET /v1/account/llm-feature-budget — Budget state
Returns this month's budget, spent, remaining, and period (YYYY-MM). Available on Free and Pro+.
{
"budgetUsd": 5,
"spentUsd": 0.42,
"remainingUsd": 4.58,
"periodStart": "2026-06",
"defaultBudgetUsd": 5,
"minBudgetUsd": 5,
"maxBudgetUsd": 500
}
PATCH /v1/account/llm-feature-budget — Change budget (Pro+)
Set monthly budget between $5–$500, in $0.01 increments.
curl -X PATCH \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/account/llm-feature-budget \
-d '{"budgetUsd":30}'
Alerts
GET /v1/alerts — List
Returns configured alerts.
POST /v1/alerts — Create
{
"name": "monthly cost",
"alertType": "monthly_budget",
"thresholdValue": 100,
"windowMinutes": 1440,
"channelKinds": ["webhook"],
"channelTargets": { "webhook": { "url": "https://..." } },
"enabled": true
}
alertType is one of cost_threshold / monthly_budget / error_rate / latency_degradation / anomaly_cost / anomaly_latency / anomaly_error_rate / eval_score (quality SLO; requires evalCriterionId) / guardian_findings (delivers new inbox findings to external channels; threshold and window are unused — pass 0 and 60).
⚠️ Free plan limits: at most 3 alerts, the
cost_threshold/monthly_budget/error_rate/latency_degradation) plusguardian_findings(anomaly_*andeval_scoreare Pro+). PassingwebhookinchannelKindsas in the example above returns 403 on Free — substitute"channelKinds": ["email"]to try it on Free. Pro / Team get all 6 channels (email / slack / webhook / discord / teams / pagerduty) and all types.
GET /v1/alerts/:id — Single alert
Returns config + recent firing events.
PATCH /v1/alerts/:id — Update
Update name / thresholdValue / windowMinutes / channelKinds / channelTargets. alertType is immutable (drop and recreate to change it).
DELETE /v1/alerts/:id — Delete
Related firing events cascade.
POST /v1/alerts/test-webhook — Test send (Pro+)
Probe a webhook URL before registering. HTTPS only, with SSRF guards (private / loopback / cloud metadata IPs rejected). Rate-limited to 5 / min.
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/alerts/test-webhook \
-d '{"url":"https://example.com/hook","alertName":"test"}'
Response:
{
"ok": true,
"delivered": true,
"message": "test webhook delivered (= 2xx response within 5s)"
}
GET /v1/alerts/events — Firing history
Returns the account's alert firing events, newest first. Filter to a single alert with alertId.
| Query parameter | Description |
|---|---|
limit | 1-100 (default 20) |
alertId | Restrict to one alert's firings |
beforeTriggeredAt + beforeId | Keyset cursor: pass the last event's triggeredAt and id from the previous page (always both together; one alone is a 400) |
Each event includes a snapshot of the conditions at trigger time (thresholdValue / windowMinutes / alertType). Events that fired before snapshots were introduced fall back to the rule's current values and are marked thresholdIsSnapshot: false.
{
"events": [
{
"id": "6f30f1b2-...",
"alertId": "71845ee2-...",
"alertName": "Cost overrun",
"alertType": "cost_threshold",
"triggeredAt": "2026-06-12T02:15:01.683Z",
"observedValue": 0.005,
"thresholdValue": 0.000001,
"thresholdIsSnapshot": true,
"windowMinutes": 60,
"channelsSent": ["email"],
"acknowledgedAt": null,
"acknowledgedBy": null
}
]
}
POST /v1/alerts/events/:eventId/acknowledge — Acknowledge
Mark a firing event as handled. Orthogonal to silence; idempotent.
Prompt management
GET /v1/prompts — List
Optional query params: label, name.
POST /v1/prompts — Create (Pro+)
{
"name": "customer_support",
"version": "v1",
"template": "Hello {{user}}",
"variables": { "user": "world" },
"labels": ["production"],
"description": "primary cs prompt"
}
A prompt's name + version must be unique within your account. Duplicates return 409.
GET /v1/prompts/:id — Single
PATCH /v1/prompts/:id — Partial update (Pro+)
Updates template / variables / labels / description. name and version are immutable (use POST /v1/prompts/:id/rename).
POST /v1/prompts/:id/rename — Rename (Pro+)
Change name and version. Useful for typo fixes.
curl -X POST \
-H "Authorization: Bearer $ARGOSVIX_API_KEY" \
-H "Content-Type: application/json" \
https://ingest.argosvix.com/v1/prompts/7/rename \
-d '{"name":"customer_support","version":"v2"}'
DELETE /v1/prompts/:id — Delete (Pro+)
⚠ Deleting a version detaches it from past eval runs, so those runs lose their link to the prompt (provenance is lost). For logical sunset, prefer PATCH to set labels to sunset.
Eval criteria
GET /v1/eval-criteria — List
Global defaults + your account's custom criteria.
POST /v1/eval-criteria — Create (Pro+)
{
"name": "helpfulness",
"rubric": "Score how helpful the answer is to the user.",
"scaleMin": 1,
"scaleMax": 5,
"scope": "call"
}
Optional scope: call (default — one score per call) or trajectory (one score per multi-turn agent run, grouping all calls that share a trace_id into a single transcript; llm_judge type only). Trajectory scores are returned under trajectoryScores in GET /v1/eval-runs/:id.
PATCH /v1/eval-criteria/:id — Full replace (Pro+)
All 4 fields required: name / rubric / scaleMin / scaleMax. scope is preserved if omitted. Global defaults return 404.
DELETE /v1/eval-criteria/:id — Delete (Pro+)
⚠ All scores recorded against the criterion are deleted too, breaking historical comparison. Prefer PATCH if you only want to rename.
POST /v1/eval-criteria/propose — LLM-judge proposes evaluation criteria (Pro+)
Asks an LLM judge (gpt-5-mini) to propose evaluation criteria tailored to your use case from useCaseHint and optional sampleCallIds. Proposal-only — nothing is created automatically (you apply it via POST /v1/eval-criteria separately). Sample decrypt failures are reported in partialFailures.
{
"useCaseHint": "Customer support bot for e-commerce (returns + refund policy)",
"sampleCallIds": ["call_abc123", "call_xyz789"],
"maxCriteria": 5
}
useCaseHint: required, 1-500 charssampleCallIds: optional, max 5, account-scopedmaxCriteria: optional, 1-10 (default 5)- Rate limit: 30 requests per 60-second fixed window (429 + Retry-After header; enforced account-wide)
Returns:
{
"criteria": [
{
"name": "tone_appropriateness",
"rubric": "Is the bot's tone polite and professional?",
"scaleMin": 1,
"scaleMax": 5,
"reasoning": "Tone is critical in customer support."
}
],
"partialFailures": [],
"budgetSpentUsd": 0.002,
"proposedRawCount": 5,
"droppedCount": 0
}
Audit: emits eval.propose_criteria to the audit log.
Eval runs
GET /v1/eval-runs — List
Returns history with summary (scoredCount / failedCount / mean scores).
GET /v1/eval-runs/:id — Single
Returns run detail with per (criterion × call) scores.
POST /v1/eval-runs — Start a run (Pro+)
{
"name": "weekly review",
"recentCount": 50,
"label": "production",
"promptRegistryId": 7,
"idempotencyKey": "uuid-1234-..."
}
With idempotencyKey, retries within 60 min return the existing run (dedupe).
Annotations
GET /v1/annotations — List
?callId=xxx lists for one call; ?label=xxx cross-call by label. One of callId or label is required (omitting both returns 400). Label search accepts limit (1-100, default 50), capped at 100.
POST /v1/annotations — Create
{
"callId": "call-xyz",
"annotationText": "good response",
"label": "approved",
"qualityScore": 5
}
At least one of annotationText / label / qualityScore required.
GET /v1/annotations/:id — Single
PATCH /v1/annotations/:id — Partial update
Updates annotationText / label / qualityScore. callId is immutable.
DELETE /v1/annotations/:id — Delete
Safety assessments
GET /v1/safety-assessments — List
?call_id=xxx returns all classifier results for that call. Without it, returns the most recent across the account.
POST /v1/safety-assessments/scan-batch — On-demand bulk safety classify (Pro+)
Complements the automatic background scan (every 15 minutes) with an immediate "scan now" trigger over up to 100 unclassified calls in your account via OpenAI Moderation. The results are recorded as on-demand runs (distinct from scheduled scans), and duplicate assessments are prevented.
Prerequisites (all required):
- Bearer API key auth only (session cookies are not accepted)
- Pro+ plan (Free gets 403 +
reason: "plan_required"; Free relies on the 15-minute automatic scan) - Plaintext storage enabled (otherwise 403 +
reason: "plaintext_disabled") - AI helper consent granted (otherwise 403 +
reason: "consent_required"; if the subscription went inactive after consent, 403 +reason: "subscription_canceled")
{
"maxRecords": 50
}
maxRecords: optional, 1-100 (default 50), integer- Rate limit: 30 requests per 60-second fixed window (429 + Retry-After header; enforced account-wide)
- Via OpenAI Moderation (cost ≈ $0); backend enforces plan + budget gates.
Returns:
{
"scanned": 50,
"assessed": 48,
"flagged": 2,
"failures": 0,
"skipped": 2
}
Audit: emits safety.scan_batch_run to the audit log.
GET /v1/safety-assessments/:id — Single
Returns labels, score, reasoning, classifier_id, source.
Error responses
{
"error": "invalid model (length 1-128 required)"
}
For 403 / 409 / 503 etc., the error field carries a specific cause. Clients should branch on status code + error string.
SDK & MCP server
Instead of calling REST directly, you can use the SDK or MCP server.
- SDK Reference — Just
wrap()the client and calls are auto-recorded. - MCP Server — Operate from chat in Claude Desktop / Cursor / Codex CLI.
Support
Last updated: 2026-07-29