pinnacle odds api / curl / jq / first request
Pinnacle odds API with curl and jq
GET/kit/v1/markets?sport_id=1
{ events, last }200 OK
The first request to the Pinnacle odds API needs nothing but a terminal. One curl call with the key in a header returns the whole soccer board as JSON, and jq turns it into a line per match. The script runs as it is once the two variables at the top are set.
Create a free keyRead the envelope before the prices
The response is an object, not a list: an events array and a last cursor. The first jq filter prints both, which is the quickest check that the key works and the board is live. Each event keeps its markets under periods; num_0 is the full match, and its money_line holds home, draw and away as decimal odds.
Send last back as since and the next call returns only the events that changed. The free key allows 20 calls a minute and 100 a day, so a loop that runs every 15 minutes fits inside it all day; a 429 carries Retry-After, and the right reaction is to wait exactly that long.
# One request, then jq. The key
# and base URL come from your
# account and the docs.
export ODDS_API_KEY="your key"
export ODDS_BASE_URL="https://..."
URL="$ODDS_BASE_URL/kit/v1/markets"
H="x-portal-apikey: $ODDS_API_KEY"
# Soccer board: cursor and count.
curl -s -H "$H" "$URL?sport_id=1" \
| jq '{last, n: (.events | length)}'
# One line per event: full-match
# money line (period 0).
curl -s -H "$H" "$URL?sport_id=1" \
| jq -r '.events[]
| [.home, .away,
.periods.num_0.money_line.home,
.periods.num_0.money_line.draw,
.periods.num_0.money_line.away]
| @tsv'
# Next call: only what changed.
LAST=$(curl -s -H "$H" \
"$URL?sport_id=1" | jq .last)
curl -s -H "$H" \
"$URL?sport_id=1&since=$LAST" \
| jq '.events | length'
Select the code to copy it.
GET/api/drops?mode=live&min_drop_pct=5
{ total, drops }200 OK
Recent drops, on request
The drops endpoint returns the moves the feed detected in the last few minutes as plain JSON, so a shell script can list what fell without holding a stream open. mode picks live or prematch, min_drop_pct the size of move, and max_age_sec the window. Each drop names the match, the market and the side, carries the price before and after, the percentage and the fair price in nvp.
This is the same detection that feeds the SSE stream, read on demand rather than pushed. For a process that should react the moment a price falls, the Python page holds the stream open instead.
# Drops of 5% or more in the last
# five minutes, live markets.
URL="$ODDS_BASE_URL/api/drops"
H="x-portal-apikey: $ODDS_API_KEY"
curl -s -H "$H" \
"$URL?mode=live&min_drop_pct=5" \
| jq -r '.drops[]
| "\(.sport_name) \(.home) v \(.away)
\(.market) \(.side): \(.from) -> \(.to)
(\(.drop_pct)%) fair \(.nvp // "n/a")"'
Select the code to copy it.
GET/kit/v1/markets?sport_id=1
Retry-After: 2429
What the other statuses mean
curl prints the status with -w '%{http_code}'. A 401 means the header is missing or the key is wrong; a 403 means the key is fine but the plan does not include what you asked for; a 429 means wait the number of seconds in Retry-After. Nothing else is worth retrying more than a couple of times from a shell.
Once the calls work by hand, the same header and cursor go into whichever language the project uses; the Node.js page is the loop version of this script. The error runbook on pnclENGINE covers each status in depth.
Every plan, including the free key.
NEXTbash curl.sh
Two variables, one script
The free key runs every request on this page. When a loop needs more than 100 calls a day, the pricing page shows the plan that lifts the limit to 10 a second.