POST the shortlist your models already chose. Get back, per symbol, a deterministic verdict on whether now is the moment — read from where the sector sits in its cycle and where the name sits inside the move. Not a signal source. Not a backtest. A market-state timing overlay on names you already like.
full size — measured, volume-confirmed, room before the chase ceiling.
leader, but at the ceiling after +19%/3d — a gap-up open is buying the top.
Same sector. Same day. Opposite calls. KLAC's fundamentals score 88 — strong, and inert. The company isn't the problem; the setup is.
Your factor models are structurally backward-looking. Their weakest read is the one that decays within the session — is now the moment? stock87 is the overlay for exactly that.
It's the read your models are worst at by construction: where the sector sits in its current cycle, and where your name sits within that move, right now. A short-term swing judgment — holding measured in days, decaying within the session. Explicitly not a long-term view.
A personal, rotatable key — sent as Authorization: Bearer or X-API-Key. No SDK, no import wizard. POST application/json, text/csv, or a bare newline-delimited list. The key also works in the path for one-line curl / browser checks.
$ curl -X POST \ https://stock87.io/v1/judge \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d @shortlist.json [ { "symbol": "AMAT", "input_rank": 6 }, { "symbol": "KLAC", "input_rank": 2 }, { "symbol": "MU", "context": "strategy:reversal" } ]
{
"as_of": "2026-06-15T20:00:00Z",
"methodology_version": "0.4.0",
"set": {
"frame": "relative_to_submitted_set",
"ranking": [
{ "symbol":"AMAT", "input_rank":6, "timing_rank":1,
"score":87, "timing_verdict":"favorable",
"scenario_profile":"regime_dependent" },
{ "symbol":"KLAC", "input_rank":2, "timing_rank":4,
"score":58, "timing_verdict":"wait",
"risk_flag":"give_back_risk", "scenario_profile":"pure_beta" }
]
},
"results": [ /* full per-symbol verdicts → */ ]
}
A strategy can route on the verdict object directly — skip, down-size, or fire — without parsing a sentence. The narrative is optional and lives behind a flag.
{
"symbol": "AMAT",
"fundamentals": {
"fundamentals_score": 80,
"fundamentals": "strong",
"event_risk": null
},
"market_state": {
"sector": "XLK",
"sector_regime": "hot_extended",
"symbol_position": "constructive_continuation",
"measures": {
"dist_piv":1.7, "ret_3d":0.136,
"vol_tr":1.24, "clpos":0.96
}
},
"timing_verdict": "favorable",
"scenarios": {
"continuation": { "score":87, "verdict":"favorable" },
"cooler": { "score":72, "verdict":"neutral" },
"bear": { "score":44, "verdict":"wait" }
},
"scenario_profile": "regime_dependent",
"risk_flag": null,
"sizing_note": "full size — measured, room before chase",
"rank_divergence": { "input_rank":6, "timing_rank":1, "delta":5 }
}
Two symbols in the same hot sector, on the same day, can earn opposite calls — because symbol_position is assigned by a precedence-ordered rule set. The order is the trading logic. First match wins.
dist_piv ≥ ceiling) or blown off (ret_3d ≥ blow_off) — regardless of how perfect the close is. A flawless close near the high does not rescue a name that already made its move. → KLAC.vol_tr > confirm) and a strong close (clpos > strong). Slightly below pivot is tolerated only if volume confirms. → AMAT.dist_piv < 0) with no volume. Hasn't confirmed anything yet — could be the next leg, could be dead money.Two precedence rules carry the whole edge: extension vetoes session quality (rule 1 beats a perfect close), and volume-confirmation is the splitter (rule 2 vs. rule 3). timing_verdict is then the deterministic product of sector_regime × symbol_position — and those two lines are exactly where our output diverges from your raw factor rank.
A name doesn't have a single timing call — it has one conditional on which way the tape goes next. We score every symbol under three fixed paths — continuation, cooler, bear — without claiming any will occur. The shape of the three is the product.
Strong if the rally holds, softer if it doesn't — good, but it needs the tape to cooperate.
Tops the continuation column, bottoms the rest — a wide spread that is the warning: a pure directional bet on the rally.
The four profiles — all_weather · pure_beta · regime_dependent · defensive — fall straight out of the three scores. The headline timing_verdict tracks the path that matches today's regime; the scenario columns are the conditional breakdown behind it, sortable at set level so a desk with no regime view can rank by the all_weather names instead. Posture under assumptions — never a forecast.
Hand the API a set and it returns the verdicts plus a ranking — best of these to trade right now, relative to the set you submitted. The gap between your factor rank and current-market fitness is the reason to call us.
Same inputs plus the same methodology_version return an identical deterministic object, every time. Versioned so a desk can reconstruct exactly why a name scored 87 versus 84.
Every consumable field — metrics, flags, sector_regime, symbol_position, timing_verdict, score — comes from the deterministic rules engine. Nothing consumable is a judgment call.
The model receives the finished object and writes only the rationale string, behind ?include=narrative. It never produces a number and never feeds back. Switch it off — not one consumable field changes.
We judge the when of names you chose. We never supply the trades. Want a strategy? Wrong product.
We owe a trustworthy current judgment — not reconstructable past. Point-in-time panels and bulk export are out of scope by design.
Reported and inert. A 5-year ROIC is the wrong input for a multi-day swing. The one carve-out is event_risk — a timing risk in fundamentals' clothing.
Every consumable field is deterministic and pinned to a methodology_version. If score and timing_verdict could ever disagree, the design is wrong.
Private beta, opening narrow and deep — semiconductors and semi-equipment first. The close-based baseline ships first; the live intraday overlay is the fast-follow.