QUARK · SIGNAL API DOCS
Dashboard →

Signal API Documentation

Full reference for the Quark signal feed. Endpoint list, response schemas, rate limits, and — most importantly — how to translate the regime score / crash probability / defensive blend into trades in your own portfolio.

Breaking change — 2026-05-03

Several JSON field names on /api/signals/* responses were renamed to public-friendly equivalents. Old names are no longer returned. Subscribers consuming the API in code must update their field reads.

old → new
instanton_actionbarrier_action
instanton_var_10dbarrier_var_10d
instanton_p_crashbarrier_p_transition
grassmann_distance / grassmannian_distanceeigenframe_rotation
tda_stresstopology_stress
tda_scoretopology_score
tau_decoheigenframe_timescale
condensate_magnitudeorder_parameter
path_sig_template_cospath_template_score
decoherence_raterotation_rate
decoherence_stabilityframe_stability
wavefunction_overlapensemble_overlap

Numeric values, units, and semantics are unchanged — only the JSON keys differ. All other fields (regime_score, crash_prob_*, equity_pct, defensive_pct, snr, barrier_depletion, etc.) are preserved as-is. Questions or migration help: [email protected].

Contents

Authentication, versioning & rate limits

Base URLhttps://api.quarkresearch.cc
API version/api/v1/* is the canonical versioned surface (shipped 2026-04-22). Unversioned legacy paths (/api/signals/current, etc.) still resolve through the deprecation window — responses carry Deprecation: true, Sunset: Thu, 22 Oct 2026 00:00:00 GMT, and Link: </api/v1/...>; rel="successor-version". Every response carries X-API-Version. Pin to /api/v1/ in new integrations.
Auth headerx-api-key: qk_...
Auth query?api_key=qk_... (for cURL convenience; prefer header)
Authenticated quotaMonthly call quota by tier (see table below). No per-minute cap on valid keys.
Unauthenticated limit20 req/min per IP (general), 4 req/min per IP on signal endpoints. Rejected with 429.
Content-Typeapplication/json (CSV endpoint returns text/csv)
Signal refreshDaily post-close; intraday if regime flips or crash-prob crosses threshold
TLSRequired. HTTP requests are rejected.
Uptime target99.5% monthly — see Service Level Agreement
Status pagequarkresearch.cc/status

Per-tier monthly quotas

TierMonthly callsAPI accessNotes
Starter ($5/mo)None (email digest only)Dashboard + email alerts; no programmatic API.
Professional ($49/mo)Dashboard onlyWeb dashboard access; no programmatic API.
Automated ($149/mo)10,000Full read API~333 calls/day — comfortable for single-strategy polling.
Automated Plus ($299/mo)50,000Read API + webhooks + execute~1,600 calls/day — multi-strategy polling + webhook delivery + auto-rebalance.
Institutional (custom)UnlimitedFull API + real-time streamNo cap. X-RateLimit-Limit: -1.
Rate-limit headers. Every authenticated response on a metered endpoint carries X-RateLimit-Limit (monthly quota; -1 for unlimited), X-RateLimit-Remaining, and X-RateLimit-Reset (Unix epoch seconds — aligned to your Stripe billing cycle when available, otherwise UTC calendar month). Back off when Remaining trends low; a 402 Payment Required response means this month's quota is exhausted and will reset at X-RateLimit-Reset.
Exempt paths. Requests from our own terminal/dashboard (Referer header ending in quarkresearch.cc) and localhost are not rate-limited or metered. The /api/signals/usage endpoint itself is never counted against your quota, so dashboards can poll it freely.
Versioning & deprecation discovery. Every response advertises its version in X-API-Version. When you hit a legacy (unversioned) path, we additionally emit three standard discovery headers so your client can migrate automatically: Deprecation: true, Sunset: <RFC 7231 date>, and an RFC 8288 Link header with rel="successor-version". The official Python SDK parses these into client.last_meta.{deprecation, sunset, successor_url} so you can log or alert on them without string-munging headers.

Sandbox trial — evaluate without payment

Before subscribing, you can issue a no-payment 7-day Automated-tier key and exercise the full API surface. Useful for backtest integration, load-shape testing, or just confirming the data lines up with your existing stack.

EndpointPOST /api/trial/start
AuthNone — public endpoint, rate-limited per IP.
Body{"email": "[email protected]"}
ReturnsAPI key (qk_trial_*), dashboard token, expiry timestamp, sample curl.
TierAutomated (10,000 calls/month) — same quota as the paid tier.
Duration7 days from issue. Hard expiry — the key starts returning 401 at the deadline.
IdempotencyRepeat calls with the same email return the original key (no farming for a fresh 7-day window).

Issue a trial key and immediately call the signal endpoint:

curl -sS -X POST https://api.quarkresearch.cc/api/trial/start \
     -H "content-type: application/json" \
     -d '{"email":"[email protected]"}'

# {"api_key":"qk_trial_abc...","trial_expires_at":"...","sample_curl":"..."}

curl -sS https://api.quarkresearch.cc/api/v1/signals/current \
     -H "x-api-key: qk_trial_abc..."
Conversion preserves history. When you upgrade via vendor.html#pricing using the same email address, your dashboard history (calls, webhook deliveries) carries over. A fresh paid key is issued at checkout; the trial key is retired.
What you get reminded. Two days before expiry, we email a reminder with hours-precise countdown and current usage stats. The morning the key flips to expired, we email an upgrade link with the email pre-filled. Both can be opted out via the unsubscribe link in any message.

Quickstart — curl, Python, JS, SDK

Three ways to pull the current regime read. All examples authenticate with a key you get from the dashboard.

1 · curl (one-liner)

curl -sS https://api.quarkresearch.cc/api/signals/current \
  -H "x-api-key: $QUARK_API_KEY" | jq .regime

2 · Python (requests — raw HTTP)

import os, requests

r = requests.get(
    "https://api.quarkresearch.cc/api/signals/current",
    headers={"x-api-key": os.environ["QUARK_API_KEY"]},
    timeout=10,
)
r.raise_for_status()
sig = r.json()
print(f"regime: {sig['regime']['regime_label']} "
      f"(score={sig['regime']['regime_score']:.2f}); "
      f"crash 21d: {sig['tail_risk']['crash_prob_21d']:.1%}; "
      f"asof: {sig['freshness']['asof']} "
      f"({sig['freshness']['staleness_seconds']}s stale)")

3 · JavaScript (fetch)

const res = await fetch('https://api.quarkresearch.cc/api/signals/current', {
  headers: { 'x-api-key': process.env.QUARK_API_KEY }
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const sig = await res.json();
console.log(`${sig.regime.regime_label} (${sig.regime.regime_score.toFixed(2)})`);
console.log(`crash 21d: ${(sig.tail_risk.crash_prob_21d * 100).toFixed(1)}%`);
console.log(`asof: ${sig.freshness.asof}, ${sig.freshness.staleness_seconds}s old`);

4 · Signal history straight into pandas

import os, io, pandas as pd, requests

r = requests.get(
    "https://api.quarkresearch.cc/api/signals/history.csv",
    params={"days": 365},
    headers={"x-api-key": os.environ["QUARK_API_KEY"]},
    timeout=30,
)
r.raise_for_status()
df = pd.read_csv(io.StringIO(r.text), parse_dates=["timestamp_utc"])
df = df.set_index("timestamp_utc").sort_index()
print(df[["regime", "regime_score", "crash_prob_21d"]].tail())

The CSV endpoint is the authoritative point-in-time-stamped export — every row carries a UTC timestamp at the moment the signal was computed, not when you pulled it. Intended for reproducibility and audit of what the signal said at a specific moment.

5 · Register a webhook for state changes

curl -sS -X POST https://api.quarkresearch.cc/api/webhook/register \
  -H "x-api-key: $QUARK_API_KEY" \
  -H "content-type: application/json" \
  -d '{"url": "https://your-server.example/quark-webhook"}'

# then fire a test payload to your endpoint:
curl -sS -X POST https://api.quarkresearch.cc/api/webhook/test \
  -H "x-api-key: $QUARK_API_KEY"

6 · Verify a webhook signature (Python)

import hmac, hashlib, os

def verify(payload_bytes: bytes, header_sig: str) -> bool:
    secret = os.environ["QUARK_WEBHOOK_SECRET"].encode()
    mac = hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, header_sig)

# In your webhook handler:
# raw = request.body  (bytes, not the parsed JSON)
# sig = request.headers["X-Quark-Signature"]
# if not verify(raw, sig): abort(401)
Do not verify against the parsed JSON — always sign the raw request body bytes exactly as they arrived. JSON re-serialization will reorder keys and break HMAC comparison.

7 · Python SDK (pip install quarkresearch)

For Python users, the official client wraps authentication, v1 routing, deprecation-header parsing, quota introspection, and typed exceptions. Single runtime dependency (requests); Python 3.9+. Published to PyPI as quarkresearch and versioned lockstep with the /api/v1 surface.

pip install quarkresearch
from quarkresearch import Client, QuotaExceededError, RateLimitError

client = Client(api_key="qk_live_...")           # or env QUARK_API_KEY

# Current signal — typed dict with the same schema as /api/v1/signals/current
snap = client.signals_current()
print(snap["regime"]["regime_label"], snap["regime"]["regime_score"])

# Every call stamps client.last_meta (ResponseMeta dataclass)
meta = client.last_meta
print(meta.api_version)          # "v1"
print(meta.quota_limit,
      meta.quota_used,
      meta.quota_reset_epoch)    # Stripe-cycle-aligned Unix epoch
if meta.deprecation:
    print(f"Migrate to {meta.successor_url} by {meta.sunset}")

# Reproducibility — what was the signal at 14:30 UTC on 2026-03-14?
historical = client.signals_at("2026-03-14T14:30:00Z")
if historical["available"]:
    # Response is flat: same field layout as signals_current, plus snapshot_timestamp
    print(historical["snapshot_timestamp"],
          historical["regime"]["regime_label"])

# History straight into pandas — no manual CSV parsing
import io, pandas as pd
df = pd.read_csv(io.StringIO(client.signals_history_csv(days=365)),
                 parse_dates=["timestamp_utc"]).set_index("timestamp_utc")
print(df[["regime", "regime_score", "crash_prob_21d"]].tail())

The SDK maps HTTP status codes to a typed exception hierarchy — catch at whatever granularity you want:

import time

try:
    snap = client.signals_current()
except QuotaExceededError:
    # Server has already told us when the quota resets — the SDK reads
    # client.last_meta.quota_reset_epoch and sleeps until then.
    client.wait_for_quota_reset()
    snap = client.signals_current()
except RateLimitError as e:
    time.sleep(e.retry_after or 60)
    snap = client.signals_current()
# AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404),
# ValidationError (400), ServerError (500+ with .error_id) are also raised
# from QuarkError — catch that as the root for defensive loops.
SDK vs raw HTTP. Both target /api/v1/*. The SDK adds (a) ResponseMeta introspection — every call stamps client.last_meta with api_version, deprecation, sunset, successor_url, quota_limit, quota_used, quota_reset_epoch; (b) the exception hierarchy above; (c) convenience methods for signals_at(), webhook_register(), billing_portal(), and keys_rotate(); (d) automatic parsing of RFC 8288 Link headers for successor-version discovery.

Response shape — the fields you'll actually use

{
  "regime": {
    "regime_label": "NEUTRAL",        // RISK_ON | NEUTRAL | RISK_OFF | CRISIS
    "regime_score": 0.48,              // 0.0 RISK_ON → 1.0 CRISIS
    "barrier_depletion": 0.22          // 0.0 stable → 1.0 transition imminent
  },
  "tail_risk": {
    "crash_prob_1d":  0.04,
    "crash_prob_5d":  0.11,
    "crash_prob_21d": 0.19             // workhorse tail-risk number
  },
  "allocation": {
    "equity_pct":    0.65,             // strategy posture (not a personal rec)
    "defensive_pct": 0.25,
    "hedge_pct":     0.10
  },
  "freshness": {
    "asof": "2026-04-21T14:32:00+00:00",
    "staleness_seconds": 42
  },
  "provenance": {
    "sources_agreed": 3,
    "primary_feed": "yahoo",
    "consensus_feeds": ["yahoo", "stooq", "finnhub"],
    "max_disagreement_bps": 5,
    "reference_symbol": "SPY",
    "probed_at": "2026-04-21T14:31:58+00:00"
  }
}

Full schema for every field — including the risk_params and rotation blocks on /full — is in the Full response schema section.

Python SDK (pip install quarkresearch)

Official client for the /api/v1 surface. MIT-licensed, Python 3.9+, a single runtime dependency (requests). Same pipeline that powers our own tooling.

Installpip install quarkresearch
PyPIpypi.org/project/quarkresearch
Sourcegithub.com/quarkresearch/quark (sdk/python/)
Python3.9+
Runtime depsrequests >= 2.28, < 3.0
Version couplingSDK major version tracks API surface major — 1.x targets /api/v1. Breaking API change ⇒ SDK major bump.
SemverMinor = backward-compatible additions (new methods, new fields on ResponseMeta). Patch = bug fixes / header parsing / docstrings.

Method surface

MethodHTTPPurpose
Client(api_key=None, *, base_url=..., timeout=..., session=None, user_agent=None)Constructor. api_key falls back to QUARK_API_KEY env var when None. Defaults: base_url="https://api.quarkresearch.cc". Pass session to share a connection pool; user_agent to override the default quarkresearch-python/<ver>.
client.health()GET /api/v1/healthPublic deep-check. No key required.
client.signals_current()GET /api/v1/signals/currentRegime + tail risk + allocation summary.
client.signals_current_full()GET /api/v1/signals/current/fullAdds risk_params + rotation blocks.
client.signals_raw()GET /api/v1/signals/rawInstitutional-tier unfiltered signal.
client.signals_history(days)GET /api/v1/signals/history?days=NJSON array; 1 ≤ days ≤ 730, client-side clamped.
client.signals_history_csv(days)GET /api/v1/signals/history.csv?days=NReturns CSV text — pipe into pandas.read_csv(io.StringIO(...)).
client.signals_at(as_of)GET /api/v1/signals/at?as_of=...Point-in-time lookup. as_of is ISO-8601 UTC. See Point-in-time signals.
client.signals_usage()GET /api/v1/signals/usageQuota + tier + webhook status. Never counted against quota.
client.quote_live()GET /api/v1/quote/liveLive flagship-portfolio quote (public). Used by the landing-page hero.
client.regime()GET /api/v1/regime-probabilityRegime-probability distribution (public).
client.benchmark_inception()GET /api/v1/benchmark/inceptionBenchmark reference frame — anchor price, ticker, start date (public).
client.subscription_status()GET /api/v1/subscription/statusTier, plan features, billing period.
client.webhook_register(url)POST /api/v1/webhook/registerRegister HTTPS URL for push delivery.
client.webhook_status()GET /api/v1/webhook/statusCurrent registration + last delivery summary.
client.webhook_test()POST /api/v1/webhook/testFire a synthetic payload to verify receiver.
client.webhook_unregister()POST /api/v1/webhook/unregisterRemove webhook registration.
client.webhook_deliveries()GET /api/v1/webhook/deliveriesRecent delivery attempts with outcome + retry count.
client.keys_rotate()POST /api/v1/keys/rotateRotate API key. Returns new key exactly once — persist immediately.
client.wait_for_quota_reset()(sleep)Sleeps until client.last_meta.quota_reset_epoch. Call after catching a QuotaExceededError or when quota_used ≥ quota_limit. Returns seconds slept.

ResponseMeta — per-call introspection

Every successful call stamps client.last_meta with a ResponseMeta dataclass so you can log version + quota state without re-parsing headers. Fields:

FieldTypeSource
api_versionstrX-API-Version header ("v1" or "legacy")
deprecationboolDeprecation: true header (RFC 8594)
sunsetstr | NoneSunset header (RFC 7231 HTTP-date)
successor_urlstr | NoneParsed out of Link: <...>; rel="successor-version" (RFC 8288)
quota_limitint | NoneX-RateLimit-Limit (-1 = unlimited)
quota_usedint | NoneX-RateLimit-Used
quota_reset_epochint | NoneX-RateLimit-Reset — Unix epoch, Stripe-cycle-aligned

Exception hierarchy

QuarkError                    # root — catch this in defensive loops
├── AuthenticationError       # 401 — bad/missing API key
├── PermissionDeniedError     # 403 — tier doesn't include this endpoint
├── NotFoundError             # 404 — unknown route or missing resource
├── ValidationError           # 400 — invalid params (also raised client-side on bad inputs)
├── RateLimitError            # 429 — exposes .retry_after (seconds)
├── QuotaExceededError        # 402 — exposes .body (quota_month, calls_month, resets_at)
└── ServerError               # 500+ — exposes .error_id (use in support tickets)
When to prefer raw HTTP. Hitting the API from a language without the SDK, or pinning to a specific requests version — the endpoint reference is the complete specification. Everything the SDK does is a thin wrapper over it.

Endpoints

GET/api/signals/current

Top-level regime + tail risk + allocation summary. The most common call.

GET/api/signals/current/full

Unabridged signal — adds risk_params (factor stability diagnostics, correlation half-life, subspace-rotation distance) and rotation state. Use this when you want to replicate the full decision layer.

GET/api/signals/history?days=N

Historical signal observations, JSON. Max days=730.

GET/api/signals/history.csv?days=N

Same data, CSV. Columns: timestamp_utc, regime, regime_score, crash_prob_1d, crash_prob_5d, crash_prob_21d, equity_pct, defensive_pct, snr. Stable — safe to pipe into pandas/R.

GET/api/v1/signals/at?as_of=<ISO8601> new · 2026-04-22

Point-in-time signal lookup — returns the exact snapshot that was live at as_of. Designed for reproducibility and audit of what the signal said at a specific moment. See Point-in-time signals for semantics and worked examples.

GET/api/signals/usage

Your account info: tier, key last-4, webhook URL, rate limit, list of endpoints available to your tier.

GET/api/alpha/attribution

Per-signal decomposition of the library's α over the attribution window. 18 tracked signals; returns contribution in dollars, percent, and normalized share.

POST/api/webhook/register

Body: {"url": "https://your-server/webhook"}. One webhook per key. Replaces any existing registration.

POST/api/webhook/test

Fires a synthetic payload to your registered URL. Useful for verifying your receiver signature check before a real regime change lands.

POST/api/webhook/unregister

Removes webhook registration.

POST/api/billing/portal

Returns Stripe customer portal URL for self-service subscription management.

GET/api/health no auth · public

Deep-check health endpoint. Structured per-check JSON covering the API DB, signal-log freshness, cross-vendor price feed consensus, and arena state. Safe to poll from status pages and uptime monitors. Response is always HTTP 200 — callers gate behavior on the top-level status field (ok / degraded / down). See Platform health for the full shape.

Point-in-time signals (/api/v1/signals/at)

The single-endpoint answer to "what signal would I have seen if my strategy ran at 14:30 UTC on March 14, 2026?" Shipped 2026-04-22 so licensees can audit and reproduce what the live feed said at any historical moment.

Signal snapshots are persisted every cycle. /api/v1/signals/at resolves the most recent snapshot at or before the as_of timestamp — so there is exactly one answer for any given historical moment, no interpolation, no forward contamination.

Request

GET /api/v1/signals/at?as_of=2026-03-14T14:30:00Z
x-api-key: qk_live_...
ParameterTypeRequiredNotes
as_ofISO-8601 UTCyesAccepts 2026-03-14T14:30:00Z or 2026-03-14T14:30:00+00:00. Missing timezone → 400 ValidationError. Future timestamps → available: false.

Response

Fields are flat (same layout as /api/v1/signals/current) with three extra fields: as_of (echo of the request), snapshot_timestamp (the snapshot actually returned), and source: "point_in_time". Drop-in compatible with code that already handles /api/v1/signals/current responses.

{
  "as_of":              "2026-03-14T14:30:00Z",        // echoed from request
  "snapshot_timestamp": "2026-03-14T14:29:12+00:00",   // snapshot actually returned (≤ as_of)
  "source":             "point_in_time",
  "available":          true,
  "regime":         { "regime_label": "...", "regime_score": ..., ... },
  "tail_risk":      { "crash_prob_1d": ..., "crash_prob_5d": ..., "crash_prob_21d": ..., ... },
  "signal_quality": { "avg_snr": ... },
  "allocation":     { "equity_pct": ..., "defensive_pct": ... }
}

When no snapshot exists at or before the requested time (request before retention window, or no data yet persisted):

{
  "as_of":              "2015-01-01T00:00:00Z",
  "snapshot_timestamp": null,
  "source":             "point_in_time",
  "available":          false,
  "note":               "No signal snapshot persisted at or before the requested time."
}

Invalid as_of values fail fast with HTTP 400:

InputStatusReason
Missing as_of400as_of query parameter is required
Unparseable ("yesterday")400as_of must be a valid ISO-8601 timestamp
Naive timestamp (no tz)400as_of must include a timezone offset — prevents silent UTC/local ambiguity bugs
Future timestamp200available: false (the snapshot just hasn't been persisted yet)

Resolution semantics

RuleWhy
Returns the snapshot whose stored timestamp is the largest value ≤ as_of.Strictly causal — no value computed after as_of ever influences what you receive.
Response field layout is a subset of /api/v1/signals/current: regime (label, score, landscape_position, barrier_depletion), tail_risk (crash_prob_{1d,5d,21d}, barrier_var_10d), signal_quality (avg_snr), allocation (equity_pct, defensive_pct).Drop-in for the subset of fields that are persisted per snapshot. Live-only fields (freshness, provenance, risk_params, rotation) are not historically recorded and are therefore absent.
Same quota + rate-limit treatment as other signal endpoints.Historical lookups count against monthly quota just like live ones.
Tier: automated, automated_plus, signal_feed, or institutional.Same access bar as /api/signals/history. Lower tiers get 403.

Retention

Snapshots are retained back to platform inception (2026-02 for current production data). History before the retention boundary returns available: false, reason: "before_retention_window". The available: false response still succeeds with HTTP 200 — callers branch on available, not on status code.

Audit / reproducibility pattern (SDK)

from quarkresearch import Client
import pandas as pd

client = Client(api_key="qk_live_...")

# Reconstruct exactly what the live feed said every Monday 14:30 UTC in 2025:
dates = pd.date_range("2025-01-06", "2025-12-29", freq="W-MON", tz="UTC")
rows = []
for dt in dates:
    resp = client.signals_at(dt.strftime("%Y-%m-%dT14:30:00Z"))
    if not resp["available"]:
        continue
    rows.append({
        "date":                 dt,
        "snapshot_timestamp":   resp["snapshot_timestamp"],
        "regime":               resp["regime"]["regime_label"],
        "regime_score":         resp["regime"]["regime_score"],
        "crash_prob_21d":       resp["tail_risk"]["crash_prob_21d"],
        "equity_pct":           resp["allocation"]["equity_pct"],
    })

df = pd.DataFrame(rows).set_index("date")
# ... df now contains the exact snapshot the live feed emitted at each timestamp
Leakage hygiene. The endpoint is designed to prevent look-ahead bias in the snapshot layer, but it cannot prevent it at the caller layer. If you consume as_of = decision_time, make sure your downstream logic only uses fields that were decision-usable at decision_time — e.g. don't feed tomorrow's regime_label into today's decision by accidentally dereferencing a later iteration's response.

Platform health (/api/health)

Public deep-check endpoint. No API key required. Designed to be poll-safe from uptime monitors (UptimeRobot, Better Uptime, Pingdom), status-page integrations, and client-side ops dashboards.

Every response is HTTP 200 with a structured JSON body. The top-level status field drives caller behavior; per-check detail under checks helps diagnose what is degraded when something's off.

Response shape

{
  "status":         "ok",                              // ok | degraded | down
  "checked_at":     "2026-04-21T14:32:00+00:00",
  "version":        "3.0.0",
  "uptime_seconds": 847293,
  "boot_at":        "2026-04-11T18:50:27+00:00",
  "checks": {
    "db.connectivity": {
      "status": "ok",
      "path":   "/app/data/subscribers.db"
    },
    "db.signal_log.fresh": {
      "status":             "ok",
      "staleness_seconds":  412,
      "last_signal_at":     "2026-04-21T14:25:08+00:00",
      "threshold_seconds":  7200
    },
    "feeds.consensus": {
      "status":                "ok",
      "sources_agreed":        3,
      "feeds_up":              ["yahoo", "stooq", "finnhub"],
      "max_disagreement_bps":  5,
      "median_price":          542.1,
      "reference_symbol":      "SPY"
    },
    "arena.initialized": {
      "status":       "ok",
      "cycle_count":  12847,
      "n_portfolios": 7
    }
  }
}

Status levels

ValueMeaningRecommended caller action
okAll checks pass.Normal operation.
degradedOne or more non-critical checks failed (stale signal, single-feed consensus, arena not yet initialized).Continue serving but alert your ops team. If feeds.consensus.sources_agreed < 2, treat new signal values with extra caution.
downCritical check failed (DB unreachable, or zero price feeds up).Stop making dependent API calls. Fall back to cached last-known signal. Back off with exponential retry — /api/health itself remains available and is the right endpoint to poll during recovery.

Check reference

CheckWhat it verifiesFailure semantics
db.connectivitySubscriber / signal SQLite DB is reachable within 1.5s.Down = critical. No API-key gated endpoints can serve while this is failing.
db.signal_log.freshMost recent signal persisted < 2h ago.Degraded only. Old signals still serve from /api/signals/current with source: "last_snapshot".
feeds.consensusSPY price probed across Yahoo / Stooq / Finnhub in parallel; counts feeds agreeing within 50 bps of median.2+ feeds = ok. 1 feed = degraded. 0 feeds = down.
arena.initializedArenaManager is hot (live signals path available).Degraded. /api/signals/current falls back to last persisted snapshot.
Status page integration. Status pages that read HTTP codes will see a green 200 regardless of internal health. Parse the status field and map: ok → up, degraded → degraded, down → down. Uptime monitoring that also needs HTTP codes can synthesise them from the JSON via a simple lambda or JQ filter.

Full response schema

Every numeric field is a float in [0, 1] unless otherwise noted. All fields carry a fallback: bool flag — when true, the section is showing a classical/default estimate because the quantum pipeline failed to produce a non-fallback value this cycle.

regime section

FieldTypeRangeMeaning
regime_scorefloat0.0 – 1.0Continuous. 0 = RISK_ON, 0.5 = NEUTRAL, 1.0 = CRISIS.
regime_labelstringDiscrete label: RISK_ON, NEUTRAL, RISK_OFF, CRISIS.
landscape_positionfloat0.0 – 1.0Where in the potential landscape the market sits. 0 = normal attractor, 1 = crisis attractor.
barrier_depletionfloat0.0 – 1.0Fraction of regime-defense barrier already consumed. Values above 0.7 mean a regime transition is imminent.
decoherence_stressfloat0.0 – 1.0Factor-correlation instability indicator. 0 = factor structure stable; 1 = correlations reorganizing.
topology_stressfloat0.0 – 1.0Topological stress indicator derived from correlation-trajectory shape features.
past_saddlebooltrue when the system has already crossed the saddle point between regimes — a transition is locked in.
crash_prob_1dfloat0.0 – 1.01-day crash probability from this section (duplicate of tail_risk.crash_prob_1d).

tail_risk section

FieldTypeRangeMeaning
crash_prob_1dfloat0.0 – 1.0Probability of a ≥3σ drawdown in the next trading day.
crash_prob_5dfloat0.0 – 1.0Same, 5-day horizon.
crash_prob_21dfloat0.0 – 1.0Same, 21-day (~1 month) horizon. The workhorse tail-risk number.
barrier_var_10dfloat0.0+10-day tail-risk VaR as a decimal fraction of portfolio NAV (e.g. 0.0695 = 6.95%).
barrier_actionfloat0.0+Barrier magnitude — the "height" of the regime-defense barrier. Higher = further from a crash.

signal_quality section

FieldTypeRangeMeaning
avg_snrfloat0.0+Mean |trend velocity| / realized vol across the universe. High = trends are clean; low = noise.
momentum_weightfloat0.0 – 1.0Recommended weight on momentum factor given current SNR.
quality_weightfloat0.0 – 1.0Recommended weight on quality factor.
volume_weightfloat0.0 – 1.0Recommended weight on volume/liquidity factor.
cyclicality_weightfloat0.0 – 1.0Recommended weight on cyclicality factor. (Weights sum to 1.0.)
n_assetsintNumber of assets with a valid SNR this cycle.

allocation section

FieldTypeRangeMeaning
per_asset_caps{ticker: float}0.0 – 1.0Max weight we would hold in each ticker given the current landscape. Use as an upper clamp on your own weights.
uniform_capfloat0.0 – 1.0Fallback per-asset cap when a ticker isn't in per_asset_caps.
defensive_blendfloat0.0 – 1.0Suggested shift toward defensive posture. 0 = no shift (all-equity), 1 = fully defensive (bonds / gold / cash).
barrier_normalizedfloat0.0 – 1.0Barrier height / full action. 1 = fresh barrier, 0 = depleted.

risk_params section (full endpoint only)

FieldTypeRangeMeaning
quantum_halflifefloatdaysHow fast your covariance/signal estimates should adapt. Short = fast adaptation needed; long = regime stable.
eigenframe_timescalefloatdaysFactor-stability timescale — days until factor structure is expected to shift.
eigenframe_rotationfloat0.0 – π/2Angular distance between today's principal subspace and τ days ago. Large = factors rotating.
quantum_shrinkage_boostfloat≥ 1.0Covariance shrinkage multiplier recommended given subspace-rotation distance.

rotation section (full endpoint only)

FieldTypeMeaning
eigenframe_rotationfloatFactor-rotation magnitude. Same number as in risk_params.
should_invalidate_cachebooltrue when cached signals / weights should be recomputed — eigenspace has rotated enough to invalidate them.
force_short_halflifebooltrue when you should temporarily halve your signal smoothing window.

fund_summary section

FieldTypeMeaning
return_pctfloatOur live ensemble portfolio's cumulative return (%), for benchmarking.
regimestringRegime label we're currently trading under.
healthstringGREEN / AMBER / RED — operational health of the live system.

freshness section

Every successful response carries a freshness block so you can tell whether the signal is live or stale. Use staleness_seconds to decide whether to trade on the value or skip this cycle.

FieldTypeMeaning
asofstring (ISO-8601 UTC)When the signal was computed. Example: 2026-04-21T20:03:17+00:00. Null only if the signal store is empty.
staleness_secondsintAge of the signal in seconds vs. wall-clock now. < 120 = live, 120–900 = within cycle window, 900–86400 = overnight/weekend (normal), > 86400 = stale; investigate.
Recommended client check: before acting on the signal, assert staleness_seconds < max_age where max_age matches your rebalancing cadence. A daily rebalancer can accept < 90000 (25h, covers weekends); an intraday system should reject > 3600.

provenance section

Cross-vendor price consensus. Every response probes three independent feeds — Yahoo Finance, Stooq, and Finnhub — for a reference symbol (SPY by default) and reports how many agreed within 50 bps of the median. Higher = stronger confidence the signal's price inputs weren't driven by a single-vendor anomaly. The probe is 60-second cached server-side; your per-request latency is not impacted.

FieldTypeMeaning
sources_agreedintCount of feeds within 50 bps of the median. 0 = all feeds down (treat as degraded). 1 = single feed (no consensus — Finnhub typically absent if no API key is configured). 2–3 = consensus.
primary_feedstringWhich vendor drove the main numeric inputs. Priority: yahoo (if agreeing) → first agreeing feed → any reachable feed → "none".
consensus_feedsstring[]Feeds that agreed this cycle. Values drawn from ["yahoo", "stooq", "finnhub"].
max_disagreement_bpsintWorst spread between any returned price and the median, in basis points. < 50 = all feeds agree. 50–100 = one outlier, one agreement bucket. > 100 = meaningful disagreement; inspect upstream.
reference_symbolstringSymbol the consensus probe ran against. Currently always SPY.
probed_atstring (ISO-8601 UTC)When the probe was last run. 60-second cache TTL, so repeated calls within a minute share the same probed_at.
Recommended client check: treat sources_agreed >= 2 as "price feeds healthy." If sources_agreed == 0 persists for more than a few minutes, poll /api/health for detail and back off until feeds return. Do not hard-gate trades on sources_agreed alone; use it alongside staleness_seconds as a second layer of upstream sanity checking.

How to apply the signal to your portfolio

The signal is deliberately decoupled from any specific trade list — it's a set of continuous knobs you translate into your own allocation logic. Below are the three patterns we see licensees use successfully. Pick one that matches your existing workflow.

Pattern 1 — Defensive overlay on a static allocation

Simplest. You already have a strategic allocation (e.g. 60/40). Use defensive_blend to scale up/down your equity weight when the signal says conditions are deteriorating.

target_equity_pct = base_equity_pct × (1 − signal.allocation.defensive_blend)
target_bond_pct   = 1 − target_equity_pct

# Example:
# base 60/40, defensive_blend = 0.25 →
#   target_equity = 0.60 × 0.75 = 45%
#   target_bond   = 55%
Rebalance trigger: only rebalance when the absolute change in defensive_blend exceeds 0.10, or when rotation.should_invalidate_cache is true. Prevents thrash.

Pattern 2 — Tail-risk gated sizing

For strategies that size positions as Kelly fractions or vol-targeted. Scale each position's size by the complement of the 21-day crash probability, so sizes shrink as crash risk rises.

crash_21 = signal.tail_risk.crash_prob_21d
sizing_multiplier = 1 − min(2 × crash_21, 0.80)   # cap de-risking at 80%

for asset in portfolio:
    target_size[asset] = base_size[asset] × sizing_multiplier

The 2× factor is a convention — we use it internally. It means a 21d crash probability of 0.40 drives sizing to 20% of baseline; anything higher clamps at 20%.

Pattern 3 — Full quant replication

For licensees running their own quant book. Use the /full endpoint and consume the per-asset caps, factor weights, and subspace-rotation state directly.

from scipy.optimize import minimize

def step(signal):
    caps = signal.allocation.per_asset_caps  # ticker → max weight

    # Halflife → covariance EWMA decay
    lam = 0.5 ** (1 / signal.risk_params.quantum_halflife)
    cov = ewma_covariance(returns, lam) × signal.risk_params.quantum_shrinkage_boost

    # Factor tilt from signal-quality weights
    factor_tilt = {
        'momentum':    signal.signal_quality.momentum_weight,
        'quality':     signal.signal_quality.quality_weight,
        'volume':      signal.signal_quality.volume_weight,
        'cyclicality': signal.signal_quality.cyclicality_weight,
    }

    # Solve mean-variance with caps as upper bound on each w_i
    weights = mean_variance_with_caps(
        expected_returns=alpha_from_factors(factor_tilt),
        covariance=cov,
        upper_bounds=caps,
        defensive_blend=signal.allocation.defensive_blend,
    )
    return weights

# Only re-solve on a regime change or rotation flag
if signal.rotation.should_invalidate_cache or regime_changed:
    weights = step(signal)

Field-by-field heuristics

FieldHeuristic
regime_scoreBlend your risk-on / risk-off target allocation with this as the mixing weight. Score 0.0 = full risk-on book; 1.0 = full crisis book.
crash_prob_21d > 0.15Hedge threshold. Start buying puts or reducing beta. Our own system buys hedges when this crosses 0.15 upward.
barrier_depletion > 0.70Regime transition imminent. Expect the regime label to flip within days. Do not add risk.
past_saddle == trueTransition is already locked in. Stop trying to fade the move.
avg_snr < 0.10Market is in chop. Reduce turnover, widen rebalance bands.
avg_snr > 0.30Trends are clean. Lengthen holding periods; don't over-trade.
eigenframe_rotation > 0.30Factors are rotating. Invalidate any cached factor-based signals and recompute.
defensive_blend == 1.0System is recommending a fully defensive posture. Match your policy — typically bonds + gold + cash.
topology_stress > 0.50Correlation shape features stressed — diversification is breaking. Consider dollar-neutral pairs instead of long-only.
fallback == true on any sectionTreat that section as a classical fallback; don't stake conviction on it that cycle.
The signal is not a trade list. You still need a universe, an execution layer, and a risk limit framework. The signal tells you the environment; you translate it to trades.

Worked examples

Example A — "Check once a day, adjust 60/40"

import requests, json

KEY = 'qk_live_...'
BASE = 'https://api.quarkresearch.cc'

def daily_allocation(base_equity=0.60):
    sig = requests.get(f'{BASE}/api/signals/current',
                       headers={'x-api-key': KEY}, timeout=10).json()
    defensive = sig['allocation']['defensive_blend']
    crash_21  = sig['tail_risk']['crash_prob_21d']

    eq_pct = base_equity * (1 - defensive)

    # Extra de-risk if crash probability elevated
    if crash_21 > 0.15:
        eq_pct *= 0.75
    if crash_21 > 0.30:
        eq_pct *= 0.50

    bond_pct = 1 - eq_pct
    return {'equity': eq_pct, 'bonds': bond_pct, 'signal': sig}

alloc = daily_allocation()
print(f"Target equity: {alloc['equity']:.1%}, bonds: {alloc['bonds']:.1%}")

Example B — "Push to Google Sheets via webhook"

# Register webhook (one-time)
curl -X POST -H "x-api-key: $KEY" \
  -H "content-type: application/json" \
  -d '{"url":"https://script.google.com/macros/s/YOUR_DEPLOY/exec"}' \
  https://api.quarkresearch.cc/api/webhook/register

# Test delivery
curl -X POST -H "x-api-key: $KEY" \
  https://api.quarkresearch.cc/api/webhook/test

Your Apps Script receives the same JSON as /api/signals/current. Write it into a sheet, pivot against your broker's positions, done.

Example C — "Size a momentum sleeve with crash gate"

def momentum_sleeve_sizing(base_gross_exposure=1.50):
    sig = requests.get(f'{BASE}/api/signals/current',
                       headers={'x-api-key': KEY}).json()

    # Sizing multiplier from crash probability
    crash_21 = sig['tail_risk']['crash_prob_21d']
    mult = 1 - min(2 * crash_21, 0.80)

    # Adjust momentum weight based on SNR
    mom_w = sig['signal_quality']['momentum_weight']

    target_gross = base_gross_exposure * mult * (mom_w / 0.25)  # 0.25 = long-run avg
    return target_gross

Example D — "Replay 2025 with the SDK + point-in-time endpoint"

Audit replay: reconstruct what the signal said at every Monday 14:30 UTC for all of 2025, against a static 60/40 baseline for reference. The key property — each call to signals_at(t) returns only information observable at time t, so you're reading the live feed as it stood at t, not an ex-post reconstruction.

from quarkresearch import Client, QuotaExceededError
import pandas as pd

client = Client()   # reads QUARK_API_KEY from environment

def target_equity_pct(snap):
    # Start from the strategy's own equity posture and layer a crash-gate on top.
    pct = snap["allocation"]["equity_pct"]
    crash_21 = snap["tail_risk"]["crash_prob_21d"]
    if crash_21 > 0.15: pct *= 0.75
    if crash_21 > 0.30: pct *= 0.50
    return pct

dates = pd.date_range("2025-01-06", "2025-12-29", freq="W-MON", tz="UTC")
rows = []
for dt in dates:
    try:
        resp = client.signals_at(dt.strftime("%Y-%m-%dT14:30:00Z"))
    except QuotaExceededError:
        client.wait_for_quota_reset()
        resp = client.signals_at(dt.strftime("%Y-%m-%dT14:30:00Z"))

    if not resp["available"]:
        continue   # before retention / in future — skip

    # Response is flat (same layout as signals_current)
    rows.append({
        "date":                 dt,
        "snapshot_timestamp":   resp["snapshot_timestamp"],
        "regime":               resp["regime"]["regime_label"],
        "crash_prob_21d":       resp["tail_risk"]["crash_prob_21d"],
        "target_equity":        target_equity_pct(resp),
    })

df = pd.DataFrame(rows).set_index("date")
print(df.describe())
# → plot df["target_equity"] vs a static 0.60 baseline across 2025
Why this is cheap. One SDK call per rebalance decision. For a weekly rebalance over 5 years that's ~260 calls — well under the Automated tier's 10k/month.

Webhook reference

Register a URL to receive push notifications when the regime changes, crash probability crosses 15%, or rotation.should_invalidate_cache fires.

Payload

Identical to /api/signals/current response, with one extra field at the top:

{
  "event": "regime_change" | "crash_threshold_crossed" | "rotation_invalidate" | "test",
  "regime": {...},
  "tail_risk": {...},
  ...
}

Signature verification

Every webhook request carries an X-Quark-Signature header with an HMAC-SHA256 of the raw body, keyed on your API key. Verify in your receiver:

import hmac, hashlib

def verify(raw_body: bytes, signature: str, api_key: str) -> bool:
    expected = hmac.new(
        api_key.encode('utf-8'),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
Reject unsigned requests. A webhook endpoint is public by definition; without signature verification anyone can spoof regime-change events to trigger your rebalance logic.

Retry policy

Webhooks that return a non-2xx status or time out after 5 seconds will be retried with exponential backoff: 30s, 2m, 10m, 1h, 6h, 24h. After that the delivery is dropped; the signal itself is still available via GET on /api/signals/current.

Errors & status codes

Response body shape

Error responses use one of three JSON shapes depending on status code. Clients should handle both error and detail when parsing — the official Python SDK does this automatically.

StatusBody shapeExample
400, 401, 403, 404{"detail": "<message>"}{"detail": "as_of must include an explicit UTC offset"}
402 (quota exhausted){"error": "<message>", "tier": "<tier>", "quota_month": <int>, "calls_month": <int>, "resets_at": <unix_epoch>}{"error": "Monthly quota exceeded.", "tier": "automated", "quota_month": 10000, "calls_month": 10000, "resets_at": 1748736000}
429 (unauth rate limit){"detail": "<message>"} + Retry-After header{"detail": "Rate limit exceeded. Try again later."}
500 (internal){"error": "<message>", "error_id": "<uuid>"}{"error": "Internal server error", "error_id": "3f2c8b4e-..."}

Status code reference

CodeMeaningWhat to do
400Invalid request parametersdetail names the specific field that failed validation (e.g. as_of without a timezone, days outside 1..730). SDK surfaces this as ValidationError; fix the input and retry.
401Missing or invalid API keydetail describes the auth failure. Check header name is exactly x-api-key. Key starts with qk_.
402Monthly quota exhaustedThis month's calls are used up. Response body uses the quota shape (error, tier, quota_month, calls_month, resets_at in Unix epoch seconds, Stripe-cycle-aligned). Either wait for resets_at or upgrade tier.
403Tier doesn't include this endpointdetail names the required tier. Upgrade, or check endpoint-available list at /api/signals/usage.
404Unknown endpointdetail: "Not Found". Confirm base URL is api.quarkresearch.cc, not quarkresearch.cc, and that the path starts with /api/v1/.
429Unauthenticated request rate limit exceededApplies to requests with no API key: 20 req/min general, 4 req/min on signal endpoints, per IP. Authenticate with a valid key to remove the per-minute cap. Retry after Retry-After seconds if continuing unauthenticated.
500Server error on our sideBody includes an error_id UUID — include this in any support email so we can correlate to server logs. Retry with exponential backoff. If persistent, email [email protected] with the error_id.
503Signal pipeline temporarily downRetry after 60s. Fall back to previous cached signal. Sanity-check platform status at /api/health or the public status page.

fallback: true semantics

When any section returns fallback: true, that section's values are from a classical/default estimator rather than the quantum pipeline. Two options:

Changelog

DateChange
2026-04-23Error response shapes documented explicitly. The Errors table now has a dedicated “Response body shape” section showing the three shapes the API actually emits: {"detail": "..."} for 400 / 401 / 403 / 404 / 429 (FastAPI standard); {"error": "...", "tier": ..., "quota_month": ..., "calls_month": ..., "resets_at": ...} for 402 quota exhaustion; {"error": "...", "error_id": "<uuid>"} for 500 internal errors. Clients should parse both error and detail keys — the Python SDK does this automatically and raises the right typed exception.
2026-04-22Institutional readiness: v1 API + Python SDK + point-in-time signals. (1) Versioned surface. /api/v1/* is now the canonical path. Legacy unversioned paths still resolve through the deprecation window; responses on legacy paths carry Deprecation: true, Sunset: Thu, 22 Oct 2026 00:00:00 GMT, and Link: </api/v1/...>; rel="successor-version". Every response carries X-API-Version. Auth section updated. (2) Point-in-time endpoint. New GET /api/v1/signals/at?as_of=<ISO8601> returns the exact snapshot live at a given historical timestamp — no interpolation, strictly causal. Designed for reproducibility and audit of what the live feed said at a specific moment. Full semantics and worked examples: Point-in-time signals. (3) Python SDK. pip install quarkresearch — official client for /api/v1. Typed exception hierarchy, ResponseMeta introspection for version/quota/deprecation state, convenience helpers for history, webhooks, billing portal, and key rotation. See Python SDK. (4) 400 ValidationError documented in Errors table.
2026-04-21Rate-limit docs corrected. Previous copy claimed "60 requests / minute / key"; the actual model is (a) monthly quota per tier for authenticated keys — 10,000 (Automated), 50,000 (Automated Plus), unlimited (Institutional) — and (b) per-minute IP limits for unauthenticated requests (20/min general, 4/min on signal endpoints). Quota exhaustion returns 402 Payment Required, not 429; X-RateLimit-Reset now documented as Stripe-cycle-aligned when available. Auth section and Errors table updated.
2026-04-21Platform health endpoint live at /api/health. Real cross-vendor price consensus now wired into the provenance block of /api/signals/current (replaces the prior hardcoded sources_agreed: 1 placeholder). New fields: max_disagreement_bps, reference_symbol, probed_at. Consensus probes Yahoo + Stooq + Finnhub; 60s server-side cache. Dashboard Platform Status panel added — see dashboard.
2026-04-21Added Quickstart section — curl / Python / JS code samples, pandas history pull, webhook registration + HMAC verification. Rate-limit-headers tip added to Auth.
2026-04-21Published Service Level Agreement v1.0 (99.5% monthly uptime, service-credit schedule, incident-response commitments).
2026-04-20Added /api/signals/current/full; added test-ping button on dashboard; signal log dedupe (no more duplicate CSV rows).
2026-04-19Dashboard UI redesigned; tier labels rendered as "Automated Plus" instead of raw IDs; Manage button wired to Stripe portal.
2026-04-18Alpha attribution endpoint live (/api/alpha/attribution).
2026-04-15Endpoints moved behind api.quarkresearch.cc Cloudflare tunnel.