# Create markets

> List your own player-prop markets on STX events from your server, with a dry run first.

Source: https://docs.stxapp.io/sdks/csharp/create-markets/

> **Availability.** Available on US demo now. See [Environments](https://docs.stxapp.io/environments/).

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](/apps/creating-markets/).

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, `CreateMarketsAsync` throws `STXNotAvailableException` (`markets.create`); see [What you can do](/sdks/csharp/capabilities/).

## 1. Give the client your app

Set the app's client id and secret, and the client mints an app token (the OAuth `client_credentials` grant) when a call needs one. It keeps the token and mints a new one shortly before it expires. See [App tokens](/sdks/csharp/authentication#app-tokens) for the other ways to pass them.

```csharp
using STX.Sdk;
using STX.Sdk.Models;

using var writer = new STXClient(new STXClientOptions
{
    Region = STXRegion.US,
    Environment = STXEnvironment.Demo,
    ClientId = configuration["Stx:ClientId"],
    ClientSecret = configuration["Stx:ClientSecret"],
    Scope = "events markets.write",
});
```

## 2. Find the event and its players

```csharp
EventPlayers? players = null;
Event? hockey = null;
await foreach (var e in writer.GetAllEventsAsync(status: "scheduled", sports: ["Hockey"]))
{
    players = await writer.GetEventPlayersAsync(e.EventId!);
    if (players.Teams.Count > 0) { hockey = e; break; }
}
Console.WriteLine($"{hockey!.Title} {hockey.EventId}");
Console.WriteLine(string.Join(", ", players!.StatKeys()));
foreach (var team in players.Teams)
    Console.WriteLine($"{team.Team}: {string.Join(", ", team.Players.Take(2).Select(p => $"{p.PlayerId} {p.Name} {p.Position}"))}");
```

Sample output:

```text
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
```

`GetEventPlayersAsync(eventId)` is the event's rosters and the stats a market on it can use. `Teams` is empty until the rosters are published. `GetMarketTypesAsync()` lists every market type by rule; the ones with `PartnerCreate` set are the ones you can create, with the fields they take and the stats per event type:

```csharp
var types = await writer.GetMarketTypesAsync();
foreach (var (rule, type) in types.Where(t => t.Value.PartnerCreate == true))
    Console.WriteLine($"{rule}: {string.Join(", ", type.Fields!.AsObject().Select(f => f.Key))}");
// 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.

## 3. Check with a dry run

```csharp
var results = await writer.CreateMarketsAsync(hockey.EventId!,
[
    new STXNewMarket("3654006", "GOAL_SO", 2.5),
    new STXNewMarket("3654006", "ASSISTS", 0.75),
    new STXNewMarket("not-a-player", "GOALS", 0.5),
], dryRun: true);

foreach (var r in results)
    Console.WriteLine($"{r.Index} {r.Status} {r.Reason}");
```

Sample output:

```text
0 valid
1 rejected invalid_line
2 rejected unknown_player
```

`dryRun: 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`; `STXNewMarket.IsValidLine(line)` checks one.

## 4. Create

Send the same list without `dryRun`. Each result is an `STXCreateMarketResult` with the market's `Index` in your list and the `Market` as you sent it:

| `Status` | Meaning |
|---|---|
| `created` | Listed now; `MarketId` is set |
| `exists` | Already listed (by you or anyone); `MarketId` 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` |

`STXCreateMarketStatus` and `STXMarketRejectionReason` name these values, and `Listed` is true for `created` and `exists`. 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 (`STXClient.MaxMarketsPerRequest`); more throws `ArgumentException` before anything is sent.

`validate: true` checks each market against `GetEventPlayersAsync` first and returns failures as `rejected` without sending them; it also matches the stat key's case to the event's:

```csharp
var checkedResults = await writer.CreateMarketsAsync(hockey.EventId!,
[
    new STXNewMarket("3654006", "goal_so", 2.5),
    new STXNewMarket("3654006", "HITS", 1.5),
    new STXNewMarket("3654006", "POINTS", 2.0),
], dryRun: true, validate: true);

Console.WriteLine(string.Join(", ", checkedResults.Select(r => $"{r.Status} {r.Reason}".Trim())));
// valid, rejected unsupported_stat, rejected invalid_line
```

## 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; `ErrorCode` is `event_not_open`, `unsupported_event` or `roster_unavailable` |
| `STXNotAvailableException` | This exchange does not create markets (`Feature == "markets.create"`) |
| `STXConfigException` | No app configured: `ClientId` and `ClientSecret` are not set |

From an address the app does not list, captured on US demo:

```text
STX.Sdk.STXIpNotAllowedException: this app does not allow writes from this IP address
status 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](/oauth/ip-allow-list/). A create request is never retried after a server error; resend it yourself, which is safe because a market already listed comes back `exists`.
