// ── StatusBody component ──
// Fetches live data from /api/status, /api/uptime, /api/latency, /api/incidents.
// Auto-refreshes every 30 s.

const { useState: useStatusState, useEffect: useStatusEffect, useCallback: useStatusCallback, useRef: useStatusRef } = React;

function pctUp(arr) {
  if (!arr || arr.length === 0) return "—";
  const good = arr.filter(v => v === 0).length;
  return ((good / arr.length) * 100).toFixed(2);
}

// ── tiny sparkline ──────────────────────────────────────────────────────
function Sparkline({ values, width = 220, height = 44, color = "var(--accent-ink)", fill = "var(--accent-soft)" }) {
  if (!values.length) return null;
  const min = Math.min(...values);
  const max = Math.max(...values);
  const range = max - min || 1;
  const stepX = width / (values.length - 1);
  const points = values.map((v, i) => [i * stepX, height - 4 - ((v - min) / range) * (height - 12)]);
  const path = points.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(" ");
  const area = `${path} L${width},${height} L0,${height} Z`;
  return (
    <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: "block" }}>
      <path d={area} fill={fill} opacity="0.5"/>
      <path d={path} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"/>
      <circle cx={points[points.length - 1][0]} cy={points[points.length - 1][1]} r="2.5" fill={color}/>
    </svg>
  );
}

// ── 90-day uptime grid ───────────────────────────────────────────────────
function UptimeGrid({ data }) {
  return (
    <div style={{ display: "flex", gap: 2 }}>
      {data.map((v, i) => (
        <div key={i} title={`${90 - i}d ago · ${v === 0 ? "operational" : v === 1 ? "degraded" : "outage"}`}
          style={{
            flex: 1, height: 32,
            borderRadius: 2,
            background: v === 0 ? "var(--ok)" : v === 1 ? "var(--warn)" : "var(--danger)",
            opacity: v === 0 ? 0.85 : 1,
          }}
        />
      ))}
    </div>
  );
}

// ── stats / quantiles helper ────────────────────────────────────────────
function quantile(arr, q) {
  if (!arr || arr.length === 0) return 0;
  const sorted = [...arr].sort((a, b) => a - b);
  const pos = (sorted.length - 1) * q;
  const base = Math.floor(pos);
  const rest = pos - base;
  return sorted[base + 1] !== undefined ? Math.round(sorted[base] + rest * (sorted[base + 1] - sorted[base])) : sorted[base];
}

// ── component cards ──────────────────────────────────────────────────────
function ComponentCard({ name, route, status, uptime90, latency24h, unit = "ms", color = "var(--accent-ink)" }) {
  const p50 = quantile(latency24h, 0.5);
  const p95 = quantile(latency24h, 0.95);
  const p99 = quantile(latency24h, 0.99);
  const last30 = uptime90.slice(-30);
  const last7 = uptime90.slice(-7);
  const last1 = uptime90.slice(-1);

  const statusLabel = status === "operational" ? "Operational" : status === "degraded" ? "Degraded" : status === "loading" ? "Loading…" : "Outage";
  const statusColor = status === "operational" ? "var(--ok)" : status === "loading" ? "var(--ink-muted)" : status === "degraded" ? "var(--warn)" : "var(--danger)";
  const statusBg    = status === "operational" ? "var(--ok-soft)" : status === "loading" ? "var(--bg-canvas)" : status === "degraded" ? "var(--warn-soft)" : "var(--danger-soft)";

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16, marginBottom: 14 }}>
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
            <span style={{
              width: 8, height: 8, borderRadius: "50%",
              background: statusColor,
              boxShadow: `0 0 0 3px ${statusBg}`,
            }}/>
            <h3 style={{ fontFamily: "var(--f-sans)", fontWeight: 500, fontSize: 16, margin: 0, color: "var(--ink-strong)" }}>{name}</h3>
          </div>
          <div style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-muted)", paddingLeft: 16 }}>{route}</div>
        </div>
        <span style={{
          padding: "3px 8px",
          background: statusBg,
          color: statusColor,
          fontFamily: "var(--f-mono)",
          fontSize: 10.5,
          textTransform: "uppercase",
          letterSpacing: ".08em",
          borderRadius: 3,
          fontWeight: 600,
          whiteSpace: "nowrap",
        }}>{statusLabel}</span>
      </div>

      {/* Latency row */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr auto", gap: 16, alignItems: "end", marginBottom: 18, paddingBottom: 14, borderBottom: "1px solid var(--rule)" }}>
        <Stat label="p50" value={p50} unit={unit}/>
        <Stat label="p95" value={p95} unit={unit}/>
        <Stat label="p99" value={p99} unit={unit}/>
        <Sparkline values={latency24h} width={120} height={36} color={color} fill={color === "var(--accent-ink)" ? "var(--accent-soft)" : "oklch(0.95 0.06 295)"}/>
      </div>

      {/* Uptime row */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
        <Stat label="24h uptime" value={pctUp(last1)} unit="%" size="sm"/>
        <Stat label="7d uptime" value={pctUp(last7)} unit="%" size="sm"/>
        <Stat label="30d uptime" value={pctUp(last30)} unit="%" size="sm"/>
      </div>
    </div>
  );
}

function Stat({ label, value, unit, size = "md" }) {
  return (
    <div>
      <div style={{
        fontFamily: "var(--f-mono)",
        fontSize: 10,
        letterSpacing: ".08em",
        textTransform: "uppercase",
        color: "var(--ink-soft)",
        marginBottom: 2,
      }}>{label}</div>
      <div style={{ fontFamily: "var(--f-mono)", color: "var(--ink-strong)" }}>
        <span style={{ fontSize: size === "sm" ? 16 : 20, fontWeight: 500 }}>{value}</span>
        <span style={{ fontSize: 11, color: "var(--ink-muted)", marginLeft: 2 }}>{unit}</span>
      </div>
    </div>
  );
}

// ── multi-series latency chart ──────────────────────────────────────────
function LatencyChart({ range }) {
  const [chartData, setChartData] = useStatusState({ rest: [], rt: [], ws: [] });

  useStatusEffect(() => {
    let cancelled = false;
    fetch(`/api/latency?range=${range}`)
      .then(r => r.json())
      .then(d => {
        if (!cancelled) setChartData({
          rest: (d.rest || []).map(v => v ?? 0),
          rt: (d.rt || []).map(v => v ?? 0),
          ws: (d.ws || []).map(v => v ?? 0),
        });
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [range]);

  const restSeries = chartData.rest;
  const rtSeries = chartData.rt;
  const wsSeries = chartData.ws;

  if (restSeries.length === 0 && rtSeries.length === 0 && wsSeries.length === 0) {
    return (
      <div style={{ height: 200, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-soft)", fontFamily: "var(--f-mono)", fontSize: 12 }}>
        Collecting latency data…
      </div>
    );
  }

  const W = 720, H = 200, PAD_L = 44, PAD_R = 12, PAD_T = 16, PAD_B = 28;
  const plotW = W - PAD_L - PAD_R;
  const plotH = H - PAD_T - PAD_B;

  const allVals = [...restSeries, ...rtSeries, ...wsSeries].filter(v => v > 0);
  const max = allVals.length > 0 ? Math.max(...allVals) * 1.15 : 100;

  function makePath(series) {
    if (series.length < 2) return "";
    const step = plotW / (series.length - 1);
    return series.map((v, i) => `${i === 0 ? "M" : "L"}${PAD_L + i * step},${PAD_T + plotH - (v / max) * plotH}`).join(" ");
  }

  const gridStep = max <= 20 ? 5 : max <= 100 ? 25 : max <= 500 ? 100 : 250;
  const gridLines = [];
  for (let g = 0; g <= max; g += gridStep) gridLines.push(g);

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: "block" }}>
      {gridLines.map(g => (
        <g key={g}>
          <line x1={PAD_L} x2={W - PAD_R} y1={PAD_T + plotH - (g / max) * plotH} y2={PAD_T + plotH - (g / max) * plotH} stroke="var(--rule)" strokeDasharray="2 3"/>
          <text x={PAD_L - 6} y={PAD_T + plotH - (g / max) * plotH + 3} textAnchor="end" fontSize="9" fontFamily="var(--f-mono)" fill="var(--ink-soft)">{g}ms</text>
        </g>
      ))}
      <line x1={PAD_L} x2={W - PAD_R} y1={PAD_T + plotH} y2={PAD_T + plotH} stroke="var(--rule-strong)"/>
      {[0, 0.25, 0.5, 0.75, 1].map(p => {
        const x = PAD_L + plotW * p;
        const label = range === "24h" ? `${Math.round(p * 24)}h` : range === "7d" ? `${Math.round(p * 7)}d` : `${Math.round(p * 30)}d`;
        return (
          <g key={p}>
            <line x1={x} x2={x} y1={PAD_T + plotH} y2={PAD_T + plotH + 3} stroke="var(--rule-strong)"/>
            <text x={x} y={PAD_T + plotH + 14} textAnchor="middle" fontSize="9" fontFamily="var(--f-mono)" fill="var(--ink-soft)">−{label}</text>
          </g>
        );
      })}
      <path d={makePath(restSeries)} fill="none" stroke="var(--accent-ink)" strokeWidth="1.5"/>
      <path d={makePath(rtSeries)} fill="none" stroke="oklch(0.65 0.18 160)" strokeWidth="1.5"/>
      <path d={makePath(wsSeries)} fill="none" stroke="oklch(0.55 0.16 295)" strokeWidth="1.5"/>
    </svg>
  );
}

// ── data hook (live) ──────────────────────────────────────────────────
const EMPTY_STATUS = {
  rest: { name: "REST API", route: "api.leandata.uk · Cloudflare → ThinkCentre", status: "loading", uptime90: [], latency24h: [] },
  rt:   { name: "RT API", route: "rt-api.leandata.uk · Cloudflare → EC2", status: "loading", uptime90: [], latency24h: [] },
  ws:   { name: "WebSocket stream", route: "ws://52.37.182.24:8767 · EC2 direct", status: "loading", uptime90: [], latency24h: [] },
  incidents: [],
  timestamp: null,
};

function uptimePctToGrid(pcts) {
  return (pcts || []).map(p => p >= 99.9 ? 0 : p >= 95 ? 1 : 2);
}

function useStatusData() {
  const [data, setData] = useStatusState(EMPTY_STATUS);

  const fetchAll = useStatusCallback(async () => {
    try {
      const [statusRes, uptimeRes, latencyRes, incidentsRes] = await Promise.all([
        fetch("/api/status").then(r => r.json()),
        fetch("/api/uptime").then(r => r.json()),
        fetch("/api/latency?range=24h").then(r => r.json()),
        fetch("/api/incidents").then(r => r.json()),
      ]);
      const filterNull = arr => (arr || []).filter(v => v !== null);
      const mapComponent = (key) => ({
        name: statusRes.components[key]?.name || key,
        route: statusRes.components[key]?.route || "",
        status: statusRes.components[key]?.status || "loading",
        uptime90: uptimePctToGrid(uptimeRes[key]),
        latency24h: filterNull(latencyRes[key]),
      });
      setData({
        rest: mapComponent("rest"),
        rt: mapComponent("rt"),
        ws: mapComponent("ws"),
        incidents: (incidentsRes.incidents || []).map(inc => ({
          ...inc,
          date: inc.date ? inc.date.slice(0, 10) : "",
        })),
        timestamp: statusRes.timestamp,
      });
    } catch (e) {
      console.error("Status fetch error:", e);
    }
  }, []);

  useStatusEffect(() => {
    fetchAll();
    const id = setInterval(fetchAll, 30000);
    return () => clearInterval(id);
  }, [fetchAll]);

  return data;
}

function SeverityChip({ s }) {
  const map = {
    minor:    { bg: "var(--warn-soft)",   fg: "var(--warn)",   label: "Minor" },
    major:    { bg: "var(--danger-soft)", fg: "var(--danger)", label: "Major" },
    resolved: { bg: "var(--ok-soft)",     fg: "var(--ok)",     label: "Resolved" },
  };
  const m = map[s] || map.minor;
  return (
    <span style={{
      padding: "2px 7px",
      background: m.bg,
      color: m.fg,
      fontFamily: "var(--f-mono)",
      fontSize: 10,
      textTransform: "uppercase",
      letterSpacing: ".08em",
      borderRadius: 3,
      fontWeight: 600,
    }}>{m.label}</span>
  );
}

// ── main component ──────────────────────────────────────────────────────
function StatusBody() {
  const data = useStatusData();
  const [range, setRange] = useStatusState("24h");

  const isLoading = data.rest.status === "loading" || data.rt.status === "loading" || data.ws.status === "loading";
  const allOp = data.rest.status === "operational" && data.rt.status === "operational" && data.ws.status === "operational";
  const anyOutage = data.rest.status === "outage" || data.rt.status === "outage" || data.ws.status === "outage";
  const overall = isLoading ? "loading" : allOp ? "operational" : anyOutage ? "outage" : "degraded";

  const overallLabel = overall === "loading" ? "Checking systems…" : overall === "operational" ? "All systems operational" : overall === "outage" ? "Service disruption" : "Partial degradation";
  const overallColor = overall === "loading" ? "var(--ink-muted)" : overall === "operational" ? "var(--ok)" : overall === "outage" ? "var(--danger)" : "var(--warn)";
  const overallBg    = overall === "loading" ? "var(--bg-canvas)" : overall === "operational" ? "var(--ok-soft)" : overall === "outage" ? "var(--danger-soft)" : "var(--warn-soft)";

  const lastUpdated = data.timestamp
    ? data.timestamp.replace("T", " ").slice(0, 16) + " UTC"
    : "loading…";

  return (
    <div>
      {/* ── Overview ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>System</div>
      <h2 id="overview" className="display-title" style={{ fontSize: 38, margin: "0 0 8px" }}>Status</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 24px" }}>
        Live health of the REST API (Cloudflare → ThinkCentre), RT API (Cloudflare → EC2), and WebSocket stream (EC2 direct).
        Uptime is sampled every minute; latency percentiles are computed over a rolling 60-minute window.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>REST API（Cloudflare → ThinkCentre）、RT API（Cloudflare → EC2）与 WebSocket 流（EC2 直连）的实时健康指标。可用性按分钟采样，延迟分位数按 60 分钟滚动窗口计算。</span>
      </p>

      {/* Hero status banner */}
      <div className="card" style={{
        padding: "18px 20px",
        marginBottom: 32,
        borderLeft: `3px solid ${overallColor}`,
        display: "flex",
        alignItems: "center",
        gap: 16,
      }}>
        <span style={{
          width: 12, height: 12, borderRadius: "50%",
          background: overallColor,
          boxShadow: `0 0 0 4px ${overallBg}`,
        }}/>
        <div style={{ flex: 1 }}>
          <div className="display-title" style={{ fontSize: 22, color: "var(--ink-strong)" }}>{overallLabel}</div>
          <div style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-muted)", marginTop: 2 }}>
            Last updated · {lastUpdated} · refreshes every 30 s
          </div>
        </div>
        <button className="btn" style={{ fontSize: 12, padding: "6px 12px" }} onClick={() => window.location.reload()}>
          ↻ Refresh
        </button>
      </div>

      {/* ── Components ── */}
      <h2 id="components" className="display-title" style={{ fontSize: 28, margin: "0 0 16px" }}>Components</h2>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16, marginBottom: 40 }}>
        <ComponentCard {...data.rest} unit="ms" color="var(--accent-ink)"/>
        <ComponentCard {...data.rt} unit="ms" color="oklch(0.65 0.18 160)"/>
        <ComponentCard {...data.ws} unit="ms" color="oklch(0.55 0.16 295)"/>
      </div>

      {/* ── Latency chart ── */}
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 14 }}>
        <h2 id="latency" className="display-title" style={{ fontSize: 28, margin: 0 }}>Latency</h2>
        <div style={{ display: "flex", gap: 0, border: "1px solid var(--rule-strong)", borderRadius: 6, overflow: "hidden", fontFamily: "var(--f-mono)" }}>
          {["24h", "7d", "30d"].map(r => (
            <button
              key={r}
              onClick={() => setRange(r)}
              style={{
                padding: "6px 12px",
                fontSize: 11,
                fontFamily: "inherit",
                border: "none",
                background: range === r ? "var(--ink-strong)" : "transparent",
                color: range === r ? "var(--ink-inverse)" : "var(--ink-muted)",
                cursor: "pointer",
              }}
            >{r}</button>
          ))}
        </div>
      </div>
      <div className="card" style={{ padding: 20, marginBottom: 8 }}>
        <div style={{ display: "flex", gap: 16, marginBottom: 8, fontSize: 12 }}>
          <span style={{ display: "flex", alignItems: "center", gap: 6, color: "var(--ink-muted)" }}>
            <span style={{ width: 12, height: 2, background: "var(--accent-ink)" }}/> REST API
          </span>
          <span style={{ display: "flex", alignItems: "center", gap: 6, color: "var(--ink-muted)" }}>
            <span style={{ width: 12, height: 2, background: "oklch(0.65 0.18 160)" }}/> RT API
          </span>
          <span style={{ display: "flex", alignItems: "center", gap: 6, color: "var(--ink-muted)" }}>
            <span style={{ width: 12, height: 2, background: "oklch(0.55 0.16 295)" }}/> WebSocket
          </span>
          <span style={{ flex: 1 }}/>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-soft)" }}>
            {range} · ms · client → response
          </span>
        </div>
        <LatencyChart range={range}/>
      </div>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 40px" }}>
        REST latency includes Cloudflare edge → ThinkCentre origin round-trip plus server processing.
        WebSocket latency is the auth-response round-trip after socket open; message delivery has near-zero added latency once the stream is warm.
      </p>

      {/* ── Uptime grid ── */}
      <h2 id="uptime" className="display-title" style={{ fontSize: 28, margin: "0 0 16px" }}>90-day uptime</h2>
      <div className="card" style={{ padding: 20, marginBottom: 12 }}>
        <UptimeBlock label="REST API" data={data.rest.uptime90} />
        <hr style={{ border: 0, borderTop: "1px solid var(--rule)", margin: "18px 0" }}/>
        <UptimeBlock label="WebSocket stream" data={data.ws.uptime90} />
      </div>
      <div style={{ display: "flex", gap: 18, fontSize: 11, color: "var(--ink-muted)", marginBottom: 40, fontFamily: "var(--f-mono)" }}>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ width: 10, height: 10, background: "var(--ok)", borderRadius: 2 }}/> operational
        </span>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ width: 10, height: 10, background: "var(--warn)", borderRadius: 2 }}/> degraded
        </span>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <span style={{ width: 10, height: 10, background: "var(--danger)", borderRadius: 2 }}/> outage
        </span>
      </div>

      {/* ── Incidents ── */}
      <h2 id="incidents" className="display-title" style={{ fontSize: 28, margin: "0 0 16px" }}>Recent incidents</h2>
      {data.incidents.length === 0 ? (
        <p style={{ color: "var(--ink-soft)", fontFamily: "var(--f-mono)", fontSize: 12 }}>No incidents recorded.</p>
      ) : (
        <div style={{ borderTop: "1px solid var(--rule)" }}>
          {data.incidents.map((inc, i) => (
            <div key={inc.id || i} style={{ padding: "16px 0", borderBottom: "1px solid var(--rule)" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
                <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-soft)" }}>{inc.date}</span>
                <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-muted)" }}>·</span>
                <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-muted)" }}>{inc.component}</span>
                <span style={{ flex: 1 }}/>
                {inc.duration && <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-soft)" }}>{inc.duration}</span>}
                <SeverityChip s={inc.severity}/>
              </div>
              <div style={{ fontSize: 14, fontWeight: 500, color: "var(--ink-strong)", marginBottom: 4 }}>{inc.title}</div>
              {inc.summary && <p style={{ fontSize: 13, color: "var(--ink-muted)", margin: 0, lineHeight: 1.55 }}>{inc.summary}</p>}
            </div>
          ))}
        </div>
      )}

      {/* ── Methodology ── */}
      <h2 id="methodology" className="display-title" style={{ fontSize: 28, margin: "40px 0 12px" }}>Methodology</h2>
      <p style={{ fontSize: 14, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Probes run on-demand from the token portal when the status page is viewed, and sample on each page refresh (every 30 s).
        REST checks: <code className="ic">GET /health</code> against the local proxy (<code className="ic">localhost:8768</code>).
        WebSocket checks: TCP connect to <code className="ic">52.37.182.24:8767</code> (EC2).
        Uptime is the fraction of probes that returned success within the timeout window, aggregated daily over 90 days.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>
          探针在状态页浏览时按需运行，每 30 秒自动刷新。REST 检查命中本地代理 /health；WS 检查 TCP 连接 EC2 端口。可用性 = 超时窗口内成功探针比例，按天聚合，展示 90 天。
        </span>
      </p>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 48px" }}>
        SLO: REST p95 ≤ 250 ms · WS auth ≤ 50 ms · monthly uptime ≥ 99.9 %.
      </p>
    </div>
  );
}

function UptimeBlock({ label, data }) {
  const pct = pctUp(data);
  const num = parseFloat(pct);
  const color = num >= 99.9 ? "var(--ok)" : num >= 99 ? "var(--warn)" : "var(--danger)";
  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 10 }}>
        <div style={{ fontSize: 14, fontWeight: 500, color: "var(--ink-strong)" }}>{label}</div>
        <div style={{ display: "flex", alignItems: "baseline", gap: 4 }}>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 18, fontWeight: 500, color }}>{pct}</span>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-muted)" }}>% uptime · 90 days</span>
        </div>
      </div>
      <UptimeGrid data={data}/>
      <div style={{ display: "flex", justifyContent: "space-between", marginTop: 6, fontFamily: "var(--f-mono)", fontSize: 10, color: "var(--ink-soft)" }}>
        <span>90 days ago</span>
        <span>today</span>
      </div>
    </div>
  );
}


// ── DocsSite component ──

// DocsSite.jsx — Public Docs Site redesign
// Top bar · hero · tabs (Proxy API / WS usage — Reports removed) · 3-col docs layout

const { useState } = React;

function DocsTopbar({ active = "proxy", onNav }) {
  return (
    <div className="topbar">
      <div className="brand">
        <span className="dot"></span>
        <span><strong>Public Docs Site</strong></span>
      </div>
      <div className="divider"></div>
      <div className="nav">
        <a className={active === "proxy" ? "active" : ""} onClick={() => onNav && onNav("proxy")} style={{ cursor: "pointer" }}>Proxy API</a>
        <a className={active === "ws" ? "active" : ""} onClick={() => onNav && onNav("ws")} style={{ cursor: "pointer" }}>WS usage</a>
        <a className={active === "status" ? "active" : ""} onClick={() => onNav && onNav("status")} style={{ cursor: "pointer" }}>Status</a>
        <a className={active === "usage" ? "active" : ""} onClick={() => onNav && onNav("usage")} style={{ cursor: "pointer" }}>Usage</a>
      </div>
      <div className="spacer"></div>
      <div className="meta">
        <span className="pill"><span className="live"></span> v2.6 · live</span>
        <a className="btn ghost" style={{ padding: "6px 10px", fontSize: 12 }}>
          <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0a8 8 0 0 0-2.53 15.59c.4.07.55-.17.55-.38v-1.34c-2.22.48-2.69-1.07-2.69-1.07-.36-.92-.89-1.17-.89-1.17-.73-.5.05-.49.05-.49.8.06 1.23.83 1.23.83.72 1.23 1.88.87 2.34.67.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.7 7.7 0 0 1 4 0c1.53-1.03 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.28.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.74.54 1.49v2.2c0 .21.15.46.55.38A8 8 0 0 0 8 0z"/></svg>
          ikkisovi/Websocket-DataFeed-Proxy-docs
        </a>
      </div>
    </div>
  );
}

function DocsSite({ initialTab = "proxy", hideTopbar = false } = {}) {
  const validTabs = ["proxy", "ws", "status", "usage"];
  const hashTab = typeof window !== "undefined" && window.location.hash ? window.location.hash.slice(1) : "";
  const startTab = validTabs.includes(hashTab) ? hashTab : initialTab;
  const [tab, setTab] = useState(startTab);

  const showTopbar = !hideTopbar;

  return (
    <div className="proxy-app" style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
      {showTopbar && <DocsTopbar active={tab} onNav={setTab} />}

      {/* Hero */}
      <div style={{
        padding: "44px 64px 28px",
        borderBottom: "1px solid var(--rule)",
        background: "var(--bg-paper)",
        position: "relative",
        overflow: "hidden",
      }}>
        <div className="eyebrow" style={{ marginBottom: 14 }}>Reference · live docs</div>
        <h1 className="display-title" style={{ fontSize: 64, margin: "0 0 14px" }}>
          Stock Options Proxy <span style={{ fontStyle: "italic", color: "var(--accent-ink)" }}>API</span>
        </h1>
        <p style={{ color: "var(--ink-muted)", maxWidth: 640, fontSize: 15, margin: 0 }}>
          Real-time US equities, options, crypto and news — one token, no Alpaca key needed.
          The <strong style={{ color: "var(--ink-strong)" }}>Proxy API</strong> covers REST endpoints and tier management;
          <strong style={{ color: "var(--ink-strong)" }}>WS usage</strong> covers the 6 realtime streaming channels.
        </p>

        {/* Tab strip */}
        <div style={{ marginTop: 32, display: "flex", gap: 0, borderBottom: "1px solid var(--rule)", marginInline: -64, paddingInline: 64 }}>
          <Tab id="proxy" tab={tab} setTab={setTab} label="Proxy API" count="45+ endpoints" />
          <Tab id="ws" tab={tab} setTab={setTab} label="WS usage" count="6 channels" />
          <Tab id="status" tab={tab} setTab={setTab} label="Status" count="live" />
          <Tab id="usage" tab={tab} setTab={setTab} label="Usage" count="30d" />
          <div style={{ flex: 1 }}></div>
          <div style={{ alignSelf: "flex-end", paddingBottom: 10, color: "var(--ink-soft)", fontFamily: "var(--f-mono)", fontSize: 11 }}>
            last sync · 2026-05-25 hybrid architecture (CF REST + EC2 WS)
          </div>
        </div>
      </div>

      {/* Content */}
      <div style={{ display: "grid", gridTemplateColumns: (tab === "status" || tab === "usage") ? "1fr" : "220px 1fr 220px", flex: 1 }}>
        {tab !== "status" && tab !== "usage" && <SideNav tab={tab} />}
        <main style={{ padding: (tab === "status" || tab === "usage") ? "36px 32px" : "40px 56px", background: "var(--bg-canvas)" }}>
          {tab === "proxy" ? <ProxyApiBody /> : tab === "ws" ? <WsUsageBody /> : tab === "usage" ? (typeof UsagePage !== "undefined" ? React.createElement(UsagePage) : React.createElement("div", null, "Loading usage…")) : (React.createElement(StatusBody))}
        </main>
        {tab !== "status" && tab !== "usage" && <OnThisPage tab={tab} />}
      </div>
    </div>
  );
}

function slugify(text) {
  return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
}

function Tab({ id, tab, setTab, label, count }) {
  const active = tab === id;
  return (
    <button
      onClick={() => setTab(id)}
      style={{
        background: "transparent",
        border: "none",
        padding: "12px 0",
        marginRight: 28,
        cursor: "pointer",
        fontFamily: "var(--f-sans)",
        fontSize: 14,
        fontWeight: 500,
        color: active ? "var(--ink-strong)" : "var(--ink-muted)",
        borderBottom: `2px solid ${active ? "var(--ink-strong)" : "transparent"}`,
        marginBottom: -1,
        display: "flex",
        alignItems: "baseline",
        gap: 8,
      }}
    >
      {label}
      <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-soft)", fontWeight: 400 }}>{count}</span>
    </button>
  );
}

function SideNav({ tab }) {
  const [activeId, setActiveId] = React.useState("");
  const [expanded, setExpanded] = React.useState({
    "Stock Data": true,
    "Multi-symbol": true,
    "Metadata": true,
    "Single symbol": true,
    "Options Data": true,
    "Snapshots": true,
  });
  React.useEffect(() => {
    const onHashChange = () => setActiveId(window.location.hash.slice(1));
    window.addEventListener('hashchange', onHashChange);
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => { if(entry.isIntersecting) setActiveId(entry.target.id); });
    }, { rootMargin: '-20% 0px -80% 0px' });
    setTimeout(() => document.querySelectorAll('h2[id], h3[id]').forEach(h => observer.observe(h)), 500);
    return () => { window.removeEventListener('hashchange', onHashChange); observer.disconnect(); };
  }, [tab]);

  const toggle = (title) => setExpanded(prev => ({ ...prev, [title]: !prev[title] }));

  const sections = tab === "proxy" ? [
    { title: "Getting started", items: ["Overview", "Authentication", "Tiers & permissions"] },
    { title: "Token API", items: ["register", "check-status", "generate-token"] },
    { title: "REST History", items: ["history/bars", "history/news", "stock trade+quote"] },
    { title: "Stock Data", items: ["overview"], children: [
      { title: "Multi-symbol", items: ["auctions", "multi bars", "multi latest bars", "multi quotes", "multi latest quotes", "multi snapshots", "multi trades", "multi latest trades"] },
      { title: "Metadata", items: ["condition codes", "exchange codes"] },
      { title: "Single symbol", items: ["single bars", "single latest bar", "single quotes", "single latest quote", "single snapshot", "single trades", "single latest trade"] },
    ]},
    { title: "Options Data", items: ["provider model", "contracts"], children: [
      { title: "Snapshots", items: ["snapshots", "quote", "snapshot trade", "open interest", "expiry", "snapshot ohlc"] },
      { title: "History", items: ["bars", "eod", "history open interest", "trades", "history ohlc"] },
      { title: "ThetaData Value", items: ["direct endpoints"] },
    ]},
    { title: "Crypto Data", items: ["orderbooks"] },
    { title: "Admin endpoints", items: ["login", "pending", "approve", "reject"] },
    { title: "Reference", items: ["Error codes", "Rate limits"] },
  ] : tab === "ws" ? [
    { title: "Connecting", items: ["WebSocket connection", "Auth message", "Heartbeat", "Error", "Success"] },
    { title: "Channels", items: ["stocks", "options", "boats", "overnight", "crypto", "news", "test"] },
    { title: "Operations", items: ["Reconnect", "Backpressure", "Rate limits"] },
  ] : [
    { title: "System", items: ["Overview", "Components", "Latency"] },
    { title: "History", items: ["Uptime", "Incidents", "Methodology"] },
  ];

  function Chevron({ open }) {
    return (
      <svg width="10" height="10" viewBox="0 0 10 10" style={{ transition: 'transform 0.2s', transform: open ? 'rotate(90deg)' : 'rotate(0deg)', marginLeft: 'auto', opacity: 0.5 }}>
        <path d="M3 1 L7 5 L3 9" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
      </svg>
    );
  }

  function Section({ s, depth = 0 }) {
    const hasChildren = s.children && s.children.length > 0;
    const hasItems    = s.items && s.items.length > 0;
    const isParent    = hasChildren || hasItems;
    const isOpen      = expanded[s.title] !== false;
    const isMono      = s.title.includes("endpoints") || s.title === "Messages";

    const BASE_PAD    = 10;
    const INDENT_STEP = 20;
    const indent      = BASE_PAD + depth * INDENT_STEP;

    // Map sidebar labels to actual document IDs
    const ID_MAP = {'Overview': 'overview', 'Authentication': 'authentication', 'Tiers & permissions': 'tiers-permissions', 'register': 'post-register', 'check-status': 'post-check-status', 'generate-token': 'post-generate-token', 'history/bars': 'post-v1-history-bars', 'history/news': 'post-v1-history-news', 'stock trade+quote': 'post-v1-stock-history-trade-quote', 'overview': 'stock-data-availability', 'auctions': 'stock-auctions', 'multi bars': 'stock-bars', 'multi latest bars': 'stock-latest-bars', 'condition codes': 'stock-condition-codes', 'exchange codes': 'stock-exchange-codes', 'multi quotes': 'stock-quotes', 'multi latest quotes': 'stock-latest-quotes', 'multi snapshots': 'stock-snapshots', 'multi trades': 'stock-trades', 'multi latest trades': 'stock-latest-trades', 'single bars': 'stock-single-bars', 'single latest bar': 'stock-single-latest-bar', 'single quotes': 'stock-single-quotes', 'single latest quote': 'stock-single-latest-quote', 'single snapshot': 'stock-single-snapshot', 'single trades': 'stock-single-trades', 'single latest trade': 'stock-single-latest-trade', 'provider model': 'provider-fallback-cache', 'contracts': 'post-v1-options-contracts', 'snapshots': 'post-v1-options-snapshots', 'quote': 'post-v1-options-snapshots-quote', 'snapshot trade': 'post-v1-options-snapshots-trade', 'open interest': 'post-v1-options-snapshots-open-interest', 'expiry': 'post-v1-options-snapshots-expiry', 'snapshot ohlc': 'post-v3-option-direct-value', 'bars': 'post-v1-history-options-bars', 'eod': 'post-v1-history-options-eod', 'history open interest': 'post-v1-options-open-interest', 'trades': 'post-v1-history-options-trades', 'history ohlc': 'post-v3-option-direct-value', 'direct endpoints': 'post-v3-option-direct-value', 'orderbooks': 'post-v1-crypto-us-latest-orderbooks', 'login': 'post-admin-login', 'pending': 'get-admin-pending', 'approve': 'post-admin-approve', 'reject': 'post-admin-reject', 'Error codes': 'error-codes', 'Rate limits': 'rate-limits'};
    const getId = (label) => ID_MAP[label] || slugify(label);

    return (
      <div style={{ marginBottom: hasChildren ? 0 : 8 }}>
        {/* ── header row (chevron right-aligned like the reference) ── */}
        <div
          onClick={() => isParent && toggle(s.title)}
          style={{
            display: "flex", alignItems: "center",
            padding: "5px 10px 5px " + indent,
            cursor: isParent ? "pointer" : "default",
            color: depth === 0 ? "var(--ink-strong)" : "var(--ink-soft)",
            fontSize: 11,
            fontWeight: depth === 0 ? 700 : 600,
            letterSpacing: "0.06em",
            textTransform: "uppercase",
            userSelect: "none",
          }}
        >
          <span style={{ flex: 1 }}>{s.title}</span>
          {isParent && <Chevron open={isOpen} />}
        </div>

        {/* ── children + guide lines ── */}
        {isOpen && (
          <>
            {/* vertical guide line for this group */}
            {isParent && (
              <div style={{
                position: "relative",
                marginLeft: indent + 8,
                paddingLeft: 12,
                borderLeft: "1px solid var(--rule)",
              }}>
                {/* leaf items */}
                {hasItems && (
                  <ul style={{ listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 2 }}>
                    {s.items.map((it, j) => (
                      <li key={j}>
                        <a href={"#" + getId(it)} style={{
                          textDecoration: "none", display: "block",
                          padding: "3px 0",
                          color: activeId === getId(it) ? "var(--ink-strong)" : "var(--ink-muted)",
                          fontWeight: activeId === getId(it) ? 500 : 400,
                          fontFamily: isMono ? "var(--f-mono)" : "var(--f-sans)",
                          fontSize: isMono ? 12 : 13,
                        }}>{it}</a>
                      </li>
                    ))}
                  </ul>
                )}

                {/* nested sub-sections */}
                {hasChildren && s.children.map((child, k) => (
                  <Section key={k} s={child} depth={depth + 1} />
                ))}
              </div>
            )}
          </>
        )}
      </div>
    );
  }

  return (
    <nav style={{
      padding: "32px 0 32px 32px",
      borderRight: "1px solid var(--rule)",
      background: "var(--bg-canvas)",
      fontSize: 13, position: "sticky", top: 0, height: "100vh", overflow: "auto"
    }}>
      {sections.map((s, i) => <Section key={i} s={s} />)}
    </nav>
  );
}

function OnThisPage({ tab }) {
  const [activeId, setActiveId] = React.useState("");
  React.useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => { if(entry.isIntersecting) setActiveId(entry.target.id); });
    }, { rootMargin: '-20% 0px -80% 0px' });
    setTimeout(() => document.querySelectorAll('h2[id], h3[id]').forEach(h => observer.observe(h)), 500);
    return () => observer.disconnect();
  }, [tab]);
  const items = tab === "proxy"
    ? ["Request", "Response", "Validation", "Examples", "Errors"]
    : tab === "ws"
    ? ["Connect", "Authenticate", "Subscribe", "Message shapes", "Reconnect"]
    : ["Overview", "Components", "Latency", "Uptime", "Incidents"];
  return (
    <aside style={{
      padding: "40px 24px",
      borderLeft: "1px solid var(--rule)",
      background: "var(--bg-canvas)", fontSize: 12.5, position: "sticky", top: 0, height: "100vh", overflow: "auto"
    }}>
      <div className="eyebrow" style={{ marginBottom: 12, color: "var(--ink-soft)" }}>On this page</div>
      <ul style={{ listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 6 }}>
        {items.map((it, i) => (
          <li key={i}>
            <a href={"#" + slugify(it)} style={{textDecoration: "none",  color: activeId === slugify(it) ? "var(--ink-strong)" : "var(--ink-muted)" }}>{it}</a>
          </li>
        ))}
      </ul>

      <TokenCard />
    </aside>
  );
}

function TokenCard() {
  const [user, setUser] = React.useState("");
  const [phone, setPhone] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const [errorMsg, setErrorMsg] = React.useState("");
  const [tokenData, setTokenData] = React.useState(null);

  const handleGenerate = async () => {
    if (!user || !phone) { setErrorMsg("Please enter both fields."); return; }
    setLoading(true); setErrorMsg(""); setTokenData(null);
    try {
      const res = await fetch('/api/generate-token', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: user, phone })
      });
      const data = await res.json();
      if (data.success) {
        setTokenData({ token: data.token, expiry: new Date(data.expiry).toLocaleString(), role: data.role || "standard" });
      } else { setErrorMsg(data.message); }
    } catch (e) { setErrorMsg("Network error."); }
    finally { setLoading(false); }
  };

  const handleCopy = () => {
    if (tokenData && tokenData.token) {
      const textToCopy = tokenData.token;
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(textToCopy)
          .then(() => alert("Token copied to clipboard!"))
          .catch(() => fallbackCopy(textToCopy));
      } else {
        fallbackCopy(textToCopy);
      }
    }
  };

  const fallbackCopy = (text) => {
    const textArea = document.createElement("textarea");
    textArea.value = text;
    textArea.style.position = "fixed";
    textArea.style.top = "0";
    textArea.style.left = "0";
    textArea.style.width = "2em";
    textArea.style.height = "2em";
    textArea.style.padding = "0";
    textArea.style.border = "none";
    textArea.style.outline = "none";
    textArea.style.boxShadow = "none";
    textArea.style.background = "transparent";
    document.body.appendChild(textArea);
    textArea.focus();
    textArea.select();
    try {
      const successful = document.execCommand('copy');
      if (successful) {
        alert("Token copied to clipboard!");
      } else {
        alert("Unable to copy automatically. Please copy it manually.");
      }
    } catch (err) {
      alert("Unable to copy automatically. Please copy it manually.");
    }
    document.body.removeChild(textArea);
  };

  return (
    <div style={{ marginTop: 28, padding: 14, borderRadius: 8, background: "var(--bg-paper)", border: "1px solid var(--rule)" }}>
      <div className="eyebrow" style={{ marginBottom: 8, color: "var(--ink-soft)" }}>Generate token</div>
      {!tokenData ? (
        <>
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <input className="input mono" placeholder="Username" value={user} onChange={e => setUser(e.target.value)}
              style={{ fontSize: 12, padding: "8px 10px" }} />
            <input className="input mono" placeholder="Phone" value={phone} onChange={e => setPhone(e.target.value)}
              style={{ fontSize: 12, padding: "8px 10px" }} />
            <button className="btn" onClick={handleGenerate} disabled={loading}
              style={{ width: "100%", justifyContent: "center", fontSize: 12, padding: "8px 10px" }}>
              {loading ? "..." : "Generate →"}
            </button>
          </div>
          {errorMsg && <p style={{ margin: "8px 0 0", color: "#d9534f", fontSize: 11 }}>{errorMsg}</p>}
          <div style={{ marginTop: 12, fontSize: 11, color: "var(--ink-muted)", display: "flex", justifyContent: "space-between" }}>
            <span>New user?</span>
            <a href="/register" style={{ color: "var(--accent-ink)", textDecoration: "none" }}>Register →</a>
          </div>
        </>
      ) : (
        <>
          <div style={{ background: "var(--accent-soft)", border: "1px solid var(--accent-rule)", borderRadius: 6, padding: "6px 10px", fontSize: 11, color: "var(--accent-ink)", marginBottom: 8, display: "flex", alignItems: "center", gap: 6 }}>
            <span style={{ width: 5, height: 5, borderRadius: "50%", background: "var(--ok)" }}></span>
            Token issued
          </div>
          <div style={{ display: "flex", gap: 6, alignItems: "stretch" }}>
            <input className="input mono" readOnly value={tokenData.token} style={{ fontSize: 10, flex: 1, padding: "6px 8px" }} />
            <button className="btn" style={{ padding: "0 10px", fontSize: 11 }} onClick={handleCopy}>Copy</button>
          </div>
          <div style={{ marginTop: 8, fontSize: 11, color: "var(--ink-muted)" }}>
            <div style={{ display: "flex", justifyContent: "space-between" }}>
              <span>Expires</span><span style={{ fontFamily: "var(--f-mono)" }}>{tokenData.expiry}</span>
            </div>
            <div style={{ display: "flex", justifyContent: "space-between", marginTop: 4 }}>
              <span>Role</span><span className={"tier " + tokenData.role}>{tokenData.role}</span>
            </div>
          </div>
          <button className="btn ghost" onClick={() => { setTokenData(null); setUser(""); setPhone(""); }}
            style={{ width: "100%", justifyContent: "center", fontSize: 11, padding: "6px", marginTop: 8 }}>
            Generate another
          </button>
        </>
      )}
    </div>
  );
}

// Hybrid architecture: REST via Cloudflare→ThinkCentre, WS via EC2 direct
const REST_BASE  = "https://api.leandata.uk";
const RT_BASE    = "https://rt-api.leandata.uk";
const TOKEN_BASE = "https://leandata.uk";
const WS_HOST    = "52.37.182.24";
const WS_BASE    = `ws://${WS_HOST}:8767`;

function ParamRow({ name, type, required, desc }) {
  return (
    <tr>
      <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, color: "var(--ink-strong)", whiteSpace: "nowrap" }}>{name}</td>
      <td style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-soft)" }}>{type}</td>
      <td style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: required ? "var(--accent)" : "var(--ink-soft)" }}>{required ? "required" : "optional"}</td>
      <td style={{ fontSize: 12, color: "var(--ink-muted)" }}>{desc}</td>
    </tr>
  );
}

function ParamTable({ rows }) {
  return (
    <table className="tbl" style={{ marginBottom: 20, width: "100%", fontSize: 13 }}>
      <thead>
        <tr>
          <th style={{ width: 180 }}>Parameter</th>
          <th style={{ width: 90 }}>Type</th>
          <th style={{ width: 90 }}>Required</th>
          <th>Description</th>
        </tr>
      </thead>
      <tbody>
        {rows.map((r, i) => <ParamRow key={i} {...r} />)}
      </tbody>
    </table>
  );
}

function EndpointBadge({ method, path }) {
  const colors = { POST: "var(--accent)", GET: "var(--ok)", WSS: "#8b5cf6" };
  return (
    <div className="card" style={{ padding: "10px 16px", marginBottom: 20, display: "flex", alignItems: "center", gap: 12 }}>
      <span className="method" style={{ background: colors[method] || "var(--accent)", color: "#fff", padding: "2px 8px", borderRadius: 4, fontFamily: "var(--f-mono)", fontSize: 11, fontWeight: 700 }}>{method}</span>
      <code style={{ fontFamily: "var(--f-mono)", fontSize: 13 }}>{path}</code>
    </div>
  );
}

const STOCK_COMMON = {
  symbols: { name: "symbols", type: "string", required: true, desc: "Comma-separated symbols, e.g. AAPL,MSFT" },
  symbolPath: { name: "symbol", type: "path", required: true, desc: "Single ticker in the URL path, e.g. AAPL" },
  start: { name: "start", type: "string", required: true, desc: "Inclusive start time/date. ISO 8601 recommended." },
  end: { name: "end", type: "string", required: true, desc: "Exclusive end time/date. ISO 8601 recommended." },
  feed: { name: "feed", type: "string", required: false, desc: "iex is safest by default; sip/delayed_sip/boats/overnight/otc depend on endpoint and entitlement." },
  limit: { name: "limit", type: "integer", required: false, desc: "Page size. Use next_page_token for pagination when returned." },
  pageToken: { name: "page_token", type: "string", required: false, desc: "Pagination token from the previous response." },
  timeframe: { name: "timeframe", type: "string", required: true, desc: "1Min, 5Min, 15Min, 30Min, 1Hour, 1Day, etc." },
  sort: { name: "sort", type: "string", required: false, desc: "asc or desc for historical tick endpoints." },
  tape: { name: "tape", type: "string", required: true, desc: "Tape A, B, or C. Example: tape=C for Nasdaq-listed symbols." },
};

const STOCK_ENDPOINT_GROUPS = [
  {
    title: "Multi-symbol market data",
    intro: "Batch endpoints for querying one or more stock symbols in one request.",
    endpoints: [
      {
        id: "stock-auctions",
        title: "Historical Auctions",
        route: "/v2/stocks/auctions",
        examplePath: "/v2/stocks/auctions?symbols=AAPL&start=2026-05-20&end=2026-05-21&limit=1&feed=sip",
        desc: "Auction prices for one or more stocks. Use this when you need official auction prints rather than continuous trade ticks.",
        zh: "查询一个或多个股票的历史 auction 数据。适合需要开盘/收盘 auction print 的场景。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.start, STOCK_COMMON.end, { ...STOCK_COMMON.feed, desc: "Alpaca supports SIP feed for auctions." }, STOCK_COMMON.limit, STOCK_COMMON.pageToken],
        keys: ["auctions", "next_page_token"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-bars",
        title: "Historical Bars",
        route: "/v2/stocks/bars",
        examplePath: "/v2/stocks/bars?symbols=AAPL&timeframe=1Day&start=2026-05-20&end=2026-05-21&limit=1&feed=sip",
        desc: "Historical OHLCV bars for multiple symbols.",
        zh: "多股票历史 OHLCV K 线。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.timeframe, STOCK_COMMON.start, STOCK_COMMON.end, STOCK_COMMON.feed, { name: "adjustment", type: "string", required: false, desc: "raw, split, dividend, or all." }, STOCK_COMMON.limit, STOCK_COMMON.pageToken],
        keys: ["bars", "next_page_token"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-latest-bars",
        title: "Latest Bars",
        route: "/v2/stocks/bars/latest",
        examplePath: "/v2/stocks/bars/latest?symbols=AAPL&feed=sip",
        desc: "Most recent minute bar for multiple symbols.",
        zh: "多股票最新分钟 K 线。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.feed],
        keys: ["bars"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-quotes",
        title: "Historical Quotes",
        route: "/v2/stocks/quotes",
        examplePath: "/v2/stocks/quotes?symbols=AAPL&start=2026-05-20T13:30:00Z&end=2026-05-20T14:00:00Z&limit=1&feed=sip",
        desc: "Historical bid/ask quote ticks for multiple symbols.",
        zh: "多股票历史 bid/ask quote ticks。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.start, STOCK_COMMON.end, STOCK_COMMON.feed, STOCK_COMMON.limit, STOCK_COMMON.sort, STOCK_COMMON.pageToken],
        keys: ["quotes", "next_page_token"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-latest-quotes",
        title: "Latest Quotes",
        route: "/v2/stocks/quotes/latest",
        examplePath: "/v2/stocks/quotes/latest?symbols=AAPL&feed=sip",
        desc: "Latest quote for multiple symbols.",
        zh: "多股票最新报价。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.feed],
        keys: ["quotes"],
        tested: "200 alpaca MISS; repeat DISK_HIT",
      },
      {
        id: "stock-snapshots",
        title: "Snapshots",
        route: "/v2/stocks/snapshots",
        examplePath: "/v2/stocks/snapshots?symbols=AAPL&feed=sip",
        desc: "Composite latest state: latest trade, latest quote, minute bar, daily bar, and previous daily bar.",
        zh: "股票综合快照：最新成交、最新报价、分钟 K、日 K、前一日 K。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.feed],
        keys: ["AAPL"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-trades",
        title: "Historical Trades",
        route: "/v2/stocks/trades",
        examplePath: "/v2/stocks/trades?symbols=AAPL&start=2026-05-20T13:30:00Z&end=2026-05-20T14:00:00Z&limit=1&feed=sip",
        desc: "Historical trade ticks for multiple symbols.",
        zh: "多股票历史逐笔成交。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.start, STOCK_COMMON.end, STOCK_COMMON.feed, STOCK_COMMON.limit, STOCK_COMMON.sort, STOCK_COMMON.pageToken],
        keys: ["trades", "next_page_token"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-latest-trades",
        title: "Latest Trades",
        route: "/v2/stocks/trades/latest",
        examplePath: "/v2/stocks/trades/latest?symbols=AAPL&feed=sip",
        desc: "Latest trade for multiple symbols.",
        zh: "多股票最新成交。",
        params: [STOCK_COMMON.symbols, STOCK_COMMON.feed],
        keys: ["trades"],
        tested: "200 alpaca MISS",
      },
    ],
  },
  {
    title: "Reference metadata",
    intro: "Lookup tables for translating stock market data condition and exchange codes.",
    endpoints: [
      {
        id: "stock-condition-codes",
        title: "Condition Codes",
        route: "/v2/stocks/meta/conditions/{ticktype}",
        examplePath: "/v2/stocks/meta/conditions/trade?tape=C",
        desc: "Maps condition code values to readable descriptions for trade or quote ticks.",
        zh: "将成交/报价条件代码映射为可读说明。",
        params: [{ name: "ticktype", type: "path", required: true, desc: "trade or quote." }, STOCK_COMMON.tape],
        keys: ["1", "4", "5", "6", "7", "8", "9", "@"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-exchange-codes",
        title: "Exchange Codes",
        route: "/v2/stocks/meta/exchanges",
        examplePath: "/v2/stocks/meta/exchanges",
        desc: "Maps exchange code values to readable venue names.",
        zh: "将交易所代码映射为可读交易场所名称。",
        params: [],
        keys: ["A", "B", "C", "D", "E", "H", "I", "J"],
        tested: "200 alpaca MISS",
      },
    ],
  },
  {
    title: "Single-symbol market data",
    intro: "Same data families as the batch endpoints, but scoped to one ticker in the URL path.",
    endpoints: [
      {
        id: "stock-single-bars",
        title: "Historical Bars, Single Symbol",
        route: "/v2/stocks/{symbol}/bars",
        examplePath: "/v2/stocks/AAPL/bars?timeframe=1Day&start=2026-05-20&end=2026-05-21&limit=1&feed=sip",
        desc: "Historical OHLCV bars for one stock.",
        zh: "单只股票历史 OHLCV K 线。",
        params: [STOCK_COMMON.symbolPath, STOCK_COMMON.timeframe, STOCK_COMMON.start, STOCK_COMMON.end, STOCK_COMMON.feed, STOCK_COMMON.limit, STOCK_COMMON.pageToken],
        keys: ["bars", "next_page_token", "symbol"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-single-latest-bar",
        title: "Latest Bar, Single Symbol",
        route: "/v2/stocks/{symbol}/bars/latest",
        examplePath: "/v2/stocks/AAPL/bars/latest?feed=sip",
        desc: "Latest minute bar for one stock.",
        zh: "单只股票最新分钟 K 线。",
        params: [STOCK_COMMON.symbolPath, STOCK_COMMON.feed],
        keys: ["bar", "symbol"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-single-quotes",
        title: "Historical Quotes, Single Symbol",
        route: "/v2/stocks/{symbol}/quotes",
        examplePath: "/v2/stocks/AAPL/quotes?start=2026-05-20T13:30:00Z&end=2026-05-20T14:00:00Z&limit=1&feed=sip",
        desc: "Historical quote ticks for one stock.",
        zh: "单只股票历史报价 ticks。",
        params: [STOCK_COMMON.symbolPath, STOCK_COMMON.start, STOCK_COMMON.end, STOCK_COMMON.feed, STOCK_COMMON.limit, STOCK_COMMON.sort, STOCK_COMMON.pageToken],
        keys: ["quotes", "next_page_token", "symbol"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-single-latest-quote",
        title: "Latest Quote, Single Symbol",
        route: "/v2/stocks/{symbol}/quotes/latest",
        examplePath: "/v2/stocks/AAPL/quotes/latest?feed=sip",
        desc: "Latest quote for one stock.",
        zh: "单只股票最新报价。",
        params: [STOCK_COMMON.symbolPath, STOCK_COMMON.feed],
        keys: ["quote", "symbol"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-single-snapshot",
        title: "Snapshot, Single Symbol",
        route: "/v2/stocks/{symbol}/snapshot",
        examplePath: "/v2/stocks/AAPL/snapshot?feed=sip",
        desc: "Composite latest state for one stock.",
        zh: "单只股票综合快照。",
        params: [STOCK_COMMON.symbolPath, STOCK_COMMON.feed],
        keys: ["dailyBar", "latestQuote", "latestTrade", "minuteBar", "prevDailyBar", "symbol"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-single-trades",
        title: "Historical Trades, Single Symbol",
        route: "/v2/stocks/{symbol}/trades",
        examplePath: "/v2/stocks/AAPL/trades?start=2026-05-20T13:30:00Z&end=2026-05-20T14:00:00Z&limit=1&feed=sip",
        desc: "Historical trade ticks for one stock.",
        zh: "单只股票历史逐笔成交。",
        params: [STOCK_COMMON.symbolPath, STOCK_COMMON.start, STOCK_COMMON.end, STOCK_COMMON.feed, STOCK_COMMON.limit, STOCK_COMMON.sort, STOCK_COMMON.pageToken],
        keys: ["trades", "next_page_token", "symbol"],
        tested: "200 alpaca MISS",
      },
      {
        id: "stock-single-latest-trade",
        title: "Latest Trade, Single Symbol",
        route: "/v2/stocks/{symbol}/trades/latest",
        examplePath: "/v2/stocks/AAPL/trades/latest?feed=sip",
        desc: "Latest trade for one stock.",
        zh: "单只股票最新成交。",
        params: [STOCK_COMMON.symbolPath, STOCK_COMMON.feed],
        keys: ["trade", "symbol"],
        tested: "200 alpaca MISS",
      },
    ],
  },
];

function StockEndpointSection({ endpoint }) {
  return (
    <section style={{ marginBottom: 42 }}>
      <h2 id={endpoint.id} className="display-title" style={{ fontSize: 24, margin: "0 0 8px" }}>{endpoint.title}</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        {endpoint.desc}
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>{endpoint.zh}</span>
      </p>
      <EndpointBadge method="GET" path={`${REST_BASE}${endpoint.route}`} />
      {endpoint.params.length > 0 ? <ParamTable rows={endpoint.params} /> : (
        <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 20px" }}>No query parameters are required.</p>
      )}
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}${endpoint.examplePath}"`}
      </pre>
    </section>
  );
}

function ProxyApiBody() {
  return (
    <div style={{ maxWidth: 760 }}>

      {/* ── Getting started ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Getting started</div>
      <h2 id="overview" className="display-title" style={{ fontSize: 38, margin: "0 0 8px" }}>Overview</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 16px", maxWidth: 640 }}>
        The Stock Options Proxy has two surfaces: a <strong style={{ color: "var(--ink-strong)" }}>token portal</strong> for registration and token issuance,
        and a <strong style={{ color: "var(--ink-strong)" }}>data proxy</strong> for market data (REST via Cloudflare, WS via EC2 direct).
        Once you have a token, use it to call historical and realtime endpoints without managing your own Alpaca / ThetaData credentials.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>Stock Options Proxy 提供两类服务：Token 门户负责账户注册与 Token 签发；数据代理负责行情数据，其中 REST 走 Cloudflare 边缘，WebSocket 直连 EC2。</span>
      </p>
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 16 }}>
        <thead><tr><th>Surface</th><th>Public URL</th><th>Auth</th></tr></thead>
        <tbody>
          <tr><td>Token portal</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>{TOKEN_BASE}</td><td style={{ fontSize: 12 }}>username + phone</td></tr>
          <tr><td>REST data proxy</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>{REST_BASE}</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>Bearer &lt;token&gt;</td></tr>
          <tr><td>REST real-time proxy</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>{RT_BASE}</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>Bearer &lt;token&gt;</td></tr>
          <tr><td>WS data proxy</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>{WS_BASE}/*</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>auth message</td></tr>
        </tbody>
      </table>
      <div style={{ background: "var(--bg-soft)", border: "1px solid var(--border)", borderRadius: 8, padding: "12px 16px", margin: "0 0 24px", fontSize: 13 }}>
        <strong style={{ color: "var(--ink-strong)" }}>{"\u26A1"} Hybrid architecture</strong> — REST historical data is served via <strong>Cloudflare</strong> global edge (<code>api.leandata.uk</code>, 7-day edge cache).
        Real-time REST endpoints (snapshots, orderbooks) are available at <code>rt-api.leandata.uk</code> (60s edge cache, faster upstream).
        <strong>WebSocket</strong> streams connect directly to EC2 for lowest latency to Alpaca. All three surfaces accept the same token.
        <br/><span style={{ color: "var(--ink-soft)" }}>REST 历史数据通过 Cloudflare 全球边缘节点提供（api.leandata.uk，7 天边缘缓存）。实时 REST 端点（快照、订单簿）可用 rt-api.leandata.uk（60 秒边缘缓存，更快的上游）。WebSocket 实时流直连 EC2。三个入口使用同一 Token。</span>
      </div>

      <h2 id="authentication" className="display-title" style={{ fontSize: 28, margin: "0 0 12px" }}>Authentication</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        All data endpoints (REST and WS) require a UUID token. Pass it as an HTTP header or in the JSON body:
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>所有数据接口（REST 和 WS）都需要 UUID Token，可通过 HTTP Header 或 JSON Body 传递。</span>
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`# Option A — Authorization header (preferred)
Authorization: Bearer c88662...720a

# Option B — token field in request body
{ "token": "c886624f-232d-4803-99fa-f8b970e4720a", "symbol": "AAPL", ... }`}
      </pre>
      <p style={{ fontSize: 13, color: "var(--ink-muted)", margin: "0 0 40px" }}>
        Tokens expire 30 days after issuance (trial: 3 days, non-renewable). The proxy returns <code>401</code> for invalid or expired tokens and <code>403</code> if your tier lacks permission for the endpoint.
      </p>

      <h2 id="tiers-permissions" className="display-title" style={{ fontSize: 28, margin: "0 0 16px" }}>Tiers &amp; permissions</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Five plans control access to channels, symbols, rate limits, and REST endpoints.
        <span style={{ color: "var(--ink-soft)", fontSize: 13 }}>五个套餐等级控制通道、标的、限速和接口权限。</span>
      </p>
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 12 }}>
        <thead>
          <tr><th style={{ width: 120 }}>Plan</th><th>Price</th><th>WS channels</th><th>WS symbols</th><th>WS conns</th><th>REST req/min<br/><span style={{ fontWeight: 400, fontSize: 11, color: "var(--ink-soft)" }}>(rolling 60 s window)</span></th><th>REST parallel</th><th>REST endpoints</th></tr>
        </thead>
        <tbody>
          <tr>
            <td><span className="tier trial">Trial</span></td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>$30/3 days</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>all 6 channels</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>50</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>3</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>1800</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>5</td>
            <td style={{ fontSize: 12 }}>Same as Standard · 3-day token · non-renewable</td>
          </tr>
          <tr>
            <td><span className="tier basic">Basic</span></td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>$40/mo</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>— (REST only)</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>—</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>1</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>600</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>2</td>
            <td style={{ fontSize: 12 }}>stocks + options history · no realtime</td>
          </tr>
          <tr>
            <td><span className="tier value">Value</span></td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>$50/mo</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>all 6 channels</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>30</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>2</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>1800</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>3</td>
            <td style={{ fontSize: 12 }}>REST: stocks OR options (pick at signup) · WS: all channels</td>
          </tr>
          <tr>
            <td><span className="tier standard">Standard</span></td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>$80/mo</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>all 6 channels</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>50</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>3</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>1800</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>5</td>
            <td style={{ fontSize: 12 }}>stocks + options history · no crypto orderbooks</td>
          </tr>
          <tr>
            <td><span className="tier premium">Premium</span></td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>$130/mo</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>all 6 channels</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>500</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>{"\u221E"}</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>6000</td>
            <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>10</td>
            <td style={{ fontSize: 12 }}>All REST endpoints including crypto orderbooks</td>
          </tr>
        </tbody>
      </table>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 8px" }}>
        Rate limits tighten automatically under load: limits halve when server is overloaded and quarter under critical load. WebSocket delivery is always prioritised over REST.
      </p>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 40px" }}>
        Per-second equivalents: Basic 10/s · Value 30/s · Standard 30/s · Premium 100/s. Exceeding the per-minute quota or the parallel-request cap returns <strong>HTTP 429</strong>; back off and retry after the 60-second window.
        <br/>每秒换算：Basic 10/s · Value 30/s · Standard 30/s · Premium 100/s。超过每分钟配额或并发上限会返回 <strong>HTTP 429</strong>，请等待 60 秒窗口刷新后再重试。
      </p>

      {/* ── Token API ── */}
      <div className="eyebrow" style={{ marginBottom: 10, marginTop: 48 }}>Token API</div>

      <h2 id="post-register" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /api/register</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>Submit a new account registration. The request enters a pending queue until approved by an admin.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>提交新账户注册申请；请求会进入待审核队列，由管理员审核后开通。</span>
      </p>
      <EndpointBadge method="POST" path={`${TOKEN_BASE}/api/register`} />
      <ParamTable rows={[
        { name: "username", type: "string", required: true, desc: "Unique display name (must not exist in approved users)" },
        { name: "phone",    type: "string", required: true, desc: "Mobile number used to verify identity on token generation" },
        { name: "tier",     type: "string", required: false, desc: "trial | basic | value | standard | premium (default: standard)." },
        { name: "mode",     type: "string", required: false, desc: "stocks | options — required when tier is value. Determines which data vertical is enabled." },
      ]} />
      <pre className="code" style={{ marginBottom: 28 }}>
{`// Request
{ "username": "tonnysun", "phone": "18717931119", "tier": "premium" }

// Value tier — mode required
{ "username": "qianyu", "phone": "13800138000", "tier": "value", "mode": "options" }

// Response 200
{ "success": true, "message": "注册成功！请等待卖家确认订单后即可生成 Token。", "id": "61ce4f82-..." }

// Error 409 — username already taken
{ "success": false, "message": "该用户名已被使用，请换一个。" }`}
      </pre>

      <h2 id="post-check-status" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /api/check-status</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>Poll approval status before attempting token generation.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>在尝试生成 Token 之前，查询账户的审核状态。</span>
      </p>
      <EndpointBadge method="POST" path={`${TOKEN_BASE}/api/check-status`} />
      <ParamTable rows={[
        { name: "username", type: "string", required: true, desc: "The username submitted at registration" },
        { name: "phone",    type: "string", required: true, desc: "The phone number submitted at registration" },
      ]} />
      <pre className="code" style={{ marginBottom: 28 }}>
{`// Request
{ "username": "tonnysun", "phone": "18717931119" }

// Response — status values: "pending" | "approved" | "rejected" | "not_found"
{ "success": true, "status": "pending", "message": "审核中，请耐心等待。" }`}
      </pre>

      <h2 id="post-generate-token" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /api/generate-token</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Exchange approved credentials for a 30-day UUID token. If a token already exists for this user in the active token list it is returned as-is (not regenerated).
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>凭已审核通过的账号信息换取 30 天有效期的 UUID Token。该用户已签发的 Token 会原样返回，不会重新生成。</span>
      </p>
      <EndpointBadge method="POST" path={`${TOKEN_BASE}/api/generate-token`} />
      <ParamTable rows={[
        { name: "username", type: "string", required: true, desc: "Must match an entry in the approved users database" },
        { name: "phone",    type: "string", required: true, desc: "Must match the phone number on record" },
      ]} />
      <pre className="code" style={{ marginBottom: 48 }}>
{`// Request
{ "username": "ikkipipi", "phone": "15213285787" }

// Response 200
{
  "success": true,
  "token":  "c886624f-232d-4803-99fa-f8b970e4720a",
  "expiry": "2026-06-19T14:15:57.059704+00:00",
  "role":   "premium"
}

// Error 401 — credentials not found or not approved
{ "success": false, "message": "User not found or payment pending." }`}
      </pre>

      {/* ── REST History ── */}
      <div className="eyebrow" style={{ marginBottom: 10, marginTop: 0 }}>REST History</div>

      <h2 id="post-v1-history-bars" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/history/bars</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Fetch historical OHLCV bars for US equities. Paginates automatically up to <code>max_pages</code>. Results are cached for 5 minutes; check the <code>X-Cache</code> response header for <code>HIT</code> / <code>MISS</code>.
        Data source: Alpaca SIP feed (pro account, split/dividend adjusted).
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>获取美股历史 OHLCV K线数据。支持自动分页，结果缓存 5 分钟。数据源：Alpaca SIP。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/history/bars`} />
      <ParamTable rows={[
        { name: "symbol",    type: "string",  required: true,  desc: "Ticker (e.g. AAPL). Comma-separated for multi-symbol." },
        { name: "start",     type: "string",  required: true,  desc: "ISO 8601 date or datetime (e.g. 2024-01-02)" },
        { name: "end",       type: "string",  required: true,  desc: "ISO 8601 date or datetime" },
        { name: "timeframe", type: "string",  required: false, desc: "1Min | 5Min | 15Min | 30Min | 1Hour | 1Day (default: 1Min)" },
        { name: "feed",      type: "string",  required: false, desc: "sip | iex (default: sip)" },
        { name: "limit",     type: "integer", required: false, desc: "Bars per page, 1–10000 (default: 10000)" },
        { name: "max_pages", type: "integer", required: false, desc: "Max pagination pages (default: 100)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/history/bars \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbol":"AAPL","timeframe":"1Day","start":"2024-01-02","end":"2024-01-05","limit":5}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 40 }}>
{`// Response  (X-Cache: MISS on first call, HIT on repeat)
{
  "bars": {
    "AAPL": [
      { "o": 185.06, "h": 186.33, "l": 181.83, "c": 183.56,
        "v": 82496943, "vw": 183.77, "n": 1009074,
        "t": "2024-01-02T05:00:00Z" },
      { "o": 182.16, "h": 183.80, "l": 181.38, "c": 182.19,
        "v": 58418916, "vw": 182.26, "n": 656956,
        "t": "2024-01-03T05:00:00Z" }
    ]
  },
  "pages": 1
}`}
      </pre>

      <h2 id="post-v1-history-news" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/history/news</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Fetch historical news articles. Source: Benzinga via Alpaca. Available to all tiers including Basic.
        Pass <code>max_pages</code> greater than 1 to auto-paginate; each page contains up to 50 articles.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>获取历史新闻文章。来源：Benzinga via Alpaca。所有套餐可用。支持自动分页，每页最多 50 篇。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/history/news`} />
      <ParamTable rows={[
        { name: "symbols",            type: "string",  required: false, desc: "Comma-separated tickers; omit for market-wide news" },
        { name: "start",              type: "string",  required: false, desc: "ISO 8601 start date" },
        { name: "end",                type: "string",  required: false, desc: "ISO 8601 end date" },
        { name: "limit",              type: "integer", required: false, desc: "Articles per page, 1–50 (default: 50)" },
        { name: "sort",               type: "string",  required: false, desc: "asc | desc (default: asc)" },
        { name: "max_pages",          type: "integer", required: false, desc: "Max pages to auto-fetch (default: 1)" },
        { name: "include_content",    type: "boolean", required: false, desc: "Include full article body" },
        { name: "exclude_contentless",type: "boolean", required: false, desc: "Skip articles with empty content" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/history/news \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"AAPL","start":"2024-01-02","end":"2024-01-03","limit":3}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 48 }}>
{`// Response
{
  "news": [
    {
      "id": 36445586,
      "headline": "Wedbush's Dan Ives Says, 'Tech Stocks Will Be Up 25% In 2024'",
      "author": "Benzinga Neuro",
      "source": "benzinga",
      "summary": "...",
      "url": "https://www.benzinga.com/...",
      "symbols": ["AAPL", "GOOG", "MSFT"],
      "created_at": "2024-01-02T02:00:46Z",
      "updated_at": "2024-01-02T02:00:46Z",
      "images": [
        { "size": "large", "url": "https://cdn.benzinga.com/..." }
      ]
    }
  ],
  "pages": 1
}`}
      </pre>

      <h2 id="post-v1-stock-history-trade-quote" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/stock/history/trade_quote</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Combined historical trade + quote data for a single stock symbol in one call.
        Fetches both <code>/v2/stocks/trades</code> and <code>/v2/stocks/quotes</code> from Alpaca in parallel, auto-paginates each leg, and returns them in a single response.
        Cached server-side; repeat calls return <code>X-Cache: DISK_HIT</code>.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>单只股票的合并历史成交+报价数据。并行从 Alpaca 拉取 trades 和 quotes 自动分页，单次返回。支持服务端缓存。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/stock/history/trade_quote`} />
      <ParamTable rows={[
        { name: "symbol", type: "string",  required: true,  desc: "Stock ticker (e.g. AAPL)" },
        { name: "start",  type: "string",  required: true,  desc: "ISO 8601 datetime (e.g. 2026-05-20T13:30:00Z)" },
        { name: "end",    type: "string",  required: true,  desc: "ISO 8601 datetime" },
        { name: "limit",  type: "integer", required: false, desc: "Max records per leg, 1–10000 (default: 1000)" },
        { name: "feed",   type: "string",  required: false, desc: "sip | iex (default: sip with pro account)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/stock/history/trade_quote \\
  -H "Authorization: Bearer ***" \\
  -H "Content-Type: application/json" \\
  -d '{"symbol":"AAPL","start":"2026-05-20T13:30:00Z","end":"2026-05-20T13:31:00Z","limit":3}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 48 }}>
{`// Response
{
  "symbol": "AAPL",
  "start": "2026-05-20T13:30:00Z",
  "end": "2026-05-20T13:31:00Z",
  "feed": "sip",
  "trades": [
    { "c": ["@","I"], "i": 1301, "p": 298.44, "s": 1,
      "t": "2026-05-20T13:30:00.002Z", "x": "K", "z": "C" }
  ],
  "trade_count": 156,
  "quotes": [
    { "ap": 298.45, "as": 200, "bp": 298.43, "bs": 500, "ax": "V", "bx": "Q",
      "t": "2026-05-20T13:30:00.001Z", "c": ["R"], "z": "C" }
  ],
  "quote_count": 312
}`}
      </pre>

      {/* ── Stock Data ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Stock Data</div>

      <h2 id="stock-data-availability" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Alpaca stock data availability</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        All native Alpaca stock market-data endpoints below are exposed as authenticated <code>GET</code> routes under <code>{REST_BASE}/v2/stocks/*</code>.
        The proxy keeps the Alpaca response shape, strips proxy credentials from cache keys, and returns <code>X-Provider: alpaca</code>.
        Feed availability still follows the upstream entitlement: <code>iex</code> is the safest default; <code>sip</code>, <code>delayed_sip</code>, <code>boats</code>, <code>overnight</code>, and <code>otc</code> depend on the requested endpoint and subscription.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>以下股票数据接口均按 Alpaca native GET 路径开放。响应结构保持 Alpaca 原样，鉴权和服务端缓存由代理统一处理。</span>
      </p>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 28px" }}>
        Live smoke-tested on 2026-05-23: every endpoint in this section returned <code>200</code> through the native provider path. Repeated latest/snapshot calls return <code>X-Cache: DISK_HIT</code> when served from cache.
      </p>

      {STOCK_ENDPOINT_GROUPS.map((group, gi) => (
        <div key={group.title} style={{ marginBottom: gi === STOCK_ENDPOINT_GROUPS.length - 1 ? 48 : 28 }}>
          <h3 id={slugify(group.title)} style={{ fontSize: 17, fontWeight: 500, margin: "0 0 8px", color: "var(--ink-strong)" }}>{group.title}</h3>
          <p style={{ fontSize: 13, color: "var(--ink-muted)", margin: "0 0 20px" }}>{group.intro}</p>
          {group.endpoints.map(endpoint => <StockEndpointSection key={endpoint.id} endpoint={endpoint} />)}
        </div>
      ))}

      {/* ── Provider Data ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Provider Data</div>

      <h2 id="provider-fallback-cache" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Provider routes and server-side cache</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Two upstreams sit behind a single proxy layer (auth, permissions, rate limits, credential stripping, cache):
        Alpaca for stocks, crypto, news, and most options; ThetaData Value for the option subset shown below.
        The table lists only routes where the dual-provider routing or fallback decision matters — pass-through Alpaca routes
        (<code>/v2/stocks/*</code>, <code>/v1beta3/crypto/*</code>, <code>/v1beta1/news*</code>, <code>/v2/options/contracts*</code>) are documented in their own sections.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>两个上游共享同一层代理。下表只列需要 dual-provider 路由/回退判断的端点；纯 Alpaca 透传端点在各自章节展开。</span>
      </p>
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 20 }}>
        <thead><tr><th>Surface</th><th>Route</th><th>Routing behavior</th></tr></thead>
        <tbody>
          {[
            ["Alpaca options (native)",       "/v1beta1/options/*",                                            "Bars, historical trades, latest quotes/trades, snapshots, chain snapshots. Historical option data starts 2024-02-01."],
            ["Option bars (wrapper)",         "/v1/history/options/bars",                                      "ThetaData OHLC first; Alpaca bars fallback. provider=thetadata|alpaca to pin."],
            ["Contracts (wrapper)",           "/v1/options/contracts",                                         "Alpaca contracts first; ThetaData Value contracts fallback. provider=thetadata requires underlying_symbols."],
            ["Full snapshots / greeks / IV",  "/v1/options/snapshots",                                         "Alpaca only — ThetaData Value lacks greeks, IV, market value."],
            ["Quote / trade snapshots",       "/v1/options/snapshots/{quote,trade}",                           "Alpaca latest quote/trade; normalized to snapshots[OCC].latestQuote/latestTrade."],
            ["OI / OHLC snapshots",           "/v1/options/snapshots/open_interest, /v3/option/snapshot/*",    "ThetaData-backed where Value permits OI and OHLC snapshots."],
            ["ThetaData Value options",       "/v3/option/*",                                                  "ThetaData Value whitelist only; JSON response; no Alpaca fallback."],
          ].map(([area, route, behavior], i) => (
            <tr key={i}>
              <td style={{ fontSize: 12, color: "var(--ink-strong)" }}>{area}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>{route}</td>
              <td style={{ fontSize: 12, color: "var(--ink-muted)" }}>{behavior}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 40px" }}>
        Successful REST responses are cached server-side.
        Cache keys strip proxy credentials, return <code>X-Cache: DISK_HIT</code> on repeat, and use tiered TTLs: historical 7 days, intraday/latest 60 seconds, snapshots 5 minutes, contracts/lists 1 hour.
        ThetaData Value does <strong>not</strong> expose direct option trades, trade_quote, market value, implied volatility, or greeks.
      </p>
      <h3 id="alpaca-native-examples" style={{ fontSize: 16, fontWeight: 500, margin: "0 0 8px", color: "var(--ink-strong)" }}>Native Alpaca examples</h3>
      <pre className="code" style={{ marginBottom: 40 }}>
{`# Latest stock quote (Alpaca native)
curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}/v2/stocks/quotes/latest?symbols=AAPL&feed=sip"

# Latest crypto quote (Alpaca native)
curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}/v1beta3/crypto/us/latest/quotes?symbols=BTC%2FUSD"

# Historical stock quotes (Alpaca native)
curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}/v2/stocks/quotes?symbols=AAPL&start=2026-05-20T13:30:00Z&end=2026-05-20T14:00:00Z&feed=sip"`}
      </pre>

      <h2 id="post-v1-options-contracts" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/options/contracts</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        List active option contracts for one or more underlying symbols. Default provider mode is <code>auto</code>: Alpaca contract metadata first, then ThetaData Value contract lists as fallback.
        Returns OCC symbol, strike, expiration, option type, open interest where available, and a <code>source</code> field.
        Use the returned <code>symbol</code> field as input to <code>/v1/options/snapshots</code> or <code>/v1/history/options/bars</code>.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>列出指定标的的活跃期权合约。默认 Alpaca，失败时回退到 ThetaData Value 合约列表。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/options/contracts`} />
      <ParamTable rows={[
        { name: "underlying_symbols",  type: "string",  required: false, desc: "Comma-separated underlyings (e.g. AAPL,TSLA). Required if symbol_or_id not set." },
        { name: "symbol_or_id",        type: "string",  required: false, desc: "Lookup a single OCC symbol or contract ID directly" },
        { name: "expiration_date",     type: "string",  required: false, desc: "Exact expiry YYYY-MM-DD" },
        { name: "expiration_date_gte", type: "string",  required: false, desc: "Expiry on or after date" },
        { name: "expiration_date_lte", type: "string",  required: false, desc: "Expiry on or before date" },
        { name: "strike_price_gte",    type: "number",  required: false, desc: "Minimum strike price" },
        { name: "strike_price_lte",    type: "number",  required: false, desc: "Maximum strike price" },
        { name: "type",                type: "string",  required: false, desc: "call | put" },
        { name: "provider",            type: "string",  required: false, desc: "auto | alpaca | thetadata (default: auto)" },
        { name: "date",                type: "string",  required: false, desc: "ThetaData contract-list date when provider uses ThetaData (YYYY-MM-DD or YYYYMMDD)" },
        { name: "request_type",        type: "string",  required: false, desc: "quote | trade for ThetaData list/contracts (default: quote; Value subscription supports list metadata)" },
        { name: "max_dte",             type: "integer", required: false, desc: "ThetaData fallback filter for max days to expiration" },
        { name: "limit",               type: "integer", required: false, desc: "1–10000 (default: 1000)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/options/contracts \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"underlying_symbols":"AAPL","limit":2,"provider":"auto"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 40 }}>
{`// Response
{
  "option_contracts": [
    {
      "symbol":           "AAPL260522C00110000",
      "name":             "AAPL May 22 2026 110 Call",
      "status":           "active",
      "tradable":         true,
      "type":             "call",
      "style":            "american",
      "strike_price":     "110",
      "expiration_date":  "2026-05-22",
      "root_symbol":      "AAPL",
      "underlying_symbol":"AAPL",
      "multiplier":       "100",
      "open_interest":    "3",
      "open_interest_date":"2026-05-20",
      "close_price":      "192.05",
      "close_price_date": "2026-05-21"
    }
  ],
  "next_page_token": null,
  "source": "alpaca"
}`}
      </pre>

      <div className="eyebrow" style={{ marginBottom: 6, marginTop: 32, fontSize: 11, color: "var(--ink-soft)" }}>Options Data · History</div>
      <h2 id="post-v1-history-options-bars" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/history/options/bars</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Historical OHLCV bars for option contracts. Default <code>provider: "auto"</code> routes to <strong style={{ color: "var(--ink-strong)" }}>ThetaData Value</strong> first and falls back to Alpaca bars only when ThetaData has no usable data.
        You can pass either OCC symbols directly (<code>AAPL260620C00200000</code>) or a plain stock ticker — the proxy will auto-resolve it to the option chain active on the <code>start</code> date.
        Supports in-flight coalescing and server-side cache; repeat historical calls return <code>X-Cache: DISK_HIT</code>.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>期权合约历史 OHLCV K线。默认 ThetaData Value，必要时回退 Alpaca。支持 OCC 或股票代码自动解析，并写入服务端缓存。</span>
      </p>
      <div style={{ background: "#fff3cd", border: "1px solid #ffc107", borderRadius: 8, padding: "10px 14px", margin: "0 0 12px", fontSize: 12 }}>
        <strong>{"\u26A0\uFE0F"} ThetaData Value plan limitation:</strong> Only <code>1Day</code> timeframe is available via ThetaData. Intraday timeframes (1Min, 5Min, 15Min, 1Hour) return <em>"No data found"</em> because the Value subscription does not include historical minute bars for options. Set <code>provider: "alpaca"</code> to use Alpaca for intraday option bars (data available from 2024-02-01).
        <br/><span style={{ color: "var(--ink-soft)" }}>ThetaData Value 仅支持 1Day 日线。分钟级（1Min/5Min/15Min/1Hour）会返回"No data found"。如需分钟级期权 K 线，请指定 provider: "alpaca"（数据从 2024-02-01 起）。</span>
      </div>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/history/options/bars`} />
      <ParamTable rows={[
        { name: "symbols",   type: "string",  required: true,  desc: "OCC symbol(s) comma-separated, or a stock ticker for auto-resolution" },
        { name: "start",     type: "string",  required: true,  desc: "ISO 8601 date" },
        { name: "end",       type: "string",  required: true,  desc: "ISO 8601 date" },
        { name: "timeframe", type: "string",  required: false, desc: "1Day only on ThetaData Value. Intraday (1Min, 5Min, 15Min, 1Hour) requires provider=\"alpaca\". Default: 1Day" },
        { name: "provider",  type: "string",  required: false, desc: "auto | thetadata | alpaca (default: auto). thetadata disables Alpaca fallback; alpaca skips ThetaData." },
        { name: "limit",     type: "integer", required: false, desc: "Bars per page, 1–10000 (default: 10000)" },
        { name: "max_pages", type: "integer", required: false, desc: "Max pagination pages (default: 100)" },
      ]} />
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 12px" }}>
        OCC symbol format: <code>{"<ROOT><YYMMDD><C|P><8-digit-strike>"}</code> — strike is in thousandths of a dollar, zero-padded to 8 digits.
        Example: AAPL $200 call expiring 2026-06-20 → <code>AAPL260620C00200000</code>
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// With explicit OCC symbol
curl -X POST ${REST_BASE}/v1/history/options/bars \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"AAPL260620C00200000","start":"2025-05-01","end":"2025-05-15","timeframe":"1Day","provider":"auto"}'

// With stock ticker (auto-resolves to chain active on start date)
  -d '{"symbols":"AAPL","start":"2025-01-02","end":"2025-01-10","timeframe":"1Hour"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 40 }}>
{`// Response
{
  "bars": {
    "AAPL260620C00200000": [
      { "o": 14.50, "h": 15.20, "l": 14.10, "c": 14.85, "v": 320, "t": "2025-05-01T..." }
    ]
  },
  "provider": "thetadata",
  "pages": 1
}`}
      </pre>

      <h2 id="post-v1-options-open-interest" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>GET/POST /v1/options/open_interest</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Historical open interest by date range with strike/expiry filters. Data source: <strong style={{ color: "var(--ink-strong)" }}>ThetaData Value</strong> (returns 503 if ThetaData is unavailable).
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>按日期范围和行权价/到期日筛选历史持仓量。</span>
      </p>
      <EndpointBadge method="GET/POST" path={`${REST_BASE}/v1/options/open_interest`} />
      <ParamTable rows={[
        { name: "symbol",       type: "string",  required: true,  desc: "Root ticker (e.g. AAPL)" },
        { name: "start",        type: "string",  required: true,  desc: "ISO 8601 date" },
        { name: "end",          type: "string",  required: true,  desc: "ISO 8601 date" },
        { name: "expiration",   type: "string",  required: false, desc: "Specific expiry date or * for all (default: *)" },
        { name: "strike",       type: "number",  required: false, desc: "Specific strike or * for all (default: *)" },
        { name: "right",        type: "string",  required: false, desc: "call | put | both (default: both)" },
        { name: "max_dte",      type: "integer", required: false, desc: "Max days-to-expiry filter" },
        { name: "strike_range", type: "integer", required: false, desc: "ATM ± N strikes filter" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/options/open_interest \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbol":"AAPL","start":"2025-01-02","end":"2025-01-05"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 40 }}>
{`// Response
{
  "count": 1840,
  "data": [
    {
      "symbol":        "AAPL",
      "expiration":    "2025-04-17",
      "strike":        170.0,
      "right":         "CALL",
      "timestamp":     "2025-01-02T06:30:15-05:00",
      "open_interest": 116
    }
  ]
}`}
      </pre>

      <h2 id="post-v1-history-options-eod" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>GET/POST /v1/history/options/eod</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        End-of-day OHLC summary for option contracts: open/high/low/close, volume, bid/ask, and trade count per contract per day.
        Data source: <strong style={{ color: "var(--ink-strong)" }}>ThetaData Value</strong> with server-side cache. Supports <code>GET</code> (query) and <code>POST</code> (JSON body).
        Also accessible at the legacy alias <code>/v1/options/eod</code> with identical behavior.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>期权合约日终 OHLC 汇总。数据源 ThetaData Value，写入服务端缓存。也可走旧别名 /v1/options/eod。</span>
      </p>
      <EndpointBadge method="GET/POST" path={`${REST_BASE}/v1/history/options/eod`} />
      <ParamTable rows={[
        { name: "symbol",       type: "string",  required: true,  desc: "Root ticker (e.g. AAPL)" },
        { name: "start",        type: "string",  required: true,  desc: "ISO 8601 date" },
        { name: "end",          type: "string",  required: true,  desc: "ISO 8601 date" },
        { name: "expiration",   type: "string",  required: false, desc: "Specific expiry or * for all (default: *)" },
        { name: "strike",       type: "number",  required: false, desc: "Specific strike or * for all (default: *)" },
        { name: "right",        type: "string",  required: false, desc: "call | put | both (default: both)" },
        { name: "max_dte",      type: "integer", required: false, desc: "Max days-to-expiry filter" },
        { name: "strike_range", type: "integer", required: false, desc: "ATM ± N strikes filter" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`# POST
curl -X POST ${REST_BASE}/v1/history/options/eod \\
  -H "Authorization: Bearer <TOKEN>" \\
  -H "Content-Type: application/json" \\
  -d '{"symbol":"AAPL","start":"2025-01-02","end":"2025-01-03","right":"call","max_dte":30}'

# GET
curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}/v1/history/options/eod?symbol=AAPL&start=2025-01-02&end=2025-01-03&right=call"`}
      </pre>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Response — each record is one contract on one trading day
{
  "count": 820,
  "data": [
    {
      "symbol":     "AAPL",
      "expiration": "2025-04-17",
      "strike":     170.0,
      "right":      "CALL",
      "open":  75.21, "high":  75.21, "low":  75.21, "close": 75.21,
      "volume": 2,  "count": 1,
      "bid": 75.45, "bid_size": 83,
      "ask": 75.70, "ask_size": 30,
      "created":    "2025-01-02T17:15:44-05:00",
      "last_trade": "2025-01-02T14:43:52-05:00"
    }
  ]
}`}
      </pre>
      <h3 id="eod-python-example" style={{ fontSize: 16, fontWeight: 500, margin: "20px 0 8px", color: "var(--ink-strong)" }}>Python example — fetch OHLC for near-term calls</h3>
      <pre className="code" style={{ marginBottom: 48 }}>
{`import requests

resp = requests.post(
    "${REST_BASE}/v1/history/options/eod",
    headers={"Authorization": "Bearer <TOKEN>"},
    json={
        "symbol": "AAPL",
        "start":  "2025-01-02",
        "end":    "2025-01-10",
        "right":  "call",
        "max_dte": 30,
        "strike_range": 5      # ATM ± 5 strikes
    }
)
data = resp.json()
print(f"{data['count']} records")
for row in data["data"][:5]:
    print(f"  {row['expiration']} {row['strike']}C  "
          f"O={row['open']} H={row['high']} L={row['low']} C={row['close']}  "
          f"vol={row['volume']}")`}
      </pre>

      <h2 id="post-v1-history-options-trades" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/history/options/trades</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Historical option trades from Alpaca. Maps to Alpaca's <code>/v1beta1/options/trades</code> endpoint, which is also available directly as a native <code>GET</code> route.
        Alpaca historical option data starts on <strong style={{ color: "var(--ink-strong)" }}>2024-02-01</strong>; earlier requests return empty data plus a warning on this wrapper route.
        <strong>No ThetaData fallback</strong> — if queried too early or on an empty dataset, returns a standard empty response.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>Alpaca 历史期权逐笔成交数据。Alpaca 历史期权数据从 2024-02-01 开始；无 ThetaData 备用源。若查询时间过早或数据集为空，返回标准空响应。</span>
      </p>
      <EndpointBadge method="GET/POST" path={`${REST_BASE}/v1/history/options/trades`} />
      <ParamTable rows={[
        { name: "symbols",   type: "string",  required: true,  desc: "Comma-separated OCC option symbols" },
        { name: "start",     type: "string",  required: true,  desc: "ISO 8601 datetime" },
        { name: "end",       type: "string",  required: true,  desc: "ISO 8601 datetime" },
        { name: "limit",     type: "integer", required: false, desc: "1–10000 (default: 1000)" },
        { name: "page_token",type: "string",  required: false, desc: "Pagination token from previous response" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/history/options/trades \\
  -H "Authorization: Bearer <TOKEN>" \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"AAPL260620C00200000","start":"2025-01-02T09:30:00Z","end":"2025-01-02T16:00:00Z"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 40 }}>
{`// Response — trades keyed by OCC symbol
{
  "trades": {
    "AAPL260620C00200000": [
      { "t": "2025-01-02T14:30:00Z", "x": "A", "p": 15.70, "s": 5, "c": ["@"] }
    ]
  },
  "next_page_token": null
}

// Empty response (too early or no data)
{
  "trades": {},
  "next_page_token": null,
  "data_availability": {
    "provider": "alpaca",
    "historical_options_since": "2024-02-01"
  },
  "warning": "Alpaca historical option data is available from 2024-02-01 onward."
}`}
      </pre>

      {/* ── Not Supported ── */}
      <div style={{ background: "#f8d7da", border: "1px solid #f5c6cb", borderRadius: 8, padding: "14px 18px", margin: "48px 0 24px", fontSize: 13 }}>
        <h3 id="not-supported-value" style={{ fontSize: 16, fontWeight: 600, margin: "0 0 8px", color: "#721c24" }}>Not supported on ThetaData Value plan</h3>
        <p style={{ margin: "0 0 8px", color: "#721c24" }}>
          The following endpoints are registered in the proxy but will return errors because the ThetaData Value subscription does not include them. Do not call these unless you have upgraded to Standard/Pro.
        </p>
        <table className="tbl" style={{ width: "100%", fontSize: 12, marginBottom: 8 }}>
          <thead><tr><th>Endpoint</th><th>Error</th><th>Alternative</th></tr></thead>
          <tbody>
            <tr><td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>/v1/history/options/trade_quote</td><td>PERMISSION_DENIED — requires Standard subscription</td><td>Use /v1/history/options/trades (Alpaca) or /v3/option/history/quote (quote only)</td></tr>
            <tr><td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>/v1/options/snapshots/market_value</td><td>ThetaData Value lacks market_value data</td><td>Use /v1/options/snapshots (Alpaca-backed, includes greeks)</td></tr>
            <tr><td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>/v1/history/options/bars (intraday)</td><td>"No data found" for 1Min/5Min/15Min/1Hour</td><td>Use timeframe=1Day or provider="alpaca" for intraday</td></tr>
          </tbody>
        </table>
        <p style={{ margin: 0, fontSize: 12, color: "#856404" }}>
          ThetaData Value includes: EOD bars, OHLC snapshots, quote snapshots, open interest, contract lists, and historical quotes. It does <strong>not</strong> include: option trades, trade_quote, market value, implied volatility, or greeks via ThetaData. Greeks/IV are available via Alpaca snapshots.
        </p>
      </div>

      {/* ── Snapshots ── */}
      <div className="eyebrow" style={{ marginBottom: 6, marginTop: 48, fontSize: 11, color: "var(--ink-soft)" }}>Options Data · Snapshots</div>
      <p style={{ fontSize: 14, color: "var(--ink-muted)", margin: "0 0 24px" }}>
        Snapshot endpoints return the <em>latest</em> state of option contracts — greeks, quotes, trade, open interest — served from a 60-second in-memory cache.
        All snapshot endpoints accept OCC symbols obtained from <code>/v1/options/contracts</code>.
      </p>

      <h2 id="post-v1-options-snapshots" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/options/snapshots</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Full snapshot per contract: latest trade, latest quote, greeks (delta, gamma, theta, vega, rho), and implied volatility.
        Use the sub-endpoints below when you only need one slice.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>每个合约的完整快照：最新成交、最新报价、希腊值与隐含波动率。只需单项时改用下方子端点。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/options/snapshots`} />
      <ParamTable rows={[
        { name: "symbols", type: "string",  required: true,  desc: "Comma-separated OCC option symbols (max 1000 per request)" },
        { name: "feed",    type: "string",  required: false, desc: "opra | indicative (default: opra for pro, indicative otherwise)" },
        { name: "limit",   type: "integer", required: false, desc: "1–1000 (default: 100)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/options/snapshots \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"AAPL260620C00200000","feed":"indicative"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 40 }}>
{`// Response — snapshots keyed by OCC symbol
{
  "snapshots": {
    "AAPL260620C00200000": {
      "greeks": {
        "delta": 0.7521,
        "gamma": 0.0624,
        "theta": -0.2848,
        "vega": 0.0475,
        "rho": 0.0099
      },
      "impliedVolatility": 0.3372,
      "latestQuote": {
        "ap": 4.30, "as": 91, "ax": "B",
        "bp": 4.15, "bs": 16, "bx": "C",
        "c": "A",
        "t": "2024-04-22T19:59:59.992734208Z"
      },
      "latestTrade": {
        "p": 4.10, "s": 1, "t": "2024-04-22T19:57:32.589554432Z",
        "c": "I", "x": "A"
      }
    }
  },
  "next_page_token": null
}`}
      </pre>

      <h2 id="post-v1-options-snapshots-quote" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/options/snapshots/quote</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Latest NBBO quote per contract, normalized to <code>snapshots[OCC].latestQuote</code>. Set <code>feed: "thetadata"</code> only to force the ThetaData Value quote snapshot.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>期权合约最新 NBBO 报价。只有明确需要 ThetaData Value 时才设置 feed: "thetadata"。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/options/snapshots/quote`} />
      <ParamTable rows={[
        { name: "symbols", type: "string",  required: true,  desc: "Comma-separated OCC option symbols (max 1000 per request)" },
        { name: "feed",    type: "string",  required: false, desc: "opra | indicative | thetadata (default follows account entitlement)" },
        { name: "limit",   type: "integer", required: false, desc: "1–1000 (default: 100)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/options/snapshots/quote \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"AAPL260522C00110000","feed":"indicative"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Response
{
  "snapshots": {
    "AAPL260522C00110000": {
      "latestQuote": {
        "ap": 200.10, "as": 101, "ax": "9",
        "bp": 197.35, "bs": 101, "bx": "9",
        "t": "2026-05-22T15:59:59.965Z"
      }
    }
  }
}`}
      </pre>
      <h3 id="quote-python-example" style={{ fontSize: 16, fontWeight: 500, margin: "20px 0 8px", color: "var(--ink-strong)" }}>Python example — bid/ask spread</h3>
      <pre className="code" style={{ marginBottom: 40 }}>
{`import requests

resp = requests.post(
    "${REST_BASE}/v1/options/snapshots/quote",
    headers={"Authorization": "Bearer <TOKEN>"},
    json={"symbols": "AAPL260620C00200000", "feed": "indicative"}
)
for sym, snap in resp.json()["snapshots"].items():
    q = snap["latestQuote"]
    spread = q["ap"] - q["bp"]
    print(f"{sym}  bid={q['bp']}  ask={q['ap']}  spread={spread:.2f}")`}
      </pre>

      <h2 id="post-v1-options-snapshots-trade" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/options/snapshots/trade</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Latest trade per contract, normalized to <code>snapshots[OCC].latestTrade</code>.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>期权合约最新成交，归一化到 snapshots[OCC].latestTrade。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/options/snapshots/trade`} />
      <ParamTable rows={[
        { name: "symbols", type: "string",  required: true,  desc: "Comma-separated OCC option symbols (max 100 per Alpaca latest endpoint)" },
        { name: "feed",    type: "string",  required: false, desc: "opra | indicative | thetadata (default follows account entitlement)" },
      ]} />
      <pre className="code" style={{ marginBottom: 40 }}>
{`curl -X POST ${REST_BASE}/v1/options/snapshots/trade \\
  -H "Authorization: Bearer <TOKEN>" \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"AAPL260522C00110000","feed":"indicative"}'`}
      </pre>

      <h2 id="post-v1-options-snapshots-open-interest" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/options/snapshots/open_interest</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Latest open interest per contract (count + timestamp). ThetaData-only — must set <code>feed: "thetadata"</code>.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>期权合约最新持仓量。仅 ThetaData，必须设 feed: "thetadata"。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/options/snapshots/open_interest`} />
      <ParamTable rows={[
        { name: "symbols", type: "string",  required: true,  desc: "Comma-separated OCC option symbols (max 1000 per request)" },
        { name: "feed",    type: "string",  required: false, desc: "thetadata (default: opra — must set to thetadata for this endpoint)" },
        { name: "limit",   type: "integer", required: false, desc: "1–1000 (default: 100)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/options/snapshots/open_interest \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"AAPL260522C00110000","feed":"thetadata"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Response
{
  "snapshots": {
    "AAPL260522C00110000": {
      "openInterest": {
        "oi": 5,
        "t": "2026-05-22T06:30:30.000Z"
      }
    }
  }
}`}
      </pre>
      <h3 id="oi-snapshot-python-example" style={{ fontSize: 16, fontWeight: 500, margin: "20px 0 8px", color: "var(--ink-strong)" }}>Python example — check OI for multiple contracts</h3>
      <pre className="code" style={{ marginBottom: 40 }}>
{`import requests

symbols = "AAPL260620C00200000,AAPL260620P00200000"
resp = requests.post(
    "${REST_BASE}/v1/options/snapshots/open_interest",
    headers={"Authorization": "Bearer <TOKEN>"},
    json={"symbols": symbols, "feed": "thetadata"}
)
for sym, snap in resp.json()["snapshots"].items():
    oi = snap["openInterest"]
    print(f"{sym}  OI={oi['oi']}  as_of={oi['t']}")`}
      </pre>

      <h2 id="post-v1-options-snapshots-expiry" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/options/snapshots/expiry</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Convenience endpoint: fetches <em>all</em> contracts for an underlying on a specific expiry date and returns their snapshots in one call.
        Resolves the contract list and batches snapshot requests (100 symbols per batch).
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>便捷接口：一次性获取指定标的在特定到期日的所有合约快照。会先解析合约列表，再批量请求快照（每批 100 个标的）。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/options/snapshots/expiry`} />
      <ParamTable rows={[
        { name: "underlying", type: "string", required: true,  desc: "Root ticker (e.g. AAPL)" },
        { name: "expiry",     type: "string", required: true,  desc: "Expiration date YYYY-MM-DD" },
        { name: "feed",       type: "string", required: false, desc: "opra | indicative (default: opra)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/options/snapshots/expiry \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"underlying":"AAPL","expiry":"2026-05-22","feed":"indicative"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Response
{
  "count": 42,
  "contracts": [ { "symbol": "AAPL260522C00110000", ... } ],
  "snapshots": {
    "AAPL260522C00110000": { "greeks": {...}, "latestQuote": {...} }
  }
}`}
      </pre>
      <h3 id="expiry-python-example" style={{ fontSize: 16, fontWeight: 500, margin: "20px 0 8px", color: "var(--ink-strong)" }}>Python example — scan all contracts for a Friday expiry</h3>
      <pre className="code" style={{ marginBottom: 48 }}>
{`import requests

resp = requests.post(
    "${REST_BASE}/v1/options/snapshots/expiry",
    headers={"Authorization": "Bearer <TOKEN>"},
    json={"underlying": "AAPL", "expiry": "2026-05-29", "feed": "indicative"}
)
data = resp.json()
print(f"{data['count']} contracts for AAPL 2026-05-29")
for sym, snap in data["snapshots"].items():
    g = snap.get("greeks", {})
    q = snap.get("latestQuote", {})
    print(f"  {sym}  delta={g.get('delta','—')}  bid={q.get('bp','—')}  ask={q.get('ap','—')}")`}
      </pre>

      {/* ── ThetaData Value direct endpoints ── */}
      <div className="eyebrow" style={{ marginBottom: 6, marginTop: 48, fontSize: 11, color: "var(--ink-soft)" }}>Options Data · ThetaData Value</div>
      <h2 id="post-v3-option-direct-value" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>GET/POST /v3/option/* (ThetaData Value)</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Authenticated proxy for ThetaData Value option endpoints included in the subscription.
        Supports both <code>GET</code> query parameters and <code>POST</code> JSON bodies, strips proxy credentials before execution, and caches successful JSON responses server-side.
        Unsupported Standard/Pro-only routes such as option trades, trade_quote, market value, implied volatility, and greeks are intentionally not exposed here.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>ThetaData Value 期权白名单代理，仅开放 Value 订阅允许的端点。支持 GET/POST，成功 JSON 响应写入服务端缓存。</span>
      </p>
      <EndpointBadge method="GET/POST" path={`${REST_BASE}/v3/option/...`} />
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 20 }}>
        <thead><tr><th>Endpoint</th><th>Required access</th><th>Notes</th></tr></thead>
        <tbody>
          {[
            ["/v3/option/list/symbols",          "Options contracts", "List option root symbols"],
            ["/v3/option/list/dates/quote",      "Options contracts", "Available quote dates"],
            ["/v3/option/list/dates/trade",      "Options contracts", "Available trade dates as list metadata"],
            ["/v3/option/list/expirations",      "Options contracts", "Expirations for a root"],
            ["/v3/option/list/strikes",          "Options contracts", "Strikes for root/expiration"],
            ["/v3/option/list/contracts/quote",  "Options contracts", "Contract list by quote availability"],
            ["/v3/option/list/contracts/trade",  "Options contracts", "Contract list by trade availability"],
            ["/v3/option/snapshot/ohlc",         "Options snapshots", "Latest OHLC snapshot"],
            ["/v3/option/snapshot/quote",        "Options snapshots", "Latest NBBO quote snapshot"],
            ["/v3/option/snapshot/open_interest","Options snapshots", "Latest open-interest snapshot"],
            ["/v3/option/history/eod",           "Options history",   "Daily EOD summary"],
            ["/v3/option/history/ohlc",          "Options history",   "Historical OHLC bars"],
            ["/v3/option/history/quote",         "Options history",   "Historical quotes"],
            ["/v3/option/history/open_interest", "Options history",   "Historical open interest"],
            ["/v3/option/at_time/quote",         "Options history",   "Quote at a timestamp"],
          ].map(([endpoint, access, notes], i) => (
            <tr key={i}>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>{endpoint}</td>
              <td style={{ fontSize: 12, color: "var(--ink-muted)" }}>{access}</td>
              <td style={{ fontSize: 12, color: "var(--ink-muted)" }}>{notes}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <h3 id="post-v3-option-history-ohlc" style={{ fontSize: 16, fontWeight: 500, margin: "20px 0 8px", color: "var(--ink-strong)" }}>Historical OHLC example</h3>
      <pre className="code" style={{ marginBottom: 12 }}>
{`# GET — query parameters forwarded to ThetaData
curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}/v3/option/history/ohlc?root=AAPL&exp=260620&strike=200.0&right=C&start_date=20250102&end_date=20250103"

# POST — JSON body forwarded to ThetaData
curl -X POST ${REST_BASE}/v3/option/history/ohlc \\
  -H "Authorization: Bearer <TOKEN>" \\
  -H "Content-Type: application/json" \\
  -d '{"root":"AAPL","exp":260620,"strike":200.0,"right":"C","start_date":20250102,"end_date":20250103}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 40 }}>
{`// Response — ThetaData native format
{
  "ohlc": [
    { "date": 20250102, "open": 14.50, "high": 15.20, "low": 14.10, "close": 14.85, "volume": 320 }
  ]
}`}
      </pre>
      <h3 id="post-v3-option-snapshot-ohlc" style={{ fontSize: 16, fontWeight: 500, margin: "20px 0 8px", color: "var(--ink-strong)" }}>Snapshot OHLC example</h3>
      <pre className="code" style={{ marginBottom: 48 }}>
{`curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}/v3/option/snapshot/ohlc?root=AAPL&exp=260620&strike=200.0&right=C"

// Response — ThetaData native format
{
  "ohlc": { "open": 14.50, "high": 15.20, "low": 14.10, "close": 14.85, "volume": 320 }
}`}
      </pre>

      <h3 id="post-v3-option-at-time-quote" style={{ fontSize: 16, fontWeight: 500, margin: "20px 0 8px", color: "var(--ink-strong)" }}>Quote at time example</h3>
      <pre className="code" style={{ marginBottom: 12 }}>
{`# GET — quote at a specific time of day
curl -H "Authorization: Bearer <TOKEN>" \\
  "${REST_BASE}/v3/option/at_time/quote?root=AAPL&exp=260620&strike=200.0&right=C&start_date=20250102&end_date=20250102&time_of_day=14:30:00"

# POST — JSON body
curl -X POST ${REST_BASE}/v3/option/at_time/quote \\
  -H "Authorization: Bearer <TOKEN>" \\
  -H "Content-Type: application/json" \\
  -d '{"root":"AAPL","exp":260620,"strike":200.0,"right":"C","start_date":20250102,"end_date":20250102,"time_of_day":"14:30:00"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 48 }}>
{`// Response — ThetaData native format
{
  "quotes": [
    { "date": 20250102, "ms_of_day": 52200000, "bid": 14.80, "bid_size": 10, "ask": 14.90, "ask_size": 15 }
  ]
}`}
      </pre>

      {/* ── Crypto ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Crypto Data</div>

      <h2 id="post-v1-crypto-us-latest-orderbooks" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /v1/crypto/us/latest/orderbooks</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Latest L2 order book snapshot for US crypto pairs. Premium tier only.
        Each side of the book is an array of <code>{"{ p: price, s: size }"}</code> objects sorted by price.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>美国加密货币对的最新 L2 订单簿快照。仅限 Premium 套餐。每侧订单簿为按价格排序的 {"{ p: price, s: size }"} 对象数组。</span>
      </p>
      <EndpointBadge method="POST" path={`${REST_BASE}/v1/crypto/us/latest/orderbooks`} />
      <ParamTable rows={[
        { name: "symbols", type: "string", required: true, desc: "Comma-separated crypto pairs (e.g. BTC/USD,ETH/USD)" },
      ]} />
      <pre className="code" style={{ marginBottom: 12 }}>
{`curl -X POST ${REST_BASE}/v1/crypto/us/latest/orderbooks \\
  -H "Authorization: Bearer *** \\
  -H "Content-Type: application/json" \\
  -d '{"symbols":"BTC/USD,ETH/USD"}'`}
      </pre>
      <pre className="code" style={{ marginBottom: 48 }}>
{`// Response
{
  "orderbooks": {
    "BTC/USD": {
      "a": [
        { "p": 76692.1,  "s": 0.774207 },
        { "p": 76705.84, "s": 1.5678 }
      ],
      "b": [
        { "p": 76680.0,  "s": 0.5 },
        { "p": 76670.5,  "s": 1.2 }
      ]
    }
  }
}`}
      </pre>

      {/* ── Admin endpoints ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Admin endpoints</div>
      <p style={{ fontSize: 14, color: "var(--ink-muted)", margin: "0 0 20px" }}>
        Token portal admin API. Auth uses <code>X-Admin-Token</code> header (obtained from <code>POST /api/admin/login</code>). Sessions are in-memory and reset on server restart.
      </p>

      <h2 id="post-admin-login" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /api/admin/login</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>Receive a session token for the admin panel. Password set via <code>ADMIN_PASSWORD</code> environment variable.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>获取管理员面板的会话 Token。密码通过 ADMIN_PASSWORD 环境变量设置。</span>
      </p>
      <EndpointBadge method="POST" path={`${TOKEN_BASE}/api/admin/login`} />
      <pre className="code" style={{ marginBottom: 28 }}>
{`// Request
{ "password": "admin123" }

// Response 200
{ "success": true, "token": "a3f9c2...64b" }

// Use in subsequent admin requests:
// X-Admin-Token: a3f9c2...64b`}
      </pre>

      <h2 id="get-admin-pending" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>GET /api/admin/pending</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>List registrations awaiting approval.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>列出待审批的注册申请。</span>
      </p>
      <EndpointBadge method="GET" path={`${TOKEN_BASE}/api/admin/pending`} />
      <pre className="code" style={{ marginBottom: 28 }}>
{`// Response
{ "success": true, "items": [
  { "id": "61ce4f82-...", "username": "tonnysun", "phone": "18717931119",
    "tier": "premium", "registered_at": "2026-05-19T...", "status": "pending" }
]}`}
      </pre>

      <h2 id="post-admin-approve" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /api/admin/approve</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Approve a pending registration. Writes the user to both the token-site database and the proxy backend, issuing a token automatically.
        Returns the generated token so you can share it directly with the user.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>批准一条待处理的注册申请；自动写入用户数据库并签发 Token，返回值可直接发给用户。</span>
      </p>
      <EndpointBadge method="POST" path={`${TOKEN_BASE}/api/admin/approve`} />
      <pre className="code" style={{ marginBottom: 28 }}>
{`// Request
{ "id": "61ce4f82-8b16-4e7e-be01-282730e53cc8" }

// Response 200
{
  "success": true,
  "message": "已批准 tonnysun，Token 已自动注册到 proxy。",
  "token":  "c886624f-232d-4803-99fa-f8b970e4720a",
  "expiry": "2026-06-19T14:15:57.059Z"
}`}
      </pre>

      <h2 id="post-admin-reject" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>POST /api/admin/reject</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>Reject a pending registration with an optional reason.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>拒绝一条待处理的注册申请，可附带原因。</span>
      </p>
      <EndpointBadge method="POST" path={`${TOKEN_BASE}/api/admin/reject`} />
      <pre className="code" style={{ marginBottom: 48 }}>
{`// Request
{ "id": "61ce4f82-...", "reason": "信息不完整" }

// Response 200
{ "success": true, "message": "已拒绝 tonnysun。" }`}
      </pre>


      {/* ── Reference ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Reference</div>

      <h2 id="error-codes" className="display-title" style={{ fontSize: 28, margin: "0 0 12px" }}>Error codes</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>Common HTTP status codes returned by the proxy and when they occur.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>代理返回的常见 HTTP 状态码及其触发场景。</span>
      </p>
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 40 }}>
        <thead><tr><th style={{ width: 80 }}>Status</th><th>Body</th><th>When</th></tr></thead>
        <tbody>
          {[
            ["400", '{"error":"Missing required fields"}', "Required parameter absent or malformed JSON"],
            ["401", '{"error":"Invalid token"}', "Token missing, expired, or not active"],
            ["403", '{"error":"Forbidden"}', "Token valid but tier lacks permission for this endpoint"],
            ["404", '{"error":"Token not found"}', "Admin lookup: user_id not in active token list"],
            ["409", '{"success":false,"message":"..."}', "Duplicate username on registration"],
            ["429", "Rate limit exceeded: N/M req/min", "REST rate limit hit; retry after 60 s"],
            ["429", '{"error":"REST concurrency limit exceeded: N/M parallel requests"}', "Too many parallel requests in flight; wait for one to finish"],
            ["500", '{"error":"Cloud missing Alpaca master keys"}', "Proxy misconfiguration"],
            ["503", '{"error":"ThetaData not available"}', "ThetaData client offline (open_interest / eod)"],
            ["503", '{"error":"Server overloaded, stream priority active."}', "High load; WS streams take priority"],
          ].map(([s, b, w], i) => (
            <tr key={i}>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, fontWeight: 600 }}>{s}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-base)" }}>{b}</td>
              <td style={{ fontSize: 12, color: "var(--ink-muted)" }}>{w}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <h2 id="rate-limits" className="display-title" style={{ fontSize: 28, margin: "0 0 12px" }}>Rate limits</h2>
      <div style={{ background: "var(--bg-soft)", border: "1px solid var(--border)", borderRadius: 8, padding: "10px 14px", margin: "0 0 12px", fontSize: 13 }}>
        <strong>HTTP 429 = rate limit hit.</strong> If you receive a <code>429</code> response, you have exceeded your tier's per-minute REST quota or your parallel-request concurrency cap. Back off and retry after the 60-second rolling window or after one in-flight request finishes — do not hammer the endpoint.
        <br/><span style={{ color: "var(--ink-soft)" }}>收到 HTTP 429 说明触发了限速：超过了套餐的每分钟 REST 配额或并发上限。请等待 60 秒滚动窗口刷新或等已有请求完成后再重试，不要持续重试。</span>
      </div>
      <p style={{ fontSize: 14, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        REST limits are per-user, per rolling 60-second window. Limits tighten automatically when the server is under load (overloaded) and further under critical load.
        WebSocket symbol subscriptions are counted separately and do not reset on reconnect.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>REST 限速按用户、按 60 秒滚动窗口计算。服务器负载升高时自动收紧，极端负载下进一步收紧。WS 标的订阅数单独计算，重连后不会重置。</span>
      </p>
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 12 }}>
        <thead>
          <tr><th style={{ width: 150 }}>Tier</th><th>REST req/min</th><th>Under load</th><th>Critical</th><th>WS symbols</th></tr>
        </thead>
        <tbody>
          {[
            ["Trial",    "1800", "900", "450", "50"],
            ["Basic",    "600",  "300", "150", "—"],
            ["Value",    "1800", "900", "450", "30"],
            ["Standard", "1800", "900", "450", "50"],
            ["Premium",  "6000", "3000","1500","500"],
          ].map(([t, r, l, c, w], i) => (
            <tr key={i}>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>{t}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>{r}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center", color: "var(--ink-soft)" }}>{l}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center", color: "var(--ink-soft)" }}>{c}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>{w}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 12px" }}>
        Cached responses (X-Cache: HIT) do not count against REST rate limits. Check the <code>X-Cache</code> header — cache TTL is 5 minutes (300 s).
      </p>

      <h2 id="concurrency-limits" className="display-title" style={{ fontSize: 28, margin: "0 0 12px" }}>Concurrency limits</h2>
      <p style={{ fontSize: 14, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        In addition to rate limits, the proxy enforces per-user concurrency limits on both REST and WebSocket connections.
        REST concurrency limits the number of parallel in-flight requests; WebSocket concurrency limits the number of simultaneously open connections across all channels.
        <br/><span style={{ color: "var(--ink-soft)", fontSize: 13 }}>除限速外，代理还对 REST 和 WebSocket 实施每用户并发限制。REST 并发限制同时在途请求数；WS 并发限制所有通道的同时连接数。</span>
      </p>
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 12 }}>
        <thead>
          <tr><th style={{ width: 150 }}>Tier</th><th>REST parallel</th><th>WS connections</th></tr>
        </thead>
        <tbody>
          {[
            ["Trial",    "5",   "3"],
            ["Basic",    "2",   "1"],
            ["Value",    "3",   "2"],
            ["Standard", "5",   "3"],
            ["Premium",  "10",  "\u221E"],
          ].map(([t, r, w], i) => (
            <tr key={i}>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12 }}>{t}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>{r}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>{w}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <p style={{ fontSize: 12, color: "var(--ink-soft)", margin: "0 0 12px" }}>
        Exceeding the REST concurrency limit returns <code>429</code> with a JSON body. Exceeding the WS connection limit rejects the <code>auth</code> message and closes the socket with code <code>1008</code>.
      </p>

      <pre className="code" style={{ marginBottom: 40 }}>
{`// 429 response body (plain text) — rate limit
Rate limit exceeded: 301/300 req/min

// 429 response body (JSON) — concurrency limit
{
  "error": "REST concurrency limit exceeded: 5/5 parallel requests"
}

// WS auth rejected — connection limit
[{"T": "error", "msg": "Connection limit exceeded: 3/3 active websockets"}]
// Socket closed with code 1008 (policy violation)

// 503 response body (JSON) — backpressure under load
{
  "error": "Server overloaded, stream priority active. Retry later."
}
// With headers: Retry-After: 5, X-Load: high | critical, X-Priority: stream`}
      </pre>

    </div>
  );
}

function WsUsageBody() {
  const H3 = ({ children }) => (
    <h3 style={{ fontFamily: "var(--f-sans)", fontWeight: 500, fontSize: 13, letterSpacing: ".1em", textTransform: "uppercase", color: "var(--ink-muted)", margin: "32px 0 12px" }}>{children}</h3>
  );
  return (
    <div style={{ maxWidth: 760 }}>
      <div className="eyebrow" style={{ marginBottom: 10 }}>Realtime</div>
      <h2 id="websocket-connection" className="display-title" style={{ fontSize: 38, margin: "0 0 8px" }}>WebSocket connection</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 20px", maxWidth: 640 }}>
        Each channel has a dedicated path on EC2. Connect to a URL, send an <code>auth</code> message with your token, then send <code>subscribe</code> messages for the feeds you want.
        Stocks/options/overnight/boats/test use binary MessagePack; crypto and news use JSON.
      </p>

      <table className="tbl card" style={{ marginBottom: 28, overflow: "hidden" }}>
        <thead><tr><th>Channel</th><th>Path</th><th>Format</th><th>Basic</th><th>Trial</th><th>Value</th><th>Standard</th><th>Premium</th></tr></thead>
        <tbody>
          {[
            ["stocks",    "/stream",           "msgpack", "—", "✓", "✓", "✓", "✓"],
            ["options",   "/stream/options",   "msgpack", "—", "✓", "✓", "✓", "✓"],
            ["boats",     "/stream/boats",     "msgpack", "—", "✓", "✓", "✓", "✓"],
            ["overnight", "/stream/overnight", "msgpack", "—", "✓", "✓", "✓", "✓"],
            ["crypto",    "/stream/crypto",    "JSON",    "—", "✓", "✓", "✓", "✓"],
            ["news",      "/stream/news",      "JSON",    "—", "✓", "✓", "✓", "✓"],
            ["test",      "/stream/test",      "msgpack", "—", "✓", "✓", "✓", "✓"],
          ].map(([ch, path, fmt, b, tr, v, s, p], i) => (
            <tr key={i}>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 12, fontWeight: 600 }}>{ch}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 11 }}>{path}</td>
              <td style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-soft)" }}>{fmt}</td>
              <td style={{ color: b === "✓" ? "var(--ok)" : "var(--ink-soft)", fontFamily: "var(--f-mono)", textAlign: "center" }}>{b}</td>
              <td style={{ color: tr === "✓" ? "var(--ok)" : "var(--ink-soft)", fontFamily: "var(--f-mono)", textAlign: "center" }}>{tr}</td>
              <td style={{ color: v === "✓" ? "var(--ok)" : "var(--ink-soft)", fontFamily: "var(--f-mono)", textAlign: "center" }}>{v}</td>
              <td style={{ color: s === "✓" ? "var(--ok)" : "var(--ink-soft)", fontFamily: "var(--f-mono)", textAlign: "center" }}>{s}</td>
              <td style={{ color: p === "✓" ? "var(--ok)" : "var(--ink-soft)", fontFamily: "var(--f-mono)", textAlign: "center" }}>{p}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <H3>Symbol & connection limits</H3>
      <p style={{ fontSize: 13, color: "var(--ink-muted)", margin: "0 0 28px" }}>
        Per-channel symbol limits by tier: basic: — · value: 30 · trial/standard: 50 · premium: 500.
        WS connection limits by tier: basic: — · value: 2 · trial/standard: 3 · premium: unlimited.
        Exceeding either limit returns an error and the subscribe/auth is rejected.
      </p>

      {/* ── Connecting ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Connecting</div>
      <h2 id="auth-message" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Auth message</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        After opening the WebSocket, send an <code>auth</code> action. Authentication happens in the message body — no HTTP headers are needed.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`import asyncio, websockets, json, msgpack

async def stream_stocks(token):
    uri = "${WS_BASE}/stream"        # stocks channel
    async with websockets.connect(uri) as ws:

        # 1. Authenticate
        await ws.send(json.dumps({"action": "auth", "token": token}))
        auth_resp = msgpack.unpackb(await ws.recv())
        # auth_resp -> [{"T": "success", "msg": "authenticated"}]

        # 2. Subscribe to the feeds you want
        await ws.send(json.dumps({
            "action": "subscribe",
            "trades": ["AAPL", "TSLA", "NVDA"],
            "quotes": ["AAPL"],
            "bars":   [],
            "updatedBars": [],
            "dailyBars": []
        }))

        # 3. Receive messages
        async for raw in ws:
            msgs = msgpack.unpackb(raw)   # list of message dicts
            for msg in msgs:
                if msg.get("T") == "t":
                    print("TRADE:", msg["S"], msg["p"], msg["s"])
                elif msg.get("T") == "q":
                    print("QUOTE:", msg["S"], msg["bp"], msg["ap"])
                elif msg.get("T") == "b":
                    print("BAR:", msg["S"], msg["o"], msg["h"], msg["l"], msg["c"])

asyncio.run(stream_stocks("YOUR_TOKEN"))`}
      </pre>
      <pre className="code" style={{ marginBottom: 28 }}>
{`# For crypto/news channels — same pattern but JSON instead of msgpack
uri = "${WS_BASE}/stream/news"
await ws.send(json.dumps({"action": "auth", "token": token}))
auth_resp = json.loads(await ws.recv())   # JSON response

await ws.send(json.dumps({
    "action": "subscribe",
    "news": ["AAPL", "*"]    # "*" subscribes to all symbols
}))`}
      </pre>

      <h2 id="heartbeat" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Heartbeat</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 28px" }}>
        The server sends WebSocket ping frames automatically. Most client libraries respond to pings automatically. If your client does not, call <code>pong()</code> on receipt to stay connected.
        The server will close connections that exceed the send queue limit (200 messages).
        Each tier has a per-user connection limit across all channels (see <a href="#rate-limits">Rate limits</a>). Exceeding it rejects auth with code <code>1008</code>.
      </p>

      <h2 id="error" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Error</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Returned on auth failure, subscription rejection, or policy violation. The connection may be closed immediately after an error.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Auth rejected — too many connections
await ws.send(json.dumps({"action": "auth", "token": token}))
// -> [{"T": "error", "msg": "connection limit exceeded", "code": 1008}]
// Connection closed with code 1008`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T":   "error",
  "msg": "connection limit exceeded",
  "code": 1008
}`}
      </pre>

      <h2 id="success" className="display-title" style={{ fontSize: 28, margin: "0 0 48px" }}>Success</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Returned after successful authentication.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// After sending auth
await ws.send(json.dumps({"action": "auth", "token": token}))
// -> [{"T": "success", "msg": "authenticated"}]`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T": "success",
  "msg": "authenticated"
}`}
      </pre>

      {/* ── Channels ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Channels</div>

      {/* stocks */}
      <h2 id="stocks" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Stocks</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Live US equities from the SIP feed (pro account). Subscribe to <code>trades</code>, <code>quotes</code>, <code>bars</code>, <code>updatedBars</code>, and/or <code>dailyBars</code>.
        Use <code>"*"</code> to subscribe to all symbols.
      </p>
      <pre className="code" style={{ marginBottom: 24 }}>
{`# Connect to /stream, then subscribe
uri = "${WS_BASE}/stream"
await ws.send(json.dumps({"action": "auth", "token": token}))
await ws.recv()   # success response

await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL", "TSLA", "NVDA"],
    "quotes": ["AAPL"],
    "bars":   ["AAPL"],
    "updatedBars": ["AAPL"],
    "dailyBars":   ["AAPL"]
}))

# You will receive Trade (T: "t"), Quote (T: "q"), Bar (T: "b"),
# Updated bar (T: "u"), and Daily bar (T: "d") messages`}
      </pre>

      <h3 id="stocks-trade" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Trade</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>trades</code> on the stocks channel. One message per executed trade.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to trades on /stream
await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL"]
}))
// Response — Trade messages (T: "t") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T": "t",                         // message type: trade
  "S": "AAPL",                      // symbol
  "p": 214.37,                      // price
  "s": 100,                         // size (shares)
  "t": "2026-05-22T14:08:12.482Z",  // timestamp
  "x": "NASDAQ",                    // exchange
  "c": ["@", "T"],                  // trade conditions
  "z": "C"                          // tape
}`}
      </pre>

      <h3 id="stocks-quote" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Quote</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>quotes</code> on the stocks channel. Contains the top-of-book bid and ask.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to quotes on /stream
await ws.send(json.dumps({
    "action": "subscribe",
    "quotes": ["AAPL"]
}))
// Response — Quote messages (T: "q") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T":  "q",                         // message type: quote
  "S":  "AAPL",
  "ax": "NASDAQ", "ap": 214.40, "as": 200,   // ask exchange, price, size
  "bx": "NYSE",   "bp": 214.35, "bs": 500,   // bid exchange, price, size
  "t":  "2026-05-22T14:08:12.522Z",
  "c":  ["R"],                       // quote conditions
  "z":  "C"
}`}
      </pre>

      <h3 id="stocks-bar" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Bar</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>bars</code> on the stocks channel. One message per minute bar.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to minute bars on /stream
await ws.send(json.dumps({
    "action": "subscribe",
    "bars": ["AAPL"]
}))
// Response — Bar messages (T: "b") arrive once per minute`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T":  "b",                         // message type: bar (minute)
  "S":  "AAPL",
  "o":  214.20,  "h": 214.50,  "l": 214.10,  "c": 214.37,
  "v":  128400,                      // volume
  "vw": 214.33,                      // VWAP
  "n":  843,                         // trade count
  "t":  "2026-05-22T14:08:00Z"       // bar open time
}`}
      </pre>

      <h3 id="stocks-updated-bar" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Updated bar</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>updatedBars</code> on the stocks channel. Emitted when a previously sent minute bar is corrected (e.g., late trade reporting). Same schema as Bar but with <code>"T": "u"</code>.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to updated bars on /stream
await ws.send(json.dumps({
    "action": "subscribe",
    "updatedBars": ["AAPL"]
}))
// Response — Updated bar messages (T: "u") arrive when a bar is corrected`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T": "u",                          // message type: updated bar
  "S": "AAPL",
  "o": 214.20, "h": 214.50, "l": 214.10, "c": 214.38,
  "v": 128500,
  "vw": 214.34,
  "n": 844,
  "t": "2026-05-22T14:08:00Z"
}`}
      </pre>

      <h3 id="stocks-daily-bar" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Daily bar</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>dailyBars</code> on the stocks channel. Emitted once per day at market close. Same schema as Bar but with <code>"T": "d"</code> and the timestamp is the trading day open.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to daily bars on /stream
await ws.send(json.dumps({
    "action": "subscribe",
    "dailyBars": ["AAPL"]
}))
// Response — Daily bar messages (T: "d") arrive once per day at close`}
      </pre>
      <pre className="code" style={{ marginBottom: 32 }}>
{`{
  "T": "d",                          // message type: daily bar
  "S": "AAPL",
  "o": 213.50, "h": 215.10, "l": 212.80, "c": 214.37,
  "v": 45283000,
  "vw": 214.15,
  "n": 128400,
  "t": "2026-05-22T04:00:00Z"       // trading day open (ET)
}`}
      </pre>

      {/* options */}
      <h2 id="options" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Options</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Live OPRA options feed. Subscribe using OCC symbols in the <code>trades</code>, <code>quotes</code>, and <code>bars</code> lists.
        All tiers except Basic.
        <strong style={{ color: "var(--ink-strong)" }}> Index options (SPX, SPXW, NDX, RUT) are not available via WebSocket</strong> — the upstream Alpaca feed only covers equity and ETF options. Use the REST <code>/v1/history/options/bars</code> endpoint with <code>provider=thetadata</code> for index option history.
      </p>
      <pre className="code" style={{ marginBottom: 24 }}>
{`# Connect to /stream/options
uri = "${WS_BASE}/stream/options"
await ws.send(json.dumps({"action": "auth", "token": token}))
await ws.recv()

await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL240621C00210000"],
    "quotes": ["AAPL240621C00210000"],
    "bars":   ["AAPL240621C00210000"]
}))

# You will receive Trade (T: "t"), Quote (T: "q"), and Bar (T: "b") messages`}
      </pre>

      <h3 id="options-trade" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Trade</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>trades</code> on the options channel. Schema is identical to equity trades.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to options trades on /stream/options
await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL240621C00210000"]
}))
// Response — Trade messages (T: "t") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T": "t",
  "S": "AAPL240621C00210000",
  "p": 5.25,
  "s": 10,
  "t": "2026-05-22T14:08:12.482Z",
  "x": "OPRA",
  "c": ["@"],
  "z": "C"
}`}
      </pre>

      <h3 id="options-quote" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Quote</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>quotes</code> on the options channel. Schema is identical to equity quotes.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to options quotes on /stream/options
await ws.send(json.dumps({
    "action": "subscribe",
    "quotes": ["AAPL240621C00210000"]
}))
// Response — Quote messages (T: "q") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T":  "q",
  "S":  "AAPL240621C00210000",
  "ax": "OPRA", "ap": 5.30, "as": 50,
  "bx": "OPRA", "bp": 5.20, "bs": 30,
  "t":  "2026-05-22T14:08:12.522Z",
  "c":  ["R"],
  "z":  "C"
}`}
      </pre>

      <h3 id="options-bar" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Bar</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>bars</code> on the options channel. One message per minute bar. Schema is identical to equity bars.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to options minute bars on /stream/options
await ws.send(json.dumps({
    "action": "subscribe",
    "bars": ["AAPL240621C00210000"]
}))
// Response — Bar messages (T: "b") arrive once per minute`}
      </pre>
      <pre className="code" style={{ marginBottom: 32 }}>
{`{
  "T":  "b",
  "S":  "AAPL240621C00210000",
  "o":  5.10,  "h": 5.35,  "l": 5.05,  "c": 5.25,
  "v":  450,
  "vw": 5.22,
  "n":  120,
  "t":  "2026-05-22T14:08:00Z"
}`}
      </pre>

      {/* boats */}
      <h2 id="boats" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Boats</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Regulatory and market-maker equity data (BOATS feed). Subscribe using <code>trades</code> and/or <code>quotes</code> lists.
        Messages are msgpack. All tiers except Basic.
      </p>
      <pre className="code" style={{ marginBottom: 24 }}>
{`# Connect to /stream/boats
uri = "${WS_BASE}/stream/boats"
await ws.send(json.dumps({"action": "auth", "token": token}))
await ws.recv()

await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL"],
    "quotes": ["AAPL"]
}))

# You will receive Trade (T: "t") and Quote (T: "q") messages`}
      </pre>

      <h3 id="boats-trade" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Trade</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>trades</code> on the boats channel. Schema is identical to equity trades.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to BOATS trades on /stream/boats
await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL"]
}))
// Response — Trade messages (T: "t") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T": "t",
  "S": "AAPL",
  "p": 214.37,
  "s": 100,
  "t": "2026-05-22T14:08:12.482Z",
  "x": "NASDAQ",
  "c": ["@", "T"],
  "z": "C"
}`}
      </pre>

      <h3 id="boats-quote" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Quote</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>quotes</code> on the boats channel. Schema is identical to equity quotes.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to BOATS quotes on /stream/boats
await ws.send(json.dumps({
    "action": "subscribe",
    "quotes": ["AAPL"]
}))
// Response — Quote messages (T: "q") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 32 }}>
{`{
  "T":  "q",
  "S":  "AAPL",
  "ax": "NASDAQ", "ap": 214.40, "as": 200,
  "bx": "NYSE",   "bp": 214.35, "bs": 500,
  "t":  "2026-05-22T14:08:12.522Z",
  "c":  ["R"],
  "z":  "C"
}`}
      </pre>

      {/* overnight */}
      <h2 id="overnight" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Overnight</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Extended-hours equity data. Same subscribe format as stocks (trades + quotes + bars).
        Messages are msgpack. All tiers except Basic.
      </p>
      <pre className="code" style={{ marginBottom: 24 }}>
{`# Connect to /stream/overnight
uri = "${WS_BASE}/stream/overnight"
await ws.send(json.dumps({"action": "auth", "token": token}))
await ws.recv()

await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL"],
    "quotes": ["AAPL"],
    "bars":   ["AAPL"]
}))

# You will receive Trade (T: "t"), Quote (T: "q"), and Bar (T: "b") messages`}
      </pre>

      <h3 id="overnight-trade" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Trade</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>trades</code> on the overnight channel. Schema is identical to equity trades.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to overnight trades on /stream/overnight
await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["AAPL"]
}))
// Response — Trade messages (T: "t") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T": "t",
  "S": "AAPL",
  "p": 214.37,
  "s": 100,
  "t": "2026-05-22T14:08:12.482Z",
  "x": "NASDAQ",
  "c": ["@", "T"],
  "z": "C"
}`}
      </pre>

      <h3 id="overnight-quote" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Quote</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>quotes</code> on the overnight channel. Schema is identical to equity quotes.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to overnight quotes on /stream/overnight
await ws.send(json.dumps({
    "action": "subscribe",
    "quotes": ["AAPL"]
}))
// Response — Quote messages (T: "q") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T":  "q",
  "S":  "AAPL",
  "ax": "NASDAQ", "ap": 214.40, "as": 200,
  "bx": "NYSE",   "bp": 214.35, "bs": 500,
  "t":  "2026-05-22T14:08:12.522Z",
  "c":  ["R"],
  "z":  "C"
}`}
      </pre>

      <h3 id="overnight-bar" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Bar</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>bars</code> on the overnight channel. One message per minute bar. Schema is identical to equity bars.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to overnight minute bars on /stream/overnight
await ws.send(json.dumps({
    "action": "subscribe",
    "bars": ["AAPL"]
}))
// Response — Bar messages (T: "b") arrive once per minute`}
      </pre>
      <pre className="code" style={{ marginBottom: 32 }}>
{`{
  "T":  "b",
  "S":  "AAPL",
  "o":  214.20,  "h": 214.50,  "l": 214.10,  "c": 214.37,
  "v":  128400,
  "vw": 214.33,
  "n":  843,
  "t":  "2026-05-22T14:08:00Z"
}`}
      </pre>

      {/* crypto */}
      <h2 id="crypto" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Crypto</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Live US crypto orderbooks and trades. Subscribe using <code>orderbooks</code> and/or <code>trades</code> lists with pairs like <code>BTC/USD</code>.
        Messages are plain JSON (not msgpack). All tiers except Basic.
      </p>
      <pre className="code" style={{ marginBottom: 24 }}>
{`# Connect to /stream/crypto
uri = "${WS_BASE}/stream/crypto"
await ws.send(json.dumps({"action": "auth", "token": token}))
await ws.recv()

await ws.send(json.dumps({
    "action": "subscribe",
    "orderbooks": ["BTC/USD", "ETH/USD"],
    "trades":     ["BTC/USD"]
}))

# You will receive orderbook updates (T: "o") and Trade (T: "t") messages`}
      </pre>

      <h3 id="crypto-orderbook" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Orderbook</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>orderbooks</code> on the crypto channel. Contains top-of-book bid/ask snapshot.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to crypto orderbooks on /stream/crypto
await ws.send(json.dumps({
    "action": "subscribe",
    "orderbooks": ["BTC/USD"]
}))
// Response — Orderbook messages (T: "o") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 24 }}>
{`{
  "T":  "o",                         // message type: orderbook
  "S":  "BTC/USD",
  "t":  "2026-05-22T14:08:12.522Z",
  "ax": "ERSX", "ap": 43400.00, "as": 0.5,   // ask
  "bx": "ERSX", "bp": 43399.50, "bs": 1.2    // bid
}`}
      </pre>

      <h3 id="crypto-trade" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Trade</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>trades</code> on the crypto channel. Same schema as equity trades.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to crypto trades on /stream/crypto
await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["BTC/USD"]
}))
// Response — Trade messages (T: "t") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 32 }}>
{`{
  "T": "t",
  "S": "BTC/USD",
  "p": 43399.50,
  "s": 0.25,
  "t": "2026-05-22T14:08:12.482Z",
  "x": "ERSX",
  "c": ["@"]
}`}
      </pre>

      {/* news */}
      <h2 id="news" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>News</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Realtime news events from Benzinga. Subscribe with a <code>news</code> list of tickers or <code>"*"</code> for all.
        Messages are plain JSON. All tiers except Basic. Historical news is also available via REST <code>/v1/history/news</code>.
      </p>
      <pre className="code" style={{ marginBottom: 24 }}>
{`# Connect to /stream/news
uri = "${WS_BASE}/stream/news"
await ws.send(json.dumps({"action": "auth", "token": token}))
await ws.recv()

await ws.send(json.dumps({
    "action": "subscribe",
    "news": ["AAPL", "*"]    # "*" subscribes to all symbols
}))

# You will receive News (T: "n") messages`}
      </pre>

      <h3 id="news-news" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>News</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>news</code> on the news channel. One message per news article.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to news on /stream/news
await ws.send(json.dumps({
    "action": "subscribe",
    "news": ["AAPL", "*"]
}))
// Response — News messages (T: "n") start arriving`}
      </pre>
      <pre className="code" style={{ marginBottom: 32 }}>
{`{
  "T":        "n",                   // message type: news
  "id":       12345678,
  "headline": "Apple Reports Q2 Earnings Beat",
  "summary":  "Apple announced revenue of $95.4B...",
  "author":   "Reuters",
  "created_at": "2026-05-22T14:08:00Z",
  "updated_at": "2026-05-22T14:08:00Z",
  "url":      "https://www.benzinga.com/...",
  "content":  "...",
  "symbols":  ["AAPL"],
  "source":   "benzinga"
}`}
      </pre>

      {/* test */}
      <h2 id="test" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Test</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Debug and validation channel. Echoes a small test payload on subscription so you can verify connectivity, auth, and msgpack parsing without consuming market data.
        Messages are msgpack. All tiers.
      </p>
      <pre className="code" style={{ marginBottom: 24 }}>
{`# Connect to /stream/test
uri = "${WS_BASE}/stream/test"
await ws.send(json.dumps({"action": "auth", "token": token}))
await ws.recv()

await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["TEST"]
}))

# You will receive a test Trade (T: "t") message for verification`}
      </pre>

      <h3 id="test-trade" className="display-title" style={{ fontSize: 20, margin: "28px 0 8px" }}>Trade</h3>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Sent when you subscribe to <code>trades</code> on the test channel. A dummy trade message for testing your parser.
      </p>
      <pre className="code" style={{ marginBottom: 12 }}>
{`// Request — subscribe to test trades on /stream/test
await ws.send(json.dumps({
    "action": "subscribe",
    "trades": ["TEST"]
}))
// Response — A test Trade message (T: "t") is sent for verification`}
      </pre>
      <pre className="code" style={{ marginBottom: 48 }}>
{`{
  "T": "t",
  "S": "TEST",
  "p": 100.00,
  "s": 1,
  "t": "2026-05-22T14:08:00Z",
  "x": "TEST",
  "c": ["@"],
  "z": "C"
}`}
      </pre>

      {/* ── Operations ── */}
      <div className="eyebrow" style={{ marginBottom: 10 }}>Operations</div>

      <h2 id="reconnect" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Reconnect</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        The server may close the connection on overload (<code>code 1013</code>) or policy violation (<code>code 1008</code>).
        Implement exponential backoff. Subscriptions are not persisted — re-auth and re-subscribe after every reconnect.
      </p>
      <pre className="code" style={{ marginBottom: 28 }}>
{`import asyncio, websockets, json, msgpack

async def with_reconnect(token, uri, handler, backoff=1):
    while True:
        try:
            async with websockets.connect(uri) as ws:
                await ws.send(json.dumps({"action": "auth", "token": token}))
                await ws.recv()                          // auth response
                await ws.send(json.dumps({               // re-subscribe
                    "action": "subscribe",
                    "trades": ["AAPL", "TSLA"]
                }))
                await handler(ws)
        except (websockets.ConnectionClosed, OSError):
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 60)`}
      </pre>

      <h2 id="backpressure" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Backpressure</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        Each client has an outbound queue of 200 messages. If your consumer is too slow to drain it, newer messages are dropped and a drop counter is incremented server-side (visible in admin stats).
        Under system load (<code>X-Load: high</code>), REST endpoints are throttled or rejected to protect stream delivery — WS is always served first.
      </p>
      <p style={{ fontSize: 13, color: "var(--ink-soft)", margin: "0 0 40px" }}>
        Best practice: process each message quickly (or offload to a queue) rather than doing heavy work inside the receive loop.
      </p>

      <h2 id="rate-limits" className="display-title" style={{ fontSize: 28, margin: "0 0 8px" }}>Rate limits</h2>
      <p style={{ fontSize: 15, color: "var(--ink-muted)", margin: "0 0 12px" }}>
        WS limits are separate from REST. Symbol subscriptions and open connections are counted per user across all channels.
      </p>
      <table className="tbl card" style={{ overflow: "hidden", marginBottom: 12 }}>
        <thead>
          <tr><th>Tier</th><th>WS symbols</th><th>WS connections</th></tr>
        </thead>
        <tbody>
          <tr><td style={{ fontSize: 12 }}>trial</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>50</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>3</td></tr>
          <tr><td style={{ fontSize: 12 }}>basic</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>—</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>—</td></tr>
          <tr><td style={{ fontSize: 12 }}>value</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>30</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>2</td></tr>
          <tr><td style={{ fontSize: 12 }}>standard</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>100</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>3</td></tr>
          <tr><td style={{ fontSize: 12 }}>premium</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>500</td><td style={{ fontFamily: "var(--f-mono)", fontSize: 12, textAlign: "center" }}>unlimited</td></tr>
        </tbody>
      </table>
      <p style={{ fontSize: 13, color: "var(--ink-soft)", margin: "0 0 40px" }}>
        Exceeding the REST concurrency limit returns <code>429</code> with a JSON body. Exceeding the WS connection limit rejects the <code>auth</code> message and closes the socket with code <code>1008</code>.
      </p>
    </div>
  );
}

window.DocsSite = DocsSite;

