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.
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 | Plan gate — the feature requires a higher plan (reason: "plan_required") |
| 403 | CSRF reject, permission denied |
| 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 (per seat on Team). 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; duplicate ids are rejected, not overwritten), 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-4o-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
Only spans that represent an LLM call (those carrying gen_ai.* attributes) are ingested. Both the newer and older attribute names are accepted.
| 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 (otherwise derived from per-model pricing) |
The provider is normalized to one of openai / anthropic / gemini / mistral. Spans that don't map to one of these, or that carry no gen_ai.* attributes (HTTP, DB, and so on), are dropped and counted as rejected. 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.
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-4o-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 the MCP export_calls tool (CSV / JSON export).
POST /v1/query/aggregate — Aggregate
Group by provider / model / day / tag, returning cost / latency / tokens / count.
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-4o-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 all spans for a trace (capped at 50). errorDetails and requestMeta are structurally stripped.
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).
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-4o-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.
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.
{
"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-09