August 15, 2026·7 min read

The product isn't the scoreboard. It's the pipeline behind it.

OZ Intelligence looks like a live sports site. The site is the demo. The real product is the data layer underneath — and the data layer is the durable asset.

By Andrew Pyle

If you land on OZ Intelligence, you see a sports site: live scores ticking over every few seconds, standings, box scores, playoff brackets. That's the part I'd tell my friends about. It's also the part I care least about. The scoreboard is a window onto the actual product, which is the thing you can't see — the pipeline that ingests, cleans, and structures sports data, and hands it back as clean JSON that anything can build on.

This is a teardown of that pipeline: why I built the data layer first and the site second, where the hard engineering actually lives, and why a good data layer is the asset that outlives every UI you wrap around it.

01The premise

The premise — Good sports data is expensive, locked, or both.

Every time I wanted clean sports data for a project, I hit the same wall. The good feeds are enterprise-priced. The affordable ones are a scraper you inherit and then maintain forever, breaking every time someone changes a table layout. There was no middle: structured, queryable, affordable, and mine to build on.

So I built the data layer the other way around — automate the ingest, own the cleaning, and expose the result as a plain REST API that returns clean JSON.

The decision that shaped everything after it: the data layer is the product, and the sports site is just the most convincing way to prove the data is real. If the pipeline is good, a scoreboard is a weekend. If the pipeline is bad, no amount of UI saves you. That is not a slogan — it is the reason I keep saying the raw feed is a commodity and the cleaning is the moat.

02The shape

The shape — Ingest, clean, serve — three stages, one warehouse.

The pipeline is boring in the way good infrastructure is boring. Three stages, each with one job, feeding a single canonical warehouse that every downstream property reads from.

Ingest

Automated collectors pull raw results, schedules, rosters, and live game state from source. This is the messy edge — every sport reports differently, and "live" means polling on a tight loop during games. The ingest stage's only job is to get raw facts in the door, tagged with where and when they came from.

Clean

Raw is not the same as trustworthy. This stage normalizes team identities across sources, reconciles duplicates, fills the gaps a single feed leaves, and rejects the impossible (a game with three teams, a stat that can't exist). It's the least glamorous stage and the one that determines whether the whole platform is believable — the part I care about enough to have written a whole essay on making the data trustworthy.

Serve

The cleaned, structured result lands in the warehouse and is exposed as a REST API returning plain JSON. No proprietary client, no scrape-and-pray — a URL and a shape. The public sports site is just the first consumer of that API; the developer API is the same data made directly available.

Here's the part that makes it an actual platform instead of one more sports site: the same warehouse feeds my other sports properties. The sports-analytics network and the high-school-football network don't each run their own scrapers — they read the one clean layer. Build the pipeline once; let the network scale on top of it. That is the same one-engine-many-domains economics I lean on everywhere else in the portfolio.

03Many front doors

One warehouse, many front doors — The network is what makes it worth building.

The clearest proof that the warehouse is the product is what reads from it. USA Prep Football Network is a multi-tenant high-school-football platform templated for all fifty states — twenty-six live today, plus a national hub — and it does not own a single scraper. It renders scores, previews, and team analysis straight off the OZ layer. The Sports Machine is a family of eight per-sport analytics sites spanning pro and college baseball, basketball, football, and hockey; same story, same layer. PickPropPunt, a betting-and-props site, sources its numbers from the same store.

Count the front doors and the logic of building the pipeline first stops being a preference and becomes arithmetic. Every one of those properties would otherwise need its own ingest, its own cleaning, its own reconciliation of the same teams and the same games — the identical miserable work, repeated N times, drifting out of agreement with itself. With one warehouse, a fix to how a team's identity is resolved lands everywhere at once. The scoreboard on each site is a skin; the thing all of them agree on is the layer underneath.

04Live is a loop

Live means a loop, not a moment — Fifteen seconds is a promise you have to keep.

During games, scores update roughly every fifteen seconds. That number sounds like a UI detail. It's actually a constraint that reaches all the way back through the pipeline: the ingest loop has to poll, the clean stage has to reconcile fast enough to not fall behind live play, and the serve layer has to hand out fresh state without buckling when every viewer refreshes at once.

The honest version of "real-time" is that it's a treadmill. A static historical record you build once and forget; a live feed you keep alive every game day, and any stage that lags shows up instantly as a stale score. That pressure is exactly why the cleaning stage can't be an afterthought — you're reconciling under a deadline that never stops during a game.

05The stack

The stack underneath — Django owns the truth, FastAPI serves it.

Under the JSON, the warehouse is a PostgreSQL database modeled as an entity graph — on the order of seventy-nine interlinked data models covering the sports plus adjacent business and civic data. Games point at teams, teams at seasons, seasons at leagues, so a stat can never float free of the entities it describes. That graph is the reason a query can join today's game to last season's roster without a fragile string match: the relationships are structure, not guesswork.

The two frameworks each do the half they're good at. Django owns the ingest-and-clean side — the ORM, the admin, the migrations, the place where a human can inspect and correct a suspect record. FastAPI owns the read side — the fast, typed JSON responses the sites and the developer API consume. Splitting them along that seam, write-heavy internals behind Django and read-heavy serving behind FastAPI, is a deliberate boundary I've written about on its own.

06The API stance

Clean JSON is a design stance — The API is the interface, so it has to be humane.

When the data is the product, the response shape is the product surface. I hold the JSON to the standard I'd want as a consumer: predictable field names, no surprise nulls where a number belongs, IDs that are stable across time so you can join today's game to last season's team. A representative response, not an exact production schema:

GET /v1/games/{id}  →  clean, stable JSON
{
  "id": "...",
  "status": "in_progress",
  "home": { "team_id": "...", "score": 21 },
  "away": { "team_id": "...", "score": 17 },
  "updated_at": "..."   # so you can trust freshness
}

The scale is what makes the discipline matter. The warehouse tracks north of 630,000 games and 25,000+ teams across 12 sports, updating live. At that size, a single sloppy identity decision — one team that exists under two IDs — quietly corrupts every standings table and every historical join that touches it. Clean isn't a nicety; it's the thing the whole platform's trust rests on. And because that number is doing real work, it has to be a measured fact, not a marketing round-up — the difference I keep coming back to on hero stats.

MetricValue
Games tracked630k+
Teams25k+
Sports, updating live12

OZ Intelligence · the warehouse behind the scoreboard

07Honest status

Honest status — Beta, with a waitlist — and that's the right order.

The developer API is in beta behind a waitlist, and I'm comfortable saying so. The public sports platform is live and proves the data is real; the API is the same data offered directly, and I'd rather harden the pipeline against real scale before I put a rate limit and a contract in front of it. The scoreboard earns the trust; the API is what that trust is for. Saying "beta, waitlist" out loud is the same instinct as reporting a status I've actually observed rather than one I wish were true.

The lesson I keep relearning across these projects is that the visible thing is rarely the valuable thing. A scoreboard is a commodity — anyone can render one. A clean, structured, queryable data layer that a dozen properties can be built on, that stays believable at 630,000 games, and that hands a stranger a URL instead of a scraper — that's the part that took the real work, and it's the part that lasts. Build the pipeline. The scoreboard is just how you prove it runs.