MLB Ticket Availability Scraper avatar

MLB Ticket Availability Scraper

Pricing

from $1.50 / 1,000 results

Go to Apify Store
MLB Ticket Availability Scraper

MLB Ticket Availability Scraper

Read MLB box-office inventory off mlb.tickets.com: one row per section and price level still on sale, with availableSeats, maxContiguousSeats, face value in price, the checkout allInPrice and the serviceCharge between them. Name a club and it finds its own home games. 18 of 30 clubs.

Pricing

from $1.50 / 1,000 results

Rating

0.0

(0)

Developer

String

String

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 days ago

Last modified

Share

What does the MLB Ticket Availability Scraper do?

Every other ticket scraper on the Apify Store reads a resale marketplace, where the number on the screen is a reseller's ask. This one reads the primary market — the seat map an MLB club publishes on mlb.tickets.com, which is the box office's own inventory at the club's own face value.

Name a club, say red-sox, and the Actor reads that club's current home schedule itself, then fetches the seat map for each upcoming game. You get one row per section and price level that still has seats: how many are left, the longest run of adjacent seats in that block, the face value, the all-in checkout price, and the per-ticket fee that is the gap between the two.

Event ids on this platform roll over every season, which is why the club is the durable input. A saved run keyed on red-sox still works after the fixtures change; one keyed on an event id stops working within months. Explicit ids are still accepted for anyone who already holds them.

  • No account, session cookie or API key, at any point in the run
  • One request per game, plus one per club to read its schedule
  • Face value and checkout price on the same row, with the fee named rather than implied
  • Values arrive typed from the venue's own feed — 174, not "$174.00"

What data does it extract?

One row per event x section x price level that has seats on sale. In a measured batch across three clubs, ten games produced 574 rows, roughly 57 per game.

FieldTypeDescription
eventIdstringThe club's own event id, stable within a season. Use it to de-duplicate or re-poll one game
eventNamestringThe event as the club names it, e.g. Giants at Red Sox
eventDateTimestringStart time, ISO 8601, carrying the ballpark's own UTC offset
eventCodestringThe club's internal code for the event
teamstringThe club slug you asked for. null when the event came in as a bare id
orgIdnumberThe club's tenant id on the platform. null on a bare id, for the same reason
venueNamestringBallpark name, e.g. Fenway Park
venueCitystringVenue city
venueStatestringTwo-letter state code
sectionIdstringThe venue's section id
sectionNamestringSection as printed, e.g. LOGE BOX-LEFT FIELD
sectionCodestringThe short public code for the section
priceLevelIdstringThe price-scale id the seats sit on
priceLevelNamestringPrice scale as the club names it, e.g. Loge Box
priceLevelCodestringThe club's short code for that price scale
availableSeatsnumberSeats on sale in that section at that price level
maxContiguousSeatsnumberLargest block sold together — what a group of four needs
sectionCapacitynumberThe section's total size
sectionAvailableSeatsnumberThe section's total on sale, across all its price levels
pricenumberFace value, as a number
allInPricenumberWhat the checkout charges once fees are added
serviceChargenumberallInPrice minus price, computed and stated
currencystringCurrency of both price fields
inventoryAsOfstringThe venue's own timestamp on the seat count
sourceUrlstringThe seat-map URL the row was read from
collectedAtstringISO timestamp of the run

The unit is a section at a price level, not an individual seat. Seat numbers live behind a separate and much heavier request that this Actor does not make.

Why scrape MLB ticket availability?

Resale data tells you what a reseller hopes to get. Box-office data tells you what the club is still selling and at what price, and the two together are the actual arbitrage picture. Only one of them is hard to obtain, and it is this one.

Re-running a club on a schedule and diffing availableSeats per section gives a demand curve for a game as it approaches: which parts of the ballpark drain first, how fast, and whether a promotion moved anything. maxContiguousSeats answers a question the totals cannot — a section with 40 seats left in ones and twos is useless to a group of four, and the field separates those cases.

price next to allInPrice puts a number on the fee load per price scale, which is otherwise something a buyer only discovers at checkout. And because priceLevelName comes straight from the club's own scale, you can compare how clubs tier an identical ballpark geometry across a homestand or across the league on the same weekend.

How to use it

  1. Start from this page: Try for free, or save the Actor to your Apify account.
  2. Put club slugs in the Clubs field, one per line — red-sox, cubs, dodgers. A club's numeric tenant id works too.
  3. Optionally set From date and To date as YYYY-MM-DD to bound the games by their own local date.
  4. Set Events per club for how deep into each schedule to read, and Maximum results for a hard ceiling on rows.
  5. Click Start, then export from the Dataset tab as JSON, CSV or Excel.
  6. Check the run's SUMMARY record: anything that could not be fetched is listed under failures with the reason.

Supply teams, eventIds, or both. An id that arrives twice — once directly, once because it is also on a club's schedule — is fetched once, and keeps the club and date the schedule gave it.

Input

FieldTypeDefaultDescription
teamsarray of stringsClub slugs whose home schedule to read, up to 40. A numeric tenant id also works
eventIdsarray of stringsSpecific event ids, or event URLs carrying a pid. Up to 200, fetched without reading a schedule
startDatestringSkip games before this local date, YYYY-MM-DD
endDatestringSkip games after this local date, YYYY-MM-DD
maxEventsPerTeaminteger10Events to read per club, 1 to 200. One event is one request
maxItemsinteger1000Ceiling on dataset rows, 1 to 50000
concurrencyinteger3Targets fetched in parallel, 1 to 5

At least one of teams or eventIds must be non-empty; a run with neither is rejected before it fetches anything.

{
"teams": ["red-sox", "cubs"],
"startDate": "2026-09-01",
"endDate": "2026-09-30",
"maxEventsPerTeam": 10,
"maxItems": 1000,
"concurrency": 3
}

Output

One row per section and price level with seats on sale. The shape below is illustrative — it shows the fields and their types, not a captured run.

{
"eventId": "9681705",
"eventName": "Giants at Red Sox",
"eventDateTime": "2026-09-12T19:10:00-04:00",
"eventCode": "26RS0912",
"team": "red-sox",
"orgId": 23,
"venueName": "Fenway Park",
"venueCity": "Boston",
"venueState": "MA",
"sectionId": "1420",
"sectionName": "LOGE BOX-LEFT FIELD",
"sectionCode": "LB157",
"priceLevelId": "12",
"priceLevelName": "Loge Box",
"priceLevelCode": "LB",
"availableSeats": 18,
"maxContiguousSeats": 4,
"sectionCapacity": 132,
"sectionAvailableSeats": 26,
"price": 174,
"allInPrice": 201.35,
"serviceCharge": 27.35,
"currency": "USD",
"inventoryAsOf": "2026-09-05T14:31:00-04:00",
"sourceUrl": "https://mlb.tickets.com/api/pvodc/v1/events/navmap/availability/?pid=9681705&agency=MLB_MPV&supportsVoucherRedemption=true&accessible=false",
"collectedAt": "2026-09-05T14:36:22.114Z"
}

A sold-out game returns no rows rather than a wall of zeroes. The venue's response describes every section of the ballpark whatever its state, so emitting all of them would bill several hundred empty rows for a game with nothing to sell.

What it covers, and what it does not

Eighteen of the thirty MLB clubs sell through this platform: angels, astros, brewers, cardinals, cubs, dodgers, guardians, marlins, nationals, phillies, pirates, rangers, rays, red-sox, reds, royals, tigers, twins. The other twelve are on Ticketmaster and have no box office on this host at all. A club that is not on the list is reported under failures immediately rather than fetched, so it costs nothing.

Home games only. A club's schedule here is what that club sells, so an away game belongs to the host club's feed, not this one.

Some clubs list non-baseball events on the same schedule. Parking passes, stadium tours and museum entry appear alongside the games — the Cardinals' schedule runs to roughly 110 entries, most of them exactly that. They return as ordinary rows and are billed as ordinary rows. eventName is what tells them apart, so filter on it (or set a tight date window) if you only want ballgames.

inventoryAsOf runs a few minutes behind live. That is the venue's own snapshot timestamp, not our latency. It is fine for trend work and comparison against resale; it is the wrong feed to sit behind a real-time booking flow.

Seat-by-seat detail, resale listings, holds, season-ticket inventory and anything requiring a login are all out of scope.

How much does it cost?

Pricing is per event: one charge for each result row written to the dataset. A club or event that fails produces no rows and costs nothing. The current rate is in the pricing panel at the top of this page.

What drives the total is rows per game, and that number is large — roughly 57 in our measured batch, because a ballpark has many sections and most of them have something on sale. Bound it with maxEventsPerTeam and maxItems before running a wide sweep, and remember that the non-baseball entries described above are charged the same as games.

Runs started from an Apify free plan stop at 250 requests and 250 results, reported in the run's status message; any paid plan runs the full input and whatever maxItems you set. The cap is there because this Actor fetches through String's own infrastructure, which Apify does not reimburse on free-plan runs, and it binds on requests as well as rows so a long club list cannot spend those fetches on rows the run will not return.

Using it with the Apify API

import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });
const run = await client.actor("usestring/mlb-tickets").call({
teams: ["red-sox", "cubs"],
startDate: "2026-09-01",
endDate: "2026-09-30",
maxEventsPerTeam: 5
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
// Sections that can still seat a group of four together, cheapest first.
const groupsOfFour = items
.filter((row) => row.maxContiguousSeats >= 4)
.sort((a, b) => a.allInPrice - b.allInPrice);
console.log(groupsOfFour.slice(0, 10));
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord("SUMMARY");
console.log(summary?.value);

The Actor reads the same public inventory a club's own ticketing page loads for a logged-out visitor. There is no account, no session cookie, no paywall and no access control being stepped around. Collecting information from a page published to the open web is lawful in the US and the EU under current case law; what invites trouble is circumventing a login, and that does not happen here.

Nothing collected is personal data. Every row describes a section of a ballpark and a price — no buyers, no ticket holders, no names of any kind. Prices and seat counts are facts rather than creative work, though a club's event descriptions and section naming are its own, so treat the text as source material for analysis rather than something to republish. Ticketing terms of service and your own reselling obligations are yours to check against what you intend to do with the data. This is not legal advice.

No other String Actor reads ticketing, so these are neighbours rather than substitutes:

FAQ

Is this resale prices or box-office prices? Box office. price is the club's face value and allInPrice is what the checkout charges once fees are added. Neither is a reseller's ask.

Do I need an account, API key or cookies? No. It reads the public inventory a logged-out visitor's browser loads, and never signs in.

Do I have to know the event id? No, and you should not have to — ids roll over every season. Give it a club slug and, if you like, a date window. Ids remain available as an input for anyone who already has them.

Which clubs work? The eighteen listed above. The other twelve sell through Ticketmaster and are rejected up front, without a fetch, rather than returning an empty schedule.

Is it seat by seat? No. A row is a section at a price level: how many seats are on sale there and the largest adjacent block. Individual seat numbers need a different and far heavier request.

How fresh are the counts? inventoryAsOf carries the venue's own timestamp on the snapshot, which in our sampling ran a few minutes behind live.

Why is team sometimes null? Because that event came in as a bare id, so there was no club to attribute it to. orgId is null for the same reason. Every other field on the row is unaffected.

Why did a run return parking or a stadium tour? Because the club lists those on the same schedule the games are on. They come back as normal rows; filter on eventName to drop them.

Do I get anything for a sold-out game? No rows, deliberately. An event id that does not resolve at all is different again — it lands in SUMMARY.failures with the site's own message rather than becoming a row of nulls.

How fast is it? A seat map took a median of 7.6 seconds at concurrency 3 in our measurements, and 2.2 seconds run sequentially; the difference is solver work happening in parallel, not the origin slowing down.

Feedback

If a club fails, a game returns nothing you expected, or a price looks wrong, open an issue from the Issues tab on this Actor's Store page and include the club slug and the eventId.