Create markets
Describes stx-python 0.8.0, the current release.
An approved app can list its own player-prop markets on STX events: you name the event, the player, the stat and the line, and STX builds the market, settles it from its stats feed and tells you what happened to each one. How it works and how to get access are on List your own markets on STX.
Creating markets needs an app token with the markets.write scope, which STX grants on request, sent from an address on your app’s IP allow-list. An API key or a member token cannot create markets. On an exchange that does not offer it, create_markets raises STXNotAvailableError (markets.create); see What you can do.
1. Find the event and its players
Section titled “1. Find the event and its players”from stx import oauth
app = oauth.OAuthClient.discover(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, region="us", env="demo")
with app.app_client("events markets.write") as writer: event = next(e for e in writer.iter_events(status="scheduled", sports="Hockey") if writer.event_players(e.event_id).teams) players = writer.event_players(event.event_id) print(event.title, event.event_id) print(players.stat_keys()) for team in players.teams: print(team.team, [(p.player_id, p.name, p.position) for p in team.players[:2]])Sample output:
Buffalo Sabres at San Jose Sharks 3f71fcfc-53fa-4888-b63b-c0d850055ce7['ASSISTS', 'GOALS', 'GOAL_SO', 'POINTS', 'SAVES']Buffalo Sabres [('3654006', 'Alex Lyon', 'G'), ('4063240', 'Beck Malenstyn', 'LW')]San Jose Sharks [('3942868', 'Adam Gaudette', 'RW'), ('3114994', 'Alex Nedeljkovic', 'G')]event_players(event_id) is the event’s rosters and the stats a market on it can use. teams is empty until the rosters are published. writer.market_types() lists every market type by rule; the ones with partner_create set are the ones you can create, with the fields they take and the stats per event type:
types = writer.market_types()print({rule: list(t.fields) for rule, t in types.items() if t.partner_create}){'player_stat_line': ['line', 'player_id', 'stat']}It changes only when STX adds a rule or a stat, so read it once and keep it. stx.Stats has the stat keys by sport for autocomplete (Stats.Hockey.GOAL_SO).
2. Check with a dry run
Section titled “2. Check with a dry run”from stx import PlayerStatLine, Stats
results = writer.create_markets( event.event_id, [ PlayerStatLine(player_id="3654006", stat=Stats.Hockey.GOAL_SO, line=2.5), PlayerStatLine(player_id="3654006", stat=Stats.Hockey.ASSISTS, line=0.75), PlayerStatLine(player_id="not-a-player", stat=Stats.Hockey.GOALS, line=0.5), ], dry_run=True,)for r in results: print(r.index, r.status, r.reason)Sample output:
0 valid None1 rejected invalid_line2 rejected unknown_playerdry_run=True runs every check on the exchange and creates nothing: a market that would be created comes back valid. A line is a positive number ending in .5.
3. Create
Section titled “3. Create”Send the same list without dry_run. Each result is a CreateMarketResult with the market’s index in your list:
status |
Meaning |
|---|---|
created |
Listed now; market_id is set |
exists |
Already listed (by you or anyone); market_id is set. Resending is always safe |
valid |
A dry run that would create it |
rejected |
Not created; reason says why: invalid_line, unsupported_stat, unknown_player, unsupported_rule, event_not_open, not_created |
A rejected market is a result, not an exception, so one bad entry never stops the others. Up to 25 markets go in one call (stx.MAX_MARKETS_PER_REQUEST); more raises ValueError before anything is sent.
validate=True checks each market against event_players first and returns failures as rejected without sending them; it also matches the stat key’s case to the event’s:
results = writer.create_markets( event.event_id, [ PlayerStatLine("3654006", "goal_so", 2.5), PlayerStatLine("3654006", "HITS", 1.5), PlayerStatLine("3654006", Stats.Hockey.POINTS, 2.0), ], dry_run=True, validate=True,)print([(r.status, r.reason) for r in results])[('valid', None), ('rejected', 'unsupported_stat'), ('rejected', 'invalid_line')]When a call is refused
Section titled “When a call is refused”The whole request can be refused, as an exception:
| Exception | When |
|---|---|
STXIpNotAllowedException |
The call came from an address not on your app’s IP allow-list |
STXInsufficientScopeException |
The app token lacks markets.write |
STXForbiddenException |
An API key or a member token |
STXNotFoundException |
No such event |
STXRejectedException |
The event is not open for new markets |
STXNotAvailableError |
This exchange does not create markets (feature == "markets.create") |
From an address the app does not list, captured on US demo:
stx.exceptions.STXIpNotAllowedException: this app does not allow writes from this IP addressstatus 403 body {'error': 'ip_not_allowed', 'error_description': 'this app does not allow writes from this IP address'}Reads keep working from any address; only writes are checked. Add the address in STX Apps; see IP allow-list.

