# Argosvix — full documentation (English) > AI agent observability with an MCP-first design. This file concatenates the > complete English documentation for one-shot ingestion by AI systems. > Human-readable docs: https://argosvix.com/en/docs > Short index: https://argosvix.com/llms.txt > OpenAPI: https://ingest.argosvix.com/openapi.json --- # Quickstart # Quickstart Send your first call record to Argosvix in five minutes. --- ## Fastest: set up everything with one command Run this in your project directory: ```bash npx @argosvix/cli init ``` Approve once in your browser, and it handles the rest automatically: least-privilege API key issuance, SDK install, `.env` setup, MCP server connection (Cursor / Claude Desktop / Codex CLI), and a test event. You can also ask Claude or Cursor to "set up Argosvix" and let the agent run this command for you. Prefer to do it step by step? Continue below. --- ## 1. Create an account and issue an API key 1. Sign up at [dashboard.argosvix.com/signup](https://dashboard.argosvix.com/en/signup). 2. After email verification, open the [API keys page](https://dashboard.argosvix.com/en/api-keys), enter a key name (e.g. `prod-server`), and click **Create**. 3. **Copy the `argk_...` token immediately** — it cannot be displayed again. > ℹ️ The API key is sensitive. Do not commit it to Git. Store it in an environment variable such as `process.env.ARGOSVIX_API_KEY`. --- ## 2. Install the SDK Install alongside the AI provider SDK you use. ```bash # OpenAI npm install @argosvix/sdk openai # Anthropic npm install @argosvix/sdk @anthropic-ai/sdk # Gemini npm install @argosvix/sdk @google/genai # Mistral npm install @argosvix/sdk @mistralai/mistralai ``` For Python: ```bash pip install argosvix openai ``` ```python import os from openai import OpenAI from argosvix import wrap, ArgosvixConfig client = wrap(OpenAI(), ArgosvixConfig(api_key=os.environ["ARGOSVIX_API_KEY"])) # then call client.chat.completions.create(...) as usual — calls are recorded automatically ``` --- ## 3. Wrap your client with `wrap()` Pass your existing AI client through `wrap()` and every call will be recorded by Argosvix automatically. ### OpenAI example ```typescript import OpenAI from "openai"; import { wrap } from "@argosvix/sdk"; const openai = wrap(new OpenAI(), { apiKey: process.env.ARGOSVIX_API_KEY, tags: { service: "my-app" }, }); const res = await openai.chat.completions.create({ model: "gpt-5.5", messages: [{ role: "user", content: "Hello" }], }); ``` ### Anthropic example ```typescript import Anthropic from "@anthropic-ai/sdk"; import { wrap } from "@argosvix/sdk"; const anthropic = wrap(new Anthropic(), { apiKey: process.env.ARGOSVIX_API_KEY, tags: { service: "my-app" }, }); const res = await anthropic.messages.create({ model: "claude-sonnet-4-6", max_tokens: 200, messages: [{ role: "user", content: "Hello" }], }); ``` `tags` is an arbitrary key/value map you can use to filter or aggregate on the dashboard — common examples include `service`, `env`, and `feature`. --- ## 4. Cloudflare Workers / AWS Lambda / Vercel Edge In short-lived runtimes, always `await flushClient()` inside the handler's `finally` block. Without it, records never reach the dashboard because the runtime terminates before the SDK finishes sending. ```typescript import { wrap, flushClient } from "@argosvix/sdk"; export default { async fetch(req, env) { // In Workers, read configuration from the fetch handler's env argument, not process.env const client = wrap(new OpenAI({ apiKey: env.OPENAI_API_KEY }), { apiKey: env.ARGOSVIX_API_KEY, }); try { return await handler(client, req, env); } finally { await flushClient(client); } }, }; ``` Long-lived processes (Node.js servers, Bun, Deno deploy, and similar) don't need it. The TypeScript SDK sends automatically once 100 records are buffered, and also runs an idle auto-flush roughly every 5 seconds while records sit in the buffer (`flushIntervalMs`, default 5000; call `flushClient()` to send earlier); the Python SDK flushes in the background roughly every 5 seconds. --- ## 5. Check the dashboard After sending a single call, open the [Calls page](https://dashboard.argosvix.com/en/calls). A new record should appear within a few seconds. - The table shows recent calls in columns: time, provider, model, tokens, cost, latency, and status. - Sort by clicking column headers. Filter by time preset (last 7 / 30 / 90 days), provider, model name (partial match), or row count. - Click a row to open the detail modal with all metadata, tags, and error details. --- ## Next steps - **[Models](https://dashboard.argosvix.com/en/models)** — per-model usage stats and cheaper-model swap recommendations. - **[Alerts](https://dashboard.argosvix.com/en/alerts)** — notifications when cost, error rate, or latency cross a threshold, over 6 channels: email / Slack / webhook / Discord / Teams / PagerDuty (Free: email only). - **[API key management](https://dashboard.argosvix.com/en/api-keys)** — rename or revoke keys. - **[Settings](https://dashboard.argosvix.com/en/settings)** — plan management and curl examples for direct API access. --- ## Support If you get stuck, email hello@argosvix.com. --- # Guides # Guides How-to guides for the main features, organized by use case. For individual API signatures, see the [REST API Reference](./rest-api) or [MCP Server](./mcp) pages. Sections: 1. [Alerts & webhooks](#1-alerts--webhooks) 2. [Prompt management](#2-prompt-management) 3. [Eval criteria & runs](#3-eval-criteria--runs) 4. [Annotations](#4-annotations) 5. [LLM budget management](#5-llm-budget-management) 6. [Autonomous AI ops](#6-autonomous-ai-ops) 7. [Framework-based instrumentation](#7-framework-based-instrumentation) --- ## 1. Alerts & webhooks ### Setup flow 1. Open [dashboard.argosvix.com/alerts](https://dashboard.argosvix.com/en/alerts). 2. Click "+ New alert". Pick a preset from the gallery (11 templates: monthly cost, error rate, anomaly detection, etc.) or build one from scratch. 3. Pick one or more notification channels (webhook / Slack / Discord / Teams / PagerDuty / Email). > ℹ️ **Free plan limits**: at most 3 alerts, the Email channel only, and only the 4 threshold types (cost, monthly budget, error rate, latency degradation) plus findings notification (guardian_findings). Anomaly detection (`anomaly_*`), quality SLO (`eval_score`), and non-Email channels require Pro+. ### Test webhook delivery Before registering a webhook URL in a production alert, probe it (Pro+): ```bash 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":"setup test"}' ``` From Claude Desktop / Cursor via MCP, the `test_webhook` tool gives the same. > ⚠️ Webhook URLs must be HTTPS. Private / loopback / cloud metadata IPs are rejected (SSRF defense). Rate-limited to 5 / min per account. ### Verify signatures on the receiver side When the alert has a `secret`, the webhook delivery carries HMAC-SHA256 signature headers: ``` X-Argosvix-Signature: sha256= X-Argosvix-Timestamp: 2026-06-03T11:22:33.456Z ``` Receiver HMAC-SHA256s `${timestamp}.${rawBody}` with the secret and compares with the header. We recommend rejecting timestamps more than 5 min off server time for replay defense. ### Silence and acknowledge - `silence_alert` / `unsilence_alert`: Mute an alert for a duration (default 24h). - `acknowledge_alert`: Mark a specific firing event as handled. Orthogonal to silence. --- ## 2. Prompt management Manage prompts by name + version (Pro+). The same name can coexist across multiple versions. ### Register a new version ```bash curl -X POST \ -H "Authorization: Bearer $ARGOSVIX_API_KEY" \ -H "Content-Type: application/json" \ https://ingest.argosvix.com/v1/prompts \ -d '{ "name": "customer_support", "version": "v1", "template": "You are a polite customer support agent. Question: {{question}}", "variables": { "question": "sample" }, "labels": ["production"], "description": "Customer support, v1" }' ``` `name` + `version` must be unique within your account; duplicates return 409. ### Partial update & rename - `PATCH /v1/prompts/:id` — Updates template / variables / labels / description. `name` and `version` are immutable. - `POST /v1/prompts/:id/rename` — Changes `name` and `version`. Useful for typo fixes. ### Logical sunset Deleting an old version detaches it from past eval runs, so those runs lose their link to the prompt (provenance is lost). **Add `sunset` to labels** instead: ```bash curl -X PATCH \ -H "Authorization: Bearer $ARGOSVIX_API_KEY" \ -H "Content-Type: application/json" \ https://ingest.argosvix.com/v1/prompts/7 \ -d '{"labels":["sunset"]}' ``` ### Operate from chat Via MCP: `create_prompt` / `update_prompt` / `rename_prompt` / `delete_prompt`. ```text You: Add "be polite" to the customer support prompt. Claude: (uses update_prompt to patch the template) Claude: Added. The production label is unchanged. ``` --- ## 3. Eval criteria & runs LLM-as-judge: have one LLM score another's responses. ### Default & custom criteria `GET /v1/eval-criteria` returns global defaults (helpfulness / safety / conciseness etc.) and your account's custom criteria. Custom criteria are Pro+. Create custom: ```bash curl -X POST \ -H "Authorization: Bearer $ARGOSVIX_API_KEY" \ -H "Content-Type: application/json" \ https://ingest.argosvix.com/v1/eval-criteria \ -d '{ "name": "domain_specificity", "rubric": "Score 1-5 based on how well the answer uses domain-specific terminology correctly.", "scaleMin": 1, "scaleMax": 5 }' ``` > ⚠️ Deleting a custom criterion also removes all scores recorded against it, breaking historical comparison. For rename, prefer PATCH (full replace). ### Run an eval ```bash curl -X POST \ -H "Authorization: Bearer $ARGOSVIX_API_KEY" \ -H "Content-Type: application/json" \ https://ingest.argosvix.com/v1/eval-runs \ -d '{ "name": "weekly review", "recentCount": 50, "label": "production", "promptRegistryId": 7, "idempotencyKey": "weekly-2026-w23" }' ``` `idempotencyKey` dedupes within 60 min (defends double-clicks and retries). ### Read results `GET /v1/eval-runs` for the list, `GET /v1/eval-runs/:id` for details. Per-call scores and reasoning are returned under `scores`; trajectory scores (see below) under `trajectoryScores`. ### Trajectory (multi-turn) evaluation Create a criterion with `"scope": "trajectory"` to score a whole multi-turn agent run as one unit instead of per call. At run time, every call sharing a `traceId` (set automatically by `withTrace`, or passed explicitly) is folded — in timestamp order, with any tool/retrieval/sub-agent observations — into a single transcript and judged once. Good for "did the agent achieve the goal?", "was tool use efficient?", "did it stay on task across turns?". Trajectory criteria are `llm_judge` only and returned under `trajectoryScores` (each with `traceId`, `score`, `turnCount`, `reasoning`). ### Operate from chat ```text You: Has our chatbot's quality dropped this past week? Claude: (uses list_eval_runs to scan the last 7 days) Claude: Out of 5 evaluations, only Tuesday's came in low (helpfulness mean 2.4). The rest stayed around 4. ``` --- ## 4. Annotations Attach labels, quality scores, and free-text comments to call records. ### Use cases - Flag issues like "badly-summarized" manually - Build a golden dataset for evaluation - Manual quality review of customer support responses ### Annotate a single call ```bash curl -X POST \ -H "Authorization: Bearer $ARGOSVIX_API_KEY" \ -H "Content-Type: application/json" \ https://ingest.argosvix.com/v1/annotations \ -d '{ "callId": "call-xyz-123", "annotationText": "Answer covers only half of the question", "label": "incomplete", "qualityScore": 2 }' ``` At least one of `annotationText` / `label` / `qualityScore` is required. ### Cross-call search by label `GET /v1/annotations?label=incomplete` lists all calls flagged with that label. The dashboard annotation view also supports filtering. ### Operate from chat ```text You: Mark any of the last 10 responses where the answer only covers half the question. Claude: (reads via query_calls, then uses create_annotation with the "incomplete" label on the matching ones) Claude: Marked 3 (two from Customer A's queries and one from Customer B). ``` --- ## 5. LLM budget management The safety classifier, PII secondary audit, and eval runs call OpenAI (and others) internally. These monthly LLM costs are capped per account and manageable from the API. ### Read current budget state ```bash curl -H "Authorization: Bearer $ARGOSVIX_API_KEY" \ https://ingest.argosvix.com/v1/account/llm-feature-budget ``` Response: ```json { "budgetUsd": 5, "spentUsd": 0.42, "remainingUsd": 4.58, "periodStart": "2026-06", "defaultBudgetUsd": 5, "minBudgetUsd": 5, "maxBudgetUsd": 500 } ``` `periodStart` is YYYY-MM; auto-resets across calendar months. ### Raise or lower budget (Pro+) ```bash 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}' ``` Range $5–$500 in $0.01 increments. A new value lower than current `spentUsd` is accepted but `remainingUsd` becomes 0 (next month resets from 0). ### Monitor budget from chat ```text You: What's left of this month's LLM budget? Claude: (uses get_llm_budget) Claude: $4.58 left of $5 ($0.42 spent, 91.6% remaining). You: Bump the cap to $30 — I want to run more checks this month. Claude: (uses raise_llm_budget to set 30) Claude: Monthly budget updated to $30. ``` --- ## 6. Autonomous AI ops Beyond observation, into action. From Claude / Cursor / Codex CLI you can close the loop in a single conversation — infra health → anomaly detection → alert design → batch classification → eval criteria proposal (only batch classification and eval criteria proposal are Pro+; the rest work on Free). > The Claude responses below are **illustrative examples**, not real production output. Actual numbers depend on your environment and traffic. ### One-question infra health check ```text You: How is my LLM infra doing right now? Claude: (calls get_account_health for the 24h window) Claude: Past 24h: 1,247 calls, $4.12 cost, 2.1% error rate, p95 latency 1,820 ms, budget 18% used. Verdict: "ok". ``` Internally fans out to 4 endpoints (aggregate / percentiles / llm-budget / audit-log) in parallel; per-endpoint failures land in `partialFailures`. ### One-prompt anomaly scan ```text You: Anything unusual going on? Claude: (calls detect_anomaly with 24h window + normal threshold) Claude: 2 anomalies: (1) cost is 2.4× baseline = major, (2) error rate at 8% is 4× baseline = critical. ``` Threshold: sensitive (1.5×) / normal (2×, default) / conservative (3×). error_rate is evaluated in percent (0-100). ### Have the AI design your alerts ```text You: Design a sensible alert set from my traffic pattern. Claude: (calls propose_alert_rules with 14-day lookback) Claude: 4 alerts proposed: - cost_daily threshold $0.50 (1.8× baseline mean $0.28) - latency_p95 threshold 3000ms (1.6× recent p95 of 1,820ms) - error_rate threshold 5% (2.4× baseline 2.1%) - anomaly_cost detection enabled Apply them? ``` Proposal-only (zero side effects). The user adopts via `create_alert` separately. ### Bulk classify unclassified calls right now ```text You: Classify all calls from last week. Claude: (calls classify_calls_batch with maxRecords=100) Claude: 87 scanned, 85 assessed, 3 flagged, 0 failed, 2 skipped. ``` Complements the automatic background scan (every 15 minutes) with an immediate "scan now" trigger. The results are recorded as on-demand runs (distinct from scheduled scans). Rate limit: 30 req / 60s fixed window (account-wide). ### Have the AI propose evaluation criteria ```text You: Propose criteria to measure my prompt's quality. Use case: customer support bot. Claude: (calls propose_eval_criteria with useCaseHint + 3 sample calls; gets 5 proposals) Claude: 5 criteria proposed: 1. tone_appropriateness — Does the tone feel polite and professional? 2. resolution_clarity — Does the response convey the resolution clearly? ... (and so on) Adopt them? ``` Proposal-only — nothing is created automatically (you apply it via `create_eval_criterion` separately). When you pass `sampleCallIds`, Argosvix decrypts those calls and sends excerpts to OpenAI gpt-5-mini for proposal generation; please be aware of this data flow. Rate limit: 30 req / 60s. > ⚠️ In this section, only batch classification (`classify_calls_batch` = scan-batch) and eval criteria proposal (`propose_eval_criteria`) are Pro+ only. `get_account_health` / `detect_anomaly` / `propose_alert_rules` work on Free. The two Pro+ endpoints enforce budget limits, write audit-log entries, and apply rate limits. See [MCP Server](./mcp) and [REST API Reference](./rest-api) for detailed specs. --- ## 7. Framework-based instrumentation `wrap()` instruments **the client instance you create**. When a framework creates the provider client internally, `wrap()` can't reach it. Here's the per-case approach: ### Using the provider SDK directly (most reliable) If you create the client yourself (`new OpenAI()` etc.), just wrap that spot ([Quickstart](./quickstart)). Centralize client creation in one module and export the wrapped client, and it's effectively one place. ### Vercel AI SDK (TypeScript) The easiest path is **`argosvixMiddleware()`**, bundled with `@argosvix/sdk`: pass it to `wrapLanguageModel` and every call through `generateText` / `streamText` / `generateObject` is recorded, with budget/policy gates honoured too (see "Framework integrations" in the [SDK Reference](./sdk-reference)). If you prefer to standardize on OpenTelemetry, that works as well. The Vercel AI SDK has built-in OpenTelemetry — pass `experimental_telemetry: { isEnabled: true }` to your generation calls and point an OTLP/HTTP **JSON** exporter at Argosvix. - exporter: `@opentelemetry/exporter-trace-otlp-http` (JSON over HTTP) - endpoint: `https://ingest.argosvix.com/v1/traces` - header: `Authorization: Bearer argk_...` (optionally `X-Project-Id: `) ```ts import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; const exporter = new OTLPTraceExporter({ url: "https://ingest.argosvix.com/v1/traces", headers: { Authorization: `Bearer ${process.env.ARGOSVIX_API_KEY}` }, }); // Register this exporter with your OTel SDK / @vercel/otel, and pass // experimental_telemetry: { isEnabled: true } to generateText/streamText. ``` > Argosvix `/v1/traces` accepts both **JSON and protobuf** OTLP/HTTP encodings. Exporters work with their default settings. ### LangChain.js (TypeScript) Pass **`argosvixLangChainHandler()`**, bundled with `@argosvix/sdk`, as a callback and every LLM call through LangChain — including chains and agents — is recorded (recording only; budget/policy gates are not evaluated. See "Framework integrations" in the [SDK Reference](./sdk-reference)). The same handler works with **LangGraph.js**. A compiled graph is a LangChain Runnable, so pass the handler in the `callbacks` array of the run config — every LLM call inside the graph's nodes is recorded, and `callbacks` propagates into nested chains, tools, and sub-agents under the same run: ```ts import { StateGraph, MessagesValue, START, END } from "@langchain/langgraph"; import { ChatOpenAI } from "@langchain/openai"; import { HumanMessage } from "@langchain/core/messages"; import { argosvixLangChainHandler } from "@argosvix/sdk"; const model = new ChatOpenAI({ model: "gpt-5.4-mini" }); const graph = new StateGraph({ messages: MessagesValue }) .addNode("model", async (state) => ({ messages: [await model.invoke(state.messages)] })) .addEdge(START, "model") .addEdge("model", END) .compile(); // One line: attach the handler to the run. const result = await graph.invoke( { messages: [new HumanMessage("Hi")] }, { callbacks: [argosvixLangChainHandler()] }, ); ``` ### OpenAI Agents SDK (TypeScript) The Agents SDK resolves a default `OpenAI` client. Wrap that client with `@argosvix/sdk` `wrap()` and register it once with `setDefaultOpenAIClient` — every agent, handoff, and tool call that goes through the model is then recorded. No per-agent wiring: ```ts import { OpenAI } from "openai"; import { setDefaultOpenAIClient, Agent, run } from "@openai/agents"; import { wrap } from "@argosvix/sdk"; // Wrap once, register once. setDefaultOpenAIClient(wrap(new OpenAI())); const agent = new Agent({ name: "Assistant", instructions: "You are helpful." }); const result = await run(agent, "Hi"); ``` `setDefaultOpenAIClient` requires `openai` 7.2 or later. ### LangChain / LlamaIndex (Python) Two options: 1. **Wrap the inner client**: if you create the provider client yourself before handing it to the framework, `wrap()` it first — most reliable. 2. **OpenTelemetry auto-instrumentation** (e.g. OpenLLMetry): point the OTLP exporter at `https://ingest.argosvix.com/v1/traces` with a Bearer header. Python's standard OTLP HTTP exporter (protobuf by default) works as is. ### Argosvix OTLP endpoint (shared spec) - `POST https://ingest.argosvix.com/v1/traces` - OTLP/HTTP accepts **both JSON and protobuf** (`Content-Type: application/json` or `application/x-protobuf`) - Auth: `Authorization: Bearer argk_...`; optional `X-Project-Id` to route records - Ingested spans are normalized into call records (tagged `source=otlp`) ### Let your AI do it Ask Claude / Cursor: > "Instrument this project's LLM calls for Argosvix. If it uses the provider SDK directly, use `@argosvix/sdk` `wrap()`. If it uses the Vercel AI SDK, enable experimental_telemetry and point an OTLP/HTTP JSON exporter at `https://ingest.argosvix.com/v1/traces`." --- ## Related docs - [Quickstart](./quickstart) — Get your first call recorded in 5 minutes - [SDK Reference](./sdk-reference) — `@argosvix/sdk` API - [REST API Reference](./rest-api) — HTTP-direct usage - [MCP Server](./mcp) — Claude Desktop / Cursor / Codex CLI integration --- ## Support hello@argosvix.com --- # SDK Reference # SDK Reference API reference for `@argosvix/sdk`. Wrap your AI client once with `wrap()` and every call is recorded automatically. --- ## Install ```bash npm install @argosvix/sdk ``` Use `openai`, `@anthropic-ai/sdk`, `@google/genai`, or `@mistralai/mistralai` as `` (you can mix multiple providers). --- ## `wrap(client, options)` Returns a wrapped instance of your AI client. For typical usage (calling `create`, consuming streams with for-await) **you can drop it in without touching the rest of your code**. OpenAI's advanced APIs mostly work as-is after wrapping: `.withResponse()`, and streaming `.tee()` / `.toReadableStream()` / `.controller` (abort) all keep working. To use them on streams, set `stream_options: { include_usage: true }` explicitly (without it, the SDK injects `include_usage: true` for usage recording and returns a compatibility wrapper, so these methods are absent). Two caveats: `.asResponse()` (reading the raw Response directly) is not supported because it cannot coexist with recording — use the unwrapped client for those specific calls. And since the full stream is read for recording, slow consumption of a received stream temporarily holds the whole response in memory. ```typescript import { wrap } from "@argosvix/sdk"; import OpenAI from "openai"; const openai = wrap(new OpenAI(), { apiKey: process.env.ARGOSVIX_API_KEY, tags: { service: "my-app", env: "prod" }, }); ``` ### `options.apiKey` (nothing is recorded without it) The `argk_...` token issued on the [API keys page](https://dashboard.argosvix.com/en/api-keys). Pass it from an environment variable. The field is optional at the type level, but when unset the SDK does not raise — it simply skips sending records (your LLM calls still pass through). If you wrapped a client and see no records, check this first. ### `options.tags` (optional) Arbitrary key/value pairs you can use as filter and aggregation axes on the dashboard. Common examples: ```typescript tags: { service: "my-app", // split multiple services in one dashboard env: "prod", // separate staging vs prod counts feature: "summarize", // compare cost / latency per feature userId: "u_xxx", // per-user usage measurement (PII caution — hash recommended) } ``` > ⚠️ Do not put end-user PII (email, name, etc.) into `tags` (see Terms § 4, paragraph 2). ### `options.sessionId` (optional) An ID that groups calls belonging to the same session (conversation). When set, it is stored on each record as `sessionId` and can be used to filter by session in the query API. In the Python SDK, pass `ArgosvixConfig(session_id="...")`. ```typescript const openai = wrap(new OpenAI(), { apiKey: process.env.ARGOSVIX_API_KEY, sessionId: "sess_2026_07_08_abc", }); ``` ### `options.captureContent` (optional, default false) When `true`, prompt and completion bodies are included in the records. Non-streaming calls are supported across every provider; streaming calls also capture bodies on all observed paths (OpenAI Chat / OpenAI Responses / Anthropic / Gemini / Mistral, in both TypeScript and Python — the five OpenAI-compatible providers ride the OpenAI Chat path), accumulating up to 256KB per call — anything beyond is dropped and a truncated marker is appended, and if a stream is interrupted the body captured so far is still recorded. > ℹ️ Server-side ingestion applies a separate 64KB limit to each of the prompt body and completion body. The SDK automatically truncates each body to fit before sending (the excess is dropped and a truncated marker is appended), so a record with an oversized body is still delivered — it is never lost. When a response contains tool calls (function calling), the tool names and arguments are also recorded as `toolCalls` (OpenAI Chat Completions / Responses calls only; PII inside arguments is masked under the same rules as bodies). ```typescript const openai = wrap(new OpenAI(), { apiKey: process.env.ARGOSVIX_API_KEY, captureContent: true, }); ``` In the Python SDK, pass `capture_content=True`. ```python from argosvix import wrap, ArgosvixConfig client = wrap(OpenAI(), ArgosvixConfig( api_key=os.environ["ARGOSVIX_API_KEY"], capture_content=True, )) ``` How it works and how it stays safe: - **PII masking always runs before sending** (emails, card numbers, phone numbers, national IDs, and IP addresses are replaced with `[REDACTED_*]`). - On the server, bodies are stored (AES-256-GCM encrypted) **only when the account is on Pro or above and has explicitly opted in to plaintext storage** in the dashboard. Without that consent, submitted bodies are discarded, not stored. - Viewing, deleting, and access logs for stored bodies are managed under Privacy & Data in the dashboard settings. See [Terms Article 4-2](https://argosvix.com/en/legal/terms) for details. ### Deployed prompts and version tagging (`resolvePrompt` / `withPrompt`) Fetch the currently deployed version of a managed prompt from the SDK, and automatically tag calls made with it as `prompt` (`{name}@v{version}`) — the foundation for per-version quality and cost comparison. ```typescript import { resolvePrompt, withPrompt } from "@argosvix/sdk"; const p = await resolvePrompt("support-bot", { apiKey: process.env.ARGOSVIX_API_KEY!, // label: "production" (default) }); await withPrompt(p, async () => { // Calls inside get tags.prompt = "support-bot@v3" automatically await openai.chat.completions.create({ model: "gpt-5.5", messages: [{ role: "system", content: p.template }], }); }); ``` - `resolvePrompt` keeps a 60-second TTL cache and falls back to a stale entry on network or server failures, so your hot path never blocks on prompt resolution (a 404 for a missing deployment still throws). - An explicit `tags.prompt` on a call takes precedence over the ambient tag. - The Python SDK mirrors this with `resolve_prompt(name, api_key=...)` and the `with_prompt(p):` context manager. --- ## `flushClient(client)` In short-lived runtimes (Cloudflare Workers, AWS Lambda, Vercel Edge, and similar) you **must** `await flushClient()` inside the handler's `finally` block. Without it, records will not reach the dashboard because the runtime terminates before the SDK finishes sending. ```typescript import { wrap, flushClient } from "@argosvix/sdk"; export default { async fetch(req, env) { // In Workers, read configuration from the fetch handler's env argument, not process.env const client = wrap(new OpenAI({ apiKey: env.OPENAI_API_KEY }), { apiKey: env.ARGOSVIX_API_KEY, }); try { return await handler(client, req, env); } finally { await flushClient(client); } }, }; ``` Long-lived processes (Node.js server, Bun, Deno deploy) don't need it. The TypeScript SDK sends automatically once its buffer reaches 100 records, and also runs an idle auto-flush roughly every 5 seconds while records sit in the buffer (`flushIntervalMs`, default 5000; call `flushClient()` any time to send earlier); the Python SDK flushes in the background roughly every 5 seconds. `wrap()` is best-effort by design: a failed send to Argosvix never breaks your application's LLM call. --- ## Framework integrations If you call models through a framework instead of a provider client directly, use the matching integration. All of them record the same core fields as `wrap()` (provider, model, tokens, cost, latency, cache tokens; the two TypeScript integrations additionally record TTFT and reasoning tokens), auto-detect the provider, and honour `withTrace` grouping. Auto-detection works when you use each vendor's **official provider package** (`@ai-sdk/xai`, `@langchain/deepseek`, and so on). If you point an OpenAI-compatible client at another `baseURL`, the framework still calls itself "openai", so Argosvix records the call as "openai" (the cost is computed from the same pricing table). To record the real provider, pass `provider` in the config or wrap the provider client directly — that path detects the provider from the destination URL. Calls made through the Vercel AI Gateway are not detected and are not recorded. Pre-call budget/policy gate checks are enforced **only by the Vercel AI SDK middleware** — the LangChain.js and LiteLLM callbacks record only and do not evaluate gates (wrap the provider client directly if you need gates there). Recording is best-effort — it never breaks your model call. ### Vercel AI SDK (`ai` package) `argosvixMiddleware()` returns a `wrapLanguageModel`-compatible middleware (LanguageModelV2 / V4). Every call through `generateText` / `streamText` / `generateObject` — including calls the AI SDK issues internally for tool loops and multi-step agents — is recorded. ```typescript import { openai } from "@ai-sdk/openai"; import { wrapLanguageModel, generateText } from "ai"; import { argosvixMiddleware, flushClient } from "@argosvix/sdk"; const observed = argosvixMiddleware({ apiKey: process.env.ARGOSVIX_API_KEY }); const model = wrapLanguageModel({ model: openai("gpt-5.5"), middleware: observed }); try { await generateText({ model, prompt: "hi" }); } finally { await flushClient(observed); // or: await observed.flush() } ``` Other providers run normally but are not recorded — pass `provider` in the config to force a mapping. ### LangChain.js `argosvixLangChainHandler()` returns a callback handler. Pass it in `callbacks` (per call or at model construction) and every LLM call through LangChain — including chains and agents — is recorded. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { argosvixLangChainHandler, flushClient } from "@argosvix/sdk"; const handler = argosvixLangChainHandler({ apiKey: process.env.ARGOSVIX_API_KEY }); const model = new ChatOpenAI({ model: "gpt-5.5" }); try { await model.invoke("hi", { callbacks: [handler] }); } finally { await flushClient(handler); // or: await handler.flush() } ``` ### LiteLLM (Python) `ArgosvixLogger` is a LiteLLM `CustomLogger`. Register it once and every LiteLLM completion — sync, async, streaming, and failures — is recorded. It works with both the LiteLLM Python SDK and the LiteLLM proxy. ```python import litellm from argosvix.litellm_callback import ArgosvixLogger logger = ArgosvixLogger() # reads ARGOSVIX_API_KEY from the environment litellm.callbacks = [logger] litellm.completion(model="gpt-4o", messages=[{"role": "user", "content": "hi"}]) logger.flush() # short-lived processes: send buffered records now ``` For the LiteLLM proxy, put the logger instance in a `custom_callbacks.py` next to your `config.yaml` and reference it as `litellm_settings.callbacks: custom_callbacks.argosvix_logger`. Cost uses LiteLLM's own `response_cost` when available (it prices 100+ providers), falling back to the Argosvix pricing table. Providers are mapped to the ingest API vocabulary (xAI Grok records as "xai" and Moonshot Kimi as "moonshot" since 0.5.5, DeepSeek as "deepseek" since 0.6.0, Alibaba Qwen (LiteLLM identifier "dashscope") as "alibaba" since 0.5.11, and Meta Muse Spark (LiteLLM identifier "meta") as "meta" since 0.5.14 — their real providers; Azure OpenAI still arrives as "openai"); calls to other providers run normally but are not recorded — a one-time warning per provider is printed instead of dropping them silently. Install the optional dependency with `pip install 'argosvix[litellm]'`. --- ## Covered methods per provider | Provider | Covered methods | |---|---| | OpenAI | `chat.completions.create`, `responses.create` (both with streaming) | | Anthropic | `messages.create` (with streaming) | | Gemini | `models.generateContent`, `models.generateContentStream` | | Mistral | `chat.complete`, `chat.stream` | | xAI Grok | Via the OpenAI-compatible API: `wrap()` an OpenAI client configured with xAI's `baseURL` (streaming supported). Since 0.5.5 the destination URL is detected and calls record with provider "xai" | | Moonshot Kimi | Via the OpenAI-compatible API, same pattern as xAI Grok — records with provider "moonshot". For `kimi-k3`, cache-hit input is priced separately | | DeepSeek | Via the OpenAI-compatible API, same pattern as xAI Grok — detected by `api.deepseek.com` and recorded with provider "deepseek". `deepseek-v4-flash` / `deepseek-v4-pro` (and the legacy names `deepseek-chat` / `deepseek-reasoner`, deprecated 2026-07-24) price cache-hit input separately | | Alibaba Qwen | Via the OpenAI-compatible API, same pattern as xAI Grok — detected by `dashscope.aliyuncs.com` / `dashscope-intl.aliyuncs.com` and recorded with provider "alibaba" (since 0.5.11). `qwen3.8-max` / `qwen3.8-flash` price cache-hit (implicit cache) input separately | | Meta Muse Spark | Via the OpenAI-compatible API, same pattern as xAI Grok — detected by `api.meta.ai` and recorded with provider "meta" (since 0.5.14). `muse-spark-1.3` / `1.2` / `1.1` and the `-contributor` variants price cache-hit input separately | ### Long-context tiered pricing Some models charge a higher rate once the prompt reaches a size threshold. The higher rate applies to **every token in that request**, not just the tokens above the threshold. The SDK follows this rule. | Provider | Tier applies when | Models | |---|---|---| | xAI | prompt **reaches** 200,000 (200,000 included) | Grok models | | OpenAI | prompt is **above** 272,000 (from 272,001) | GPT-5.5 / 5.6 family | | Google | prompt is **above** 200,000 (from 200,001) | Gemini Pro family | The boundaries differ by one token between providers because the official wording differs ("reaches" vs "greater than"). Cached-input rates also switch to the tier's cached rate. If a provider reports more cached tokens than the prompt contains, the SDK clamps the cached count to the prompt size so the cost cannot come out inflated. Example with xAI Grok (Moonshot Kimi works the same way — just swap the `baseURL` and API key): ```typescript import OpenAI from "openai"; import { wrap } from "@argosvix/sdk"; const client = wrap( new OpenAI({ apiKey: process.env.XAI_API_KEY, baseURL: "https://api.x.ai/v1" }), { apiKey: process.env.ARGOSVIX_API_KEY }, ); // From here, call grok-4.5 etc. exactly like the OpenAI SDK — calls are recorded automatically ``` > A note on interrupting streams early in the Python SDK: breaking out of a synchronous iteration records immediately, but breaking out of `async for` does not guarantee immediate cleanup due to Python language semantics. To record reliably, consume the stream with `async with`, or call `await stream.aclose()` after breaking. Any other method passes through (not recorded, but errors are not swallowed either). If you want a method outside this list to be recorded, email hello@argosvix.com. --- ## Types ```typescript import type { ArgosvixConfig } from "@argosvix/sdk"; interface ArgosvixConfig { /** argk_... token issued on the dashboard (record sending is a no-op when unset) */ apiKey?: string; /** Arbitrary key/value pairs (filter / aggregation axes) */ tags?: Record; /** * Full POST target URL including the path (default: https://ingest.argosvix.com/v1/ingest). * The SDK POSTs to this URL as-is and does not append a path (e.g. proxy or testing; usually not needed) */ endpoint?: string; } ``` --- ## Troubleshooting - **No records appear on the dashboard** — check that you didn't forget `flushClient` (in short-lived runtimes), verify network reachability, and double-check the `apiKey` for typos. - **Records take a while to appear** — sends are batched (TypeScript: at 100 records, on an ~5-second idle auto-flush while records are buffered (`flushIntervalMs`, default 5000), or on `flushClient()`; Python: roughly every 5 seconds). For low-traffic processes, call `flushClient()` / `flush()` to see records immediately. - **Cost shows as zero** — the pricing table for that provider or model may not be registered yet. Email hello@argosvix.com and we can add it. --- ## Support hello@argosvix.com --- # REST API # REST API Reference Bypass the dashboard and operate Argosvix data directly over HTTP. Usable from any framework or curl. This page covers the main endpoints with curl examples. The machine-readable list of every endpoint lives in the [OpenAPI spec (openapi.yaml)](https://argosvix.com/openapi.yaml). --- ## 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 as `Authorization: Bearer argk_...`. Standard for server-to-server. - **Session cookie** — When called from a logged-in `dashboard.argosvix.com` browser 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-After` header) 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`), `error`, `errorDetails`, `traceId` / `spanId` / `parentSpanId` (for trace linking), `sessionId`. ```bash 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: ```json {"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 | — | ```bash 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`) | ```bash 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`): ```json { "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`). ```bash 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`). ```bash 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: ```json { "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. ```bash 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: ```json {"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.). --- ### `GET /v1/query/session/:id` — Session Returns the calls sharing the same `sessionId`, in chronological order. Useful for reconstructing a conversation thread. ### `POST /v1/query/distribution` — Distribution (histogram) Returns histogram buckets for latency or cost — the same data behind the dashboard distribution charts. --- ## Account & budget ### `GET /v1/account` — Identity snapshot Returns plan, this month's usage, retention — non-sensitive identity. No subscription details. Bearer-only. ```bash 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+. ```json { "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. ```bash 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 ```json { "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 `email` channel only, and only the 4 threshold types (`cost_threshold` / `monthly_budget` / `error_rate` / `latency_degradation`) plus `guardian_findings` (`anomaly_*` and `eval_score` are Pro+). Passing `webhook` in `channelKinds` as 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. ```bash 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: ```json { "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`. ```json { "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+) ```json { "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. ```bash 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`. --- ### Deployments (assigning versions to environment labels) Assign a version to an environment label (any string — `production`, `staging`, ...) and have your app resolve "whatever is currently deployed to that label" by name. The SDK's `resolvePrompt()` / `withPrompt()` use this resolve API. ### `POST /v1/prompts/:id/deploy` — Deploy (Pro+) Assigns the version to a label. ```bash curl -X POST \ -H "Authorization: Bearer $ARGOSVIX_API_KEY" \ -H "Content-Type: application/json" \ https://ingest.argosvix.com/v1/prompts/7/deploy \ -d '{"label":"production"}' ``` ### `GET /v1/prompts/resolve` — Resolve the deployed version Given `name` and `label` (both required), returns the currently deployed version. A label with no deployment returns 404. ### `GET /v1/prompts/deployments` — List deployments Filter with the `name` / `label` query parameters. ### `POST /v1/prompts/deployments/rollback` — Roll back (Pro+) `{"name":"...","label":"..."}` moves the label back to the previous version. --- ## Eval criteria ### `GET /v1/eval-criteria` — List Global defaults + your account's custom criteria. ### `POST /v1/eval-criteria` — Create (Pro+) ```json { "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`. ```json { "useCaseHint": "Customer support bot for e-commerce (returns + refund policy)", "sampleCallIds": ["call_abc123", "call_xyz789"], "maxCriteria": 5 } ``` - `useCaseHint`: required, 1-500 chars - `sampleCallIds`: optional, max 5, account-scoped - `maxCriteria`: optional, 1-10 (default 5) - Rate limit: 30 requests per 60-second fixed window (429 + Retry-After header; enforced account-wide) Returns: ```json { "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. --- ### `GET /v1/eval-criteria/presets` — Built-in presets Returns LLM-judge criterion templates by category — RAG (faithfulness / answer_relevance), quality (conciseness, ...), and safety. Pass the returned rubric and scale straight into `POST /v1/eval-criteria` to create them. --- ## 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+) ```json { "name": "weekly review", "recentCount": 50, "label": "production", "promptRegistryId": 7, "idempotencyKey": "uuid-1234-..." } ``` With `idempotencyKey`, retries within 60 min return the existing run (dedupe). --- ### `GET /v1/eval-runs/compare` — Compare two runs Given `baseline` and `candidate` (both run IDs, required), returns per-criterion deltas and an overall verdict. ### `GET /v1/eval-runs/gate` — CI regression gate Compares a candidate run against a baseline and returns `pass: false` with `violations` when a regression exceeds your thresholds. Built for pass/fail decisions in CI. - `candidate` (required). Pick the baseline via `baseline` (run ID) or `baselineName` (auto-selects the latest completed run with that name before the candidate). For the first run with no baseline, pass `allowMissingBaseline=true` - Thresholds: `maxMeanDrop` / `maxNewFailures` / `maxCriterionDrop` - With `requireSignificance=true`, per-criterion drops only count as violations when statistically significant (p < 0.05) ### `GET /v1/eval-runs/judge-agreement` — Judge vs human agreement For calls that have both a human score (annotation quality_score) and a judge score, returns agreement stats such as the within-±1 rate and mean absolute difference. ### `GET /v1/eval-runs/judge-trust` — Judge trust components Returns the last 30 days of the weekly automatic probes (tests that check whether the judge can spot mechanically degraded responses) plus the human-agreement component. This is the API behind "the judge itself is verified weekly" on the feature page. --- ## Eval datasets A container for running evals repeatedly against a fixed input set (up to 20 items per set). ### `GET /v1/eval-datasets` — List ### `POST /v1/eval-datasets` — Create (Pro+) Along with `name` (required), you can pass `description` / `promptRegistryId` / `frozen` / `items[]` (each item requires `inputText`). ### `GET /v1/eval-datasets/:id` — Get (with items) ### `PATCH /v1/eval-datasets/:id` — Update (Pro+) ### `DELETE /v1/eval-datasets/:id` — Delete (Pro+) ### `POST /v1/eval-datasets/:id/run` — Run an eval on the dataset (Pro+) Rate-limited to 10 requests per 5 minutes. Pass `idempotencyKey` (max 64 chars) to dedupe re-runs within 60 minutes — the same key returns the existing run. Response: `{ runId, deduplicated }`. --- ## 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 ```json { "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"`) ```json { "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: ```json { "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. --- ## Saved views Saved filter presets for the records screen in the dashboard. Up to 20 per account. ### `GET /v1/saved-views` — List ### `POST /v1/saved-views` — Create / update Pass `{"name":"...","filter":{...}}` (`name` up to 50 chars). ### `DELETE /v1/saved-views/:id` — Delete --- ## Error responses ```json { "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](./sdk-reference) — Just `wrap()` the client and calls are auto-recorded. - [MCP Server](./mcp) — Operate from chat in Claude Desktop / Cursor / Codex CLI. --- ## Support hello@argosvix.com --- # MCP Server # MCP Server ## What it does Through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), MCP-capable clients (Claude Desktop, Cursor, Codex CLI, anything else that speaks MCP) can observe, query, manage, and operate Argosvix data directly. The server exposes **89 tools + 3 resources + 8 resource templates + 3 prompts = 103 entities in total** (86 of the tools are generally available; the remaining 3 are reserved for service operations), over both stdio and HTTP transports. ### Sample usage ```text # In Claude Desktop chat You: Which model had the highest cost this month? Claude: (invokes get_cost_summary on the argosvix MCP server) Claude: claude-opus-4-1-20250805 was invoked 23 times at $0.452, which is 95% of the total Anthropic spend. ``` ```text # In Cursor's AI agent You: gpt-5.5 has been feeling slow lately — what's going on? Agent: (looks via query_calls + argosvix://traces/{id}) Agent: 3 of the last 10 calls hit 429 rate-limits, clustered around peak hours. The other 7 ran at normal latency. ``` ```text # In Claude Desktop, mutating ops You: Add "be polite" to the customer support prompt. Claude: (uses update_prompt to patch the template) Claude: Added. The production label is unchanged. ``` --- ## Install ```bash # stdio mode (subprocess driven by Claude Desktop, Cursor, etc.) npm install -g @argosvix/mcp-server ``` ### Claude Code config (plugin) The fastest path on Claude Code is the plugin: it bundles this MCP server plus skills that let the agent handle setup and per-user tagging. ``` /plugin marketplace add argosvix/claude-plugin /plugin install argosvix@argosvix /reload-plugins ``` ### Claude Desktop config Add to `claude_desktop_config.json`: ```json { "mcpServers": { "argosvix": { "command": "argosvix-mcp", "env": { "ARGOSVIX_API_KEY": "argk_..." } } } } ``` Issue an API key at the [dashboard API keys page](https://dashboard.argosvix.com/en/api-keys). Restart Claude Desktop and tools like `query_calls` will appear. > **About key permissions**: keys issued by `npx @argosvix/cli init` are least-privilege (read + record ingest only) and cannot call write tools like deletes or prompt changes. Keys issued from the dashboard currently have full access. Unless you specifically want your agent to perform write operations, we recommend handing agents the least-privilege CLI-issued key. ### Cursor config If you use Cursor, you can add the server in one click (then replace `YOUR_API_KEY` with your own `argk_...` key): [**+ Add Argosvix MCP to Cursor**](cursor://anysphere.cursor-deeplink/mcp/install?name=argosvix&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBhcmdvc3ZpeC9tY3Atc2VydmVyIl0sImVudiI6eyJBUkdPU1ZJWF9BUElfS0VZIjoiWU9VUl9BUElfS0VZIn19) Manual setup: Add to `~/.cursor/mcp.json`: ```json { "mcpServers": { "argosvix": { "command": "argosvix-mcp", "env": { "ARGOSVIX_API_KEY": "argk_..." } } } } ``` Restart Cursor and Argosvix tools become available from the AI agent settings. ### VS Code (GitHub Copilot) config One-click install (starts keyless with the two public data tools; set `ARGOSVIX_API_KEY` afterwards to use the tools for your own account): [**+ Add Argosvix MCP to VS Code**](https://insiders.vscode.dev/redirect/mcp/install?name=argosvix&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40argosvix%2Fmcp-server%22%5D%7D) ### Codex CLI config Add to `~/.codex/config.toml`: ```toml [mcp_servers.argosvix] command = "argosvix-mcp" env = { ARGOSVIX_API_KEY = "argk_..." } ``` ### Common failure patterns | Symptom | Cause / fix | |---|---| | `argosvix-mcp: command not found` | Re-run `npm install -g @argosvix/mcp-server`. Node.js 20 or newer is required. | | `argosvix` does not appear in the tool list | Fully restart the client (Claude Desktop / Cursor). Check the config file for JSON syntax errors or key typos. | | Tool calls return `ARGOSVIX_API_KEY is not set. Get an API key at ...` | Without the API key the server still starts and lists tools (introspection-only mode), and the two public data tools (`get_synth_daily` / `get_daily_readthrough`) remain callable — every other tool call returns this guidance error. Issue a key on the dashboard API keys page, add the `argk_...` value to the `env` of your MCP config, and restart the client. | | 401 auth error | The key may be invalid, expired, or revoked. Issue a new one and update the config. | | 404 endpoint not found | If you override `ARGOSVIX_API_BASE`, verify its value. Default is `https://ingest.argosvix.com`. | | HTTP transport rejects subscribe (`-32601 Method not found`) | HTTP transport does not support subscribe. Use stdio transport when you need real-time updates. | ### Trimming the tool list (core profile) If you don't need all 89 tools all the time, set `ARGOSVIX_MCP_PROFILE=core` to expose only the 11 day-to-day essentials (querying and aggregating records, cost, latency, health check, anomaly detection, basic alert operations, and resolving deployed prompts). This reduces client context usage and tool-selection confusion. ```json { "mcpServers": { "argosvix": { "command": "npx", "args": ["-y", "@argosvix/mcp-server"], "env": { "ARGOSVIX_API_KEY": "argk_...", "ARGOSVIX_MCP_PROFILE": "core" } } } } ``` The default is `full` (all tools). Calling a tool outside the profile returns an error with instructions to switch to full. ### HTTP transport mode ```bash # Remote MCP server / multi-tenant. Per-request Bearer auth. argosvix-mcp --http # Alternate port / bind: MCP_HTTP_PORT=4000 MCP_HTTP_HOST=0.0.0.0 \ MCP_HTTP_ALLOWED_HOSTS=mcp.example.com \ argosvix-mcp --http ``` Send JSON-RPC bodies to `POST /mcp` with the API key in the Authorization header. localhost binds have DNS rebinding protection by default. Non-localhost binds require an explicit `MCP_HTTP_ALLOWED_HOSTS`; otherwise the server fails closed. See [README Security notes](https://www.npmjs.com/package/@argosvix/mcp-server) for details. --- ## Tools (89 total; 3 of them operator-only) ### Public data (no API key required) The only two tools that work without `ARGOSVIX_API_KEY` — they read Argosvix's own public daily datasets, produced by the Argosvix agent pipeline and observed end to end. | Tool | Purpose | |---|---| | `get_synth_daily` | Fetch a Synth Daily issue (daily AI digest: new launches, model / provider changes, first-party numbers). Omit `date` for the latest issue, or pass `YYYY-MM-DD`. Bilingual fields (`desc`/`why` Japanese, `descEn`/`whyEn` English). | | `get_daily_readthrough` | Fetch a Wisp readthrough issue (the agent reads one trending OSS repo per day via the GitHub API). A reading guide, not a review: files worth reading, design highlights, and facts to check before adopting — plus the run's own generation record. | ### Autonomous AI ops (diagnostics & proposals) | Tool | Purpose | |---|---| | `classify_calls_batch` | Runs an on-demand safety classification pass over your account's unclassified calls (OpenAI Moderation via POST /v1/safety-assessments/scan-batch). Complements the automatic background scan (every 15 minutes) with an immediate "scan now" trigger. Pro+ plan required (Free relies on the automatic scan); backend enforces plan + budget gates. `maxRecords` is 1-100 (default 50). Returns `{ scanned, assessed, flagged, failures, skipped }`. The results are marked as MCP-triggered to distinguish them from scheduled scans. Audit: emits `safety.scan_batch_run` to the audit log. **Rate limit: 30 requests per 60-second fixed window** (429 + Retry-After header; enforced account-wide). Example prompt: "classify all calls from last week" in one prompt. | | `get_account_health` | Returns a one-shot health summary of your LLM infra, folding aggregates, percentiles, LLM budget, and recent audit events into a single response: per-window (1h / 24h / 7d) call count + cost + error rate (percent 0-100) + p50/p95/p99 latency (ms) + budget % used (0-100) + recent event count + an `ok / warn / critical` verdict. Thresholds: critical = errorRate ≥ 10% / budget ≥ 90% / p95 ≥ 10s; warn = ≥ 3% / ≥ 70% / ≥ 3s. Resilient to per-endpoint failures (returns partial results with a `partialFailures` array). Example prompt: "how is my LLM infra doing right now?" in one prompt. | | `propose_alert_rules` | Analyzes the past `lookbackDays` (7-30, default 14) of call patterns and proposes alert rules in JSON for `cost_daily` / `latency_p95` / `error_rate` / `anomaly_cost`. Proposal-only (zero side effects); the customer reviews and applies via `create_alert` separately. Alert types already configured land in a `skipped` array with reason. Baseline = past daily-mean cost (USD), p95 latency (ms), observed error rate (percent 0-100), daily call count. The proposed `thresholdValue` for `error_rate` is also in percent (matching the backend `create_alert` contract). Example prompt: "design a sensible alert set from my traffic pattern" — one prompt to get baseline + 4 proposals. | | `detect_anomaly` | Compares the current window against the baseline window (same length, one period earlier) and flags anomalies across 4 axes: cost / latency / error_rate / call_volume. Sensitivity tunable via `threshold`: sensitive (= 1.5×) / normal (= 2×, default) / conservative (= 3×). Each anomaly carries a severity (minor / major / critical) and an explanation. error_rate is evaluated and displayed in percent (0-100) to match backend units; the +5 pp buffer applies in percent too. When baseline records are under 10, returns `anomalies: []` with a warning. Example prompt: "anything unusual going on?" — a single prompt scans all 4 axes. | | `propose_eval_criteria` | Asks an LLM judge (gpt-5-mini) to propose evaluation criteria tailored to your use case (POST /v1/eval-criteria/propose). Inputs: `useCaseHint` (1-500 chars, required, e.g. "customer support bot for an e-commerce site") and optional `sampleCallIds` (up to 5, account-scoped representative calls). Returns up to `maxCriteria` (1-10, default 5) criteria, each with `name` (snake_case ≤ 32 chars) + `rubric` (1-200 chars) + `scaleMin` (= 1) + `scaleMax` (5 or 10) + `reasoning` (1-200 chars). Pro+ only; backend enforces plan + budget + decrypt + LLM call. Proposal-only — nothing is created automatically (you apply it via `create_eval_criterion` separately, so LLM hallucination impact is structurally bounded). Sample decrypt failures are reported in `partialFailures` (the LLM call still runs without samples). Audit: emits `eval.propose_criteria`. **Rate limit: 30 requests per 60-second fixed window** (429 + Retry-After header; enforced account-wide). Example prompt: "propose criteria to measure my prompt's quality" — one prompt to get 5 proposals. | ### Autonomous AI ops (billing operations — operator-only) | Tool | Purpose | |---|---| | `extend_customer_trial` | Extends the Stripe subscription trial on your own account by 1-30 days (POST /v1/tier2/trial/extend). Cumulative cap of 60 days (aggregated from the past 30 days of audit log); returns 409 unless the subscription status is `trialing`. `dryRun` must be passed explicitly, and `dryRun=false` additionally requires an `idempotencyKey` (16-128 chars) — re-calling with the same key returns the cached result. **Operator-only** (other accounts receive 403). | | `apply_promo_code_to_customer` | Applies a promotion code already registered in Stripe (e.g. "LAUNCH50") to your own account's subscription (POST /v1/tier2/promo/apply). Returns 409 if an active discount already exists, or if the subscription status is `canceled` / `incomplete_expired`. `dryRun=true` previews the resolved discount without touching Stripe; `dryRun=false` applies it (idempotencyKey required, same-key re-calls return the cached result). **Operator-only** (other accounts receive 403). | ### Autonomous AI ops (alert auto-remediation) | Tool | Purpose | |---|---| | `auto_silence_noisy_alert` | Batch-silences alerts that have been firing too often. Pass `alertId` (single mute) or `byVolumeThreshold` (silence every alert that fired ≥ N times in the past hour); `silenceDurationMinutes` defaults to 60 (range 5-1440). `dryRun=true` (default) previews matches with their fire counts; `dryRun=false` updates the silence expiry and emits `tier2.auto_silence_noisy_alert` per silenced alert with deterministic idempotency. Reversible via the existing `unsilence_alert` tool. The operation stays within your own account and is reversible, so it is available to any Pro+ user. Example prompt: "alert X has been firing 50 times an hour, silence it for the next hour" in one prompt. | ### Autonomous AI ops (data cleanup & recovery) | Tool | Purpose | |---|---| | `purge_expired_plaintext` | Bulk-deletes retention-expired stored plaintext (prompt and completion content and related fields) from your call records. `olderThanDays` (1-365, default 30) sets the cutoff. `dryRun=true` (default) previews affected rows; `dryRun=false` executes. Emits `tier2.purge_expired_plaintext` to the audit log with deterministic idempotency. **Pro+ plans only** (Free gets 403). **A real purge (dryRun=false) requires an `approvalId`**: create one with `request_approval` (action: `purge_expired_plaintext`) and have a human approve it first — deleting stored plaintext is irreversible. Example prompt: "purge any plaintext older than 30 days" — dry-run, request approval, then execute. | | `retry_failed_webhook` | Re-queues failed Stripe webhook events. Either pass `eventIds` (up to 100) for specific events, or `fromTimestamp` / `toTimestamp` (7-day window cap) to range-select. `dryRun=true` (default) previews matches; `dryRun=false` emits a per-event audit row marked for manual redispatch. Emits `tier2.retry_failed_webhook` per event to the audit log with deterministic idempotency. **Operator-only** (billing-webhook recovery is a cross-account internal operation, so it will not be opened up). Example prompt: "retry every Stripe webhook failure from last week" in one prompt. | ### Observability (LLM calls) | Tool | Purpose | |---|---| | `query_calls` | Fetch recent call records. Filter by provider / model / time / tag (tagKey + tagValue pair, exact match; passing only one of the two returns 400) / latency range (latencyMin / latencyMax, ms). tagKey allows alphanumerics plus `_` `-` only, 1-64 chars, and must not start or end with `-`; tagValue is 1-256 chars. Paginate with the beforeTimestamp + beforeId cursor (timestamp-descending only). | | `get_cost_summary` | Aggregate cost / call count / tokens by provider or model over a window. | | `aggregate_calls` | Aggregation cube grouped by one of 7 axes — `provider` / `model` / `day` / `hour` / `minute` / `tag` / `error` — with one of 12 metrics: `cost` / `latency` / `tokens` / `count` / `error_rate` / `input_tokens` / `output_tokens` / `cached_tokens` / `cache_savings` / `reasoning_tokens` / `audio_tokens` / `ttft`. Hour mode supports up to 168h, minute mode up to 60min. With `groupBy=day`, pass `tzOffsetMinutes` (minutes, Tokyo = +540) to bucket by local days instead of UTC days. | | `get_percentiles` | Returns p50 / p95 / p99 / p99.9 / max for call latency or cost. Add `groupBy` to get a daily / hourly / minute series instead of a single window; `groupBy=day` also accepts `tzOffsetMinutes` for local-day bucketing. | | `export_calls` | Large batch export with per-plan caps (Free 1,000 / Pro 50,000). Available on all plans. Returns the same JSON shape as `query_calls`, ready for CSV or stats. | | `bulk_delete_calls` | Delete up to 100 call records by ID in a single transaction. Pass `dryRun: true` to preview the match count first. The operation is recorded as `calls.bulk_deleted` in the audit log. Related traces, annotations, and eval scores are deleted along with the records. | ### Saved views | Tool | Purpose | |---|---| | `list_saved_views` | List the account's saved /calls filters (startDate / endDate, provider, model, limit, preset). Max 20 per account. | | `create_saved_view` | Create or overwrite (same-name) a saved filter. Lets an agent persist favorite filter combinations such as "last week, GPT-4 only" under a name. | | `delete_saved_view` | Delete a saved view. | ### Audit log | Tool | Purpose | |---|---| | `list_audit_log` | Read the account's audit log. Filter by `eventType` / `targetKind` / `actorUserId` / time range and paginate via `cursor`. Required for Team plan operational compliance (who did what, when). | ### Projects / workspaces | Tool | Purpose | |---|---| | `list_projects` | List the account's projects (id / name / slug / archived_at). | | `create_project` | Create a new project. Slug must match `^[a-z][a-z0-9-]{0,31}$`. | | `rename_project` | Update an existing project's name / slug. Either field, or both, may be set. | | `delete_project` | Soft-delete a project (sets `archived_at`). The default project cannot be deleted (returns 400). | ### Team members (Team plan) | Tool | Purpose | |---|---| | `list_members` | Lists the Team account's members — email / role (admin / member / viewer) / status / joined-at (GET /v1/memberships, removed members excluded). Read-only: invitations, role changes, and removals are permission-sensitive operations and are intentionally not exposed over MCP (use the dashboard). | ### Alerts | Tool | Purpose | |---|---| | `list_alerts` | List configured alerts and their most recent firing status. | | `get_alert` | Single alert with rule + recent firing history. | | `list_alert_events` | Account-wide firing history, newest first (filter to one alert with `alertId`). Each event carries a snapshot of the conditions at trigger time (`thresholdValue` / `windowMinutes` / `alertType`), so editing a rule later does not lose the historical context. For the next page, pass the last event's `triggeredAt` and `id` as `beforeTriggeredAt` + `beforeId` (keyset cursor; always both together). | | `create_alert` | Create a new alert (9 types: cost, error rate, latency, anomaly detection, quality SLO (`eval_score`), and findings notification (`guardian_findings`)). | | `update_alert` | Partially update threshold, window, channels, etc. `alertType` is immutable. | | `delete_alert` | Delete an alert. Its fire history is deleted with it. | | `silence_alert` | Mute an alert temporarily (default 24h). | | `unsilence_alert` | Unmute. | | `acknowledge_alert` | Mark a specific firing event as handled (orthogonal to silence, idempotent). | | `test_webhook` | Send a test payload to a webhook URL (Pro+). HTTPS-only with SSRF defense and a 5 / min rate limit. | ### Prompt management | Tool | Purpose | |---|---| | `list_prompts` | List registered prompts. Filter by `label`. | | `get_prompt` | Single prompt with template, variables, labels, description. | | `create_prompt` | Create a new prompt version (Pro+). `name + version + template` required. `name` + `version` must be unique within your account (duplicates return 409). | | `update_prompt` | Partially update template / variables / labels / description (Pro+). `name` and `version` are immutable. | | `rename_prompt` | Change `name` and `version` (Pro+). Useful for typo fixes. | | `delete_prompt` | Delete a prompt (Pro+). ⚠ This detaches the prompt from past eval runs, so those runs lose their link to the prompt (provenance is lost). For logical sunset, prefer `update_prompt` to set labels to `sunset`. | | `deploy_prompt` | Deploy a specific prompt version to a label (an environment such as `production` / `staging`) (Pro+, POST /v1/prompts/:id/deploy). If a deployment already exists, the previous version is kept aside so `rollback_prompt` can restore it in one click. Re-deploying the same version does not create a previous entry. Labels are scoped per prompt name (each name has its own production version). | | `rollback_prompt` | Roll a label's deployed prompt back to the previous version (Pro+, POST /v1/prompts/deployments/rollback). Returns 409 if there is no previous version (first deploy only) or if the previous version has already been deleted. Calling it again swaps current and previous back, so you can toggle between the two versions. | | `get_deployed_prompt` | Resolve the prompt version currently deployed to an environment (`name` + `label`) (GET /v1/prompts/resolve, available on Free). The main path for an agent to fetch the "production prompt" at runtime. Returns that version's template, variables, labels, and version; 404 if nothing is deployed. | | `list_prompt_deployments` | List the current deployment state (GET /v1/prompts/deployments, available on Free). Each row contains `promptName` / `label` / `currentVersion` / `canRollback` / `deployedAt`. Filter by `name` / `label` (omit both for everything). | ### Eval criteria | Tool | Purpose | |---|---| | `list_eval_criteria` | List global default criteria plus your account's custom criteria. | | `get_eval_criterion` | Single criterion detail (rubric, scale, createdAt etc.). | | `create_eval_criterion` | Create a custom criterion (Pro+). All 4 fields (name, rubric, scaleMin, scaleMax) required. Optional `scope`: `call` (default — scores each call) or `trajectory` (scores a whole multi-turn agent run grouped by `trace_id` as one unit; `llm_judge` type only). | | `update_eval_criterion` | Full-replace update of a custom criterion (Pro+). Global defaults are immutable. | | `delete_eval_criterion` | Delete a custom criterion (Pro+). ⚠ All scores recorded against it are deleted too, breaking historical comparison. Prefer `update_eval_criterion` if you only want to rename. | ### Eval runs | Tool | Purpose | |---|---| | `list_eval_runs` | List eval run history with summary (scoredCount / failedCount / mean scores). | | `get_eval_run` | Single run detail with per (criterion × call) scores. | | `run_eval` | Start a new eval run (Pro+). Pass `idempotencyKey` to dedupe within 60 min. | | `compare_eval_runs` | Compare two eval runs (baseline vs candidate). Returns per-criterion mean delta, failed-count delta, and a verdict (improved / regressed / mixed / unchanged). Useful for prompt-improvement checks and regression detection. | ### Eval datasets | Tool | Purpose | |---|---| | `list_eval_datasets` | List your account's golden datasets (GET /v1/eval-datasets). Each dataset carries a name, description, item count, and frozen state. A golden dataset is a fixed test set with expected outputs — the population `run_eval_dataset` runs a target model against to measure regressions. | | `get_eval_dataset` | Single dataset detail with all items (GET /v1/eval-datasets/:id). Get `datasetId` from `list_eval_datasets`. | | `create_eval_dataset` | Create a golden dataset (Pro+, POST /v1/eval-datasets). Pass up to 20 test cases with expected outputs in `items` (each input 1–4,000 chars). Max 50 datasets per account. Passing `frozen: true` freezes the population — items can no longer be changed and the dataset cannot be unfrozen (this pins comparability for regression verdicts). | | `run_eval_dataset` | Run a golden dataset against a target model and check for regressions (Pro+, POST /v1/eval-datasets/:id/run). Each item's input is sent to `targetModel`, and the output is scored against the default criteria by an LLM judge (default gpt-5-mini, override with `judgeModel`) using the expected output as the reference answer. Compare runs with `compare_eval_runs`; records produced by a run are excluded from production cost / analytics / alert aggregation. Cost: items × criteria LLM calls. Pass `idempotencyKey` so retries with the same key return the existing run (prevents double billing). Target and judge models must be OpenAI models on the pricing table; unknown models return 400. | | `delete_eval_dataset` | Delete a golden dataset (Pro+, DELETE /v1/eval-datasets/:id). Its items are deleted with it; past eval runs and scores are kept. | ### Annotations | Tool | Purpose | |---|---| | `list_annotations_for_call` | List annotations attached to a single call. | | `list_annotations_by_label` | List annotations across calls filtered by label. | | `get_annotation` | Single annotation detail. | | `create_annotation` | Attach an annotation to a call (at least one of text, label, quality score required). | | `update_annotation` | Partially update an existing annotation. `callId` is immutable. | | `delete_annotation` | Delete an annotation. | ### Safety classification | Tool | Purpose | |---|---| | `list_safety_assessments` | List classifier assessments. With `callId`, returns all assessments for that call; without, returns the most recent across the account. | | `get_safety_assessment` | Single assessment detail (labels, score, reasoning, classifier_id, source). | ### LLM feature budget | Tool | Purpose | |---|---| | `get_llm_budget` | Returns this month's LLM-feature budget, spent, and remaining. Use this to let an AI agent decide "did we hit 80%?". | | `raise_llm_budget` | Change the monthly budget (Pro+, $5–$500). Auto-resets across calendar months. | ### Runtime control plane (budget gates) | Tool | Purpose | |---|---| | `get_budget_gate` | Returns the runtime budget gate configuration plus this month's LLM spend. Same source the SDK's budgetGate opt-in evaluates before each call. Distinct from `get_llm_budget` (which caps Argosvix's internal AI features) — this one caps your own LLM spend. | | `create_budget_gate` | Creates a runtime budget gate (Pro+). Sets an account-wide monthly LLM spend limit ($0.01–$1,000,000); the SDK blocks over-budget calls before they execute. Enforcement is optimistic: spend is cached for ~60s and in-flight calls pass, so the limit can be exceeded slightly — treat it as a guardrail, not a strict hard cap. `enforceMode` is `fail_open` (default — calls pass if the backend is unreachable) or `fail_closed` (calls stop too; cold start before the SDK has ever fetched the config additionally requires the SDK-side failClosed opt-in). Scope it three ways (most restrictive applicable limit wins, AND-evaluated): omit `projectId`/`tagKey` for an account-wide gate (one per account); set `projectId` for a per-project gate (one per project); or set `tagKey`+`tagValue` together for a per-tag gate (e.g. `service`=`checkout`, one per tag pair). `projectId` and `tagKey` are mutually exclusive. Returns 409 if a gate of the same scope already exists (use `update_budget_gate` instead). | | `update_budget_gate` | Partially updates a budget gate's `monthlyLimitUsd` / `enforceMode` / `enabled` (Pro+). | | `delete_budget_gate` | Deletes a budget gate (Pro+). Pre-flight enforcement in the SDK stops after deletion; if you only want to pause, `update_budget_gate` with `enabled: false` is safer. | ### Runtime control plane (policy gates) | Tool | Purpose | |---|---| | `get_policy_gate` | Returns the runtime policy gate configuration (model allowlist / PII block / secret block / enforceMode / enabled). The SDK's policyGate opt-in evaluates this locally before each LLM call. | | `create_policy_gate` | Creates a policy gate (Pro+). Configure a model allowlist (exact match, 1-100 entries; a leading `models/` prefix is normalized before comparison), PII-detection blocking, and API-key-like token blocking; at least one rule is required. PII detection covers email, card numbers (Luhn-verified), delimiter-separated phone numbers and national IDs, IPv4, and IPv6 (full + common compressed forms); undelimited digit runs are intentionally out of scope to avoid false-positive blocking. One per account; returns 409 if one exists. Redact mode is not supported (block only). | | `update_policy_gate` | Partially updates a policy gate (Pro+). Pass `modelAllowlist: null` to lift the model restriction. | | `delete_policy_gate` | Deletes a policy gate (Pro+). If you only want to pause, `update_policy_gate` with `enabled: false` is safer. | ### Runtime control plane (human-approval gates) | Tool | Purpose | |---|---| | `request_approval` | Creates a human-approval request before a dangerous operation (delete / payment / account closure, etc.) (Pro+). The account owner receives an email and approves or denies via the dashboard or the email link. **No MCP tool can approve or deny** — an AI agent cannot self-approve its own request. `timeoutSeconds` (60-86400, default 3600) elapsing marks the request expired = treated as denied. | | `get_approval` | Returns the current state of an approval request: `pending` / `approved` / `denied` / `expired`. Anything other than `approved` means do not execute the gated operation (default-deny). In addition, the six dangerous mutation tools `bulk_delete_calls` / `purge_expired_plaintext` / `retry_failed_webhook` / `auto_silence_noisy_alert` / `extend_customer_trial` / `apply_promo_code_to_customer` accept an optional `approvalId`: the backend verifies approved status, expiry, action match, and unconsumed state, then consumes the approval at execution time (one approval = one execution). Create the approval request with `action` exactly matching the target tool name. Dry runs validate without consuming. | | `list_approvals` | Lists approval requests (latest 50). Status filter: `pending` (default) / `approved` / `denied` / `expired` / `all`. | ### Proposals (approval queue) | Tool | Purpose | |---|---| | `list_proposals` | List unresolved improvement proposals Argosvix detected automatically (quality drift / reliability anomalies / cost switching / safety / silencing noisy alerts). Approving, dismissing, and executing happen in the dashboard inbox (agents can only view and converse). | | `get_proposal_thread` | Return a single proposal's thread (questions asked so far and the AI's replies). Get `proposalId` from `list_proposals` (IDs start with `prp_`). | | `reply_proposal` | Post a question (`body`) about a proposal and get the AI's reply (same conversation as in the inbox). Explanation only — nothing is executed. | ### Event webhooks | Tool | Purpose | |---|---| | `list_webhooks` | List registered outbound event webhooks (GET /v1/webhooks). Each webhook includes id, url, hasSecret, enabled, eventTypes, last delivery status, and consecutive-failure count (the secret itself is never returned). This is the subscription surface that notifies external endpoints of account events (approval requests, proposal execution / reversal) via signed POSTs. Viewable on Free. | | `create_webhook` | Register an outbound event webhook (POST /v1/webhooks). `url` must be HTTPS; private / loopback hosts are rejected as SSRF defense. Optionally pass `secret` (HMAC-SHA256 signing key; when set, deliveries carry an `X-Argosvix-Signature` header) and `eventTypes` (array of event types to subscribe to: `approval.requested` / `proposal.executed` / `proposal.reversed`; omit or pass an empty array to subscribe to all). Pro+ allows up to 10 webhooks across all events; Free allows one webhook subscribed to `approval.requested` only. Slack incoming webhook URLs (hooks.slack.com) automatically receive a human-readable `{ text }` message; other endpoints receive the machine-readable `{ event, eventId, occurredAt, accountId, data }` envelope. | | `update_webhook` | Partially update a webhook (PATCH /v1/webhooks/:id). Only the fields you pass change (url / secret / eventTypes / description / enabled). Passing an empty string `""` as `secret` removes signing (`null` cannot be sent per the schema; omit the field to keep the current value); re-enabling with `enabled: true` also resets the consecutive-failure counter. `webhookId` is the id from `list_webhooks`. | | `delete_webhook` | Delete a webhook (DELETE /v1/webhooks/:id). `webhookId` is the id from `list_webhooks`. | --- ## Resources (3 — static snapshots) Read-only data fetched via `resources/read`. Host apps prefetch these into the conversation context. | URI | Contents | |---|---| | `argosvix://account` | Plan / quota / this-month record usage / retention snapshot (subscription details excluded). | | `argosvix://alerts/active` | All enabled=true alerts. | | `argosvix://cost/today` | Last 24h cost aggregate (by provider + total). | --- ## Subscribe (stdio only) stdio transport declares the `resources.subscribe` capability. Clients can subscribe to the 3 static resources; the server polls every 60s and emits `notifications/resources/updated` when content changes. ```text # subscribe → change notification → auto-refresh client: resources/subscribe { uri: "argosvix://cost/today" } server: {} # accept ... internal 60s polling cycle ... server: notifications/resources/updated { uri: "argosvix://cost/today" } client: resources/read { uri: "argosvix://cost/today" } # fetch latest ``` Notes: - Subscribe is limited to the 3 URIs above; resource templates are not subscribable. - HTTP transport is per-request stateless and does not advertise subscribe. `resources/subscribe` requests get `-32601 Method not found`. - `notifications/resources/list_changed` is not supported (resource list is fixed for the lifetime of the server). - On shutdown / disconnect, the polling timer stops and the subscription set clears (handlers for SIGINT / SIGTERM / beforeExit are registered). --- ## Resource templates (8 — URI parameterized) Substitute `{id}` with an id from `query_calls`, `list_alerts`, etc., then call `resources/read`. | URI template | Contents | |---|---| | `argosvix://calls/{id}` | Single call record (from `query_calls.records[].id`). | | `argosvix://alerts/{id}` | Single alert config + last 20 firing events (channelTargets stripped). | | `argosvix://traces/{id}` | Full spans for a single trace (capped at 50, original count carried in meta; errorDetails / requestMeta stripped). | | `argosvix://annotations/{id}` | Single annotation detail. | | `argosvix://eval-criteria/{id}` | Single eval criterion detail. | | `argosvix://eval-runs/{id}` | Single eval run with per-criterion scores. | | `argosvix://safety-assessments/{id}` | Single safety assessment detail. | | `argosvix://prompts/{id}` | Single prompt detail. | For all resource template reads, fields surfaced to the LLM context are explicitly allow-listed (structural defense against prompt injection and internal-implementation leaks). --- ## Prompts (3 — templated messages) Fetched via `prompts/get`. Users typically invoke as slash commands. | Name | Purpose | |---|---| | `cost_review` | Analyze cost trends over 24h / 7d / 30d and flag anomalies (sudden spikes, single-model dominance, unexpected models). | | `alert_audit` | Audit current alert rules, checking plan / threshold / channel appropriateness. | | `incident_triage` | Investigate error / latency anomalies in the last N hours and infer root cause (hours optional, default 24). | --- ## Plan limits | Item | Free | Pro | Team | |---|---|---|---| | MCP server connectivity | ✓ | ✓ | ✓ | | Read tools | ✓ | ✓ | ✓ | | Write tools (prompt, eval, budget, etc.) | partial | ✓ | ✓ | There is no cap on the number of MCP requests themselves (backend-side rate limits and the monthly record quota that some tools depend on apply as noted in each tool's description). Team plan is in beta. See [Pricing](https://argosvix.com/en#pricing) for details. --- ## Debug logging By default, raw backend error bodies are not logged to stderr. Opt-in explicitly for debugging: ```bash ARGOSVIX_MCP_DEBUG=1 argosvix-mcp # stdio ARGOSVIX_MCP_DEBUG=1 argosvix-mcp --http # HTTP ``` Without the env var, only `path / status / x-request-id` are logged. This keeps backend-side sensitive info out of production log aggregators by default. --- ## Links - npm package / changelog: - MCP protocol: --- ## Support hello@argosvix.com --- # Errors # Error Response Reference A single place that documents the shape of all Argosvix REST API + MCP tool error responses, plus the paid-impact `reason` enum values and actionable messages. This page defines the public contract that callers (MCP server / SDK / third-party clients) can branch on programmatically. --- ## Response shape All 4xx / 5xx errors return the following JSON shape. ```json { "error": "human readable summary in English", "reason": "plan_required", "actionable": "Pro プランに加入してから /settings/billing で..." } ``` | Field | Type | Required | Description | |---|---|---|---| | `error` | string | Yes | Developer-facing English summary. Use for log aggregation / error-tracking search. | | `reason` | string | Only for paid-impact / consent / quota errors | Canonical string for programmatic branching. See enum below. | | `actionable` | string | Only for paid-impact / consent / quota errors | A ready-to-display message for the end user (currently Japanese). Includes concrete navigation hints like `dashboard の /settings/billing で...`. | Note that `reason` / `actionable` are **not** present on every 4xx response. Authentication / CSRF / role gate / validation errors deliberately omit them (caller-side programmatic branching has no value there). They are only attached to paid-impact / consent / quota errors on 402 / 403 / 409. The `actionable` field is currently Japanese-only. English messages will follow. --- ## `reason` enum Each error response may include a `reason` field with one of the following stable values, so clients can branch on it programmatically. ### `plan_required` Status: 402 (Payment Required) or 403 (Forbidden). 402 is returned only by the consent endpoints (plaintext storage / AI helper consent) and Team invitation / membership management; every other plan gate returns 403. Meaning: The caller's plan does not meet the endpoint's required plan (Pro / Team). One of: - A Free user called a Pro+-only endpoint - A Pro user called a Team-only feature (invitation issue / membership management) - The plan was valid when the request started, but the subscription was downgraded while it was being processed Typical actionable: "dashboard の /settings/billing で Pro / Team プランに加入してください" plus an endpoint-specific note. Recommended caller handling: - Surface the `actionable` string directly in the UI - Provide a deep link to `/settings/billing` - Do not retry automatically (the result will be identical until the plan actually changes) Example endpoints: - `POST /v1/eval-criteria` (Pro+ only) - `POST /v1/eval-runs` (Pro+ only) - `POST /v1/prompts` (Pro+ only) - `POST /v1/invitations` (Team only) - `PATCH /v1/memberships/:id` reinstate / promote_admin (Team only) - `POST /v1/ai-helper/consent` (Pro+ only) - `POST /v1/plaintext/consent` (Pro+ only) - `GET /v1/calls/:id/plaintext` (Pro+ active required) ### `subscription_canceled` Status: 403 Meaning: A Pro / Team subscription is no longer active at the moment an AI helper feature (batch safety classification, eval criteria proposal, etc.) is called. The subscription was active when AI helper consent was granted, but has since moved to a status other than active / trialing / past_due (e.g. canceled), so the request is rejected with this reason. Distinguish from `plan_required`: `plan_required` means "the plan was insufficient from the start", while `subscription_canceled` means "the subscription lapsed after consent was granted". Typical actionable: "Pro / Team プランに再加入してから 再試行してください". Recommended caller handling: - Surface the `actionable` string directly - Do not implement automatic retry after re-subscription (billing webhook carries eventual consistency) ### `consent_required` Status: 403 Meaning: The AI helper features (automated safety classification + eval criterion proposals + plaintext PII secondary audit) or plaintext storage feature consent has not been granted. The terms-of-service v2.2 article 4-3 consent must be acknowledged once. Typical actionable: "dashboard の /settings/privacy で AI 補助機能 / 平文保管同意を ON にしてください". Recommended caller handling: - Surface the `actionable` string directly - Provide a deep link to `/settings/privacy` - Always show the v2.2 consent text in the consent dialog before calling `POST /v1/ai-helper/consent` (or `/v1/plaintext/consent`) Example endpoints: - `POST /v1/eval-criteria/propose` (AI helper consent required) - `POST /v1/safety-assessments/scan-batch` (AI helper consent + plaintext opt-in required) - `GET /v1/calls/:id/plaintext` (plaintext opt-in required) ### `plaintext_disabled` Status: 403 Meaning: The plaintext storage feature is disabled workspace-wide. This is a server-side operational setting that user-level settings cannot change. Typical actionable: "ご利用の Argosvix workspace では 平文保管機能が無効化されています。 administrator にお問い合わせください". Recommended caller handling: - Surface the `actionable` string directly - Do not link to `/settings/privacy` (user-level settings cannot fix this) - Point the user to hello@argosvix.com if plaintext storage should be available on their workspace ### `project_limit_reached` Status: 409 (Conflict) Meaning: The project count plan limit (Free 1 / Pro 5 / Team unlimited) is reached. New project creation is rejected. Typical actionable: "dashboard の /settings/projects で 既存 project を archive するか、 上位プランへの upgrade を検討してください". Recommended caller handling: - Surface the `actionable` string directly - Provide a deep link to `/settings/projects` --- ## 4xx errors without a `reason` The following errors do not carry `reason` / `actionable`. Callers should branch on `status` + `error`. | Case | Status | Recommended caller handling | |---|---|---| | API key missing / invalid | 401 | Reissue the API key and retry | | Session expired | 401 | Re-log in from the dashboard | | CSRF (Origin / Referer missing) | 403 | Use a browser session for the session route, or switch to Bearer for server-to-server use cases | | Role gate (viewer mutation rejected) | 403 | Ask an admin to change the role | | Operator-only tools | 403 | Reserved for the service operator; not available to normal API callers | | Validation error | 400 | Inspect the `error` English summary for the cause | | Record not found | 404 | Verify the ID | | Rate limit / monthly quota exceeded | 429 | Wait for the number of seconds in the `Retry-After` header and retry (for quota, wait until next month or upgrade the plan) | These omit `reason` deliberately. The trade-off between "caller programmatic branching value" and "internal implementation leakage risk" tips toward omission for access-control failures, which are not paid-impact product gates. --- ## Retrieving `reason` via SDK / MCP server ### Argosvix SDK (`@argosvix/sdk` / `argosvix` Python) The SDK does not yet expose `reason` / `actionable` through a dedicated error type — it currently focuses on ingest wrap helpers. If you need programmatic branching, call `fetch` directly and read the response body — the backend emits `reason` / `actionable` as plain JSON fields. ```typescript // Interim pattern (= until SDK exposes structured errors) const res = await fetch(`${API_BASE}/v1/eval-runs`, {...}); if (!res.ok) { const body = await res.json(); if (body.reason === "plan_required") { showUpgradeBanner(body.actionable); } } ``` ### MCP server (`@argosvix/mcp-server`) The MCP server extracts only the `error` / `reason` / `actionable` fields from the backend response body and forwards them as a **single plain-text line** in the tool response (`isError: true`) — the raw body is never forwarded, for security reasons. The format is: ``` Argosvix API failed with status (requestId=) — / reason= / ``` ```jsonc // tool result (isError: true) { "content": [ { "type": "text", "text": "Argosvix API /v1/eval-runs failed with status 403 — Pro+ plan required for eval runs / reason=plan_required / dashboard の /settings/billing で Pro プランに加入してください" } ], "isError": true } ``` Because this is not JSON, branch programmatically by string-matching the text, e.g. on `reason=plan_required` (the value after `reason=` is one of the enum values above; `requestId` and each field are omitted when absent from the body). The LLM agent can read the text and present the next step to the user.