Building a prop-analysis bot
paidCall /analysis/player-prop with a person id, read the card, act on it.
The analysis endpoint returns a person-keyed prop card: the resolved person, the requested measure, the per-game values from their in-scope appearances, windowed averages, and hit rates against your line. You supply the person id, stat, line, and optional competition/season scope; the API does the math.
If you only have a name, resolve it first with /v1/persons?q=Jayson%20Tatum and pass the
returned per_… id into the analysis call.
The call
curl -H "X-API-Key: $STATSHAWK_KEY" \
"https://api.statshawk.ai/v1/analysis/player-prop\
?person_id=per_3f9kq0v2&stat=pts&line=27.5&competition=nba"Scope and stat selection:
stat— the measure to analyse. The canonical form is phase-qualified (pitching.so,passing.yards) to disambiguate a measure that exists in more than one phase; a bare key (pts) works when it's unambiguous, and friendly aliases are accepted. An unknown stat returns400 UNKNOWN_STATlisting the valid keys.competition— slug (nba) or mintedcomp_…id. When you passcompetitionand omitseason/edition/from/to, the window defaults to that competition's latest edition with at least one final contest — so in-season you get current-season numbers without hardcoding a year.season,edition,from,to— supply any of these to opt out of the default edition window.stage— by default the card only includes regular-season (plus unknown-stage) games, the same window season stats use. Passstage=allto include every stage, playoffs and preseason included.line— omit it if you only need the game log and averages;hit_rateandhit_ratesare only built when a line is supplied.
The response
The data object is a PlayerPropCard:
{
"person": {
"id": "per_3f9kq0v2",
"bio": {
"full_name": "Jayson Tatum",
"display_name": "Jayson Tatum",
"position": "SF"
}
},
"stat": "pts",
"game_log": [31, 24, 35, 29],
"games": 72,
"average": 28.1,
"averages": {
"season": 28.1,
"last_5": 31.2,
"last_10": 29.4,
"home": 29.0,
"away": 27.2
},
"hit_rate": {
"line": 27.5,
"hits": 44,
"games": 72,
"over_rate": 0.611
},
"hit_rates": {
"season": { "line": 27.5, "hits": 44, "games": 72, "over_rate": 0.611 },
"last_5": { "line": 27.5, "hits": 4, "games": 5, "over_rate": 0.8 },
"last_10": { "line": 27.5, "hits": 7, "games": 10, "over_rate": 0.7 }
},
"context": null
}Key fields:
| Field | What it tells you |
|---|---|
hit_rate.over_rate | Season hit rate over the supplied line (hits / games; a hit is a game that strictly cleared the line) |
hit_rates.last_5 / .last_10 | The same hit rate over the trailing 5 / 10 games — where the recency signal lives |
average | Mean value across games that carried the stat (same as averages.season) |
averages | Windowed means: season, last_5, last_10, and the home/away split |
games | Number of games included in the denominator |
game_log | The raw per-game values, chronological — last_5/last_10 are its tail |
person.bio | The canonical person profile resolved from the supplied person_id |
context | Best-effort sport-specific extras (e.g. pitcher rates) when cheaply available; often null |
hit_rate and hit_rates are null when no line was passed.
A 20-line Python script
import os, requests
KEY = os.environ["STATSHAWK_KEY"]
BASE = "https://api.statshawk.ai/v1"
def analyze(competition: str, player: str, stat: str, line: float) -> dict:
person = requests.get(
f"{BASE}/persons",
headers={"X-API-Key": KEY},
params={"q": player, "limit": 1},
timeout=10,
).json()["data"]["items"][0]
r = requests.get(
f"{BASE}/analysis/player-prop",
headers={"X-API-Key": KEY},
params={
"person_id": person["id"],
"stat": stat,
"line": line,
"competition": competition,
},
timeout=10,
)
r.raise_for_status()
return r.json()["data"]
card = analyze("nba", "Jayson Tatum", "pts", 27.5)
season = card["hit_rate"]
recent = card["hit_rates"]["last_10"]
verdict = "LEAN OVER" if recent["over_rate"] >= 0.6 and card["averages"]["last_5"] > season["line"] else "PASS"
print(f"{card['person']['bio']['full_name']} | line {season['line']} pts")
print(f"Season: {season['over_rate']:.0%} | L10: {recent['over_rate']:.0%} | L5 avg: {card['averages']['last_5']:.1f}")
print(f"Verdict: {verdict}")Scanning a whole slate
If you're evaluating one line, player-prop is the tool. If you're scanning tonight's
slate for the best candidates, don't loop it over every player — call
/v1/analysis/stat-board once instead:
curl -H "X-API-Key: $STATSHAWK_KEY" \
"https://api.statshawk.ai/v1/analysis/stat-board\
?competition=mlb&date=2026-07-07&stat=batting.h"The board ranks the day's slate by over-rate over a recent-game window (window, default
20 games) and returns two lists: recommended (entries with at least min_games games in
the window, default 10) and full_slate (everyone, up to limit). date is interpreted
in the competition's local scoreboard timezone — US Eastern for MLB. Same 10× weight and
paid tier as player-prop, but one call replaces dozens.