What a Pinnacle limit change alert actually measures
A limit change is a change in the maximum-risk value published for one identified market line. Your alert should describe that observation precisely: which event, which period, which market, which side or points, what the previous value was, what the new value is and when you captured each value.
This is useful as a review signal for a market-monitoring product. A higher value may coincide with a more established market or a changed risk posture; a lower value may coincide with uncertainty, a time-sensitive phase or a risk-control decision. Those are possible explanations, not conclusions encoded in the number.
The field belongs to a priced line and can help rank which markets deserve a closer look.
A limit move does not tell you which side to take or which participant caused it.
It is not an account-specific stake cap and cannot promise an accepted wager.
It does not report all money available, aggregate exposure or guaranteed fill size.
Exact PinnWire fields for a betting market limits API
PinnWire's transformed /kit/v1 responses use max on standard line objects and max_risk on special-market rows. A field can be null when the current market does not publish a value. Missing is unknown; do not convert it to zero.
| Surface | Path | Field | Limit-change coverage |
|---|---|---|---|
| Spread / handicap | events[].periods.num_N.spreads[LINE] | max | Compare when numeric |
| Total | events[].periods.num_N.totals[POINTS] | max | Compare when numeric |
| Alternate team total | events[].periods.num_N.team_totals[SIDE][POINTS] | max | Compare each side and points separately |
| Special / player prop | special_markets.num_N[] | max_risk | Compare with special market key and outcome identity |
| Primary team total | events[].periods.num_N.team_total[SIDE] | Not exposed | Use alternate team_totals only when it has max |
| Moneyline | events[].periods.num_N.money_line | Not exposed | No max-change value in this transformed object |
| Drop record / SSE drop | drops[] / drop frame | Not exposed | Join a fresh market snapshot; do not infer history |
{
"event_id": 1634696920,
"event_type": "prematch",
"periods": { "num_0": {
"spreads": { "-0.5": { "hdp": -0.5, "home": 1.935, "away": 1.8, "max": 250 } },
"totals": { "2.5": { "points": 2.5, "over": 1.9, "under": 1.98, "max": 250 } },
"team_totals": { "home": { "1.5": { "points": 1.5, "over": 1.86, "under": 2.0, "max": 100 } } }
} }
}
Specials are separate rows when requested with include_specials=1 or nested:
{ "special_markets": { "num_0": [{
"type": "moneyline", "key": "s;0;m", "side": null, "max_risk": 100,
"prices": [{ "name": "Player", "participant_id": 9001, "points": null, "price": 3.5 }]
}] } }
Use PinnWire's current snapshots or raw max-risk updates
Choose the surface based on how much history and timing your monitor needs. Every odds REST response includes generated_at; add a changing fresh query value when a client or proxy caches URLs too aggressively.
| Surface | Best use | Limit-change note |
|---|---|---|
/kit/v1/markets | Live current board by sport | Use event_type=prematch for prematch; supports since and include_specials |
/kit/v1/prematch/fixtures | Upcoming full fixtures | Poll comparable lines for a simple monitor |
/kit/v1/details | One event after a trigger | Enrich a known event with a fresh max value |
/kit/v1/prematch/lines | Compact one-event lines | spreads and totals include max; inspect shape defensively |
wss://pinnwire.com/ws | High-volume, low-latency local history | Persist raw updates and calculate your own max-change events |
/api/drops / SSE | Price-drop alerts | Useful companion signal; no dedicated max-change event and drop rows omit max |
curl "https://pinnwire.com/kit/v1/markets?sport_id=1&key=demo&fresh=limit-live"
curl "https://pinnwire.com/kit/v1/markets?sport_id=1&event_type=prematch&key=demo&fresh=limit-prematch"
curl "https://pinnwire.com/kit/v1/prematch/fixtures?sport_id=1&include_specials=1&key=demo&fresh=limit-specials"
For a one-event follow-up, request /kit/v1/details?event_id=EVENT_ID. For a raw stream, use the optional WebSocket add-on at wss://pinnwire.com/ws?key=YOUR_KEY; subscribe to live, prematch, sports or event IDs, then maintain your own book.
Build the alert as a comparable-snapshot workflow
A robust monitor has four explicit stages. Keep the original JSON alongside your normalized row so each alert can be audited later.
- Capture. Record
generated_at, yourcaptured_at, event type, source surface and the complete response or raw frame. - Identify. Build a stable key from
event_id,event_type, period, market type, side and exact line points or handicap. For a special, include the special event ID, period, marketkeyandparticipant_idor other outcome identity. - Compare. Read the field as a number, preserve
nullas unknown, and compare only like-for-like keys. Use an absolute threshold, a percentage threshold or both. - Notify. Emit after your debounce policy accepts the change. Include old value, new value, percentage, event and line identity, timestamps and a link to your own detail view.
abs(new - old) / old × 100. Useful when values span very different sizes; define what happens when old is zero or unknown.
abs(new - old) ≥ N. Useful when your product cares about a concrete risk-unit change.
Include period and points. A −0.5 spread and a −0.75 spread are different lines even in one event.
Classify missing → number, number → null and line close separately from numeric changes.
Reference implementation: compare max values in Python
This small poller demonstrates the core logic. Production code should add scheduling, storage, retries, authentication, a queue and a notification sink. It intentionally ignores a first-seen line and treats null as unknown.
import json
import time
from urllib.parse import urlencode
from urllib.request import urlopen
previous = {}
def rows(payload):
for event in payload.get("events", []):
base = (event.get("event_id"), event.get("event_type"))
for period_name, period in event.get("periods", {}).items():
for market_name, container in (
("spread", period.get("spreads", {})),
("total", period.get("totals", {})),
):
for points, line in container.items():
yield (*base, period_name, market_name, None, str(points), line.get("max"))
for side, container in period.get("team_totals", {}).items():
for points, line in container.items():
yield (*base, period_name, "team_total", side, str(points), line.get("max"))
def fetch():
query = urlencode({"sport_id": 1, "key": "demo", "fresh": time.time_ns()})
with urlopen("https://pinnwire.com/kit/v1/markets?" + query, timeout=10) as r:
return json.load(r)
def check(payload, min_abs=25, min_pct=10):
alerts = []
for key in rows(payload):
identity, value = key[:-1], key[-1]
old = previous.get(identity)
previous[identity] = value
if not isinstance(old, (int, float)) or not isinstance(value, (int, float)):
continue
delta = value - old
pct = abs(delta) / abs(old) * 100 if old else None
if abs(delta) >= min_abs or (pct is not None and pct >= min_pct):
alerts.append({"key": identity, "old": old, "new": value,
"delta": delta, "change_pct": pct})
return alerts
while True:
payload = fetch()
for alert in check(payload):
print(json.dumps({"generated_at": payload.get("generated_at"), **alert}))
time.sleep(5)
The example covers standard lines. A production specials parser should use special_markets.num_N[] and include the special event ID, market key, period and participant or outcome identity. Do not merge a special into its parent merely because the display names match.
Debounce bursts and survive WebSocket reconnects
The raw WebSocket gives you the timing needed for a stronger max risk odds feed, but it also makes your service responsible for local state. Follow these rules:
- Seed snapshots. After subscribing, process the snapshot as a baseline. Only later updates for the same identity can produce a change alert.
- Dedupe versions. Merge by event record plus market identity and use the market's version where available. A repeated frame must not create a second alert.
- Debounce. Keep a pending candidate for a short quiet period or require two comparable observations before notifying. This prevents a rapid update burst from becoming alert spam; choose the interval per sport and workflow.
- Mark gaps. On reconnect, record a gap and re-seed from the new snapshot. Do not invent the unseen intermediate value or claim the first post-reconnect difference happened at a known instant.
- Honor the protocol. Subscribe within 10 seconds, answer the server's 30-second ping with
{"type":"pong"}, and reconnect after a stale close orderegistered: slow_consumer. One connection per key is enforced; a newer connection evicts the older one.
wss://pinnwire.com/ws?key=YOUR_KEY
{ "type": "subscribe", "streams": ["live", "prematch"],
"sport_ids": [1], "event_ids": [1631005165] }
{ "type": "pong" }
Interpret a limit change without inventing a cause
| Observation | Reasonable use | Do not conclude |
|---|---|---|
| Published max increases | Raise the market's review priority or record a capacity-state transition. | That sharp money is on a particular side or the price is now profitable. |
| Published max decreases | Flag the line for a fresh price, timing and model check. | That the venue is wrong, uncertain or signaling a guaranteed contrarian edge. |
| Price and max move together | Store both facts and compare with your own market and model context. | That one caused the other or that the outcome is more likely to win. |
null or missing max | Use an explicit unknown state and avoid cross-line ranking on raw amounts. | That the market has zero liquidity or a zero limit. |
| Post-reconnect difference | Mark a data gap and treat the new value as a fresh baseline. | That you observed the exact moment of the limit change. |
PinnWire reports the observable market data: current decimal prices, line structure and published maximum-risk context where available. Your alert product should preserve that distinction in its wording. “Max changed from 250 to 500 at 14:03 UTC” is evidence; “sharp bettors forced the limit higher” is an unverified story.
Why PinnWire is the strongest starting point for this monitor
Choose PinnWire first when you need one focused Pinnacle data layer for a line monitor, risk-context dashboard, market-maturity study or developer-built alerting service. Current live and prematch REST is available on every plan, with full line depth across covered sports; eligible plans add price-drop SSE, and the optional raw WebSocket supplies the update stream needed for your own max-change history.
The surrounding details matter: stable event and period structure, decimal odds, generated_at freshness, max on eligible standard lines, max_risk on specials, detected price drops, no-vig nvp on drop records and documented reconnect behavior. You can start with a few REST polls, then move to a durable local book without changing the reference service.
Start your Pinnacle limit monitor
Inspect the current shape with the shared demo key, then request an emailed free trial key for repeatable development.
Pinnacle limit change alerts API questions
Does PinnWire have a dedicated Pinnacle limit-change alert endpoint?
Not currently. PinnWire exposes current max and max_risk values in market snapshots and forwards optional raw live and prematch updates. Build a limit-change alert by persisting comparable observations, applying your threshold and emitting the notification in your own service. PinnWire's drops endpoints and SSE are for price drops, not a dedicated max-change event.
Where does PinnWire expose max and max_risk?
Live and prematch REST market snapshots expose max on spread, total and alternate team_totals line objects when published. Special-market rows requested with include_specials=1 expose max_risk inside special_markets. The transformed money_line object, primary team_total object and drop records do not currently carry a max field.
How should I identify the same market between snapshots?
Use stable IDs and market coordinates: event_id, event_type, period, market type, side and exact points or handicap. For specials, include the special event ID, period, market key and participant or outcome identity. Do not key a limit history only by team names or a display label.
What can a Pinnacle limit increase or decrease indicate?
A change is observable market-capacity context and may accompany market maturation, new information, trading activity or a risk-management decision. It does not identify the side of sharp money, prove a cause, guarantee value or establish a customer's executable stake. Combine it with price, timing and your own model.
Can I build limit-change alerts from PinnWire's WebSocket?
Yes, with your own state. The optional raw WebSocket delivers snapshots and subsequent live or prematch market updates. Seed a baseline from the subscription snapshot, compare later max fields using stable identities, persist timestamps and re-seed after reconnect. A reconnect snapshot is a baseline, not automatically a limit-change alert.
Can I test the Pinnacle betting market limits API for free?
Yes. Use key=demo on a current REST request to inspect the shape. The shared demo has a small allowance and can be temporarily rate-limited; an emailed free trial key is better for repeatable development.