# App tokens and market data

> Read the market catalog and stream market data as your app, with a client_credentials app token.

Source: https://docs.stxapp.io/sdks/python/app-tokens/

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

An app token is your app acting as itself: no member, no sign-in, no consent screen. It reads the market and event catalog and streams the market data channels, which is how an app shows markets and prices to people who have not connected an STX account. It comes from the `client_credentials` grant, so it needs a client secret (a Web app or Server-to-server app). See [App tokens](/oauth/authorization-flow/#app-tokens-client-credentials) for the protocol.

App tokens need an exchange that offers them; [What you can do](/sdks/python/capabilities/) lists which do. In Ontario an app reads public market data only, with the `market_data` and `events` scopes.

## An app client

```python
from stx import oauth

app = oauth.OAuthClient.discover(
    client_id="stxapp_client_g2XU2Kyg3EJoOhC9sndPe2bPvZENsbkkFkzCQFgkWHa0emOUt",
    client_secret=CLIENT_SECRET,
    region="us",
    env="demo",
)

with app.app_client("market_data events") as catalog:
    for market in catalog.markets(status="open", trading=True, limit=3):
        print(market.symbol, market.title)
```

Sample output:

```text
STXMLB-26OCT182000LADMIL-GAMELAD MLB Playoff LAD @ MIL
STXMLB-26OCT182000LADMIL-GAMEMIL MLB Playoff LAD @ MIL
STXMLB-26OCT192000LADMIL-GAMELAD MLB Playoff LAD @ MIL
```

`app_client(scope)` returns an `STX` client whose requests carry an app token. The SDK mints the token on first use, keeps it until 60 seconds before it expires, mints a new one then, and once more if the exchange answers `401`. Every client built for the same scope shares one token. The token request and its answer look like this:

```python
tokens = app.client_credentials("market_data events")
print(tokens.scope, tokens.token_type, tokens.expires_in, tokens.refresh_token)
```

```text
market_data events bearer 3600 None
```

An app token has no refresh token: the SDK simply mints another.

## What an app token can call

| Scope | Calls |
|---|---|
| `market_data` | `markets()`, `iter_markets()`, `market()`, and the market data channels: `orderbook`, `ticker`, `trades`, `markets`, `market_stats`, `market_updates`, `market` |
| `events` | `events()`, `iter_events()`, `event_players()`, `market_types()` |
| `markets.write` | `create_markets()`, granted by STX on request. See [Create markets](/sdks/python/create-markets/) |

Member calls (`balance()`, `place_order()`...) are refused with `STXForbiddenException`: an app token has no member. A scope your app does not hold is refused with `oauth.STXOAuthException` (`invalid_scope`), and a scope the exchange does not offer at all with `STXNotAvailableError` (`scope:<name>`).

## Stream market data

With `oauth.AsyncOAuthClient`, the app client's socket streams the market data channels with the app token:

```python
import asyncio

from stx import oauth

async def main():
    app = await oauth.AsyncOAuthClient.discover(
        client_id=CLIENT_ID, client_secret=CLIENT_SECRET, region="us", env="demo"
    )
    async with app, app.app_client("market_data") as catalog:
        page = await catalog.markets(status="open", trading=True, limit=1)
        async with catalog.websocket() as ws:
            channel = await ws.market(page.items[0].market_id)
            market = (await channel.wait_snapshot())["reply"]
            print(market["symbol"], market["last_traded_price"], market["max_price"], market["event_brief"])
            await ws.ticker(on_message=lambda msg: print(msg.event, msg.payload))
            await ws.run_forever()

asyncio.run(main())
```

Sample output:

```text
STXMLB-26OCT182000LADMIL-GAMELAD 0.4000 1.0000 Oct 18 at 08:00 PM EDT
```

`ws.market(market_id)` is one market in full: the join reply is every market field plus `ob`, its aggregated book, then `market_update` carries the fields that change and `order_book_update` a fresh book about every 200 ms. Prices are dollar strings, as everywhere in the SDK. The socket mints a fresh app token on every reconnect. Every channel is in [WebSockets](/sdks/python/websockets/).

## A token you already have

An access token from anywhere (your own OAuth code, another service) can be used directly, without refresh:

```python
from stx import STX

with STX(region="us", env="demo", access_token=token) as client:
    print(client.markets(limit=1).items[0].symbol)
```
