Invarians

DEVELOPERS

Certified infrastructure-context API, v2.0
Three primitives: Attestation · Regime · Delta

Python SDK

The fastest way to integrate Invarians. Handles authentication, retry, STALE policy, and calibration metadata automatically.

API key required. Create a free account to get your inv_ key before running the examples below.
pip install "invarians[requests]>=0.5.0"   # v2.0 client (Attestation + Regime + Drift)
from invarians import InvariansClient

client = InvariansClient(api_key="inv_your_key_here")

# v2.0 panel with full diagnostic (regime + Delta per metric)
panel = client.get_panel_v2(
    chains=["ethereum", "arbitrum"],
    bridges=["cctp"],
    include="diagnostic",    # core | diagnostic | full
)

eth = panel.l1_by_chain("ethereum")
arb = panel.l2_by_chain("arbitrum")
br  = panel.bridge_by_id("arbitrum-ethereum/cctp")

# Primitive 2: regime classification (12 codes per chain)
if eth.regime.startswith("S2") or arb.regime.startswith("S2"):
    pause_agent_execution()    # structural stress detected

# Primitive 3: Delta (per-chain calibrated precursors, v3 preview)
for p in arb.precursors:
    if p.is_firing and p.baseline_lift >= 1.5:
        defer_action()        # calibrated precursor fired, defer for p.lead_hours

# Per-metric inspection (only available in diagnostic mode)
print(eth.demand.tx.shift)                  # current deviation vs 30d baseline
print(eth.demand.tx.is_drifting_away)       # True if deviation amplifying
print(eth.demand.tx.is_reverting)           # True if returning to nominal

# Primitive 1: HMAC integrity
ok = client.verify_panel_v2(
    panel_dict, panel.signed_execution_context.signature
)

For raw HTTP access (JavaScript, curl, other languages), see sections below.

Quick Start
Base URL: https://api.invarians.com
Auth: Authorization: Bearer inv_your_api_key

Endpoint 1, Panel: GET /v2/panel
Returns the full state of every tracked L1, L2 and bridge in one signed response. Optional filters: ?chains=ethereum,arbitrum · ?bridges=ccip,cctp.

Endpoint 2, Verify: POST /v2/verify
Verifies the HMAC signature over a panel payload. Body: {"payload": <panel without signed_execution_context>, "signature": "hmac-sha256:..."}.

Endpoint 3, CCTP attestation retrieval: GET /v2/cctp/attestation/{message_hash}
Returns the per-message Circle ECDSA signature (secp256k1, 65 bytes) for a CCTP message, captured by the Invarians CCTP collector. The signature is independently verifiable against Circle's published attester public key. Use this for audit-trail or independent verification of any CCTP route signal exposed in the panel.

Endpoint 4, CCIP message retrieval: GET /v2/ccip/message/{message_id}
Returns the per-message CCIP row for a bytes32 messageId: source send metadata (sender, receiver, sequence_number, nonce, gas_limit, fee_token), and, when executed, destination metadata (dest_tx_hash, dest_block_number, dest_block_timestamp, execution_state). A status field reads "pending" or "executed". crypto_anchor is null today; will become non-null once DON multi-sig capture is added.

Panel coverage: L1 ethereum, polygon, avalanche · L2 arbitrum, base, optimism · 10 Circle CCTP routes (capability_level: per_message_attested, Circle ECDSA per message) · 10 Chainlink CCIP lanes (capability_level: per_message_attested, source-to-execute matched by messageId). Each bridge entry carries unified BS1 / BS2 state, a structured metrics block, and a crypto pointer to the verifiable attestation when applicable.

Direction-agnostic: the panel exposes states, not routes. Your agent composes from/to client-side by picking the relevant L1/L2/bridge entries.
# Python — raw HTTP, v2.0 panel with diagnostic mode
import requests

API_KEY = "inv_your_api_key"
BASE_URL = "https://api.invarians.com"

def get_panel(chains=None, bridges=None, include="core") -> dict:
    params = {"include": include}
    if chains:  params["chains"]  = ",".join(chains)
    if bridges: params["bridges"] = ",".join(bridges)
    r = requests.get(
        f"{BASE_URL}/v2/panel",
        headers={"Authorization": f"Bearer {API_KEY}"},
        params=params,
    )
    r.raise_for_status()
    return r.json()

panel = get_panel(chains=["ethereum"], bridges=["cctp"], include="diagnostic")

print(panel["version"])                                    # "2.0.0"
print(panel["oracle_status"])                              # "OK" | "DEGRADED"
print(panel["panel"]["l1"][0]["regime"])                      # 12 possible codes per chain
for p in panel["panel"]["l2"][0].get("precursors", []):
    print(p["axis"], p["fires"], p["baseline_lift"])    # v3 per-chain calibrated precursors
print(panel["signed_execution_context"]["signature"])      # "hmac-sha256:..."
// JavaScript / Node — v2.0 panel
const getPanel = async (params = {}) => {
  const qs = new URLSearchParams(params).toString();
  const res = await fetch(
    `https://api.invarians.com/v2/panel?${qs}`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  return res.json();
};

const panel = await getPanel({
  chains: "ethereum,arbitrum",
  bridges: "cctp",
  include: "diagnostic",
});

console.log(panel.version);                                    // "2.0.0"
console.log(panel.panel.l1[0].regime);                         // "S1D1" | "S1D2+" | ... (12 codes)
console.log(panel.panel.l1[0].demand.tx.shift);                // per-metric deviation magnitude
console.log(panel.panel.l1[0].demand.tx.shift_magnitude_delta); // per-metric trend
console.log(panel.panel.bridges[0].state);                     // "BS1" | "BS2" | null
# Python — compose a route from the v2.0 panel (agent-side)
panel = get_panel(chains=["ethereum", "arbitrum"], bridges=["cctp"], include="diagnostic")

l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
l2 = next(e for e in panel["panel"]["l2"] if e["chain"] == "arbitrum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")

# Primitive 2: regime per chain (12 codes)
print(l1["regime"], l1["status"])               # e.g. "S1D2+" "OK"
print(l2["regime"], l2["status"])               # e.g. "S1D1" "OK"

# Primitive 3: Delta (v3 per-chain precursors + raw shift inputs)
for p in l2.get("precursors", []):
    print(p["axis"], p["fires"],
          p["lead_hours"], p["baseline_lift"])  # calibrated per-chain precursor
print(l1["demand"]["sigma"]["shift"])           # per-metric current deviation (raw)
print(l1["demand"]["sigma"]["shift_magnitude_delta"])  # per-metric trend (raw)

# Bridge state
print(br["state"], br["calibrated"], br["status"])  # e.g. "BS1" True "OK"
Filters & Best Practices

The default panel returns every tracked chain and bridge (~25 entries, ~30 KB JSON in diagnostic mode). Most agents only need a small subset. Always filter your queries.

Three query parameters let you trim the payload to exactly what your agent consumes:
  • chains=ethereum,arbitrum  — filter to a subset of L1/L2 chains
  • bridges=cctp  — filter bridge types (ccip, cctp)
  • include=core | diagnostic | full  — payload tier (default core)
# Minimal panel for a single L1+L2 pair, CCTP route only
GET /v2/panel?chains=ethereum,arbitrum&bridges=cctp&include=core
# → ~2 KB, regime + status + precursors[] per chain

# Diagnostic mode adds per-metric raw shift fields
GET /v2/panel?chains=ethereum&bridges=cctp&include=diagnostic
# → ~3 KB, with shift + shift_delta + shift_magnitude_delta per metric

# Research / debugging (raw EMA baselines included)
GET /v2/panel?chains=ethereum&include=full
# → ~5 KB, with baseline_short + baseline_long

# Multi-bridge route comparison (CCIP + CCTP)
GET /v2/panel?chains=ethereum,arbitrum&bridges=ccip,cctp&include=core
# → ~6 KB
Query Approx size Use case
?chains=ethereum&bridges=cctp&include=core ~1.5 KB Most common: regime + status + precursors[] per chain, single chain pair
?chains=ethereum&include=diagnostic ~3 KB Stateful agent reading shifts directly
?include=core (no chain filter) ~5 KB Multi-chain dashboard, compact payload
?include=diagnostic (no filter) ~30 KB Full panel dump (avoid in production polling)
Bridge classification scope: CCTP routes and CCIP lanes are both captured per message (capability_level: per_message_attested). CCTP carries the Circle ECDSA signature per message (crypto.anchor: circle_ecdsa); CCIP matches source send and destination execute by bytes32 messageId (crypto.anchor: null today, DON multi-sig CommitReport capture is the next step). BS1/BS2 classification is preliminary on the per-message latency baseline.

Polling cadence: the L1/L2 entries refresh every ~5 min, bridges every ~10 min. Polling more frequently than once per minute returns the same payload (cached behind Cache-Control: max-age=60).
Panel Response Format (v2.0.0)

Full response for GET /v2/panel. One signed payload with every tracked L1, L2, and bridge, axis-grouped by chain. The agent picks the entries relevant to its planned action.

{
  "version":       "2.0.0",
  "oracle_status": "OK",                       // "OK" | "DEGRADED"
  "issued_at":    "2026-04-30T12:30:00Z",

  "panel": {
    // L1 chains, axis-grouped (structural / demand) per chain
    "l1": [
      {
        "chain":       "ethereum",
        "regime":      "S1D1",                   // 12 codes possible: S1, S2+, S2- × D1, D2+, D2-, D2±
        "status":      "OK",                     // "OK" | "STALE" | "UNAVAILABLE" | "UNCALIBRATED"
        "computed_at": "2026-04-30T12:25:00Z",

        // Structural axis (S): rhythm, continuity, plus beacon_participation on Ethereum only
        "structural": {
          "rhythm": {
            "ratio":                 1.0013,    // short EMA (~10h) vs short baseline
            "ratio_long":            0.9996,    // long EMA (~30d) vs long baseline (diagnostic+)
            "shift":                 0.0017,    // = ratio - ratio_long, current deviation
            "shift_delta":           0.0008,    // = shift - shift_prev, direction of value movement
            "shift_magnitude_delta": 0.0008     // = |shift| - |shift_prev|, deviation grows or shrinks
          },
          "continuity": {
            "ratio": 1.0, "ratio_long": 1.0, "shift": 0.0, "shift_delta": 0.0, "shift_magnitude_delta": 0.0
          },
          "beacon_participation": {                  // Ethereum only (Beacon Chain validators)
            "ratio":           0.998,
            "epoch":           312547,
            "shift_available": false                // slow EMA stabilizing, ~30d post-launch
          }
        },

        // Demand axis (D): sigma, size, tx (echo of activity on confirmed blocks)
        "demand": {
          "sigma": { "ratio": 1.003,  "ratio_long": 0.99, "shift":  0.013, "shift_delta":  0.005, "shift_magnitude_delta":  0.005 },
          "size":  { "ratio": 1.0095, "ratio_long": 1.05, "shift": -0.04,  "shift_delta": -0.012, "shift_magnitude_delta":  0.012 },
          "tx":    { "ratio": 1.2037, "ratio_long": 1.18, "shift":  0.024, "shift_delta":  0.018, "shift_magnitude_delta":  0.018 }
        },

        // Precursors per chain (v3 design preview; empty on ethereum, calibration registry is per-chain)
        "precursors": []
      }
    ],

    // L2 rollups, axis-grouped. structural also includes sequencer_publish_latency.
    // demand has 5 observables on L2 (sigma + size + tx + complexity + gas_complexity).
    "l2": [
      {
        "chain":       "arbitrum",
        "regime":      "S1D1",
        "status":      "OK",
        "computed_at": "2026-04-30T12:25:00Z",

        "structural": {
          "rhythm":     { "ratio": 1.001, "ratio_long": 0.992, "shift": 0.009, "shift_delta": 0.001, "shift_magnitude_delta": 0.001 },
          "continuity": { "ratio": 1.0,   "ratio_long": 1.006, "shift": -0.006, "shift_delta": 0.0, "shift_magnitude_delta": 0.0 },
          "sequencer_publish_latency": {              // L2 third structural observable
            "ratio":           null,
            "seconds":         11.4,                // raw delay between L2 batches inscribed on L1
            "shift_available": false                // slow EMA pending
          }
        },

        "demand": {
          "sigma":          { "ratio": 1.0,   "ratio_long": 1.0, "shift": 0.0, "shift_delta": 0.0, "shift_magnitude_delta": 0.0 },
          "size":           { "ratio": 0.94,  "ratio_long": 0.97, "shift": -0.03, "shift_delta": -0.005, "shift_magnitude_delta": 0.005 },
          "tx":             { "ratio": 0.91,  "ratio_long": 1.01, "shift": -0.10, "shift_delta": -0.02, "shift_magnitude_delta": 0.02 },
          "complexity":     { "ratio": 1.02,  "ratio_long": 1.0, "shift": 0.02, "shift_delta": 0.005, "shift_magnitude_delta": 0.005 },
          "gas_complexity": { "ratio": 1.01,  "ratio_long": 1.0, "shift": 0.01, "shift_delta": 0.002, "shift_magnitude_delta": 0.002 }
        },

        // Precursors per chain (v3 design preview; 6 calibrated configurations on arbitrum from ETH-ARB-CCTP 2025)
        "precursors": [
          {
            "axis":                 "arb_struct_seq_publish_latency_shift",
            "fires":                false,
            "current_smd":          0.080,
            "smd_threshold_value":  0.2258,
            "k_consecutive_hours":  2,
            "pctl_threshold":       0.90,
            "lead_hours":           6,
            "outcome_category":     "latency_high_only",
            "bridge_corridor":      "ETH-ARB-CCTP",
            "baseline_lift":        1.913,
            "baseline_p_adj":       0.023,
            "baseline_precision":   0.439,
            "baseline_alert_rate":  0.0057,
            "cross_chain_status":   "FAIL_on_optimism",
            "calibrated_at":        "2026-05-19T00:00:00Z"
          }
          // + 5 other arbitrum precursors (seq_publish_latency at lead 3h/12h, demand_tx, demand_size, eth_demand_tx cross-substrate)
        ]
      }
      // + base, optimism (same shape, each with its own per-chain precursors[])
    ],

    // Bridges. Unified state codes BS1/BS2 across all protocols (CCIP, CCTP, future fast bridges); type field distinguishes the protocol.
    "bridges": [
      {
        "id":                     "arbitrum-ethereum/cctp",
        "endpoints":              ["ethereum", "arbitrum"],
        "type":                   "cctp",
        "state":                  "BS1",
        "calibrated":             true,
        "status":                 "OK",
        "observed_at":            "2026-05-04T12:28:00Z",
        "window":                 "1h",
        "circle_api_status":      "OK"
      }
      // + base-ethereum/cctp, optimism-ethereum/cctp, plus CCIP entries with calibrated:false (raw observability until throughput sustained)
    ]
  },

  "coverage": {
    "l1_chains":       ["ethereum"],
    "l2_chains":       ["arbitrum", "base", "optimism"],
    "bridges_ccip":    10,
    "bridges_ccip":    10,
    "bridges_cctp":    10,
    "include_mode":    "diagnostic",                       // echoes the ?include= query param
    "methodology_url": "https://invarians.com/methodology"
  },

  // HMAC-SHA256 over canonical JSON of everything above. anchor populated in v2.x with the on-chain Merkle anchor.
  "signed_execution_context": {
    "payload_hash": "0x84f549ab8c50cdf609a2a84bae3d23d37fc1496de23acddaebe4d78c6080b268",
    "signature":    "hmac-sha256:8ad60a8c399a1f779056fca4005841aa9257b9cf721da49a4a68c7173ac76a06",
    "key_id":       "invarians-v1",
    "anchor":       null
  }
}
Three include modes trim or extend the metric block per ?include= param :
  • core (default) : each metric exposes only ratio (+ epoch/seconds for beacon/sequencer)
  • diagnostic : adds ratio_long, shift, shift_delta, shift_magnitude_delta per metric
  • full : adds raw EMA baselines (baseline_short, baseline_long) for research and external calibration

The v2.0 drift composite is exposed for backward compatibility regardless of mode. It is flagged deprecated_unvalidated in v3 and replaced by the per-chain precursors[] array (see Consuming precursors).

Per-item status: "OK" fresh and calibrated · "STALE" no fresh observation in the item's window · "UNAVAILABLE" no data at all · "UNCALIBRATED" raw signals exposed, no regime classification yet (chains pending July 2026 for SOL/AVAX, bridges CCIP/CCTP pending P3).

Global oracle_status: "OK" if every item is OK or UNCALIBRATED, else "DEGRADED". UNCALIBRATED is not an error, it is a known in-progress state.

The shift_available: false flag appears on metrics whose slow EMA is still stabilizing upstream (Ethereum beacon_participation and L2 sequencer_publish_latency during the v2.0 launch period). The flag flips to true automatically once the long EMA is operational, no API version change required.
State Reference

The four base codes below classify chains against an upper threshold on each axis. On chains with calibrated lower bounds (since 2026-04-29: ETH, POL on L1; ARB, BASE, OP on L2), the API also emits signed regime codes with explicit direction suffixes (+, -, ±). The four base codes remain valid as aliases for chains without lower bounds yet calibrated. See foundations §03b for the full extended grid.

S1D1
structure ≈1 · demand ≈1
structural nominal rhythm · normal continuity demand nominal, within historical baseline
S1D2
structure ≈1 · demand >1
structural nominal rhythm · normal continuity demand elevated, above historical percentiles
S2D2
structure >1 · demand >1
structural slowed rhythm · degraded continuity demand elevated, both layers converge
S2D1
structure >1 · demand ≈1
structural slowed rhythm · degraded continuity demand nominal, invisible to fee monitors
State Condition Typical infrastructure context
S1D1 Structural and demand layers within historical baseline Both layers within historical norms. Standard operating conditions.
S1D2 Demand elevated beyond calibrated historical percentiles Elevated demand relative to historical baseline.
S2D2 Structural stress AND demand elevated simultaneously Both layers above historical baseline simultaneously. Structural strain coinciding with elevated demand.
S2D1 Structural stress WITHOUT elevated demand Structural divergence without corresponding activity conditions. Invisible to fee-based monitors.
Signed regime codes, since 2026-04-29

On chains with calibrated lower bounds, each axis carries an explicit direction suffix. Total: 12 codes on L1 and 12 codes on L2 (since v2.0, L2 also reaches S2 thanks to sequencer_publish_latency). Parse with a regex on the regime field, branch on the suffix.

Code Axis · Direction Meaning
D1 demand · nominal All demand ratios within their two-sided bounds.
D2+ demand · high At least one ratio above its high threshold, none below. Legacy D2 alias.
D2- demand · low (starvation) At least one ratio below its low threshold, none above. Sequencer slowing user-side, censorship of small transfers, mempool gel.
D2± demand · composition split At least one ratio above AND at least one below. Composition asymmetry, agentic concentration signature (rsETH 2026-04-18 cascade was D2±).
S1 structure · nominal Rhythm and continuity within their two-sided bounds.
S2+ structure · slowed Rhythm slows abnormally, blocks taking longer than baseline. Legacy S2 alias.
S2- structure · accelerated Rhythm accelerates abnormally, blocks coming faster than baseline. Disabled on L2 rollups (rhythm too tight to be informative).

Active chains as of 2026-04-29: ETH, POL on L1 (event-based + statistical lows); ARB, BASE, OP on L2 (multi-dim demand statistical). SOL and AVAX remain on the legacy 4-state grid until July 2026 calibration. Until per-chain activation, the API emits the four-state grid as today, additive and non-breaking.

oracle_status: "OK" → all panel items within their freshness window · "DEGRADED" → at least one L1/L2/bridge item is STALE or UNAVAILABLE. UNCALIBRATED items do not trigger DEGRADED.

Per-item freshness: each entry in panel.l1[] · panel.l2[] · panel.bridges[] carries its own status and timestamp (computed_at for L1/L2, 1h window; observed_at for bridges, 10m window). Always inspect the specific items you consume, not only oracle_status.
MetricBlock & Trend Reading
Delta calibration is chain-type-exclusive — V3 design documented

Two corpora were tested in 2025: ETH-ARB-CCTP and ETH-OP-CCTP. A 648-configuration grid with Benjamini-Hochberg FDR correction on each corpus produces validated precursor configurations on each chain (six on ARB with lift 1.53 to 2.36x, one on OP with lift 3.72x). Each set is disjoint from the other, and configurations transferred from one corpus to the other do not hold (ARB survivors fail on OP; the OP survivor fails on ARB). The empirical reading is that Delta calibration is chain-type-exclusive: each chain warrants its own discovery and validation. The v3 redesign replaces the composite Delta block with an explicit precursors[] array scoped per chain, with calibration metadata and cross-chain status carried in the payload. See the full analysis in the research note: Delta calibration is chain-type-exclusive: ETH-ARB-CCTP and ETH-OP-CCTP, 2025. The v2.0 composite remains exposed for now without a validated orientation claim; per-metric shift, shift_delta, shift_magnitude_delta remain raw inputs the agent can use freely.

The Regime classification answers what is the substrate doing now. The Delta answers is the deviation amplifying or reverting. Each classifying observable is exposed as a self-contained MetricBlock with the current ratio, the long-term reference, the deviation magnitude, and two trend signals.

{
  "ratio":                 1.094,    // short EMA (~10h) vs short baseline
  "ratio_long":            0.991,    // long EMA (~30d) vs long baseline
  "shift":                 0.103,    // = ratio - ratio_long, deviation magnitude (signed)
  "shift_delta":           0.016,    // = shift_now - shift_prev, raw direction of value movement
  "shift_magnitude_delta": 0.016     // = |shift_now| - |shift_prev|, deviation growing or shrinking
}
Two trend signals, complementary:

shift_delta tells you the direction the value moved between cycles (positive = up, negative = down).
shift_magnitude_delta tells you whether the deviation is amplifying or shrinking (positive = regime persists or worsens, negative = reverting toward nominal).

The two values typically agree, except when shift crosses zero between cycles (regime transition through D1/S1 nominal).

Reading rules

Situation shift shift_magnitude_delta Lecture agent
Demand surge accelerating +0.15 +0.07 D2+ persists or worsens, prepare for sustained gas competition
Demand surge plateau +0.15 ≈ 0 D2+ stable, regime sticky
Demand surge reverting +0.15 −0.07 D2+ ending, return to D1 within hours likely
Starvation worsening −0.10 +0.05 D2- deepens, depressed demand persists
Starvation recovering −0.10 −0.05 D2- ending, return to D1 likely

Each chain entry exposes a composite drift summary computed server-side. The block below is what the API returns today on /v2/panel. The empirical reading below the table is an initial heuristic that did not pass independent validation on the 2025 corpora (see the research note). The v3 design that replaces this block with calibrated precursors per chain is documented in the next subsection.

"drift": {
  "structural":                 0.0017,   // max |shift| over structural observables
  "structural_delta":           0.0008,
  "structural_magnitude_delta": 0.0008,
  "demand":                     0.103,    // max |shift| over demand observables
  "demand_delta":               0.016,
  "demand_magnitude_delta":     0.016
}
Magnitude drift.XInitial heuristic reading (v2.0, unvalidated)
< 0.005Substrate stable on this axis, regime expected to persist
0.005 - 0.02Mild drift, transition within hours possible
0.02 - 0.05Active drift, vigilance recommended
> 0.05Transition probable on a multi-hour horizon
Pre-shift period for new observables: when an observable is newly added to the structural axis (Beacon Chain participation on Ethereum, sequencer publish latency on L2), the slow EMA needs ~30 days to stabilize. During that period, the MetricBlock includes "shift_available": false and the trend fields are absent. Once stabilized, the flag flips to true automatically without API version change.

The thresholds above are initial heuristics, not validated as orientation signal. Independent testing across 648 configurations with Benjamini-Hochberg FDR correction on two corpora (ETH-ARB-CCTP and ETH-OP-CCTP, 2025) showed that the composite drift block does not carry a validated agent-orientation signal in this aggregation. The validated configurations are exposed as per-chain precursors in v3.

In v3 the composite drift block is replaced by a precursors[] array on each L1 and L2 entry. Each element is a single calibrated configuration that survived a 648-configuration Benjamini-Hochberg FDR exploration on its discovery corpus. The element carries everything the agent needs to act: the boolean fire flag, the current value of the signal, the calibration metadata (axis, threshold, lead, outcome, lift, precision), and the cross-chain test status. There is no composite Delta score and no aggregation across chains: an agent reads the precursors that belong to the chain it is acting on, applies its own decision policy, and routes accordingly.

"l2": [
  {
    "chain": "arbitrum",
    "regime": "S1D1",
    "structural": { ... },
    "demand":     { ... },
    "precursors": [
      {
        "axis":                 "arb_struct_seq_publish_latency_shift",
        "fires":                false,
        "current_smd":          0.080,
        "smd_threshold_value":  0.2258,
        "k_consecutive_hours":  2,
        "pctl_threshold":       0.90,
        "lead_hours":           6,
        "outcome_category":     "latency_high_only",
        "bridge_corridor":      "ETH-ARB-CCTP",
        "baseline_lift":        1.913,
        "baseline_p_adj":       0.023,
        "baseline_precision":   0.439,
        "baseline_alert_rate":  0.0057,
        "cross_chain_status":   "FAIL_on_optimism",
        "cross_chain_lift":     0.000,
        "cross_chain_placebo_p": 1.000,
        "calibrated_at":        "2026-05-19T00:00:00Z"
      },
      // 5 other precursors on arbitrum, one per calibrated configuration
    ]
  }
]

The user does not configure thresholds. The calibration values (smd_threshold_value, k_consecutive_hours, pctl_threshold, lead_hours) are derived by Invarians from the discovery corpus and seeded server-side. The agent only chooses its own decision policy on top of fires and the metadata.

FieldMeaning · how the agent uses it
firesBoolean. true when the current single-hour shift_magnitude_delta on axis meets or exceeds smd_threshold_value. To confirm a full K-consecutive condition the agent reads two successive panel responses and treats the configuration as fully engaged when both report fires=true. null means the upstream signal is unavailable this cycle.
current_smdThe signal value the precursor reads at this cycle. Useful for the agent to compute its own K-consecutive logic or to log proximity to threshold.
lead_hoursHow far ahead of the outcome this configuration predicts. An agent acting now reads this as the horizon over which it should defer or route differently.
outcome_categoryWhat this precursor predicts: latency_high_only, bs2_only, bridge_stress_full, or a directional bridge_<src>_to_<dst>.
bridge_corridorCorridor on which the outcome was evaluated. If the agent is routing on a corridor not covered by any precursor, the precursors[] array carries no actionable entry for that corridor.
baseline_liftLift achieved on the calibration corpus (precision divided by base rate). 1.5x means the precursor's precision is 50% above the unconditional outcome rate. Use this as the agent's gating threshold.
baseline_precisionPrecision on the calibration corpus. When fires=true, expect the outcome at this rate over the next lead_hours.
baseline_alert_rateFraction of hours the precursor fired on the calibration corpus. Use this to size the expected alert volume.
cross_chain_statusNOT_TESTED, PASS_on_<chain>, or FAIL_on_<chain>. Carries the result of testing this exact configuration on another chain's corpus. FAIL_on_X means the configuration is valid on its own chain but did not generalize to X.

The same payload supports several decision policies. The choice is the agent's, not Invarians'. Below are three reference policies in increasing order of permissiveness.

# Suitable for RWA settlement with low alert tolerance.
# Defer only on precursors whose calibration also held on another chain.
# With the 2025 ETH-ARB-CCTP and ETH-OP-CCTP corpora, no precursor
# currently passes this filter (all carry FAIL_on_X). Policy A is
# aspirational: it documents the bar a future precursor must clear
# to be acted on in this mode.

for p in panel.l2_by_chain("arbitrum").precursors:
    if p.is_firing and p.baseline_lift >= 2.0 and p.cross_chain_held:
        agent.defer(
            reason=f"strict precursor {p.axis} fired",
            horizon_hours=p.lead_hours,
        )
# Defer when any precursor on the relevant chain fires with lift >= 1.5.
# No cross-chain gating: the configuration is valid on the chain
# the agent is acting on, which is what matters operationally.
# This is the recommended default for most agent designs in 2025.

for p in panel.l2_by_chain("arbitrum").precursors:
    if p.is_firing and p.baseline_lift >= 1.5:
        agent.defer(
            reason=f"{p.axis} expects {p.outcome_category} in {p.lead_hours}h",
            expected_precision=p.baseline_precision,
            corridor_scope=p.bridge_corridor,
        )
# Flag any precursor with lift above unconditional baseline (lift > 1).
# Higher alert volume, lower precision per alert.
# Suitable for monitoring or human-in-the-loop scenarios where false
# positives are cheap and missed signals are expensive.

for p in panel.l2_by_chain("arbitrum").precursors:
    if p.is_firing and p.baseline_lift > 1.0:
        agent.flag(p)  # surface to operator, do not auto-defer

The payload exposes a single-hour fire check. If the calibration says k_consecutive_hours: 2, an agent that wants the full K-consecutive condition reads two consecutive panel responses (or polls at 1h intervals) and treats the configuration as fully engaged only when both reports return fires=true. The agent can implement this with a small state buffer:

from collections import deque

fire_history = deque(maxlen=2)  # for K=2; widen for larger K

def on_panel_response(panel):
    p = next(
        (p for p in panel.l2_by_chain("arbitrum").precursors
         if p.axis == "arb_struct_seq_publish_latency_shift"
         and p.lead_hours == 6),
        None,
    )
    if p is None:
        return
    fire_history.append(p.is_firing)
    if len(fire_history) == p.k_consecutive_hours and all(fire_history):
        agent.defer(reason="K-consecutive condition met", horizon_hours=p.lead_hours)

v3 removes the drift.* composite block from each entry. Per-metric shift, shift_delta, shift_magnitude_delta remain exposed on every MetricBlock as raw inputs. v3 is announced one release cycle in advance with the v2 drift block flagged deprecated_unvalidated: true on each entry root. Integrations relying on drift.demand_magnitude_delta > threshold migrate to iterating on precursors[] with one of the three policies above.

Field Reference
Field Description Notes
panel.l1[].regime · panel.l2[].regime Current certified regime per chain 4 base codes (S1D1 / S1D2 / S2D2 / S2D1) plus 11 signed codes (S1D2+, S1D2-, S1D2±, S2+D1, S2-D1, S2+D2±, S2-D2±, etc.) on chains with calibrated lower bounds since 2026-04-29. Parse with a regex on the structure prefix (S1, S2+, S2-) and demand prefix (D1, D2+, D2-, D2±).
panel.bridges[].state Current certified state per bridge Unified BS1 / BS2 across all variable-latency bridges (CCIP, CCTP, future fast bridges). The type field on the bridge entry distinguishes the protocol. CCTP routes carry preliminary BS1/BS2 since 2026-05-04. CCIP lanes remain state:null with calibrated:false until sustained throughput emerges.
oracle_status Global panel status OK / DEGRADED
panel.{l1,l2,bridges}[].status Per-item freshness/availability OK / STALE / UNAVAILABLE / UNCALIBRATED
panel.l1[].computed_at · panel.l2[].computed_at Timestamp of last L1/L2 computation (~1h window) ISO 8601 UTC
panel.bridges[].observed_at Timestamp of last bridge observation (~10m window) ISO 8601 UTC
panel.l1[].structural · panel.l2[].structural Axis object grouping the structural observables (rhythm, continuity, plus beacon_participation on ETH or sequencer_publish_latency on L2) Each child is a MetricBlock
panel.l1[].demand · panel.l2[].demand Axis object grouping the demand observables (sigma, size, tx, plus complexity and gas_complexity on L2) Each child is a MetricBlock
{axis}.{observable}.ratio Short-term EMA ratio. Calibrated index, ≈1.0 nominal core mode and above
{axis}.{observable}.ratio_long Long-term EMA ratio (~30-day baseline) diagnostic mode and above
{axis}.{observable}.shift ratio - ratio_long. Current deviation magnitude, signed diagnostic mode and above
{axis}.{observable}.shift_delta shift_now - shift_prev. Raw direction of value movement between cycles diagnostic mode and above
{axis}.{observable}.shift_magnitude_delta |shift_now| - |shift_prev|. Whether the deviation is growing or shrinking diagnostic mode and above
{axis}.{observable}.shift_available false during the pre-shift period when the long EMA is not yet stabilized for a newly added observable diagnostic mode and above
{axis}.{observable}.baseline_short · baseline_long Raw EMA values full mode only
structural.beacon_participation.epoch Current Beacon Chain epoch Ethereum only, all modes
structural.sequencer_publish_latency.seconds Raw observed sequencer publish latency in seconds L2 only, all modes
panel.l1[].drift · panel.l2[].drift v2.0 composite drift per axis: structural, structural_delta, structural_magnitude_delta, demand, demand_delta, demand_magnitude_delta. Unvalidated as orientation signal in 2025 corpora. core mode and above. Removed in v3, replaced by precursors[].
panel.l1[].precursors · panel.l2[].precursors v3 PREVIEW Array of calibrated precursor configurations scoped to this chain. Each element carries axis, fires, current_smd, k_consecutive_hours, pctl_threshold, smd_threshold_value, lead_hours, outcome_category, bridge_corridor, baseline_lift, baseline_p_adj, baseline_precision, baseline_alert_rate, cross_chain_status, cross_chain_lift, cross_chain_placebo_p, calibrated_at. v3 design, deployment pending. See the Consuming precursors subsection.
signed_execution_context HMAC envelope: payload_hash, signature, key_id, anchor Same on every panel response
version Panel API version, e.g. "2.0.0" Negotiable via Accept-Version header
issued_at When this panel snapshot was assembled ISO 8601 UTC
Agent Integration Pattern
Principle: Invarians certifies execution state, it does not prescribe actions. Interpretation and execution logic remain entirely with your agent.
# Python, single-chain evaluation from the panel
def evaluate_chain(panel: dict, chain: str) -> str:
    # First: global panel freshness
    if panel["oracle_status"] != "OK":
        return "PANEL_DEGRADED"

    l1 = next((e for e in panel["panel"]["l1"] if e["chain"] == chain), None)
    l2 = next((e for e in panel["panel"]["l2"] if e["chain"] == chain), None)
    entry = l1 or l2

    if entry is None or entry["status"] != "OK":
        return "CHAIN_UNAVAILABLE"

    return entry["regime"]   # "S1D1" | "S1D2" | "S2D2" | "S2D1" | signed code (S1D2+, S1D2-, S1D2±, S2+D1, S2-D2±, ...)

import re
SIGNED_PATTERN = re.compile(r"^(S1|S2[+-])(D1|D2[+\-±])$")

def parse_regime(code):
    # Returns (structure_axis, demand_axis) where each is the prefix-stripped direction.
    # Legacy 4-state codes are mapped to their topological equivalent.
    legacy = {"S1D1": ("S1",  "D1"),
              "S1D2": ("S1",  "D2+"),
              "S2D1": ("S2+", "D1"),
              "S2D2": ("S2+", "D2+")}
    if code in legacy: return legacy[code]
    m = SIGNED_PATTERN.match(code)
    return (m.group(1), m.group(2)) if m else (None, None)

# Your agent decides, Invarians only reads the substrate
panel = client.get_panel_v2(chains=["ethereum"], include="core")
state_code = evaluate_chain(panel, "ethereum")
struct, demand = parse_regime(state_code)

if struct == "S1" and demand == "D1":
    handle_nominal()
elif demand == "D2+":
    handle_demand_high()                   # elevated demand, organic surge or coordinated buying
elif demand == "D2-":
    handle_demand_starvation()             # depressed demand, sequencer slowing or censorship of small transfers
elif demand == "D2±":
    handle_composition_split()             # size/tx asymmetry, agentic concentration signature
elif struct == "S2+":
    handle_structural_slowdown()           # rhythm degraded, sequencer or validator-side
elif struct == "S2-":
    handle_structural_acceleration()       # rhythm faster than baseline, infra change or client optimisation
else:
    fallback_behavior()                    # panel degraded, chain unavailable, or regime not yet calibrated
// JavaScript, multi-chain check from a single panel fetch
const checkAllChains = async () => {
  const panel = await getPanel();
  const all = [...panel.panel.l1, ...panel.panel.l2];

  return all.reduce((acc, entry) => {
    acc[entry.chain] = entry.status === "OK"
      ? { regime: entry.regime, status: entry.status }
      : { regime: null, status: entry.status };   // STALE | UNAVAILABLE | UNCALIBRATED
    return acc;
  }, {});
};
# Python, cross-chain agent pattern, panel API
panel = get_panel()

if panel["oracle_status"] == "DEGRADED":
    fallback()

l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")

# Your agent applies its own policy
if l1["regime"].startswith("S1") and br["state"] == "BS1":
    proceed()
elif br["status"] == "UNCALIBRATED":
    # Bridge classification deferred (CCIP), raw signal available in br["last_sequence_advance_seconds"]
    proceed_with_observation_only()
else:
    defer_execution()
On-Chain Execution Context

Invarians produces three distinct primitives per query.

Primitive 1, Attestation: every panel response carries a HMAC-SHA256 envelope (payload_hash + signature + key_id + anchor) covering the full payload. Independently verifiable via POST /v2/verify. This is what makes the certified execution state provable, not just observable.

The execution policy is not prescribed. The agent reads the certified state and applies its own thresholds, risk tolerance, and fallback logic. Invarians certifies the infrastructure state, the decision belongs to the agent.

{
  "action": "execution_deferred",
  "reason": "L1 structural stress detected, ethereum regime S2D1",
  "invarians_proof": {
    // Excerpt of the panel entries relevant to the decision
    "l1":     { "chain": "ethereum", "regime": "S2D1", "status": "OK" },
    "l2":     { "chain": "arbitrum", "regime": "S1D1", "status": "OK" },
    "bridge": { "id": "arbitrum-ethereum/cctp", "state": "BS1", "calibrated": true },
    "signed_execution_context": {
      "payload_hash": "0x714b64...",
      "signature":    "hmac-sha256:9b5b1d...",
      "key_id":       "invarians-v1",
      "anchor":       null
    },
    "issued_at": "2026-04-20T10:00:00Z"
  }
}
This is what distinguishes a structural attestation from a monitoring signal. The signature makes the execution context provable, not just observable.
Surveillance topology

Invarians tracks blockchain behavior under AI agent load and defines safe execution windows. Current monitoring scope:

Layer Coverage
L1 Ethereum, Solana, Polygon, Avalanche
L2 (Ethereum-anchored) Arbitrum, Base, Optimism
Chainlink CCIP lanes Bidirectional, L1 ↔ L1 and L1 ↔ L2
Circle CCTP routes Bidirectional, L1 ↔ L1 and L1 ↔ L2

Bridge classification scope is variable-latency surfaces only (Chainlink CCIP, Circle CCTP). Institutional RWA settlement moves stablecoins via CCTP and tokens via CCIP, both bidirectional.

Per-Chain Coverage
Chain Structural axis Demand axis 3rd structural observable Calibration
Ethereum rhythm + continuity sigma (gas) + size + tx beacon_participation (epoch + ratio live, threshold pending) 12 signed codes (S2- on beacon pending)
Polygon rhythm + continuity sigma (gas) + size + tx 12 signed codes since 2026-04-29
Avalanche rhythm + continuity sigma (gas) + size + tx 4 base codes (12 signed scheduled July 2026)
Solana rhythm + continuity size + tx (user only) UNCALIBRATED (calibration scheduled July 2026)
L2 ROLLUPS, 3rd structural axis = sequencer_publish_latency (batch gap on L1)
Arbitrum rhythm + continuity sigma + size + tx + complexity + gas_complexity sequencer_publish_latency ✓ calibrated (600 s halt threshold) 12 signed codes since 2026-05-01
Base rhythm + continuity sigma + size + tx + complexity + gas_complexity sequencer_publish_latency ✓ calibrated (480 s halt threshold) 12 signed codes since 2026-05-01
Optimism rhythm + continuity sigma + size + tx + complexity + gas_complexity sequencer_publish_latency ✓ calibrated (1800 s halt threshold) 12 signed codes since 2026-05-01

Delta (Primitive 3) shift per metric is computed against the long-term EMA (~30-day baseline). For newly added structural observables (beacon_participation on ETH, sequencer_publish_latency on L2), shift_available: false until the slow EMA stabilizes (~30 days post-launch). The regime trigger uses raw ratio or seconds against calibrated bounds during this period.

Errors
401  Unauthorized    , invalid or missing API key
403  Forbidden       , key does not have access to this chain
404  Not Found       , no signal available for this chain
429  Too Many Requests, rate limit exceeded
500  Server Error    , internal error, retry after 60s
Signature Verification
Every panel payload includes a cryptographic signature ("hmac-sha256:...") under signed_execution_context.signature. The signature covers a canonical JSON serialization of the entire payload with signed_execution_context stripped. Any agent can independently verify that a panel was issued by Invarians and has not been altered in transit.

Use the /verify endpoint, no additional secret required, only your API key.

# POST /v2/verify
# Returns: { "valid": true|false, "key_id": "invarians-v1", "version": "2.0.0" }

import requests

# 1. Fetch a panel
r = requests.get(
    "https://api.invarians.com/v2/panel",
    headers={"Authorization": "Bearer YOUR_API_KEY"}
)
panel = r.json()
sig   = panel["signed_execution_context"]["signature"]

# 2. Verify the signature independently
#    Send the panel with signed_execution_context stripped + the signature.
payload = {k: v for k, v in panel.items() if k != "signed_execution_context"}

verify = requests.post(
    "https://api.invarians.com/v2/verify",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"payload": payload, "signature": sig}
)
print(verify.json())
# → { "valid": true, "key_id": "invarians-v1", "version": "2.0.0" }
What "valid": true guarantees
The panel was produced by Invarians infrastructure and has not been modified in transit. It does not guarantee the freshness of the underlying chains. Always check per-item status ("OK" / "STALE" / "UNAVAILABLE") and the global oracle_status ("OK" / "DEGRADED") before acting on a signal.

Key rotation & roadmap
key_id identifies the current HMAC key (invarians-v1). V1.1+ populates signed_execution_context.anchor with an on-chain Merkle anchor on Arbitrum, adding a second independent verification path.
Handling STALE Signals
Every panel payload attaches a per-item status and a per-item timestamp (computed_at for L1/L2, observed_at for bridges). A global oracle_status summarizes the panel. A signal is marked STALE when its freshest observation is older than the item's window.

Important: STALE does not mean the regime has changed, it means the current regime is unknown. Structural regimes (L1/L2) change slowly (~3h). A signal that became STALE 30 minutes ago is likely still valid, but carries uncertainty.
Tier Panel field Window STALE threshold Why different
Tier 1, L1 / L2 panel.l1[].status · panel.l2[].status window: "1h" now - computed_at > 3600s Regimes are integrals, change slowly (~3h)
Tier 2, Bridge panel.bridges[].status window: "10m" now - observed_at > 900s Bridge is a liveness snapshot, changes instantly
Item age status Confidence Recommended behavior
L1/L2 < 3600s · Bridge < 900s OK (or UNCALIBRATED for bridges) Full Execute as per regime/state, signal is fresh
L1/L2 3600s – 7200s STALE Degraded Use last known regime with caution. Regime likely unchanged (structural change takes ~3h). Log the staleness. Avoid initiating new high-value positions.
L1/L2 > 7200s · Bridge > 900s STALE or UNAVAILABLE Unknown State unknown. For time-sensitive or low-value operations, proceed at agent's discretion with explicit staleness flag in the decision log.
Bridge signals measure liveness (instantaneous snapshot, no hysteresis). A stale bridge signal carries more uncertainty than a stale L1/L2 signal. When a bridge status is STALE or UNAVAILABLE, treat cross-chain execution as unknown infrastructure state and defer.

When status: "UNCALIBRATED", state is null but the per-message signal is exposed for observation (metrics.execute_latency_p90_s, message counts, etc.). This applies when sustained throughput has not yet allowed P97 baseline calibration on that surface.
# Complete STALE handling, Python, panel API

from datetime import datetime, timezone

import requests

r = requests.get(
    "https://api.invarians.com/v2/panel",
    headers={"Authorization": "Bearer YOUR_API_KEY"}
)
panel = r.json()

def age_seconds(iso: str) -> float:
    return (datetime.now(timezone.utc) - datetime.fromisoformat(iso.replace("Z", "+00:00"))).total_seconds()

l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")

l1_age     = age_seconds(l1["computed_at"])
bridge_age = age_seconds(br["observed_at"]) if br["observed_at"] else 9999

if l1["status"] == "OK" and br["status"] in ("OK", "UNCALIBRATED"):
    # Fresh. Apply your own policy.
    if l1["regime"].startswith("S1") and br["state"] == "BS1":
        proceed()
    elif br["status"] == "UNCALIBRATED":
        proceed_with_observation_only()
    else:
        defer_execution()

elif l1["status"] == "STALE" and l1_age <= 7200:
    log_warning(f"L1 stale ({int(l1_age)}s), regime likely unchanged: {l1['regime']}")

else:
    log_warning(f"Infrastructure state unknown, deferring, global: {panel['oracle_status']}")

# Always log the full item state in the decision record
decision_log = {
    "oracle_status": panel["oracle_status"],
    "l1":            {"chain": l1["chain"], "regime": l1["regime"], "status": l1["status"], "computed_at": l1["computed_at"]},
    "bridge":        {"id": br["id"], "state": br["state"], "calibrated": br["calibrated"], "status": br["status"]},
    "issued_at":     panel["issued_at"],
    "payload_hash":  panel["signed_execution_context"]["payload_hash"],
}
Migration v1.1.0 → v2.0
v2.0 launched 2026-04-30. v1.1.0 endpoints (/attestation/panel, /attestation/verify) remain live for a 60-day grace period and will sunset on 2026-06-30. Migrate before that date.
v1.1.0v2.0Status
GET /attestation/panel GET /v2/panel Live until 2026-06-30, then 410 Gone
POST /attestation/verify POST /v2/verify Live until 2026-06-30, then 410 Gone
v1.1.0 (flat)v2.0 (axis-grouped)Reason
panel.l1[].rhythm_ratio panel.l1[].structural.rhythm.ratio Axis-grouped layout (structural / demand)
panel.l1[].sigma_ratio panel.l1[].demand.sigma.ratio Axis-grouped layout
panel.l1[].structural_slow.rhythm_ratio_slow panel.l1[].structural.rhythm.ratio_long Naming consistency (long > slow)
panel.l1[].shifts.rhythm_shift panel.l1[].structural.rhythm.shift Co-located with the metric it derives from
(no demand shifts exposed) panel.l1[].demand.{sigma,size,tx}.shift Symmetric exposure on all classifying observables
(no trend signal) shift_delta + shift_magnitude_delta per metric Primitive 3, drift trend (raw + magnitude)
(no L2 sequencer obs in regime) panel.l2[].structural.sequencer_publish_latency 3rd structural axis on L2 (sequencer halt detection)
(no Ethereum beacon in regime) panel.l1[].structural.beacon_participation (ETH only) Beacon Chain participation enters S axis on Ethereum
panel.l1[].execution_profile.index_a/b/c Removed, replaced by demand.{sigma,size,tx}.ratio Named fields are clearer than alphabetical indices
(no drift composite) panel.l1[].drift.{structural,demand} + delta + magnitude_delta Primitive 3 summary per axis
# v1.1.0 client (deprecated)
from invarians import InvariansClient
client = InvariansClient(api_key="inv_...")
panel = client.get_panel()
eth = panel.l1_by_chain("ethereum")
print(eth.regime, eth.shifts.rhythm_shift)

# v2.0 client (recommended)
panel = client.get_panel_v2(
    chains=["ethereum"],
    bridges=["cctp"],
    include="diagnostic",
)
eth = panel.l1_by_chain("ethereum")
arb = panel.l2_by_chain("arbitrum")
print(eth.regime, eth.structural.rhythm.shift)
for p in arb.precursors:                       # v3 per-chain calibrated precursors
    print(p.axis, p.is_firing, p.baseline_lift)
print(eth.demand.tx.is_drifting_away)            # trend helper on raw shift
Both clients can coexist in the same agent: get_panel() still works against the v1.1.0 endpoint until sunset. After 2026-06-30, only get_panel_v2() will resolve.
  1. Upgrade SDK : pip install --upgrade "invarians>=0.5.0"
  2. Replace client.get_panel(...) with client.get_panel_v2(chains=..., bridges=..., include=...)
  3. Update field accessors : eth.structural.rhythm_ratioeth.structural.rhythm.ratio, eth.shifts.rhythm_shifteth.structural.rhythm.shift
  4. If your agent uses execution_profile.index_a/b/c, switch to demand.sigma.ratio / demand.size.ratio / demand.tx.ratio
  5. If your agent uses long-term EMA: structural_slow.rhythm_ratio_slowstructural.rhythm.ratio_long
  6. Always specify chains= and bridges= filters (see Filters & Best Practices) — default behavior is to return all entries
  7. Replace verify_panel() with verify_panel_v2()
  8. Optional: consume the new drift composite and shift_magnitude_delta per-metric trends to make fitness-for-action decisions (see Primitive 3)
API Versioning Policy
Current version: v2.0.0 (panel API), exposed as "version": "2.0.0" on every panel response. v1.1.0 endpoints (/attestation/panel, /attestation/verify) remain live until 2026-06-30 then return 410 Gone.

Breaking changes are announced a minimum of 30 days in advance via a Deprecation header in API responses and an entry in the changelog below.
Non-breaking changes (new optional fields, new chain coverage, new bridge types) are additive and require no notice.
Change type Classification Example
Field renamed or removed Breaking structural.rhythm_ratio (flat) → structural.rhythm.ratio (axis-grouped, v1.1.0 → v2.0)
Field semantics changed Breaking shift redefined relative to a different baseline
Endpoint URL changed Breaking /attestation/panel/v2/panel (v1.1.0 → v2.0)
New optional field added Non-breaking structural.beacon_participation added for ETH only
New chain added Non-breaking New L2 endpoint deployed alongside existing ones
When a breaking change is scheduled, responses include:

Deprecation: date="2026-06-01"
Sunset: date="2026-06-01"

These headers appear at least 30 days before the change takes effect. Parse them in your integration to detect upcoming breaking changes programmatically.

Security-critical fixes may be applied immediately, in that case the header is present with the shortest possible notice and the changelog entry is dated retroactively.
Version Date Changes
v2.0.0 April 30, 2026 Three Primitives, axis-grouped payload (breaking) · Payload restructured by axis (structural · demand) per chain, with a MetricBlock per observable carrying ratio, ratio_long, shift, shift_delta, shift_magnitude_delta · beacon_participation promoted to a 3rd structural classifying observable on Ethereum (S2- on low-side participation drops) · sequencer_publish_latency promoted to a 3rd structural classifying observable on all L2s (S2+ on sequencer halts) · Demand axis gains shifts on sigma, size, tx (and complexity · gas_complexity on L2) · New composite drift object per axis with magnitude + raw delta + magnitude delta · Tiered exposure via ?include=core|diagnostic|full · Endpoints: GET /v2/panel, POST /v2/verify. v1.1.0 endpoints remain live until 2026-06-30 then return 410 Gone · Python SDK bumped to invarians>=0.5.0 with get_panel_v2(), verify_panel_v2(), MetricBlock dataclass and trend helpers
v1.1.0 April 29, 2026 Long-term EMA + shifts (additive, non-breaking) · Per-entry structural_slow object exposing rhythm_ratio_slow and continuity_ratio_slow (~30-day baseline) on L1 and L2 entries · Per-entry shifts object exposing rhythm_shift and continuity_shift (delta between short-term ~10h and long-term ~30d EMAs) · Calibration thresholds extracted from server-side code into dedicated Postgres tables (l1_thresholds, l2_thresholds) for transparency · Old v1.0.0 clients keep working unchanged · Python SDK bumped to invarians>=0.3.0 with StructuralSlow and Shifts dataclasses
v1.0.0 April 2026 Panel API (breaking) · Directional /attestation/execution-context?from=&to=, single-chain /attestation/{chain}, and L2 /attestation/l2/{chain} endpoints return 410 Gone · Replaced by GET /attestation/panel returning the full L1/L2/bridge panel in one signed payload · Direction is composed client-side by the agent (from / to no longer accepted) · proof_of_execution_context renamed to signed_execution_context (payload_hash + signature + key_id + anchor: null) · Per-item status (OK / STALE / UNAVAILABLE / UNCALIBRATED) and per-item timestamps (computed_at for L1/L2, observed_at for bridges) · Global oracle_status (OK / DEGRADED) · Canonical bridge IDs {chainA}-{chainB}/{type} · coverage block with per-type counts and methodology_url · Cache-Control: public, max-age=60 and ETag · Python SDK bumped to invarians>=0.2.0 with get_panel(), verify_panel(), and helpers
v2 March 2026 demand field replaced by execution_profile with named indices index_aindex_h · L2 endpoints added (/attestation/l2/{chain}) · calibration.version field introduced · divergence_index (F3) added
v1 January 2026 Initial release, structural, state, proof_of_execution_context · execution_window (removed in v2)
Feedback Program