# Integrate with STX

> Pick your environment, register and create an API key, sign your first request, and connect over WebSocket.

Source: https://docs.stxapp.io/quick-start/

export const US = ENV.regions.find((r) => r.id === 'us').environments.find((e) => e.id === 'integration')
export const CA = ENV.regions.find((r) => r.id === 'ca').environments.find((e) => e.id === 'integration')
export const CA_PROD = ENV.regions.find((r) => r.id === 'ca').environments.find((e) => e.id === 'production')

Go from nothing to a resting order on the book, with a socket streaming your own
fills as they happen.

By the end you will have:

- an API key you created yourself, and a signed request the exchange accepts
- your `user_id`, which your account channel topics are keyed on
- an order placed and cancelled against a live market
- a WebSocket connection pushing your orders, fills and positions as they happen

Everything here runs against a demo environment: a real-time order book
with other developers trading in it, and no real money at risk. You pick which
one in the next step, and the interactive panels below follow that choice.

## Authentication

### 1. The environment

STX runs two exchanges, one in the US and one in Ontario. Pick the one you are
integrating with. They sit under different regulators and accounts do not carry
across, so a key from one will not work on the other. See [United States and
Canada](/concepts/us-and-canada/) if you are not sure which.

| Exchange | Demo | Production |
| --- | --- | --- |
| **United States** | <code>{US.url}</code> | not yet open for trading |
| **Canada (Ontario)** | <code>{CA.url}</code> | <code>{CA_PROD.url}</code> |

These are the API hosts your requests go to. Demo environments carry no real
money and are shared by every developer, so the order books have genuine
activity in them.

:::caution[Demo keys do not work in production]
A key belongs to the environment it was created in. When you move to production,
register there and create a new key; a demo key is rejected with a 401.
:::

### 2. Register and create an API key

Keys are self-service. Register on the web app for the exchange you are
integrating with, then mint a key from your profile.

| Exchange | Register at |
| --- | --- |
| United States | <a href={US.appUrl}>{US.appUrl.replace('https://', '')}</a> |
| Ontario | <a href={CA.appUrl}>{CA.appUrl.replace('https://', '')}</a> |
| Ontario production | <a href={CA_PROD.appUrl}>{CA_PROD.appUrl.replace('https://', '')}</a> |

Accounts do not carry across exchanges. Register on the one you are targeting,
and see [United States and Canada](/concepts/us-and-canada/) if you are not sure
which.

1. Register, and **verify your email address**
2. Open the profile icon, top right → **My Profile** → **API Keys**
3. **Create key**, and give it a nickname
4. Choose **Read only** or **Read/Write**. Read/Write is required to place or
   cancel orders; this is the value `GET /api/v1/me` returns as `scope`
5. Optionally paste your own **Ed25519 public key**. Leave it blank and STX
   generates the keypair for you
6. **Create**

You get a **key ID** and, unless you supplied your own public key, a
**private key PEM**:

```text
Key ID:      a1b2c3d4e5f60718293a4b5c6d7e8f90
Private key: -----BEGIN PRIVATE KEY-----
             MC4CAQAwBQYDK2VwBCIEIH...
             -----END PRIVATE KEY-----
```

:::caution[The private key is shown once]
It cannot be re-downloaded. If you lose it, delete that key and create a new one.
:::

<details>
<summary>Optional: bring your own key, so the private half never leaves your infrastructure</summary>

Leaving the public key field blank is fine. STX generates the pair and shows you
the private key once. If you would rather it never existed on someone else's
server, generate the pair yourself and paste the **public** half into step 5:

```bash title="Generate an Ed25519 keypair"
openssl genpkey -algorithm ed25519 -out stx.pem
openssl pkey -in stx.pem -pubout        # paste this into the dialog
```

Worth doing for a production trading system; unnecessary while you are trying
the API out.

</details>

One key authenticates both surfaces: APIs and the WebSocket channels.

### 3. Deposit

Nothing to do. Demo accounts are credited with test funds automatically
when you register, so you have a balance to trade against from the moment your
key works. There is no deposit step and no funding request.

The money is not real and neither is the risk: demo environments settle
in test currency and are wiped independently of production. Read your available
balance and exposure with [`GET /api/v1/account/balance`](/api/rest/account/get-account-balance/),
or stream them as they change on the [`balances`](/websockets/channels/balances/)
socket channel.

Placing an order reserves liability against that balance. If an order is
rejected for insufficient funds on an account you have just created, check that
you registered on the demo host in the table above rather than a
production one.

Run the balance down and you can ask for a top-up in **#dev** on
[Discord](https://discord.gg/yF9eVzPzNZ), which is also where to bring anything
else that comes up while you integrate. You will be talking to the engineers who
build the exchange.

## REST

### 4. Send a request

Enter your demo key ID and private key below, pick any endpoint and send it
for real. You see the signed headers, a copyable curl command and the actual
response. Signing happens in your browser with WebCrypto, and your private key
is never transmitted. It is saved in this browser's local storage so the other
widgets on this site can use it, until you clear it. Only demo hosts are
offered, so use a demo key.

## WebSockets

### 5. Connect over WebSocket

The same key authenticates the WebSocket, but the handshake signs differently
from a REST call: always `GET`, and against the socket path only.

```
message = timestamp_ms + "GET" + "/socket/websocket"
```

Note the path excludes the query string, even though you connect to
`wss://<host>/socket/websocket?vsn=2.0.0` on whichever exchange you chose.

Send a `User-Agent` header on the WebSocket handshake. A handshake without one
is refused with `403`. Browsers always send one; some WebSocket libraries, such
as Node's `ws`, do not unless you set it.

Your `user_id` comes only from `GET /api/v1/me`. There is no other way to look it up.

:::tip[Handshake accepted, join refused]
A bad handshake does not fail where you would expect it to. The connection opens,
and the failure surfaces later when you join an account channel, as
`{"status":"error","reason":"unauthorized"}`. That almost always means the header
prefix rather than the key: the socket needs `X-STX-ACCESS-*`, and the REST names
without the prefix are invisible to it. See
[Authentication](/api/authentication/).
:::

Connect for real, right now. This joins the live market feed from your browser.
A browser cannot add signed headers to a WebSocket handshake, so this connection
is unsigned, which market-wide channels accept:

<SocketTry />

### Account channels

Your own channels, such as `orders`, `fills`, `positions` and `balances`, need a
signed connection, and a browser cannot sign a WebSocket handshake. Run one from
your machine instead:

- [stx-api-examples](https://github.com/stxapp/stx-api-examples): `watch_channel.py`
  and `watch_channel.mjs` join any channel with your API key, for example
  `--topic 'orders:<user_id>'`, and look up your `user_id` for you.
- The [SDKs](/sdks/) sign the connection and keep it alive for you.

See [WebSocket channels](/websockets/) for the full channel list and frame format,
or watch it live on [`cancel_on_disconnect`](/risk-controls/).

## Next

<div>

- [Postman collection](/downloads/stx-rest-api.postman_collection.json): every REST endpoint, with the signing script wired in
- [REST API reference](/api/rest/): every endpoint, request and response
- [WebSocket channels](/websockets/): every channel, with its topic and payloads
- [Runnable examples on GitHub](https://github.com/stxapp/stx-api-examples): Python quickstart and a live watcher
- [Rate limits](/concepts/rate-limits/)
- [Fees](/concepts/fees/)
- [Account limits](/concepts/account-limits/)

</div>
