The exact same seats can sell for very different prices depending on where you buy them. At Harry Styles' Madison Square Garden show on October 2, 2026, Section 223, Row 17, seats 15 to 22 were listed on three marketplaces at once. Per ticket, all-in:
- $447 on Vivid Seats
- $454 on SeatGeek
- $582 on TickPick
Buy all 8 on TickPick and you pay $1,080 more for the same seats.
Prices also move all the time, especially as the show gets closer. To know what a seat really costs and where it's cheapest, you have to watch every marketplace. We'll walk through that same event step by step: find it on each marketplace, capture the listings, and turn the captures into price history.
Tickets.dev handles the capture part for Ticketmaster, StubHub, Vivid Seats, SeatGeek, TickPick and the other supported marketplaces, and every capture comes back in the same schema.
How price tracking works
- Event URLsOne event, mapped on every marketplace
- Live dataEvery listing with its all-in price
- SnapshotsOne timestamped capture per run
- Price historySnapshots compared over time
None of this depends on Tickets.dev. The same steps work with your own scrapers or another data provider. The hard part is the capture: marketplaces block scrapers with anti-bot systems, every site lays out its listings differently, and those pages change without warning. Tickets.dev runs real browsers on residential IPs and keeps up with those changes, so you get the data and skip the upkeep.
1. Match events across marketplaces
First you need the event's URL on every marketplace. Each one gives the October 2 show its own ID, and two of them even name it differently:
- 160334460 on StubHub
- 6564648 on Vivid Seats
- 18027119 on SeatGeek, which calls it "Harry Styles with Jamie XX"
- 7695881 on TickPick, which calls it "Harry Styles & Jamie xx"
Matching these by hand is fine for one show, but it becomes slow and tedious once you track more than a few.
Tickets.dev keeps one identity per event across marketplaces. Search the free events API by name, or give it one marketplace URL, and you get the event back with its URL on each marketplace that lists it. The catalog has no rate limit. Try it:
Searching the catalog…
2. Capture live inventory
Each link above is a page you can capture. Send it to the capture endpoint for that marketplace:
curl -G https://api.tickets.dev/v1/capture \
--data-urlencode "url=https://www.stubhub.com/harry-styles-new-york-tickets-10-2-2026/event/160334460/" \
-H "x-api-key: $TICKETS_DEV_KEY"Tickets.dev loads the page live in a real browser and returns every listing: section, row, quantity, ticket price, fee and total. For the October 2 show, all four came back in under 9 seconds:
- 203 listings from StubHub
- 392 listings from SeatGeek
- 392 listings from TickPick
- 288 listings from Vivid Seats
capturedAt. A URL with no event on it returns 404 event_not_found rather than an empty capture, and isn't billed.No marketplace is cheapest across the board. TickPick had the cheapest ticket for the whole show and the priciest listing for those 8 seats:
- $160 on TickPick
- $166 on Vivid Seats
- $173 on SeatGeek
- $179.15 on StubHub
If you only need one ticket, look at singles: they're harder to sell, so sellers often price them lower. In the 31 rows that had both, the cheapest single beat the cheapest pair or group 23 times, and the median saving was 27%. In Section 118, Row 7, one seat on StubHub was $492.80, while pairs started at $798.
3. Normalize the listings
Before you can compare listings across marketplaces, they have to line up. Section names don't: the left side of the floor is "Left General Admission" on StubHub and TickPick, "left ga" on SeatGeek, and both "Left GA" and "LFTGA2" on Vivid Seats. Match on the section name as text and you'll miss it, so map names like these to one section before you compare.
The same tickets can also turn up more than once. The 8 seats from the top of this guide are one set of tickets listed on three marketplaces. Count them once.
The rest Tickets.dev lines up for you. Each marketplace's response is normalized into one listing schema: section, row, quantity, ticket price, fee and an all-in totalPrice you can compare directly. StubHub folds its fees into the listed price, so its fee is 0. Every endpoint returns that same snapshot, so one parser covers every marketplace.
{
"listingId": "14278135569",
"inventoryType": "resale",
"section": "118",
"row": "7",
"quantity": 1,
"ticketPrice": 492.8,
"fee": 0,
"totalPrice": 492.8,
"ticketType": "Mobile ticket",
"sellableQuantities": "1",
"group": "100 Level",
"seats": "",
"listingNotes": "Third Row of Section, Clear view, Instant download",
"dealScore": "7.38"
}{
"listingId": "VB15348332327",
"inventoryType": "resale",
"section": "118",
"row": "7",
"quantity": 2,
"ticketPrice": 594,
"fee": 208,
"totalPrice": 802,
"ticketType": "Ticketmaster Transfer",
"sellableQuantities": "2",
"group": "100 Level",
"seats": "5,6",
"listingNotes": "Please note that you will need to use an iOS or Android mobile device to gain entry to your event.",
"dealScore": "4.4"
}4. Track prices over time
Save each capture with a timestamp and you have a snapshot. Capture every marketplace on a schedule and the snapshots add up to price history for the whole event.
Capture more often as the show gets closer, since that's when prices move most. A week out, twice a day is plenty. On the last day, every hour.
Here's the whole tracker, filled in with the event picked in step 1. Every minute it checks which marketplaces are due, captures them through /v1/capture, and appends each snapshot to a file. If a capture fails, it tries again on the next pass.
import json
import os
import time
from datetime import datetime, timezone
import requests
API_KEY = os.environ["TICKETS_DEV_KEY"]
SHOW_STARTS = datetime.fromisoformat("2026-10-02T20:00:00-04:00")
EVENT_URLS = [
"https://www.stubhub.com/harry-styles-new-york-tickets-10-2-2026/event/160334460/",
"https://seatgeek.com/harry-styles-tickets/new-york-new-york-madison-square-garden-2026-10-02-8-pm/concert/18027119",
"https://www.tickpick.com/buy-harry-styles-jamie-xx-tickets-madison-square-garden-10-2-26-8pm/7695881/",
"https://www.vividseats.com/harry-styles-tickets-new-york-madison-square-garden-10-2-2026--concerts-pop/production/6564648",
]
# Capture more often as the show gets closer. Example numbers, tune to taste.
# (hours before the show, minutes between captures)
SCHEDULE = [
(24 * 7, 12 * 60), # more than a week out: twice a day
(24, 3 * 60), # the last week: every 3 hours
(0, 60), # the last day: every hour
]
# Each capture costs one credit. Stop after this many; raise it once a test
# run looks right.
MAX_CAPTURES = 50
def minutes_between_captures(hours_left):
for hours, minutes in SCHEDULE:
if hours_left >= hours:
return minutes
return SCHEDULE[-1][1]
def capture(url):
response = requests.get(
"https://api.tickets.dev/v1/capture",
params={"url": url},
headers={"x-api-key": API_KEY},
timeout=120,
)
response.raise_for_status()
return response.json()
last_capture = {}
captures = 0
while datetime.now(timezone.utc) < SHOW_STARTS and captures < MAX_CAPTURES:
now = datetime.now(timezone.utc)
hours_left = (SHOW_STARTS - now).total_seconds() / 3600
every = minutes_between_captures(hours_left) * 60
for url in EVENT_URLS:
if captures >= MAX_CAPTURES:
break
if url in last_capture and (now - last_capture[url]).total_seconds() < every:
continue # not due yet
try:
snapshot = capture(url)
except requests.RequestException as error:
print(f"capture failed, will retry: {url} ({error})")
continue
last_capture[url] = now
captures += 1
with open("snapshots.jsonl", "a") as file:
file.write(json.dumps(snapshot) + "\n")
stats = snapshot["stats"]
print(f"{snapshot['source']}: {stats['listingCount']} listings, "
f"cheapest ${stats['getInPrice']}")
time.sleep(60)
print(f"stopped after {captures} captures")Each capture costs one credit, so the schedule is also your budget. With the numbers above, the final week is 72 captures per marketplace. The script also stops after MAX_CAPTURES (50 by default), so a test run can't overspend. Once you track more than a handful of events, swap snapshots.jsonl for a database.
429 rate_limited with a Retry-After header, which is never billed.Automate price collection
The tracker above stops when you close your laptop. To keep it running, store the snapshots somewhere that lasts, like Postgres on Supabase or Neon, or just a Google Sheet, and have it run on a schedule in the cloud. The easy way: ask an AI coding tool like Claude Code or Cursor to deploy it for you. We'll walk through that setup in a separate guide.
Supabase or NeonA Postgres database. Keeps every snapshot, ready to query and chart.
Google SheetsOne row per capture. Nothing to set up but a sheet.
What you can do with this data
A single price gap only tells you about one seat at one moment. Track hundreds of events and you see where gaps show up, how big they get and how fast they close. That's data you can build on:
- Buyers can find the cheapest all-in price for a seat before they pay.
- An alert can tell someone the moment a section drops below their price.
- A comparison site can show the same seats on every marketplace side by side.
- Resellers can watch their listings against the market without checking every marketplace by hand.
Build your own price tracker
You now have every piece: matched event URLs, live captures, one listing schema and a capture schedule. Put them together in your own stack with the code above, or hand the build to an AI coding tool like Claude Code, Cursor or Codex.
For the AI route, paste your event below and copy the prompt. It builds the tracker, finds your event on every marketplace, captures on the schedule from step 4, charts the price history and deploys it so it runs on its own.
Leave it empty and the AI will ask you which event.
Build me a small ticket price tracker in Python using the Tickets.dev API, then deploy it so it keeps running on its own. My Tickets.dev API key is in the environment variable TICKETS_DEV_KEY. Send it in the x-api-key header on every request. 1. Find the event. Ask me which event to track (a name, or its URL on any marketplace). Call GET https://api.tickets.dev/v1/events?query=<my answer>. The results are in an events array. Show me each match's name, eventDateLocal and venue name, and let me pick one. Each event has a sources array with the url for every marketplace that lists it. Ask me which of those marketplaces to track (all of them by default; fewer uses fewer credits), and save my choice to event.json so later runs reuse it. 2. Capture prices. For each URL, call GET https://api.tickets.dev/v1/capture?url=<url>. The response has a source field (the marketplace), stats (listingCount, getInPrice, medianPrice) and a listings array. getInPrice is the cheapest all-in price. If you get 429 rate_limited, wait as long as its Retry-After header says and retry. Treat any other error or timeout as a failed capture: skip that marketplace and try it again on the next run. 3. Store every capture: capturedAt, the marketplace, listingCount, getInPrice, medianPrice, and the full response as JSON. 4. Repeat on a schedule until the event starts (use eventDateUtc): every 12 hours while it's more than a week away, every 3 hours in the last week, and every hour on the last day. Each capture costs one API credit per marketplace. Before starting, tell me roughly how many credits the full schedule would use, ask me for a credit budget (suggest 50 for a first test), and stop capturing when it's used up. Add a --once option that does a single run, so I can test it first. 5. Chart the history: a page with one line per marketplace showing getInPrice over time, refreshed after every run. 6. Deploy it. Run it once with --once to check it works, then deploy it to Fly.io as a small always-on app, so it runs while my computer is off. The tracker's own loop decides when each capture is due, so don't use cron or GitHub Actions. Run the fly commands yourself; only ask me to log in to Fly in the browser and add a payment method. Use a hosted database (Supabase or Neon Postgres both have free tiers) instead of a local file. Store my API key as a secret there. When it's live, give me the link to the chart. Walk me through every step I have to do myself, like creating an account, as if I've never used a terminal.


