Documentation menu

Reference

API reference

The ValidAnytime API is a small, predictable REST surface — JSON in, JSON out, Bearer-authenticated — for creating monitors, ingesting events idempotently, and reading alarms.

Base URL & auth

All requests go to https://api.validanytime.com and carry your API key as a Bearer token.

shell
curl https://api.validanytime.com/v1/monitors \
  -H "Authorization: Bearer $VA_API_KEY"

Endpoints

  • GET/healthz

    Liveness check.

  • POST/v1/monitors

    Create a monitor. The config is validated by constructing the real engine — an invalid config is a 422 here, never a surprise at first ingest. A template resolves server-side into the full config it runs; the resolved config is persisted and returned. Optional webhook_url turns on outbound alarm delivery (see below).

    { "name": "llm-answer-quality", "config": { "template": "llm_quality", "mu_baseline": 0.92 }, "webhook_url": "https://ops.example.com/hooks/validanytime" }
  • GET/v1/monitors

    List every monitor for the tenant. Each config is the resolved config that actually runs (templates are already expanded).

  • GET/v1/monitors/{id}

    Fetch a monitor.

  • PATCH/v1/monitors/{id}

    Rename, reconfigure, and/or re-point the webhook. A config change resets the monitor's derived engine state (carry, learned baseline, fired-alarm latches — reported as state_reset: true) and applies to future events only. webhook_url is tri-state: omit to keep, set a URL, or pass null to clear — never a state reset.

    { "webhook_url": "https://ops.example.com/hooks/validanytime" }
  • POST/v1/monitors/{id}/events

    Ingest one event. event_id makes it idempotent.

    { "value": 0.91, "event_id": "evt_1042", "ts": "2026-06-24T14:02:11Z", "label": 1.0 }
  • POST/v1/monitors/{id}/events:batch

    Ingest a batch of events in order.

    { "events": [ { "value": 0.88, "event_id": "evt_1043" } ] }
  • GET/v1/monitors/{id}/events

    Read the event history (seq cursor: ?after_seq= & limit=). Each drained event also carries stats — the page tier's evidence at that step (log e-value for the coverage/risk e-processes, log Shiryaev–Roberts statistic for the e-detector), captured by the engine when the event was processed; null for events ingested before capture shipped.

  • GET/v1/monitors/{id}/alarms

    List alarms. Optional ?since= timestamp.

  • GET/v1/fleet/fdr

    Your fleet's pooled certificate: online FDR (e-LOND) status across all e-value streams.

  • POST/v1/onboarding/backtest

    Replay history through the gate (quiet on normal, fires on degradation). Omit config to run the default menu config, or pass an explicit one — this endpoint takes menu configs, not template ids.

    { "history": [0.92, 0.90, ...], "inject_shift": 4.0 }
  • POST/v1/onboarding/suggest

    Suggest a menu config from a sample and ratify it via the gate.

    { "sample": [0.9, 0.88, ...], "history": [0.92, 0.90, ...] }
  • POST/v1/monitors/{id}/ingest-tokens

    Mint a least-privilege INBOUND ingest token (prefix vait_), scoped to this monitor, with a dot-path field map. The plaintext token + ingest path are returned once. Point any source at the URL — the token is the credential, no API key.

    { "value_path": "properties.score", "event_id_path": "event_id" }
  • GET/v1/monitors/{id}/ingest-tokens

    List a monitor's ingest tokens: id, prefix, mapping, created_at — never the secret.

  • DELETE/v1/monitors/{id}/ingest-tokens/{tokenId}

    Revoke an ingest token — that URL stops ingesting immediately.

  • POST/v1/ingest/{token}

    Inbound webhook receiver — NO Authorization header; the URL token is the credential. POST any source payload; the token's mapping pulls out value/event_id/ts (non-finite or missing value → 422). Metered and drained like an authenticated single ingest. This is the no-code ingest path (PostHog/Grafana/Zapier).

    { "properties": { "score": 0.91 }, "event_id": "evt_88" }
  • GET/v1/use-cases

    The use-case → config matrix (public, no auth): each row's id, direction, when-to-use, rationale, and the resolved config it maps to.

  • GET/v1/me

    The signed-in caller's tenant and plan/status — the dashboard bootstrap call.

  • GET/v1/keys

    List API keys: id, prefix, and created_at only — the secret is never returned.

  • POST/v1/keys

    Mint an API key. The plaintext key (va_...) is returned once and never stored or shown again — copy it immediately.

The monitor menu

A monitor config picks its detectors from a finite, documented menu of seven families across two tiers. Page-tier families carry an anytime-valid guarantee: their false-alarm control holds at every look at once, within a budget stated up front. Warning-tier families are the classical control charts (tagged heuristic_adaptive): sensitive and fast, but their false-warning calibration is model-based (iid standardized inputs) and is not anytime-valid — off-model, the false-warning rate can exceed the nominal rate by orders of magnitude, which is exactly why they warn rather than page.

  • coverage_e_process (page) — anytime-valid coverage monitor; alpha (defaults to the calibrator’s), delta (default 0.05): false-alarm probability ≤ delta, ever.
  • e_detector (page) — Shiryaev–Roberts e-detector; arl_target (default 100), null_mean, input_field (default "miss"), detector (default "sr").
  • risk_e_process (page) — anytime-valid risk-budget monitor; risk_budget (required), delta, risk_lo/risk_hi.
  • cusum (warning) — self-standardizing CUSUM; k (default 0.5), exactly one of h or arl_target (Siegmund-solved, a model-based promise), two_sided, train (default 30), input_field (default "loss").
  • ewma_chart (warning) — EWMA chart with exact time-varying limits; lam (default 0.2), L (default 3), side, train, input_field (default "loss").
  • static_threshold (warning) — mean ± k·sigma frozen from the first train observations; k (default 3), side, input_field (default "loss").
  • rolling_band (warning) — trailing-window band that re-learns level as it goes; window (default 30), k (default 3), side, input_field (default "loss").

input_field selects what the detector watches: "miss" (the calibrated interval missed), "score" (the absolute residual), or "loss" — the signed residual value − baseline, which keeps direction, so a quality drop and a latency spike are different events.

Templates & the baseline

config: {"template": "llm_quality"} resolves server-side at create time into a complete two-tier config — an e_detector and a coverage_e_process on the budgeted page tier plus a directional control chart on the warning tier — and the resolved config is what gets persisted and returned, so GET /v1/monitors/{id} always shows exactly what runs. The ids (default, ml_drift, llm_quality, latency, kpi) map to distinct, gate-verified configs — they differ in calibrator stiffness and which direction the warn tier watches. See the use-case matrix for the full table, or GET /v1/use-cases for the resolved configs as JSON. template combines only with mu_baseline; anything else is a 422.

mu_baseline is the level your metric should hold — detectors measure deviation from it. Set it explicitly if you know it. Omit it and the monitor learns it at ingest: the median of your first 30 values, then frozen — frozen on purpose, so a slow regression is never re-learned as the new normal. (The very first event only seeds the learner; it is a no-op for every detector.) Every monitor response includes a read-only baseline summary — {"mode", "mu", "learning_count"} — showing the explicit value, the training progress, or the frozen learned value.

Validation

Configs are validated at create by constructing the actual engine: an unknown monitor family, an unknown top-level key, a malformed kwarg, or a bad template id returns 422 with the menu in the message — never a 500 at first ingest. Event values must be finite: an Infinity or NaN value is a 422 at ingest (a batch is rejected atomically, with the offending element named in the error), so a bad number in your pipeline can never corrupt a monitor’s stream.

Alarm delivery (webhooks)

A monitor with a webhook_url gets one POST per new alarm, sent after the alarm is persisted. Delivery is best-effort — a short timeout and one retry; a slow or down endpoint never blocks ingestion, and alarms stay readable via GET /v1/monitors/{id}/alarms either way. The tier field is the routing key: "page" alarms arrive within the monitor’s stated false-alarm budget; "warn" alarms are the sensitive classical tier and carry no budget.

webhook payload
{
  "type": "alarm.fired",
  "monitor_id": "mon_a1",
  "monitor_name": "llm-answer-quality",
  "alarm_id": "alm_2",
  "seq": 78,
  "tier": "page",
  "guarantee_tag": "anytime_valid_under_conditional_mean_null",
  "statistic": 3.01,
  "e_value": 20.2,
  "theorem_ref": "predictable_betting_e_process",
  "message": "coverage_e_process fired at seq 78",
  "fired_at": "2026-06-25T00:02:11Z",
  "event_value": 0.87
}

Example: alarms response

json
{
  "alarms": [
    {
      "id": "alm_1",
      "monitor_id": "mon_a1",
      "seq": 68,
      "fired_at": "2026-06-24T14:02:11Z",
      "statistic": 8.30,
      "guarantee_tag": "heuristic_adaptive",
      "theorem_ref": "page_1954_cusum_siegmund_arl_calibration",
      "e_value": null,
      "discovery": null,
      "message": "cusum fired at seq 68"
    },
    {
      "id": "alm_2",
      "monitor_id": "mon_a1",
      "seq": 78,
      "fired_at": "2026-06-25T00:02:11Z",
      "statistic": 3.01,
      "guarantee_tag": "anytime_valid_under_conditional_mean_null",
      "theorem_ref": "predictable_betting_e_process",
      "e_value": 20.2,
      "discovery": true,
      "message": "coverage_e_process fired at seq 78"
    }
  ]
}

Every alarm includes its seq (the 1-based event position at which it fired), its guarantee_tag — the guarantee in force — its theorem_ref, and a statistic. Warning-tier alarms carry guarantee_tag: "heuristic_adaptive" and always have e_value: null: a warning is a sensitive early hint whose calibration is model-based, not a budgeted page.

Fleet-wide FDR (e-LOND)

Alarms from e-process monitors also enter your tenant’s pooled e-LOND sequence: each alarm’s e_value is tested against a shared false-discovery budget, and the confirmed subset is marked discovery: true — controlling the false discovery rate at level α across all your streams, under arbitrary dependence between them. Detector-family alarms (e_detector) keep their own average-run-length guarantee and are not pooled, and warning-tier alarms never enter the pool at all — none of them emit e-values, so for both, e_value and discovery stay null.

GET /v1/fleet/fdr
{
  "alpha": 0.1,
  "hypotheses_tested": 7,
  "discoveries": 2,
  "gamma_remaining": 9993,
  "guarantee_tag": "online_evalue_fdr_control",
  "theorem_ref": "xu_ramdas_2024_e_lond_theorem_1"
}