Home · docs/examples.md

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.

FileSequenceWhat it is
participant.example.json104000A team or individual as a catalogue object
stage.example.json104010A named slice of a season
player.example.json104100Roster membership
stall.example.json104110Racing gate (order)
toss.example.json104120Who won the cricket toss
odds_change_conflated.example.json104872A tick that skipped intermediates (conflated: true)
lineup.example.json105500Starting player ids for one fixture
series.example.json105510Live series lead; round on stage

Invalid cases (must be rejected) live in ../conformance/invalid/. Run python3 tools/validate.py.

Source: docs/examples.md