Examples — one match, in order
This is the OpenBook equivalent of GTFS's example feed: worked documents that already live in ../examples/, shown in sequence order, which is the order a consumer would apply them. They validate against the schemas. They are not a live sandbox.
The sample match is Arsenal v Manchester City, fixture EVT-88213, 19 September 2026. Publisher acme-feeds is fictional. Source names in the sample (acme-book, pinnacle, bigbook) are labels on prices, not a requirement to use those books. OpenBook is vendor-neutral.
Plain-language story: the guide. Rules: the specification. Raw dump of every file: schemas § examples.
1. Find the feed
A publisher SHOULD offer one discovery URL. ttl is how long a consumer may cache this list. Snapshot, stream, and docs are named feeds — not the publisher object.
Source: discovery.example.json
{
"lastUpdated": "2026-09-19T12:00:00Z",
"ttl": 60,
"feeds": [
{
"name": "snapshot",
"kind": "snapshot",
"url": "https://example.invalid/acme-feeds/snapshot"
},
{
"name": "stream",
"kind": "stream",
"url": "https://example.invalid/acme-feeds/stream"
},
{
"name": "docs",
"kind": "docs",
"url": "https://example.invalid/acme-feeds/docs"
},
{
"name": "OpenBook MCP",
"kind": "mcp",
"id": "openbook-mcp",
"schemaUrl": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"url": "https://example.invalid/.well-known/mcp.json"
},
{
"name": "settlement plugin",
"kind": "plugin",
"id": "settlement",
"schemaUrl": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"url": "https://example.invalid/plugins/settlement/plugin.json"
}
]
}
2. Who is transmitting
The publisher record is identity: name, currency, heartbeat bound, and the sources this feed carries. One feed, three books in the sample (acme-book, pinnacle, bigbook).
Source: publisher.example.json
{
"openbookVersion": "0.3.0-draft",
"id": "acme-feeds",
"sequence": 1,
"dateModified": "2026-09-19T12:00:00Z",
"name": "Acme Feeds",
"inLanguage": "en",
"baseCurrency": "GBP",
"heartbeatMs": 5000,
"ttl": 60,
"url": "https://example.invalid/acme-feeds",
"sources": [
{
"id": "acme-book",
"name": "Acme Book",
"sourceType": "sportsbook"
},
{
"id": "pinnacle",
"name": "Pinnacle",
"sourceType": "sportsbook"
},
{
"id": "bigbook",
"name": "Big Book",
"sourceType": "sportsbook"
}
]
}
3. The fixture (the catalogue)
Standard facts: sport, league, start time, participants with role and order, Wikidata sameAs when it exists. This is the printed list, not a price tick. Sequence 104870.
Source: fixture.example.json
{
"@context": "https://schema.org",
"@type": "SportsEvent",
"openbookVersion": "0.3.0-draft",
"id": "EVT-88213",
"sequence": 104870,
"dateModified": "2026-09-12T18:02:11Z",
"name": "Arsenal v Manchester City",
"sport": {
"id": "sport:soccer",
"name": "Soccer"
},
"league": {
"id": "LG-17",
"name": "Premier League",
"territory": "GB-ENG",
"competitionType": "league",
"gender": "men",
"ageGroup": "open",
"sameAs": "https://www.wikidata.org/entity/Q9448"
},
"season": {
"id": "S-2025",
"name": "2025-26"
},
"stage": {
"id": "ST-REG",
"name": "Regular Season",
"stageType": "phase"
},
"startDate": "2026-09-19T14:00:00Z",
"cutoffDate": "2026-09-19T14:00:00Z",
"eventStatus": "scheduled",
"participants": [
{
"id": "T-3",
"name": "Arsenal",
"territory": "GB-ENG",
"role": "home",
"order": 1,
"sameAs": "https://www.wikidata.org/entity/Q9617"
},
{
"id": "T-11",
"name": "Manchester City",
"territory": "GB-ENG",
"role": "away",
"order": 2,
"sameAs": "https://www.wikidata.org/entity/Q50602"
}
],
"location": {
"name": "Emirates Stadium",
"addressLocality": "London",
"territory": "GB-ENG",
"timeZone": "Europe/London",
"latitude": 51.555,
"longitude": -0.108,
"sameAs": "https://www.wikidata.org/entity/Q19478"
},
"surface": "grass",
"identifier": [
{
"propertyID": "sportradar",
"value": "sr:match:8412480"
}
]
}
4. Prices move
odds/change carries only what moved. Sequence 104871 — the first live tick after the fixture. Three sources in one message in this sample — licensed, official, and observed — so a consumer can tell them apart. A suspended spread is a status, not a zero price.
Source: odds_change.example.json
{
"openbookVersion": "0.3.0-draft",
"sequence": 104871,
"datePublished": "2026-09-19T14:37:12Z",
"publisher": "acme-feeds",
"object": "odds",
"action": "change",
"sport": "soccer",
"id": "EVT-88213",
"changes": {
"markets": [
{
"marketType": "market:moneyline",
"segment": "segment:soccer:full-time",
"status": "open",
"source": "pinnacle",
"provenance": "licensed",
"outcomes": [
{
"side": "home",
"odds": "2.1"
},
{
"side": "away",
"odds": "3.25"
}
]
},
{
"marketType": "market:total",
"segment": "segment:soccer:full-time",
"line": "2.5",
"basis": "corners",
"source": "acme-book",
"provenance": "official",
"outcomes": [
{
"side": "over",
"odds": "1.95"
}
]
},
{
"marketType": "market:spread",
"segment": "segment:soccer:full-time",
"status": "suspended",
"source": "bigbook",
"provenance": "observed",
"outcomes": [
{
"side": "home",
"line": "-0.5",
"active": false
},
{
"side": "away",
"line": "0.5",
"active": false
}
]
},
{
"marketType": "market:correct-score",
"segment": "segment:soccer:full-time",
"basis": "corners",
"source": "acme-book",
"provenance": "official",
"outcomes": [
{
"side": "home",
"homeTotal": 1,
"awayTotal": 0,
"odds": "8.5"
},
{
"side": "other",
"odds": "1.12"
}
]
},
{
"marketType": "market:half-time-full-time",
"segment": "segment:soccer:full-time",
"source": "acme-book",
"provenance": "official",
"outcomes": [
{
"side": "draw",
"halfTime": "home",
"fullTime": "draw",
"odds": "15.0"
}
]
},
{
"marketType": "market:player-points",
"segment": "segment:soccer:full-time",
"line": "1.5",
"source": "acme-book",
"provenance": "official",
"outcomes": [
{
"side": "over",
"player": "PL-9",
"odds": "1.85"
},
{
"side": "under",
"player": "PL-9",
"odds": "1.95"
}
]
},
{
"marketType": "market:double-chance",
"segment": "segment:soccer:full-time",
"source": "pinnacle",
"provenance": "licensed",
"outcomes": [
{
"side": "home-or-draw",
"odds": "1.25"
}
]
},
{
"marketType": "market:winning-margin",
"segment": "segment:soccer:full-time",
"source": "acme-book",
"provenance": "official",
"outcomes": [
{
"side": "participant",
"participant": "T-3",
"line": "1",
"odds": "4.50"
},
{
"side": "participant",
"participant": "T-11",
"atLeast": 3,
"odds": "6.00"
},
{
"side": "other",
"odds": "1.40"
}
]
},
{
"marketType": "market:player-anytime-scorer",
"segment": "segment:soccer:full-time",
"source": "acme-book",
"provenance": "official",
"outcomes": [
{
"side": "yes",
"player": "PL-9",
"odds": "3.40"
},
{
"side": "yes",
"player": "PL-11",
"odds": "4.20"
}
]
}
]
}
}
5. A priced market, as a document
A market snapshot for one source (bigbook). limit is on the document; later price-only ticks do not have to repeat it. Sequence 104880.
Source: market.example.json
{
"openbookVersion": "0.3.0-draft",
"sequence": 104880,
"dateModified": "2026-09-19T14:05:00Z",
"fixture": "EVT-88213",
"marketType": "market:spread",
"segment": "segment:soccer:full-time",
"line": "-0.5",
"source": "bigbook",
"provenance": "observed",
"status": "open",
"limit": {
"amount": "500"
},
"outcomes": [
{
"side": "home",
"odds": "1.91",
"line": "-0.5",
"active": true
},
{
"side": "away",
"odds": "1.95",
"line": "0.5",
"active": true
}
]
}
6. Caught up, then quiet is bounded
After snapshot + replay, the stream says snapshotComplete (104900). Heartbeats on the same stream (104901) mean “still here”; silence longer than heartbeatMs is an alarm, not a feature.
Sources: snapshot_complete.example.json, heartbeat.example.json
{
"openbookVersion": "0.3.0-draft",
"sequence": 104900,
"datePublished": "2026-09-19T14:37:20Z",
"publisher": "acme-feeds",
"object": "fixture",
"action": "snapshotComplete",
"sport": "soccer",
"id": "EVT-88213",
"changes": {}
}
{
"openbookVersion": "0.3.0-draft",
"sequence": 104901,
"datePublished": "2026-09-19T14:37:25Z",
"publisher": "acme-feeds",
"object": "publisher",
"action": "heartbeat",
"sport": "unknown",
"changes": {}
}
7. Kick-off delayed
A fixture update is a Merge Patch: only the fields that changed. x_ is a vendor extra; consumers ignore unrecognised fields. Sequence 104902.
Source: fixture_update.example.json
{
"openbookVersion": "0.3.0-draft",
"sequence": 104902,
"datePublished": "2026-09-19T13:05:00Z",
"publisher": "acme-feeds",
"object": "fixture",
"action": "update",
"sport": "soccer",
"id": "EVT-88213",
"changes": {
"startDate": "2026-09-19T14:15:00Z",
"x_reason": "kick-off delayed 15 minutes"
}
}
8. A market comes back on
After a goal, the observed spread re-opens. msgType / reason / references point at the earlier sequence. Taking a market off (or putting it back) is market/update, not a sentinel odds value. Sequence 105330.
Source: market_update.example.json
{
"openbookVersion": "0.3.0-draft",
"sequence": 105330,
"datePublished": "2026-09-19T14:41:03Z",
"publisher": "acme-feeds",
"object": "market",
"action": "update",
"sport": "soccer",
"id": "EVT-88213",
"msgType": "update",
"reason": "goal",
"references": [
104871
],
"changes": {
"marketType": "market:spread",
"segment": "segment:soccer:full-time",
"line": "-0.5",
"source": "bigbook",
"status": "open"
}
}
9. The score
Live, second half, 67:00 on the broadcast clock. First half is down (final, once). Goals and corners are separate score lines. Sequence 105410.
Source: score.example.json
{
"openbookVersion": "0.3.0-draft",
"sequence": 105410,
"datePublished": "2026-09-19T15:32:08Z",
"publisher": "acme-feeds",
"object": "score",
"action": "update",
"sport": "soccer",
"id": "EVT-88213",
"changes": {
"eventStatus": "live",
"currentSegment": "segment:soccer:2nd-half",
"segments": [
{
"segment": "segment:soccer:1st-half",
"status": "down",
"downAt": "2026-09-19T14:47:30Z",
"order": 1
},
{
"segment": "segment:soccer:2nd-half",
"status": "live",
"order": 2
}
],
"clock": {
"elapsed": 1320,
"running": true,
"display": "67:00"
},
"scores": [
{
"participant": "T-3",
"unit": "goals",
"total": 2,
"bySegment": {
"segment:soccer:1st-half": 1,
"segment:soccer:2nd-half": 1
}
},
{
"participant": "T-11",
"unit": "goals",
"total": 1,
"bySegment": {
"segment:soccer:1st-half": 1,
"segment:soccer:2nd-half": 0
}
},
{
"participant": "T-3",
"unit": "corners",
"total": 5,
"bySegment": {
"segment:soccer:1st-half": 3,
"segment:soccer:2nd-half": 2
}
},
{
"participant": "T-11",
"unit": "corners",
"total": 3,
"bySegment": {
"segment:soccer:1st-half": 1,
"segment:soccer:2nd-half": 2
}
}
]
}
}
10. A grade
Once a segment is down, the book grades a market against it. This grade is first-half total 1.5: over wins, under loses. A correction is a new grade with supersedes, never an edit. Sequence 106001.
Source: grade.example.json
{
"openbookVersion": "0.3.0-draft",
"sequence": 106001,
"datePublished": "2026-09-19T14:48:02Z",
"publisher": "acme-feeds",
"object": "grade",
"action": "create",
"sport": "soccer",
"id": "EVT-88213",
"changes": {
"gradeId": "GRD-77120",
"fixture": "EVT-88213",
"segment": "segment:soccer:1st-half",
"source": "acme-book",
"marketType": "market:total",
"line": "1.5",
"basis": "goals",
"basedOn": 105388,
"dateGraded": "2026-09-19T14:48:02Z",
"outcomes": [
{
"side": "over",
"result": "win"
},
{
"side": "under",
"result": "lose"
}
]
}
}
The rest of the corpus
These files are in ../examples/ too. They are the valid half of the conformance suite. Catalogue objects use earlier sequences than the match stream above; the conflated tick sits between 104871 and 104880.
| File | Sequence | What it is |
|---|---|---|
participant.example.json | 104000 | A team or individual as a catalogue object |
stage.example.json | 104010 | A named slice of a season |
player.example.json | 104100 | Roster membership |
stall.example.json | 104110 | Racing gate (order) |
toss.example.json | 104120 | Who won the cricket toss |
odds_change_conflated.example.json | 104872 | A tick that skipped intermediates (conflated: true) |
lineup.example.json | 105500 | Starting player ids for one fixture |
series.example.json | 105510 | Live series lead; round on stage |
Invalid cases (must be rejected) live in ../conformance/invalid/. Run python3 tools/validate.py.