

## Exit velo allowed, per pitcher [#exit-velo-allowed-per-pitcher]

```python title="pitcher_exit_velo.py"
import os, requests
from collections import defaultdict
KEY = os.environ["STATSHAWK_KEY"]
BASE = "https://api.statshawk.ai/v1"

games = requests.get(
    f"{BASE}/competitions/mlb/editions/2026/games",
    headers={"X-API-Key": KEY},
    params={"date": "2026-04-15"},
    timeout=10,
).json()["data"]["items"]

pbp = requests.get(
    f"{BASE}/contests/{games[0]['id']}/play-by-play",
    headers={"X-API-Key": KEY},
    timeout=15,
).json()["data"]

velo = defaultdict(list)
for pa in pbp["plate_appearances"]:
    for pitch in pa["pitches"]:
        if pitch["is_in_play"] and "launch_speed" in pitch:
            velo[pa["pitcher_id"]].append(pitch["launch_speed"])

for pitcher_id, speeds in sorted(velo.items(), key=lambda kv: -max(kv[1])):
    print(f"{pitcher_id}: avg EV {sum(speeds)/len(speeds):.1f} mph, max {max(speeds):.1f} ({len(speeds)} BIP)")
```

<Callout type="info">
  Play-by-play weighs 5× per call and returns every plate appearance with its ordered
  Statcast pitches: `launch_speed`, `launch_angle`, `spin_rate`, `pitch_type`, and location
  (`plate_x`/`plate_z`). Optional fields are omitted (not `null`) when the pitch has no
  batted ball or the `detail` tier excludes them. Narrow to one pitcher with
  `?pitcher_id=per_…`, and resolve pitcher ids to names via `/v1/persons/{person_id}`.
</Callout>

<PlaygroundLink href="/docs/api">
  Open the contest play-by-play endpoint in the playground →
</PlaygroundLink>
