# Market data

> Read the STX market catalogue, prices, order-book depth, trades and events in your own product, with server-to-server credentials of your own.

Source: https://docs.stxapp.io/market-data/

> **Availability.** Available on US demo now. Live access is coming soon in the US and Ontario. See [Environments](https://docs.stxapp.io/environments/).

**Market data access lets your product read STX markets, prices and order-book depth in real time, with credentials of your own.** It is for odds comparison sites, media, research and trading tools. It reads public market data only: it never touches a member's account, so there is no consent screen and no member data.

Trading on your own account instead? You don't need data credentials: a [signed API key](/api/authentication/) reads the same data and trades too.

## What you can read

| Data | REST | WebSocket |
| --- | --- | --- |
| Market catalogue: every listed market, its status, rules and details | [`GET /api/v1/markets`](/api/rest/markets/list-markets/) | [`markets`](/websockets/channels/markets/), [`market_updates`](/websockets/channels/market-updates/) |
| Prices and depth: best bid and offer, the aggregated book, recent trades | `bids`, `offers` and `recent_trades` on each market | [`orderbook`, `ticker` and `trades`](/websockets/channels/market-data/) |
| Price history | | [`market_stats`](/websockets/channels/market-stats/) |
| Events and competitions, the catalogue markets hang off | [`GET /api/v1/events`](/api/rest/events/list-events/) | [`events`](/websockets/channels/events/) |

The `market_data` and `events` scopes cover all of it. The full list of what each scope opens is under [App scopes](/oauth/scopes/#app-scopes).

## Get data credentials

1. Go to [apps.stxapp.io/data](https://apps.stxapp.io/data) and choose **Get data access**.
2. Create your account and name your organization. You land on **Market data access** in STX Apps.
3. Confirm your email: open the link STX Apps sends you. Until you do, the page shows **Confirm your email to get demo keys.** with a **Send the link again** link instead of the key form.
4. Choose **Get data access keys**. You get an **access key ID** and a **secret key** for the US demo exchange. In OAuth terms they are your client ID and client secret.
5. Copy the secret key: it is shown once. You can get a new one from STX Apps at any time, and the old one stops working.

No redirect URI or server IP list is needed for data keys.

Owners, admins and data developers manage data access and its keys. See [STX Apps](/apps/#your-organization-and-its-roles).

Data credentials are a Server-to-server OAuth client: they hold app scopes only and use the `client_credentials` grant, with no member involved.

## Get a token

`$STX` is the exchange's base URL. On the US demo exchange that is `https://demo.stxapp.io`; every host is on [Environments](/environments/).

Send the access key ID and secret key as HTTP Basic (`-u`). A secret in the form body is refused with `invalid_client`.

```bash
curl -s -u "$STX_CLIENT_ID:$STX_CLIENT_SECRET" "$STX/oauth/token" \
  -d grant_type=client_credentials \
  -d "scope=market_data events"
```

```json
{ "access_token": "stxapp_at_…", "token_type": "bearer", "expires_in": 3600, "scope": "market_data events" }
```

`scope` is optional. Leave it out and the request still succeeds with `200`: the token carries every app scope your client holds. Captured on US demo with a client that can also create markets:

```bash
curl -s -u "$STX_CLIENT_ID:$STX_CLIENT_SECRET" "$STX/oauth/token" \
  -d grant_type=client_credentials
```

```json
{ "scope": "market_data events markets.write", "expires_in": 3600, "access_token": "stxapp_at_…", "token_type": "bearer" }
```

Read `scope` in the response for what the token can do, and name the scopes you need when you want a narrower token. A scope your client does not hold is `invalid_scope`.

The token lasts an hour and has no refresh token: request a new one when it expires. Keep the secret and the token on your server. See [App tokens](/oauth/authorization-flow/#app-tokens-client-credentials) and [Tokens and security](/oauth/tokens-and-security/).

## Read a snapshot over REST

Send the token as a bearer. This reads open markets that are taking orders:

```bash
curl -s -H "Authorization: Bearer $STX_APP_TOKEN" \
  "$STX/api/v1/markets?status=open&trading=true&competitions=NBA"
```

```json
{
  "cursor": "g2gDbQAAABCiuxfKlv2VBukIZG7zFzCAbgUB0pA2N4ZoA2EAbgcAANzktGFeBmIAAJzc",
  "markets": [
    {
      "market_id": "1eb49edd-9bf0-4e86-8971-f8a19ff44b7d",
      "symbol": "STXNBA-26OCT211900ATLORL-PRAORLNALEXANDERWALKER493112-25.5",
      "title": "N. Alexander-Walker 25.5 PTS + REB + AST",
      "question": "Will Nickeil Alexander-Walker have more than 25.5 Points + Rebounds + Assists?",
      "status": "open",
      "trading": true,
      "price": "0.4850",
      "last_traded_price": null,
      "max_price": "1.0000",
      "bids": [
        { "quantity": "652.00", "price": "0.4600" },
        { "quantity": "1111.00", "price": "0.4500" },
        { "quantity": "1704.00", "price": "0.4400" },
        { "quantity": "2439.00", "price": "0.4100" },
        { "quantity": "3076.00", "price": "0.3900" }
      ],
      "offers": [
        { "quantity": "2926.00", "price": "0.5900" },
        { "quantity": "2272.00", "price": "0.5600" },
        { "quantity": "1630.00", "price": "0.5400" },
        { "quantity": "1063.00", "price": "0.5300" },
        { "quantity": "612.00", "price": "0.5100" }
      ],
      "recent_trades": [],
      "open_interest": "0.00",
      "event_id": "776f2c7f-afc8-43fa-8a90-3cf581294143",
      "event_title": "Atlanta Hawks at Orlando Magic",
      "event_start": "2026-10-21T23:00:00.000000Z",
      "competition": "NBA",
      "sport": "Basketball"
    }
  ]
}
```

The response is trimmed to one market and a few fields; [List markets](/api/rest/markets/list-markets/) documents every field and filter. Pass `cursor` back to read the next page; it is `null` on the last one. Prices are dollar strings below the market's `max_price`.

:::caution[Best bid first, best offer last]
`bids` and `offers` carry the top of the resting book, and both are sorted by price, highest first. So the best bid is the **first** entry in `bids` (`0.4600` above) and the best offer is the **last** entry in `offers` (`0.5100`), not `offers[0]` (`0.5900`, the worst of the five).

```js
const bestBid = market.bids[0]?.price, bestOffer = market.offers.at(-1)?.price
```
:::

A token without the scope an endpoint needs gets `403`, naming the missing scope in the challenge and the body. This is a token minted with `events` only, calling `GET /api/v1/markets`:

```
HTTP/2 403
www-authenticate: Bearer error="insufficient_scope", scope="market_data"

{"error":"insufficient scope for this operation","scope":"market_data"}
```

`GET /api/v1/events` with a `market_data`-only token is refused the same way, with `scope="events"`.

### Times: microseconds on events, ISO strings on markets

An event's start and its markets' copy of it arrive in different formats. The same game, read both ways on US demo:

| Where | Field | Value |
| --- | --- | --- |
| [`GET /api/v1/events`](/api/rest/events/list-events/) | `start_time` | `1792623600000000`, an integer of Unix **microseconds** |
| `GET /api/v1/events` | `start_time_iso` | `"2026-10-21T23:00:00.000000Z"`, the same instant as an ISO 8601 string |
| [`GET /api/v1/markets`](/api/rest/markets/list-markets/) | `event_start` | `"2026-10-21T23:00:00.000000Z"`, an ISO 8601 string |
| `market:<market_id>` join reply and `market_update` | `event_start` | `1792623600000000`, Unix microseconds again |

Divide a microsecond value by 1,000,000 for Unix seconds, or by 1,000 for the milliseconds JavaScript's `Date` takes. Read `start_time_iso` when you would rather not convert.

## Stream over the WebSocket

Connect to `/socket/websocket?vsn=2.0.0` with the token in the `x-stx-oauth-token` handshake header and a `User-Agent`, then join the topics you want. The socket runs Phoenix channels, so connecting subscribes you to nothing until you join; see [WebSockets](/websockets/).

Join the book for the markets you show:

```
["1","1","orderbook","phx_join",{"market_ids":["0eae64df-90bb-48d6-92f1-37bebc90ddad"]}]
```

```
["1","1","orderbook","phx_reply",{"status":"ok","response":{"selected_market_ids":["0eae64df-90bb-48d6-92f1-37bebc90ddad"]}}]
```

What arrives next depends on whether the book has orders in it.

- **A market with resting orders** sends one `book` push straight after the join reply, carrying the current book. This is the first push from the join above, on the market from the REST example:

  ```
  ["1",null,"orderbook","book",{"timestamp":"2026-10-21T06:12:18.095898Z","market_id":"1eb49edd-9bf0-4e86-8971-f8a19ff44b7d","timestamp_us":1792563138095898,"offers":[{"quantity":"612.00","price":"0.5100","total_liquidity":"312.1200","liquidity":"312.1200","total_quantity":"612.00"},{"quantity":"1063.00","price":"0.5300","total_liquidity":"875.5100","liquidity":"563.3900","total_quantity":"1675.00"},{"quantity":"1630.00","price":"0.5400","total_liquidity":"1755.7100","liquidity":"880.2000","total_quantity":"3305.00"},{"quantity":"2272.00","price":"0.5600","total_liquidity":"3028.0300","liquidity":"1272.3200","total_quantity":"5577.00"},{"quantity":"2926.00","price":"0.5900","total_liquidity":"4754.3700","liquidity":"1726.3400","total_quantity":"8503.00"}],"bids":[{"quantity":"652.00","price":"0.4600","total_liquidity":"299.9200","liquidity":"299.9200","total_quantity":"652.00"},{"quantity":"1111.00","price":"0.4500","total_liquidity":"799.8700","liquidity":"499.9500","total_quantity":"1763.00"},{"quantity":"1704.00","price":"0.4400","total_liquidity":"1549.6300","liquidity":"749.7600","total_quantity":"3467.00"},{"quantity":"2439.00","price":"0.4100","total_liquidity":"2549.6200","liquidity":"999.9900","total_quantity":"5906.00"},{"quantity":"3076.00","price":"0.3900","total_liquidity":"3749.2600","liquidity":"1199.6400","total_quantity":"8982.00"}]}]
  ```

  On the socket both sides are best first: `offers` starts at the lowest price, unlike REST.

- **A market with an empty book** sends nothing after the join reply. There is no empty snapshot: silence means no orders. The first `book` with levels in it comes when an order rests. A push that arrives while the book is still empty has `"bids": []` and `"offers": []`.

After that, a market pushes when its book changes, at most once every 200 ms however many orders change in that time. It is not a timer. On US demo over five minutes, the live market above sent its join snapshot, one push at 32 seconds, then six pushes within three seconds while the house maker re-quoted; an empty market sent nothing in six minutes; four new crypto markets sent nothing for 23 seconds, then their first books as the maker quoted them. Each push is the whole book for that market and can repeat the levels you already hold, so replace what you hold rather than applying it as a change.

Join `trades` the same way for executed trades, and `ticker` for a price summary per market. The pushes and their fields are under [Order book, ticker and trades](/websockets/channels/market-data/).

Take a snapshot over REST when you start, then keep it current from the socket. A market that sent no `book` after your join has an empty book. Limits on both are under [Rate limits](/concepts/rate-limits/).

## Showing STX prices

Live data terms, including how STX prices are shown and credited on your site, come with live access: STX shares them before you go live. Building and testing on demo credentials is free.

### Pair it with the affiliate program

Showing STX prices on your site? Link each price to STX with your own [affiliate links](/affiliates/), and earn on the players you send. Use `to=` to land them on the event you show, for example `https://demo.stxapp.io/r/ip7owude?to=/events/06f19033-c427-4cef-9647-794204e4e2e9`.

## Ontario

STX is live for trading in Ontario. Market data access for Ontario is waitlisted in STX Apps for now: choose Ontario when you sign up, and STX emails you when Ontario demo access opens. In Ontario an app reads public market data only (`market_data` and `events`) and does not act for members.

## Next

- [App scopes](/oauth/scopes/#app-scopes): Every REST route and channel market_data and events open.
- [Order book, ticker and trades](/websockets/channels/market-data/): The streaming feeds, with every field.
- [Affiliates](/affiliates/): Earn on the players your prices send to STX.
