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.
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.
instanton_action → barrier_actioninstanton_var_10d → barrier_var_10dinstanton_p_crash → barrier_p_transitiongrassmann_distance / grassmannian_distance → eigenframe_rotationtda_stress → topology_stresstda_score → topology_scoretau_decoh → eigenframe_timescalecondensate_magnitude → order_parameterpath_sig_template_cos → path_template_scoredecoherence_rate → rotation_ratedecoherence_stability → frame_stabilitywavefunction_overlap → ensemble_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].
pip install quarkresearch)/api/v1/signals/at)| Base URL | https://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 header | x-api-key: qk_... |
| Auth query | ?api_key=qk_... (for cURL convenience; prefer header) |
| Authenticated quota | Monthly call quota by tier (see table below). No per-minute cap on valid keys. |
| Unauthenticated limit | 20 req/min per IP (general), 4 req/min per IP on signal endpoints. Rejected with 429. |
| Content-Type | application/json (CSV endpoint returns text/csv) |
| Signal refresh | Daily post-close; intraday if regime flips or crash-prob crosses threshold |
| TLS | Required. HTTP requests are rejected. |
| Uptime target | 99.5% monthly — see Service Level Agreement |
| Status page | quarkresearch.cc/status |
| Tier | Monthly calls | API access | Notes |
|---|---|---|---|
| Starter ($5/mo) | — | None (email digest only) | Dashboard + email alerts; no programmatic API. |
| Professional ($49/mo) | — | Dashboard only | Web dashboard access; no programmatic API. |
| Automated ($149/mo) | 10,000 | Full read API | ~333 calls/day — comfortable for single-strategy polling. |
| Automated Plus ($299/mo) | 50,000 | Read API + webhooks + execute | ~1,600 calls/day — multi-strategy polling + webhook delivery + auto-rebalance. |
| Institutional (custom) | Unlimited | Full API + real-time stream | No cap. X-RateLimit-Limit: -1. |
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.
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.
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.
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.
| Endpoint | POST /api/trial/start |
|---|---|
| Auth | None — public endpoint, rate-limited per IP. |
| Body | {"email": "[email protected]"} |
| Returns | API key (qk_trial_*), dashboard token, expiry timestamp, sample curl. |
| Tier | Automated (10,000 calls/month) — same quota as the paid tier. |
| Duration | 7 days from issue. Hard expiry — the key starts returning 401 at the deadline. |
| Idempotency | Repeat 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..."
Three ways to pull the current regime read. All examples authenticate with a key you get from the dashboard.
curl -sS https://api.quarkresearch.cc/api/signals/current \ -H "x-api-key: $QUARK_API_KEY" | jq .regime
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)")
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`);
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.
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"
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)
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.
/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.{
"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.
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.
| Install | pip install quarkresearch |
|---|---|
| PyPI | pypi.org/project/quarkresearch |
| Source | github.com/quarkresearch/quark (sdk/python/) |
| Python | 3.9+ |
| Runtime deps | requests >= 2.28, < 3.0 |
| Version coupling | SDK major version tracks API surface major — 1.x targets /api/v1. Breaking API change ⇒ SDK major bump. |
| Semver | Minor = backward-compatible additions (new methods, new fields on ResponseMeta). Patch = bug fixes / header parsing / docstrings. |
| Method | HTTP | Purpose |
|---|---|---|
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/health | Public deep-check. No key required. |
client.signals_current() | GET /api/v1/signals/current | Regime + tail risk + allocation summary. |
client.signals_current_full() | GET /api/v1/signals/current/full | Adds risk_params + rotation blocks. |
client.signals_raw() | GET /api/v1/signals/raw | Institutional-tier unfiltered signal. |
client.signals_history(days) | GET /api/v1/signals/history?days=N | JSON array; 1 ≤ days ≤ 730, client-side clamped. |
client.signals_history_csv(days) | GET /api/v1/signals/history.csv?days=N | Returns 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/usage | Quota + tier + webhook status. Never counted against quota. |
client.quote_live() | GET /api/v1/quote/live | Live flagship-portfolio quote (public). Used by the landing-page hero. |
client.regime() | GET /api/v1/regime-probability | Regime-probability distribution (public). |
client.benchmark_inception() | GET /api/v1/benchmark/inception | Benchmark reference frame — anchor price, ticker, start date (public). |
client.subscription_status() | GET /api/v1/subscription/status | Tier, plan features, billing period. |
client.webhook_register(url) | POST /api/v1/webhook/register | Register HTTPS URL for push delivery. |
client.webhook_status() | GET /api/v1/webhook/status | Current registration + last delivery summary. |
client.webhook_test() | POST /api/v1/webhook/test | Fire a synthetic payload to verify receiver. |
client.webhook_unregister() | POST /api/v1/webhook/unregister | Remove webhook registration. |
client.webhook_deliveries() | GET /api/v1/webhook/deliveries | Recent delivery attempts with outcome + retry count. |
client.keys_rotate() | POST /api/v1/keys/rotate | Rotate 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 introspectionEvery successful call stamps client.last_meta with a
ResponseMeta dataclass so you can log version + quota state
without re-parsing headers. Fields:
| Field | Type | Source |
|---|---|---|
api_version | str | X-API-Version header ("v1" or "legacy") |
deprecation | bool | Deprecation: true header (RFC 8594) |
sunset | str | None | Sunset header (RFC 7231 HTTP-date) |
successor_url | str | None | Parsed out of Link: <...>; rel="successor-version" (RFC 8288) |
quota_limit | int | None | X-RateLimit-Limit (-1 = unlimited) |
quota_used | int | None | X-RateLimit-Used |
quota_reset_epoch | int | None | X-RateLimit-Reset — Unix epoch, Stripe-cycle-aligned |
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)
requests
version — the endpoint reference is the complete
specification. Everything the SDK does is a thin wrapper over it.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.
/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.
GET /api/v1/signals/at?as_of=2026-03-14T14:30:00Z x-api-key: qk_live_...
| Parameter | Type | Required | Notes |
|---|---|---|---|
as_of | ISO-8601 UTC | yes | Accepts 2026-03-14T14:30:00Z or 2026-03-14T14:30:00+00:00. Missing timezone → 400 ValidationError. Future timestamps → available: false. |
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:
| Input | Status | Reason |
|---|---|---|
Missing as_of | 400 | as_of query parameter is required |
Unparseable ("yesterday") | 400 | as_of must be a valid ISO-8601 timestamp |
| Naive timestamp (no tz) | 400 | as_of must include a timezone offset — prevents silent UTC/local ambiguity bugs |
| Future timestamp | 200 | available: false (the snapshot just hasn't been persisted yet) |
| Rule | Why |
|---|---|
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. |
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.
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
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./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.
{
"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
}
}
}
| Value | Meaning | Recommended caller action |
|---|---|---|
ok | All checks pass. | Normal operation. |
degraded | One 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. |
down | Critical 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 | What it verifies | Failure semantics |
|---|---|---|
db.connectivity | Subscriber / signal SQLite DB is reachable within 1.5s. | Down = critical. No API-key gated endpoints can serve while this is failing. |
db.signal_log.fresh | Most recent signal persisted < 2h ago. | Degraded only. Old signals still serve from /api/signals/current with source: "last_snapshot". |
feeds.consensus | SPY 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.initialized | ArenaManager is hot (live signals path available). | Degraded. /api/signals/current falls back to last persisted snapshot. |
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.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| Field | Type | Range | Meaning |
|---|---|---|---|
regime_score | float | 0.0 – 1.0 | Continuous. 0 = RISK_ON, 0.5 = NEUTRAL, 1.0 = CRISIS. |
regime_label | string | — | Discrete label: RISK_ON, NEUTRAL, RISK_OFF, CRISIS. |
landscape_position | float | 0.0 – 1.0 | Where in the potential landscape the market sits. 0 = normal attractor, 1 = crisis attractor. |
barrier_depletion | float | 0.0 – 1.0 | Fraction of regime-defense barrier already consumed. Values above 0.7 mean a regime transition is imminent. |
decoherence_stress | float | 0.0 – 1.0 | Factor-correlation instability indicator. 0 = factor structure stable; 1 = correlations reorganizing. |
topology_stress | float | 0.0 – 1.0 | Topological stress indicator derived from correlation-trajectory shape features. |
past_saddle | bool | — | true when the system has already crossed the saddle point between regimes — a transition is locked in. |
crash_prob_1d | float | 0.0 – 1.0 | 1-day crash probability from this section (duplicate of tail_risk.crash_prob_1d). |
tail_risk section| Field | Type | Range | Meaning |
|---|---|---|---|
crash_prob_1d | float | 0.0 – 1.0 | Probability of a ≥3σ drawdown in the next trading day. |
crash_prob_5d | float | 0.0 – 1.0 | Same, 5-day horizon. |
crash_prob_21d | float | 0.0 – 1.0 | Same, 21-day (~1 month) horizon. The workhorse tail-risk number. |
barrier_var_10d | float | 0.0+ | 10-day tail-risk VaR as a decimal fraction of portfolio NAV (e.g. 0.0695 = 6.95%). |
barrier_action | float | 0.0+ | Barrier magnitude — the "height" of the regime-defense barrier. Higher = further from a crash. |
signal_quality section| Field | Type | Range | Meaning |
|---|---|---|---|
avg_snr | float | 0.0+ | Mean |trend velocity| / realized vol across the universe. High = trends are clean; low = noise. |
momentum_weight | float | 0.0 – 1.0 | Recommended weight on momentum factor given current SNR. |
quality_weight | float | 0.0 – 1.0 | Recommended weight on quality factor. |
volume_weight | float | 0.0 – 1.0 | Recommended weight on volume/liquidity factor. |
cyclicality_weight | float | 0.0 – 1.0 | Recommended weight on cyclicality factor. (Weights sum to 1.0.) |
n_assets | int | — | Number of assets with a valid SNR this cycle. |
allocation section| Field | Type | Range | Meaning |
|---|---|---|---|
per_asset_caps | {ticker: float} | 0.0 – 1.0 | Max weight we would hold in each ticker given the current landscape. Use as an upper clamp on your own weights. |
uniform_cap | float | 0.0 – 1.0 | Fallback per-asset cap when a ticker isn't in per_asset_caps. |
defensive_blend | float | 0.0 – 1.0 | Suggested shift toward defensive posture. 0 = no shift (all-equity), 1 = fully defensive (bonds / gold / cash). |
barrier_normalized | float | 0.0 – 1.0 | Barrier height / full action. 1 = fresh barrier, 0 = depleted. |
risk_params section (full endpoint only)| Field | Type | Range | Meaning |
|---|---|---|---|
quantum_halflife | float | days | How fast your covariance/signal estimates should adapt. Short = fast adaptation needed; long = regime stable. |
eigenframe_timescale | float | days | Factor-stability timescale — days until factor structure is expected to shift. |
eigenframe_rotation | float | 0.0 – π/2 | Angular distance between today's principal subspace and τ days ago. Large = factors rotating. |
quantum_shrinkage_boost | float | ≥ 1.0 | Covariance shrinkage multiplier recommended given subspace-rotation distance. |
rotation section (full endpoint only)| Field | Type | Meaning |
|---|---|---|
eigenframe_rotation | float | Factor-rotation magnitude. Same number as in risk_params. |
should_invalidate_cache | bool | true when cached signals / weights should be recomputed — eigenspace has rotated enough to invalidate them. |
force_short_halflife | bool | true when you should temporarily halve your signal smoothing window. |
fund_summary section| Field | Type | Meaning |
|---|---|---|
return_pct | float | Our live ensemble portfolio's cumulative return (%), for benchmarking. |
regime | string | Regime label we're currently trading under. |
health | string | GREEN / AMBER / RED — operational health of the live system. |
freshness sectionEvery 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.
| Field | Type | Meaning |
|---|---|---|
asof | string (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_seconds | int | Age of the signal in seconds vs. wall-clock now. < 120 = live, 120–900 = within cycle window, 900–86400 = overnight/weekend (normal), > 86400 = stale; investigate. |
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 sectionCross-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.
| Field | Type | Meaning |
|---|---|---|
sources_agreed | int | Count 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_feed | string | Which vendor drove the main numeric inputs. Priority: yahoo (if agreeing) → first agreeing feed → any reachable feed → "none". |
consensus_feeds | string[] | Feeds that agreed this cycle. Values drawn from ["yahoo", "stooq", "finnhub"]. |
max_disagreement_bps | int | Worst 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_symbol | string | Symbol the consensus probe ran against. Currently always SPY. |
probed_at | string (ISO-8601 UTC) | When the probe was last run. 60-second cache TTL, so repeated calls within a minute share the same probed_at. |
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.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.
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%
defensive_blend exceeds 0.10, or when rotation.should_invalidate_cache is true. Prevents thrash.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%.
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 | Heuristic |
|---|---|
regime_score | Blend 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.15 | Hedge threshold. Start buying puts or reducing beta. Our own system buys hedges when this crosses 0.15 upward. |
barrier_depletion > 0.70 | Regime transition imminent. Expect the regime label to flip within days. Do not add risk. |
past_saddle == true | Transition is already locked in. Stop trying to fade the move. |
avg_snr < 0.10 | Market is in chop. Reduce turnover, widen rebalance bands. |
avg_snr > 0.30 | Trends are clean. Lengthen holding periods; don't over-trade. |
eigenframe_rotation > 0.30 | Factors are rotating. Invalidate any cached factor-based signals and recompute. |
defensive_blend == 1.0 | System is recommending a fully defensive posture. Match your policy — typically bonds + gold + cash. |
topology_stress > 0.50 | Correlation shape features stressed — diversification is breaking. Consider dollar-neutral pairs instead of long-only. |
fallback == true on any section | Treat that section as a classical fallback; don't stake conviction on it that cycle. |
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%}")
# 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.
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
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
Register a URL to receive push notifications when the regime changes, crash probability crosses 15%, or rotation.should_invalidate_cache fires.
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": {...},
...
}
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)
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.
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.
| Status | Body shape | Example |
|---|---|---|
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-..."} |
| Code | Meaning | What to do |
|---|---|---|
400 | Invalid request parameters | detail 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. |
401 | Missing or invalid API key | detail describes the auth failure. Check header name is exactly x-api-key. Key starts with qk_. |
402 | Monthly quota exhausted | This 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. |
403 | Tier doesn't include this endpoint | detail names the required tier. Upgrade, or check endpoint-available list at /api/signals/usage. |
404 | Unknown endpoint | detail: "Not Found". Confirm base URL is api.quarkresearch.cc, not quarkresearch.cc, and that the path starts with /api/v1/. |
429 | Unauthenticated request rate limit exceeded | Applies 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. |
500 | Server error on our side | Body 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. |
503 | Signal pipeline temporarily down | Retry after 60s. Fall back to previous cached signal. Sanity-check platform status at /api/health or the public status page. |
fallback: true semanticsWhen any section returns fallback: true, that section's values are from a classical/default estimator rather than the quantum pipeline. Two options:
| Date | Change |
|---|---|
| 2026-04-23 | Error 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-22 | Institutional 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-21 | Rate-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-21 | Platform 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-21 | Added Quickstart section — curl / Python / JS code samples, pandas history pull, webhook registration + HMAC verification. Rate-limit-headers tip added to Auth. |
| 2026-04-21 | Published Service Level Agreement v1.0 (99.5% monthly uptime, service-credit schedule, incident-response commitments). |
| 2026-04-20 | Added /api/signals/current/full; added test-ping button on dashboard; signal log dedupe (no more duplicate CSV rows). |
| 2026-04-19 | Dashboard UI redesigned; tier labels rendered as "Automated Plus" instead of raw IDs; Manage button wired to Stripe portal. |
| 2026-04-18 | Alpha attribution endpoint live (/api/alpha/attribution). |
| 2026-04-15 | Endpoints moved behind api.quarkresearch.cc Cloudflare tunnel. |