Build a PrizePicks Discord bot.

A line alert bot needs one loop: read new board events, keep the ones your channel cares about, post them, and remember where you stopped. A Discord webhook is enough, with no bot account or gateway connection to run.

Pixel-art terminal connected to a compact data panel.

What you need

  • A free PrizePicks API key in PRIZEPICKS_API_KEY.
  • A Discord webhook URL in DISCORD_WEBHOOK_URL. Create it under Channel settings, Integrations, Webhooks.
  • Python 3.9 or newer. The worker uses only the standard library.

The worker

Run this from cron or a loop. It keeps its place in a small file, so a restart resumes where it stopped and sends nothing twice. It advances the cursor only after Discord accepts the message.

import json, os, pathlib, time
from urllib.parse import urlencode
from urllib.request import Request, urlopen

KEY = os.environ["PRIZEPICKS_API_KEY"]
HOOK = os.environ["DISCORD_WEBHOOK_URL"]
LEAGUE, THRESHOLD = "NFL", 2.0
STATE = pathlib.Path("cursor.txt")

def changes(cursor):
    params = {"league": LEAGUE, "limit": 200}
    if cursor:
        params["after"] = cursor
    req = Request("https://prizepicksapi.com/api/v1/changes?" + urlencode(params),
                  headers={"Authorization": "Bearer " + KEY})
    with urlopen(req, timeout=20) as res:
        return json.load(res)

def post(text):
    body = json.dumps({"content": text[:2000]}).encode()
    req = Request(HOOK, data=body, headers={"Content-Type": "application/json", "User-Agent": "prop-alerts"})
    urlopen(req, timeout=20).close()

def is_move(e):
    prev = e.get("previous_line")
    return e["type"] == "offer.changed" and prev is not None and abs(e["line"] - prev) >= THRESHOLD

def run_once():
    cursor = STATE.read_text().strip() if STATE.exists() else None
    page = changes(cursor)
    moves = [e for e in page["data"] if is_move(e)]
    if moves:
        post("
".join(f"{e['player_name']} {e['stat']}: {e['previous_line']} to {e['line']}" for e in moves[:20]))
    if page["data"]:
        STATE.write_text(page["data"][-1]["cursor"])

while True:
    run_once()
    time.sleep(60)

A first run starts from the oldest available event. To start from now, store the newest cursor first and post nothing on that run. At one request a minute the worker uses about 1,440 requests a day, so the Free plan suits a test and the Developer plan suits a running bot. Lower the rate if you only need alerts on game days.

Keep the channel readable

FilterHowWhy
Leagueleague=NFLEach league has its own volume. Start with one.
Move sizeabs(line - previous_line)Small moves are most of the feed.
Markete["stat"] in a setMembers usually follow a handful of stats.
BatchOne message per runDiscord limits a message to 2,000 characters and rate limits each webhook.

Handle errors without losing events

Do not advance the cursor when the post fails. On HTTP 429, Discord returns retry_after in seconds, so wait that long and retry. On a 429 from this API, read the Retry-After header. Because the cursor only moves after a successful post, a crash replays the last batch instead of dropping it.

Add more than the move

Each event carries a projection_id. GET /api/v1/projections/{id}/history returns the whole path of that line, so a message can say how many times it has moved and where it opened. For NBA players, GET /api/v1/hit-rates adds how often the over has hit recently.

curl "https://prizepicksapi.com/api/v1/projections/$PROJECTION_ID/history" \
  -H "X-User-Request: Read PrizePicks data for an integration check" -H "Authorization: Bearer $PRIZEPICKS_API_KEY"

Try it

Endpoint docs:GET /api/v1/changesWebhooksPlans and limits

Frequently asked questions

Do I need a Discord bot account to post PrizePicks alerts?

No. A channel webhook URL can post messages by itself. You only need a full bot account for slash commands or for replying to members.

How many alerts will a PrizePicks Discord bot send?

It depends on the leagues and your threshold. The table on this page shows the real move counts for the last 24 hours, so you can size a filter before you turn it on.

Should a Discord bot poll or use webhooks?

Polling GET /api/v1/changes works on every plan (Free reads the last 7 days) and suits a small worker. Signed webhooks arrive on the Production plan and suit a server that is already reachable from the internet.

Continue building

References