# What you can do

> Every feature of the TypeScript SDK, the calls that use it, and which STX exchange has it today.

Source: https://docs.stxapp.io/sdks/typescript/capabilities/

One SDK works against every STX exchange. Not every exchange has every feature yet: OAuth apps are on US demo first, for example. A call that needs a feature the exchange does not have throws `STXNotAvailableException`, and `capabilities()` tells you up front, so the same code runs against Ontario production today and picks up new features as each exchange gets them.

## Features and their calls

| Feature | Id | SDK calls | Guide |
|---|---|---|---|
| Markets, events and prices | | `markets()`, `iterMarkets()`, `market()`, `events()`, `iterEvents()`; channels `ws.ticker()`, `ws.market()`, `ws.orderbook()`, `ws.trades()`, `ws.marketStats()`, `ws.marketUpdates()` | [Markets](/sdks/typescript/markets/), [WebSockets](/sdks/typescript/websockets/) |
| Trading | | `placeOrder()`, `placeOrders()`, `cancelOrder()`, `cancelOrders()`, `cancelAllOrders()`, `order()`, `orders()` | [Trading](/sdks/typescript/trading/) |
| Your account | | `me()`, `balance()`, `positions()`, `fills()`, `settlements()`, `deposits()`, `withdrawals()`, `fees()`, `accountMarketStats()`; channels `ws.accountView()`, `ws.balances()`, `ws.orders()`, `ws.fills()`, `ws.positions()`, `ws.settlements()` | [Portfolio](/sdks/typescript/portfolio/) |
| Event players | `event_players` | `eventPlayers()`, `statsFor()` | [Markets](/sdks/typescript/markets#creating-markets) |
| Leaderboard and public profile | `leaderboard` | `leaderboard()`, `leaderboardMe()`, `updateProfile()` | [Leaderboard](https://docs.stxapp.io/concepts/leaderboard/) |
| Combos | `combos` | `ws.comboPlayer()`, `ws.comboMaker()` | [Combos](/sdks/typescript/combos/) |
| OAuth | `oauth` | `discoverMetadata()`, `OAuthClient.discover()` | [Build an app](https://docs.stxapp.io/apps/) |
| Members connect their STX account | `oauth.member` | `oauth.beginAuthorization()`, `oauth.completeAuthorization()`, `oauth.memberClient()`, `oauth.refreshToken()`, `oauth.unlink()`, `oauth.revoke()` | [Sign in with STX](/sdks/typescript/sign-in-with-stx#connect-your-stx-account) |
| App tokens | `oauth.app` | `oauth.appClient()`, `oauth.appToken()`, `oauth.clientCredentials()` | [Acting as your app](/sdks/typescript/sign-in-with-stx#acting-as-your-app) |
| Sign in with STX | `connect` | `createConnect()`: `start()`, `finish()`, `refresh()`, `disconnect()`, `userInfo()` | [Sign in with STX](/sdks/typescript/sign-in-with-stx#sign-in-with-your-stx-account) |
| Terms accepted on an STX page | `terms.stx_page` | `STXTermsRequiredException.termsUrl`, `termsUrlWithReturn()`, the `onTermsRequired` session hook | [When STX has new terms](/sdks/typescript/sign-in-with-stx#when-stx-has-new-terms-for-the-member) |
| Creating markets | `markets.create` | `createMarkets()` (with `dryRun`), as an app holding `markets.write` | [Creating markets](/sdks/typescript/markets#creating-markets) |
| Market types | `market_types` | `marketTypes()` | [Creating markets](https://docs.stxapp.io/apps/creating-markets/) |

Features without an id are on every exchange. Members still being verified (`STXAccountPendingException`) and IP allow-list refusals (`STXIPNotAllowedException`) come with OAuth wherever it is on.

## Where each feature is available

Checked against the live exchanges on 2026-10-21, and Ontario demo again on 2026-10-23.

| Feature | US demo (`demo.stxapp.io`) | Ontario demo (`demo.stxapp.ca`) | Ontario production (`api.on.stxapp.ca`) |
|---|---|---|---|
| Markets, trading, your account | yes | yes | yes |
| `event_players` | yes | yes | yes |
| `leaderboard` | yes | no | no |
| `oauth` | yes | no | no |
| `oauth.member` | yes | no | no |
| `oauth.app` | yes | no | no |
| `connect` | yes | no | no |
| `terms.stx_page` | yes | no | no |
| `markets.create` | yes | no | no |
| `market_types` | yes | no | no |
| Exchange version (`/api_version`) | `3.3.196` | `3.3.194-5` | `3.3.186-6` |

Exchanges gain features with their releases, so ask the exchange rather than this table when your code needs to know.

## Ask the exchange: `capabilities()`

```ts
import { STX } from "@stxapp/stx-typescript";

const stx = new STX({ environment: "ontario-demo" });
const caps = await stx.capabilities();
console.log(caps.toJSON());
```

On Ontario demo:

```json
{
  "host": "demo.stxapp.ca",
  "apiVersion": "3.3.194-5",
  "checkedAt": "2026-10-23T17:37:03.512Z",
  "scopesSupported": [],
  "features": {
    "oauth": false,
    "oauth.member": false,
    "oauth.app": false,
    "connect": false,
    "terms.stx_page": false,
    "markets.create": false,
    "market_types": false,
    "event_players": true,
    "leaderboard": false,
    "combos": true
  }
}
```

On US demo every feature is `true`, and `scopesSupported` is the list the exchange publishes:

```json
[
  "openid", "profile.read", "balance.read", "portfolio.read", "orders.read",
  "transfers.read", "orders.write", "market_data", "events", "markets.write"
]
```

- `caps.features.combos` is `true`, `false`, or `null` when it could not be checked (a check that needs credentials on a client without them, or an exchange that did not answer).
- `caps.has("combos")` is true only when the exchange is known to have it.
- `caps.require("combos")` throws `STXNotAvailableException` when the exchange is known not to have it.

The answer is cached on the client; `capabilities({ refresh: true })` checks again. A missing feature never makes `capabilities()` or `new STX()` throw, and the calls you already make are unaffected: only a call that needs the missing feature throws.

### How each feature is decided

Every check reads what the exchange publishes. Nothing is created, changed or cancelled.

| Id | Available when |
|---|---|
| `oauth` | `GET /.well-known/oauth-authorization-server` returns the discovery document. A 404, or a 403 page from in front of the exchange (not JSON), means no. |
| `oauth.member` | OAuth is on, `grant_types_supported` lists `authorization_code`, and `scopes_supported` lists a member scope such as `orders.read`. |
| `oauth.app` | OAuth is on and `grant_types_supported` lists `client_credentials`. |
| `connect` | OAuth is on and `scopes_supported` lists `openid`. |
| `terms.stx_page` | Members can connect and `scopes_supported` does not list `terms.write`. |
| `markets.create` | App tokens are on and `scopes_supported` lists `markets.write`. |
| `market_types` | A signed `GET /api/v1/types` answers. A 404 with `{"error": "not_found"}` (no such route) means no. |
| `event_players` | A signed `GET /api/v1/events/{event_id}/players` for an event that does not exist answers 404 `{"error": "event not found"}`. A 404 `{"error": "not_found"}` means no such route. |
| `leaderboard` | A signed `GET /api/v1/leaderboard?limit=1` answers. A 404 (no such route, or `{"error": "Leaderboard is not enabled"}`) means no. |
| `combos` | A join of the combo player topic for a user id nobody has is refused as `unauthorized`. `unmatched topic` means the exchange has no combo topics. |

The OAuth discovery document and its fields are described in [OAuth](https://docs.stxapp.io/oauth/), and the combo topics in [Combo negotiation](https://docs.stxapp.io/websockets/channels/combo-negotiation/).

## When a feature is not available

`STXNotAvailableException` extends `STXAPIException`, so it is an `STXException` like every other SDK error. It carries:

- `feature`: the id from the tables above, or `scope:<name>` for a scope the exchange does not offer.
- `host`: the exchange that answered.
- `message`: a sentence naming both.
- `statusCode` and `body`: the answer it was decided from, when there was one.

Captured on Ontario demo on 2026-10-21:

```ts
await stx.marketTypes();
// STXNotAvailableException: Market types are not available on demo.stxapp.ca yet.
//   feature: "market_types", host: "demo.stxapp.ca", statusCode: 404, body: { error: "not_found" }

await discoverMetadata("https://demo.stxapp.ca");
// STXNotAvailableException: OAuth is not available on demo.stxapp.ca yet.
//   feature: "oauth", host: "demo.stxapp.ca", statusCode: 404
```

`STXCombosUnavailableException` is still the channel exception it was, and `instanceof STXNotAvailableException` is true for it too, so either catch works:

```ts
import { STXNotAvailableException } from "@stxapp/stx-typescript";

try {
  const player = await ws.comboPlayer();
  // ...
} catch (err) {
  if (err instanceof STXNotAvailableException) {
    console.log(`${err.feature} is not on ${err.host} yet; hiding combos`);
  } else {
    throw err;
  }
}
```

Which calls throw it, by feature:

| Id | Throws from |
|---|---|
| `combos` | `ws.comboPlayer()`, `ws.comboMaker()` |
| `oauth` | `discoverMetadata()`, `OAuthClient.discover()`, and any token, refresh, revoke or introspect call on an exchange without OAuth |
| `connect` | `createConnect()` calls, when the exchange has no OpenID configuration or userinfo endpoint |
| `market_types` | `marketTypes()` |
| `event_players` | `eventPlayers()`, `statsFor()` |
| `leaderboard` | `leaderboard()`, `leaderboardMe()`, `updateProfile()` |
| `markets.create` | `createMarkets()` |
| `scope:<name>` | `clientCredentials()`, `appClient()` calls and `completeAuthorization()`, when the exchange answers `invalid_scope` for a scope its discovery document does not list (the client must come from `OAuthClient.discover()` or `fromMetadata()` to know the list) |
