---
name: quantgate
description: Crypto market-intelligence API for AI agents — computed quant signals and Ed25519-signed, decision-grade risk verdicts that agents can't reliably compute themselves.
version: "1.0"
author: Parsa Ayoubi and Noah Rubin (QuantGate)
---

# QuantGate

QuantGate is a crypto market-intelligence API that returns computed quant
signals and a decision-grade, cryptographically signed risk verdict that AI
agents can't reliably compute themselves.

> **Disclaimer:** QuantGate returns computed technical indicators. It is NOT
> financial advice and does NOT provide investment recommendations. Treat every
> verdict as one input among many, never as an instruction to trade.

## When to use me

Use QuantGate whenever you are reasoning about crypto markets and need real
computed numbers instead of estimates. LLMs are unreliable at precise numeric
technical analysis (RSI, EMA crossovers, annualized volatility, drawdowns) —
QuantGate computes them from live CoinGecko daily price history.

Concrete triggers:
- A user asks "is it a good time to buy/sell X?", "how risky is X right now?",
  or "what's the trend on X?" → `GET /api/analyze/{coin}`
- You need raw indicator values (RSI, SMA/EMA, Bollinger, volatility, momentum,
  max drawdown, Sharpe proxy) for your own reasoning → `GET /api/signals/{coin}`
- You just need the current price / 24h change / market cap → `GET /api/price/{coin}`
- You need to prove to another agent that a verdict genuinely came from
  QuantGate and wasn't tampered with → verify the Ed25519 signature via `GET /pubkey`

## Base URL

```
https://usequantgate.com
```

Local development: `http://localhost:3000`.

## Coin inputs

Every `{coin}` path param accepts:
- Any **CoinGecko coin id**: `bitcoin`, `ethereum`, `solana`, `chainlink`, …
- These **common tickers** (mapped server-side): `btc`, `eth`, `sol`, `xrp`,
  `ada`, `doge`, `bnb`, `ltc`, `dot`, `link`

Unknown inputs return `404`. Case-insensitive.

## The `window` query param

`/api/signals` and `/api/analyze` accept `?window=<days>`: how many days of
daily price history the window-scoped metrics (volatility, momentum,
max_drawdown, sharpe_like) are computed over. Default **30**, clamped to
**14–365** (out-of-range values are clamped, not rejected — check the `window`
field in the response for the value actually used).

## Endpoints

### GET /api/health

Liveness check, no external calls.

```
GET /api/health
```
```json
{ "status": "ok", "service": "QuantGate", "time": 1783661675,
  "endpoints": ["/api/health", "/api/price/{coin}", "/api/signals/{coin}", "/api/analyze/{coin}", "/pubkey", "/skill.md"] }
```

### GET /api/price/{coin}

Current market snapshot.

```
GET /api/price/btc
```
```json
{
  "coin": "bitcoin",
  "price_usd": 63928.9,
  "change_24h_pct": 2.75,
  "volume_24h": 27525178286,
  "market_cap": 1284512570555,
  "last_updated": "2026-07-10T04:54:32.695Z"
}
```

### GET /api/signals/{coin}?window=30

All nine computed indicators, each with raw value and (where applicable) an
interpreted state label. Any indicator with insufficient history is `null`,
never NaN.

```
GET /api/signals/bitcoin?window=30
```
```json
{
  "coin": "bitcoin",
  "window": 30,
  "price_usd": 63928.9,
  "signals": {
    "volatility": { "value": 0.323, "regime": "low" },
    "sma_7": 63351.8, "sma_30": 62677.9,
    "ema_7": 63053.8, "ema_30": 63521.5,
    "crossover": "none",
    "rsi_14": { "value": 52.97, "state": "neutral" },
    "momentum": { "value": 3.96, "direction": "up" },
    "bollinger": { "upper": 65283.6, "mid": 61808.9, "lower": 58334.2, "position": "inside" },
    "max_drawdown": -11.69,
    "sharpe_like": 1.62
  },
  "disclaimer": "Computed technical indicators, not financial advice.",
  "timestamp": 1783660906
}
```

Signal semantics:
- `volatility` — annualized stdev of daily log returns; regime bands: <0.4 low,
  0.4–0.8 normal, 0.8–1.2 elevated, >1.2 extreme
- `sma_7` / `sma_30` — simple moving averages of closes
- `ema_7` / `ema_30` — exponential moving averages (multiplier 2/(n+1))
- `crossover` — `"golden"` (EMA-7 just crossed above EMA-30), `"death"`
  (crossed below), or `"none"`
- `rsi_14` — Wilder's RSI; <30 `"oversold"`, >70 `"overbought"`, else `"neutral"`
- `momentum` — % change over the window; `"flat"` within ±1%
- `bollinger` — 20-period SMA ± 2 stdev; `position` is `"riding_upper"`,
  `"inside"`, or `"riding_lower"`
- `max_drawdown` — worst peak-to-trough decline in the window, negative %
- `sharpe_like` — simplified Sharpe proxy (mean/stdev of daily returns ×
  √365, risk-free rate 0)

### GET /api/analyze/{coin}?window=30 — the main endpoint

Fuses the signals into one decision-grade verdict, Ed25519-signed.

```
GET /api/analyze/bitcoin?window=30
```
```json
{
  "coin": "bitcoin",
  "window": 30,
  "price_usd": 63987.5,
  "verdict": {
    "trend": "neutral",
    "momentum": "strengthening",
    "volatility_regime": "low",
    "risk_level": "moderate",
    "confidence": 0.6,
    "summary": "Bitcoin is in a neutral trend with strengthening momentum and low volatility; risk is moderate and signals partially agree."
  },
  "signature": {
    "alg": "Ed25519",
    "sig_version": 2,
    "value": "M8yxdnT9...base64...",
    "public_key_id": "2108413565c1bdfc"
  },
  "computed_from": {
    "volatility": { "value": 0.323, "regime": "low" },
    "rsi_14": { "value": 53.19, "state": "neutral" },
    "momentum": { "value": 4.06, "direction": "up" },
    "max_drawdown": -11.69,
    "crossover": "none",
    "sma_30": 62679.8, "ema_7": 63068.4, "ema_30": 63525.2,
    "bollinger_position": "inside"
  },
  "disclaimer": "Computed technical indicators, not financial advice. QuantGate does not provide investment recommendations.",
  "timestamp": 1783661675
}
```

Verdict fields:
- `trend` — `"bullish"` (price > SMA-30 and EMA-7 > EMA-30), `"bearish"`
  (both inverted), else `"neutral"`; a fresh golden/death cross breaks a
  neutral tie toward the cross direction
- `momentum` — `"strengthening"` (rising with RSI < 70), `"weakening"`
  (falling with RSI > 30), else `"flat"`
- `volatility_regime` — pass-through of the volatility regime
- `risk_level` — `"high"` if volatility extreme OR drawdown ≤ −25% OR riding
  the lower band in a bearish trend; `"low"` if volatility low AND drawdown
  > −10%; else `"moderate"`. Never `"low"` while volatility is `"extreme"`.
- `confidence` — 0–1 signal-agreement score (base 0.5; +0.15 trend/momentum
  agree, +0.15 crossover confirms trend, +0.1 no conflicting RSI extreme,
  −0.2 outright contradiction)
- `summary` — deterministic plain-language sentence composed from the fields

If the coin has too little price history to fuse a verdict, the endpoint
returns `422` (see error codes).

If the server has no signing key configured, `signature` is `null` and a
`signature_note` field explains; all other fields are unaffected.

### GET /pubkey

The Ed25519 public key for signature verification.

```
GET /pubkey
```
```json
{
  "alg": "Ed25519",
  "public_key": "MCowBQYDK2VwAyEA...base64 DER SPKI...",
  "public_key_id": "2108413565c1bdfc",
  "verify_instructions": "Ed25519-verify signature.value (base64) against the UTF-8 bytes of the canonical JSON (recursively sorted keys, no whitespace, default JSON number formatting) of {coin, computed_from, timestamp, verdict, window} from the /api/analyze response, using public_key (base64 DER SPKI)."
}
```

Returns `503` if the server has no signing key configured.

### GET /skill.md

This document.

## Verifying a signature

`/api/analyze` responses are signed so you can prove a verdict came from
QuantGate and was not altered.

**Exactly what is signed** (`signature.sig_version` 2, the current version):
the UTF-8 bytes of the canonical JSON of

```
{ coin, computed_from, disclaimer, price_usd, timestamp, verdict, window }
```

taken from the response, where canonical JSON means: object keys sorted
alphabetically **at every nesting level**, no whitespace, default JSON number
formatting (JS shortest round-trip). Only the `signature` object itself is
outside the signed bytes. `sig_version` tells you which field set to
reconstruct: v1 (historical) signed `{coin, computed_from, timestamp, verdict,
window}`; v2 added `disclaimer` and `price_usd` so those can't be rewritten in
transit either.

**To verify:**
1. `GET /pubkey` → `public_key` (base64 DER SPKI). Check `public_key_id`
   matches `signature.public_key_id`.
2. Rebuild the canonical JSON of the seven fields above from the response.
3. Ed25519-verify `signature.value` (base64) over those UTF-8 bytes.

**Staleness:** the signed `timestamp` is when the verdict was produced.
Verifiers SHOULD reject verdicts more than a few minutes old to prevent an
old signed verdict being replayed as current.

Reference implementation (Node, ~40 lines): `scripts/verify-verdict.mjs` in
the repo — fetches a verdict and the pubkey, verifies, then demonstrates that
a tampered field makes verification fail.

## Error codes

All errors are JSON: `{ "error": "<human-readable message>" }`.

| Code | Meaning |
|------|---------|
| 404 | Unknown coin (not a CoinGecko id or supported ticker) |
| 422 | Coin exists but has too little price history to compute a verdict (`/api/analyze` only; only affects very new listings, rare in practice) |
| 429 | Upstream data provider rate limit — back off and retry shortly |
| 502 | Upstream data provider returned an error |
| 503 | Upstream unreachable, or (on `/pubkey`) no signing key configured |

Responses are computed from live data fetched per request (with a ~60s
server-side cache per coin+window), so repeated calls within a minute may
return identical numbers.

Note on prices: `/api/price` returns the live spot price, while `/api/signals`
and `/api/analyze` use the most recent daily close from historical data, so
`price_usd` may differ slightly between them.
