Configure Pinnacle odds drop alerts that stay useful
PinnWire is the clearest way to build Pinnacle line movement alerts in code. Start with transparent REST filters for recent drops, switch to SSE for push delivery, and keep your own cooldown and dedupe policy at the notification edge.
/api/drops or /v1/drops to select mode, min_drop_pct, max_drop_pct, sport, market, period, and max_age_sec. When the query is right, use /odds-drop or /odds-drop-prematch with min_drop; add prematch recheck when a move must persist. Finish with a client-side key, cooldown, and exact-market check before sending a human notification.
Choose the right PinnWire alert surface
Query recent drops
Use /api/drops for a filtered JSON list. It is ideal for tuning thresholds, dashboards, backfills into your own store, and recovery after a client restart.
Receive qualifying drops
Use /odds-drop for live or /odds-drop-prematch for prematch. The stream pushes detected drops instead of making your process poll.
Track every reprice
Choose the optional raw WebSocket when you need upward moves, opens, closes, and every subscribed market update—not only drops.
A drop is a price decrease detected by PinnWire's engine. It is not a claim about why the price moved, and it is not a complete movement history. PinnWire keeps those meanings separate so your alert policy can be precise.
Try a focused drop query
The public demo key can test REST without signup. It is shared and capped, so use a free emailed trial key for an application or repeated tests.
curl "https://pinnwire.com/api/drops?mode=prematch&sport_id=1&min_drop_pct=2&max_drop_pct=12&markets=spread&periods=0&max_age_sec=900&limit=25&key=demo&fresh=alert-guide-1"
This asks for soccer full-match spread drops from the last 15 minutes, between 2% and 12%, with at most 25 rows. fresh is an optional cache-buster; it does not change the filter.
{
"total": 1,
"generated_at": "2026-08-26T10:05:12.412Z",
"drops": [{
"event_id": 1629725918,
"sport_id": 1,
"sport_name": "Soccer",
"home": "Bentleigh Greens",
"away": "St Albans Saints",
"market": "spread",
"period": 0,
"side": "home",
"points": -0.5,
"from": 2.37,
"to": 2.25,
"drop_pct": 5.06,
"nvp": 2.21,
"starts": "2026-08-26T11:00:00Z",
"is_live": false,
"alerted": 1787738712,
"age_s": 12
}]
}
The REST names are intentionally straightforward: from and to are decimal prices, drop_pct is the percentage fall, side identifies the outcome, and age_s tells you how old the detection is at response time. Check generated_at before presenting a response as current.
REST odds alert filters, exactly
These query parameters apply to GET /api/drops and its alias /v1/drops. They filter PinnWire's recent in-memory drop buffer, which covers roughly the latest three hours. The default response is live drops with a 5% minimum and up to 500 rows.
| Parameter | Example | What it does |
|---|---|---|
mode | live / prematch | Selects the live or prematch drop buffer. Default: live. |
sport_id | 1 | Restricts results to one sport. The current IDs are listed in the API docs. |
min_drop_pct | 2 | Minimum percentage fall. Default: 5; fractional values such as 1.5 are accepted. |
max_drop_pct | 12 | Optional upper bound. Useful for separating ordinary moves from outliers or bad-data investigations. |
max_age_sec | 900 | Returns only detections newer than this many seconds. It is an age filter, not a scheduled scan interval. |
markets | moneyline,spread | Comma-separated exact market types: moneyline, spread, total, or team_total. |
periods | 0,1 | Comma-separated numeric periods. 0 is normally full match; 1 is commonly first half or first set. Keep the period in your key. |
live | 1 | Excludes records whose scheduled start is still in the future. This is useful when you need only already-started events. |
limit | 25 | Maximum rows returned, newest first. Default: 500. |
min_drop_pct=0.25 cannot create sub-1% events that were never recorded; it only lowers the query threshold for the events already in the buffer.Three useful starting profiles
There is no universal perfect configuration. Begin with a profile that matches the job, measure alert count and useful-alert rate, then adjust one parameter at a time. These profiles use only fields PinnWire actually supports.
Fast market monitor
mode=live&min_drop_pct=5&markets=moneyline,spread,total&periods=0&max_age_sec=120&limit=50
Good for a small dashboard or an action queue where a two-minute-old drop is already stale.
Persistent move check
mode=prematch&min_drop_pct=3&max_drop_pct=15&markets=spread,total&periods=0&max_age_sec=1800
Pair the equivalent SSE stream with recheck=30 when bounce-back suppression matters.
Tune before narrowing
mode=live&min_drop_pct=1&max_age_sec=600&limit=100
Inspect volume first, then add sport, market, period, and an upper cap based on observed traffic.
These are configuration starting points, not betting instructions. A larger minimum produces fewer rows; a smaller maximum age favors recency; a sport or market filter trades coverage for a cleaner queue.
Use SSE when an alert should arrive immediately
PinnWire's SSE endpoints provide a focused push surface for detected drops. The live endpoint is /odds-drop; the prematch endpoint is /odds-drop-prematch. Both accept min_drop, measured in percentage points, with a 1% floor and a default of 5.
curl -N "https://pinnwire.com/odds-drop?key=YOUR_KEY&min_drop=3"
# Wait 30 seconds, then emit only if the fall still meets 7%
curl -N "https://pinnwire.com/odds-drop-prematch?key=YOUR_KEY&min_drop=7&recheck=30"
recheck is prematch-only. PinnWire waits the requested number of seconds, reads the current tracked price, and compares it with the original from_price. If the price bounced back below the threshold, that candidate is silently suppressed. An emitted frame includes rechecked_ms. Live ignores recheck.
data: {"type":"connected","id":"…"}
data: [{"home":"…","away":"…","sect":"Spread","period":0,
"outcome":"home","from_price":2.24,"to_price":2.10,
"drop_pct":6.25,"nvp":2.18,"is_live":false,
"rechecked_ms":30012}]
:ka
Frames containing drops are JSON arrays. The initial connected frame is an object, and :ka is a keep-alive comment every 25 seconds. Ignore comments and parse only data: payloads. SSE is available on Stream, Pro + Drops, and Scale plans; the free trial and demo are REST-only.
min_drop and prematch recheck, but it does not accept REST's sport, market, period, maximum-age, or maximum-drop query parameters. Apply those additional filters in your consumer, or use REST for the filtered query and SSE as the immediate trigger.Keep the alert identity exact
Match the event first
Use event_id from REST, or id from the underlying SSE-shaped record. Team names are display fields, not a safe primary key. Parent and child matchup records can exist, so verify the event with PinnWire details when an alert matters.
Keep market and period
moneyline, spread, total, and team_total are different market types. A full-game spread and a first-half spread are different selections even when the teams and side match.
Keep points and outcome
A spread at -0.5 is not the same as -1.0. A total at 2.5 is not 3.0. Include points and side/outcome in your normalized alert key. For special markets, include participant_name too.
Stamp receipt and source age
REST supplies generated_at; drop rows supply age_s; health supplies last_odds_update_seconds_ago. Store your own receipt timestamp as well. A notification policy should say how old is too old.
Use nvp and published limits as context
Every detected drop with at least two priced outcomes carries nvp, PinnWire's no-vig decimal reference at detection time. The reciprocal 1 / nvp is the corresponding fair implied probability. If you compare a separate offered decimal price O, the simple fair-price edge estimate is O / nvp - 1. Treat it as an estimate, not a guarantee.
function fairContext(drop, offeredDecimal) {
const nvp = Number(drop.nvp);
if (!Number.isFinite(nvp) || nvp <= 1) return { fairProbability: null, edge: null };
return {
fairProbability: 1 / nvp,
edge: offeredDecimal / nvp - 1
};
}
console.log(fairContext({ nvp: 2.21 }, 2.30));
// { fairProbability: 0.452488..., edge: 0.04072... }
The REST drop record does not include a max limit field. If limit context is part of your policy, fetch the matching current event from /kit/v1/markets, /kit/v1/details, or /kit/v1/prematch/lines. Published spread and total rows can expose max; fields appear only where the market publishes them.
nvp helps compare a price with a fair baseline, while max describes a published market limit. Neither one proves why the price moved or that an alert is actionable by itself. Read the no-vig guide for the calculation boundaries.Add a cooldown and dedupe layer in your client
One event can move more than once. A robust notification worker should allow the stream to report new qualifying drops while preventing repeated messages for the same exact event, market, period, line, and side. Choose the cooldown for your product; 30 seconds below is only an example.
const lastAlert = new Map();
const COOLDOWN_MS = 30_000;
function alertKey(d) {
return [
d.event_id ?? d.id,
d.market ?? d.market_type,
d.period ?? 0,
d.points ?? "",
d.side ?? d.outcome ?? "",
d.participant_name ?? ""
].join("|");
}
function acceptDrop(d, now = Date.now()) {
const pct = Number(d.drop_pct);
if (!Number.isFinite(pct) || pct < 3) return false;
const key = alertKey(d);
const previous = lastAlert.get(key) ?? 0;
if (now - previous < COOLDOWN_MS) return false;
lastAlert.set(key, now);
// Periodically prune so a long-running process stays bounded.
if (lastAlert.size > 50_000) {
for (const [k, t] of lastAlert) if (now - t > COOLDOWN_MS * 4) lastAlert.delete(k);
}
return true;
}
// REST rows use event_id/market/side; SSE rows use id/market_type/outcome.
// Normalize both before calling acceptDrop().
For REST, also reject rows where age_s exceeds your notification budget even if the request used max_age_sec. For SSE, keep the stream's connected id in logs, reconnect with exponential backoff, and never assume a reconnect replays events you missed. Use REST to recover the recent buffer after a disconnect.
A production configuration workflow
- Discover. Query a broad REST profile with
min_drop_pct=1and a modestmax_age_secto measure volume. - Narrow. Add
sport_id, exactmarkets,periods, and an optionalmax_drop_pctone at a time. Log counts per filter. - Set a freshness budget. Use
max_age_secfor REST and enforceage_sagain before a notification or model action. - Choose delivery. Keep REST for query and recovery. Use live SSE for instant live drops, prematch SSE plus
recheckfor persistence, or the raw WebSocket for complete market updates. - Normalize. Match event, market, period, points, and side exactly. Preserve decimal
from/toprices and the source timestamps. - Filter locally. Apply SSE-only client filters for sport, market, period, age, and maximum drop; then run cooldown/dedupe.
- Verify before acting. Fetch the current matching market when you need full outcomes, published
max, or a current line after a delayed notification. - Persist your own history. PinnWire's recent buffer is not a permanent archive. Store accepted alerts or raw updates if you need long windows, charts, or closing-line analysis.
Know the boundaries before you ship
- The drop engine detects decreases of at least 1% in decimal price and skips the
dzvolatility channel; it does not report every upward move. - The REST buffer is roughly three hours and held in memory. It is not historical opening-line or closing-line storage.
min_drop_pctis REST syntax;min_dropis SSE syntax. Both mean percentage points, not decimal fractions.recheckapplies only to prematch SSE. It is not a REST parameter and it does not make live alerts stable.- SSE does not provide REST's sport, market, period, maximum-age, or maximum-drop filters. Filter and dedupe in your consumer.
nvpcan be null for a one-outcome market. A drop alone is not an EV decision, a bet instruction, or proof of cause.- Respect API rate limits and
Retry-After. SSE is not counted against REST request limits, but each key has a combined cap of five SSE connections.
Odds alert configuration questions
What is the best way to configure Pinnacle odds drop alerts?
Start with PinnWire's REST drop endpoint to inspect volume, then choose a mode, minimum and optional maximum drop, sport, market, period, and maximum age. Move to SSE when you want qualifying drops pushed to your process, and add client-side cooldown and dedupe for notifications.
What is the difference between min_drop_pct and SSE min_drop?
PinnWire REST uses min_drop_pct in percentage points, such as 5 for a 5% minimum. The SSE endpoints use min_drop, also as a percentage, with a 1% floor. REST supports the broader query filters; SSE adds push delivery and prematch recheck.
How does PinnWire's prematch recheck filter work?
On /odds-drop-prematch, recheck=N waits N seconds, reads the current price again, and emits the alert only if the drop still meets min_drop against the original from_price. A bounce-back is suppressed. recheck is ignored on the live endpoint.
Does PinnWire include no-vig and limit context in a drop alert?
PinnWire drop records include nvp when at least two outcomes are priced, so 1 / nvp is the fair implied probability. The drop object itself does not include a limit field; fetch the matching current event from the PinnWire market endpoints, where published spread and total lines can include max.
How do I stop duplicate Pinnacle line movement alerts?
Use a stable key made from event ID, market, period, points, side, and optionally participant name. Store the most recent drop timestamp and suppress the same key during a strategy-specific cooldown. Keep a separate connection ID for each SSE stream and use REST to recover after disconnects.