MIS β ops-api endpoint contracts (π MIS β π§ Brain to build)
Owner of these specs: π MIS. Builds + deploys them: π§ Brain (into ops-api, the one deploy after deploy-safety). Last updated: Thu Jun 25, 2026.
Hard rules for all endpoints: every value carries asof (ISO) + source; broker email = truth, API = freshness; if asof older than the staleness threshold (below), set stale:true (so nothing silently rots β this is the watchdog). Read-only. No secret VALUES in git/chat β Brain sets them from the secret store.
Staleness threshold (stale:true rule) β market-hours-aware (answer to Brain's tightening):
- Market hours (MonβFri 9:30 AMβ4:00 PM ET): stale if asof > 30 min old (covers GF's ~15-min lag + one refresh cycle; flags a stuck feed).
- Off-hours / overnight / weekend / NYSE holiday: stale if asof predates the last NYSE session close (the brief must reflect at least the most recent completed session).
- Absolute backstop (always): asof > 26h β stale:true + loud. This is the blackout-catcher β the 3-week dark period would've tripped this on day 1.
- If full NYSE-holiday-calendar awareness is too much for v1: default to 30 min in-hours / 18h off-hours / 26h hard cap, and I'll tune. (Better than the 24h default β 24h hides hours-old prices as "live" during a session.)
1. mis-bridge (the live data bridge β build first)
Why: lets ops-api serve MIS data without running GOOGLEFINANCE per request. Source: the MIS v2 Apps Script web app (misV2VerdictFor_, fn=book/brief/portmetrics).
Contract: ops-api calls the v2 web app /exec with RUN_TOKEN (secret name: MIS_RUN_TOKEN), caches the JSON in KV (TTL ~120s, ?fresh=1 bypass), and exposes the endpoints below. If the v2 app is unreachable or its data asof is old β return stale:true + last-good, never a silent blank.
2. GET /mis/brief (feeds the morning-spine + cockpit headline β concise JSON, NOT HTML)
Source of truth = the v2 web app fn=briefjson (keyed with MIS_SETUP_KEY). Brain proxies it 1:1 (KV-cache + asof/stale wrap). Verified-live shape (deploy @110, Jun 30 2026, HTTP 200):
{
"asof": "2026-06-30T16:31:42",
"source": "mis-v2",
"stale": false,
"regime": { "label": "NORMAL", "vix": 16.4, "factor": 1.0 },
"equity": { "net": 21725, "total": 21725, "equity": 21725, "cash": 528,
"cashPct": 0.024, "topSector": "Communication Services", "topSectorPct": 0.264,
"bookRiskPct": 0.0199, "stopPct": -20, "drawdownPct": null },
"topHolding": { "sym": "META", "pct": 25 },
"mtd": { "targetPct": 2.5, "actualPct": 0, "earned": 0, "targetDollar": 362,
"gapDollar": 362, "daysLeft": 0, "status": "live", "text": "2.5% CLOCK (β¦) β¦" },
"schwabTokenAgeD": 0.2, "peakEquity": null,
"actionLine": "2.5% clock: $362 to go this month, 0 trading days left.",
"flags": []
}
equity.net/total/equity= the RECONCILED net across all 5 accounts (~$22K real, not the $32K gross).drawdownPctis null untilPEAK_EQUITYis set.- β
topHolding.pctscope: this is % of TOTAL equity (all 5 accounts) β META β 25%. The "META 49%" figure is META within acct 0600 alone β a different (single-account) scope. Don't conflate.flags/actionLinefire off the all-account view (40% threshold), so single-account concentration won't trip them. mtd.textis the human one-liner for the phone;mtd.actualPct/gapDollarare the structured fields. The morning-spine reads this and renders its MIS block from it (replaces the stale "not run; last $22,135" line).
3. GET /mis/tile (the dynamic home MIS tile β tiny payload; Bee wires the tile UI to it)
{ "asof": "...", "stale": false, "topHolding": {"sym":"META","pct":49}, "equity": 22285, "drawdownPct": 0.0, "regime": "NORMAL" }
Replaces the hardcoded ?sym=NVDA. Tile shows the real top holding/risk, not a fixed bellwether.
4. Schwab-API-in-Worker (off-sheets β the 200β250 scale fix; co-build, sequenced AFTER deploy-safety + an engine-freeze window)
Why: replaces GOOGLEFINANCE (the 6-min wall) AND kills the weekly Schwab re-auth. Secret NAMES (values via secret store): SCHWAB_CLIENT_ID, SCHWAB_SECRET, SCHWAB_REFRESH_TOKEN.
Contract:
- OAuth refresh-token handler (auto-refresh β no more weekly manual re-auth).
- GET /mis/quotes?syms=... β batched real-time quotes for the universe; cache KV 5-min.
- GET /mis/holdings?acct=898|0600 β positions; cache; reconcile against broker-email truth.
- History β D1 (one long Price_History table, not per-ticker tabs).
- Generalizes the existing single-ticker /mis/peek real-time path to the whole universe.
Two gates, timed together: Brain's deploy-safety (adds persistent Schwab creds) + my engine-freeze window (scores/scale stay frozen until Sam opens B.75βCβSTOP).
5. Entry/bracket pad β "Size & Protect this trade" (added Jun 29, from a live gap)
Why: the peek gives a verdict but no actionable levels β useless mid-trade (Sam bought 6 META @ $565.55 and had to compute the bracket by hand). The engine already has fn=entry + Quick Trade Calc; surface it in the cockpit, one tap from any ticker.
Contract β GET /mis/entry?sym=&shares=|amt=&risk= β returns, per ticker:
{ "asof":"...", "sym":"META", "price":558.44, "atr14":15.26, "atrPct":2.46,
"stops": { "tight_1_3x":546, "swing_2x":535, "wide_3x":520 },
"byRiskBudget": { "risk":300, "shares":6, "stop":515, "stopAtrMult":3.3 },
"targets": { "plus10pct":622, "plus20pct":679 }, "rr_at_plus10":1.1 }
- ATR-based stops for EVERY ticker (held + watched) β 1.3Γ/2Γ/3Γ ATR options + the $-risk-budget stop, with the ATR multiple shown so Sam sees if a budget-stop is too tight. This is the data he was missing.
- Reads ATR + price from the same source as
/mis/brief(mis-bridge β v2 web app, later Schwab API for speed).
Universe (the scale-target) β docs/MIS_UNIVERSE.md
The recovered 183-tradeable / 186-symbol original universe is canonical (docs/MIS_UNIVERSE.md). /mis/quotes + the cockpit watchlist + scoring scale to THIS list (20β183β250). The 4 index pseudo-tickers feed the macro strip only.
Build order β UPDATED per Brain (Jun 25)
Brain's single deploy: mis-bridge + GET /mis/brief + GET /mis/tile + deploy guard + the portal items (5609 / regulars-remove / bee-send key). Secrets for #1β2 already exist in the good version (MIS_RUN_TOKEN, MIS_EXEC_URL/MIS_V2_URL) β no new secrets needed. Unblocks: morning-spine fresh MIS, dynamic tile, cockpit live data.
- Cameras are OFF Brain's bundle β Sam reassigned the camera fix fully to the camera session's lane. CAM_CF_* only rides Brain's deploy if the camera session asks Brain to set one value. (See outputs/2026-06-25_ticket_cameras-brain-infra.md β re-pointed to the camera lane.)
- Schwab-API-in-Worker β separate, gated pass (Brain deploy-safety + my engine-freeze window). Secret names: SCHWAB_CLIENT_ID/SECRET/REFRESH_TOKEN.
MIS provides specs + the v2 data source; Brain builds/deploys; Bee wires the tile UI. No parallel pusher. Daily MIS push resumes through Brain's rail once mis-bridge + the data refresh are live.