Sports Inventory Hierarchy
This page explains what the Sports Inventory model is—what each entity represents, how Optimove Personalize uses it, and the conventions and gotchas that are not obvious from the field list alone.
Looking for the full field list?For the exhaustive field-by-field reference—every field name, type, and whether it is required—see the generated Sports Inventory API Reference. This page is deliberately about meaning and behavior, not population.
The six entities
Sports Inventory has six entity types. Each child references its parent by key:
Category.categoryKey
└── Class.categoryKey
└── Type.classKey
└── Event.typeKey
└── Market.eventKey
└── Selection.marketKey| Entity | Represents | Example |
|---|---|---|
| Category | The top-level sport or product grouping | Football, Tennis |
| Class | A country, region, or broad grouping inside a Category | England, Europe |
| Type | A competition, league, or championship inside a Class | Premier League |
| Event | A sporting event that markets are offered on | Man Utd vs Liverpool |
| Market | A bettable market within an Event | Match Result, Total Goals |
| Selection | A specific bettable outcome within a Market | Arsenal, Draw, Chelsea |
Global conventions
- Keys are yours, and they are identity. They must be non-empty where required, stable, and unique for that entity type within the brand feed.
- Treat keys as immutable. To replace a key, create the new entity and then remove the old one—do not repurpose an existing key.
- Names are public. They are display labels and are used by search and recommendation output.
- Translation fields are JSON objects of strings, for example
{"en":"Football","es":"Fútbol"}. Agree locale-key conventions during onboarding. displayOrderis an integer, lower values first per the API contract—but configured Personalize experiences can apply additional ranking on top of it, so do not treat it as the final order.- Case matters.
displayStatusisDisplayedorNotDisplayed; entity status isActiveorInactive. These are case-sensitive. - Do not rely on fields not documented in the API Reference. Undocumented fields have no contract.
Parent-propagation gotcha: the API contract describes NotDisplayed on a parent as applying to its children. For predictable results across configured models, still keep the affected child records' display and status values accurate—do not rely on parent propagation alone.
Denormalized parent context
Beyond the direct parent key that every child requires, Type, Event, and Market accept optional denormalized ancestor keys (for example categoryKey/classKey on an Event). These are a convenience for your own pipeline.
If you send them, they must agree with the actual hierarchyA denormalized key that contradicts the real parent chain is a data error, not an override.
Event timing and lifecycle
Events carry the model's time awareness, and this is where the least-obvious behavior lives.
eventDateTimemust include a timezone. Accepted offsets includeZ,±hh,±hhmm, and±hh:mm. It drives future/in-play logic, ordering, and minutes-to-start calculations.isEventStartedshould be senttrueonce the event has started; it feeds in-play state, search behavior, and configured ordering.eventEndDateTimehas a defaulting rule you must plan for. It is optional and UTC (Zor+0000). If omitted, the effective end iseventDateTime+ 24 hours. An end before the start is coerced to that same 24-hour default. Inventory lifecycle processing uses the effective end, so an event with a real duration longer or shorter than 24 hours needs an expliciteventEndDateTime.isEventFinishedstores the provider's completion state, but not every model path consumes it directly—set it accurately, but do not assume it alone removes an event from results.
Provider-specific fields—agree during onboarding
Several fields carry provider-specific values with no universal enum in the public contract. Their meaning must be agreed during onboarding; do not assume a fixed vocabulary:
| Field | Where | What to know |
|---|---|---|
classSortCode | Event | Provider classification used by configured sports-search. Known configured behavior recognizes values such as RACE and ANTEPOST. |
eventSort | Event | Can be surfaced as an external Event identifier in configured rules/results. It is not an enumerated sort direction. |
marketSort | Market | Stored provider metadata; no universal Personalize behavior is guaranteed. |
participants | Market | Powers participant-aware search and same-game-parlay behavior where configured. The serialized format is provider-specific. |
selectionPriceType | Selection | Price classification for configured model filtering. Some paths recognize LP; there is no universal public enum. |
outcomeMeaningMajorCode | Selection | Can be surfaced as an external Selection identifier. |
Selection display and status
Selection is the entity recommendation APIs return, so two of its fields behave more strictly than their equivalents higher up:
displayStatusis required on a Selection and directly filters displayability in common recommendation paths—it is not just advisory metadata.displayOrderis combined with price as an additional ordering signal in configured paths.
Selection pricing
Pricing is the most behavior-heavy part of the model. A Selection can be priced as fractional (numPrice/denPrice), decimal (decPrice), or left explicitly unpriced. The valid shapes accepted on create (POST):
| State | Example fragment | Behavior |
|---|---|---|
| Fractional and decimal | {"numPrice":1,"denPrice":1,"decPrice":2.0} | When both fractional values are non-zero and denPrice > 0, Personalize derives decimal odds and can overwrite the supplied decPrice. |
| Fractional only | {"numPrice":1,"denPrice":1} | Personalize derives decPrice as (numPrice / denPrice) + 1, rounded to two decimals. |
| Decimal only | {"decPrice":2.0} | Decimal odds are stored directly. |
| Explicitly unpriced (decimal form) | {"decPrice":null} | Selection remains present without a usable price. |
| Explicitly unpriced (fractional form) | {"numPrice":null,"denPrice":null} | Selection remains present without a usable price. |
| All pricing fields null | {"numPrice":null,"denPrice":null,"decPrice":null} | Selection remains present without a usable price. |
Zero-numerator gotcha: although the request schema accepts a zero numerator, the current runtime does not derive decimal odds from a zero-valued fractional pair. For a priced Selection, send a positive numerator, a positive denominator, and a consistent decPrice.
On update (PUT), automatic decimal derivation is not guaranteed. When changing odds, always send numPrice, denPrice, and a consistent decPrice together—see the PUT operation in the Sports Inventory API Reference. Do not update only the numerator or only the denominator; the fractional pair must stay consistent.
extra pass-through
extra pass-throughEvery entity accepts a free-form extra JSON object, passed through for configured customer use (for example participant extraction on Events).
- Do not place secrets or sensitive personal data in
extra. - A PUT replaces the whole
extraobject—it does not merge. Retain your authoritative copy before sending an update meant to preserve existing keys.
Updated about 2 hours ago
