> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trdrs.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect quickstart

> Get an API key, open hosted Connect for a test trader on a page you serve, and route that trader's first order from your backend, all in the sandbox.

This quickstart runs the whole Connect journey once, in the sandbox. You'll get an API key, add your
website, offer white-label paper, open hosted Connect for a test trader on a page you serve, and route
an order on the Demo that trader opens. Use this page the first time you build on Connect.

By the end you have a test trader holding a Demo they opened in hosted Connect on your page, and an
order of theirs that your backend routed. Everything here runs in the sandbox, which is free. You
need curl and Node.js 20 or later.

## Get your API key

You create your app and its API key in the Connect dashboard, where your team signs in with an email
code. Your app's API key is your backend's key: it has a name and an expiry, and carries exactly what
your trading interface needs.

1. Sign in to the Connect dashboard with your email, and create your app in the sandbox.
2. Open **API keys**, name a key, and create it.

The key is shown once, so copy it now. It starts `trdrs_ck_sandbox_` and works only in the sandbox.
Put it in your shell, beside the sandbox's address:

```bash theme={null}
export TRDRS_API_BASE_URL=https://sandbox.trdrs.co
export TRDRS_API_KEY=trdrs_ck_sandbox_...
```

Check the key with one read that changes nothing: the accounts of a trader no Connect link has named
yet.

```bash theme={null}
curl "$TRDRS_API_BASE_URL/api/account/list" \
  -H "Authorization: Bearer $TRDRS_API_KEY" \
  -H "x-trdrs-trader: trader-test-1"
```

You get `404` `trader_not_found`: the key works. An id becomes one of your traders the first time a
Connect link names it, which this quickstart does below. A `401` `invalid_key` means the key is wrong,
revoked or expired, or the base URL isn't the sandbox.

## Add your website

Hosted Connect opens only on a website you added, so add the one this quickstart serves its page on.
In the Connect dashboard, open **Websites** and add:

```text theme={null}
http://localhost:8787
```

A sandbox website is approved at once. A website is an exact origin: its scheme, host and port, with
no path and no trailing slash. It must use `https`, except `http` on `localhost` while you develop,
so `http://localhost:8787` and `http://127.0.0.1:8787` are two different websites.

<Steps>
  <Step title="Offer white-label paper">
    Switch on your white-label paper, so your test trader can open a Demo with nothing to type. In the
    Connect dashboard, open **Connect** and switch on white-label paper at its starting balance of
    100,000 US dollars.

    Your Connect now shows your white-label paper first, under your app's name.
    [Tiles and white-label paper](/guides/connect-tiles-and-paper) covers both settings.
  </Step>

  <Step title="Serve a page that opens Connect">
    Your page asks your server for a Connect link, and your server creates one for the trader it
    signed in, with your API key. Save these two files in an empty folder. The server stands in for
    your own sign-in: it names one test trader, `trader-test-1`, where your server names the trader
    your own sign-in verified.

    ```js server.mjs theme={null}
    import { randomUUID } from 'node:crypto'
    import { createServer } from 'node:http'
    import { readFile } from 'node:fs/promises'

    const base = process.env.TRDRS_API_BASE_URL ?? 'https://sandbox.trdrs.co'
    const apiKey = process.env.TRDRS_API_KEY
    if (!apiKey) throw new Error('Set TRDRS_API_KEY')

    const website = 'http://localhost:8787'
    // Your own sign-in names the trader. Never take the id from the browser.
    const trader = process.env.TRADER ?? 'trader-test-1'
    const page = await readFile(new URL('./index.html', import.meta.url))

    createServer(async (req, res) => {
      if (req.method === 'GET' && req.url === '/') {
        res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
        return res.end(page)
      }
      if (req.method !== 'POST' || req.url !== '/api/connect-link') {
        res.writeHead(404)
        return res.end()
      }
      try {
        const created = await fetch(`${base}/api/connect/links`, {
          method: 'POST',
          headers: {
            authorization: `Bearer ${apiKey}`,
            'content-type': 'application/json',
            'idempotency-key': randomUUID(),
          },
          body: JSON.stringify({ origin: website, trader, expiresInSeconds: 300 }),
        })
        const answer = await created.json()
        if (created.status !== 201) throw new Error(`${created.status} ${answer.error}`)
        console.log(`Connect link ${answer.link.id} for ${trader}`)
        // The page gets the short-lived token, and nothing else.
        res.writeHead(201, { 'content-type': 'application/json', 'cache-control': 'no-store' })
        res.end(JSON.stringify({ token: answer.link.token }))
      } catch (error) {
        console.error('No Connect link:', error.message)
        res.writeHead(502, { 'content-type': 'application/json' })
        res.end(JSON.stringify({ error: 'connect_link_failed' }))
      }
    }).listen(8787, 'localhost', () => console.log(`Open ${website}`))
    ```

    ```html index.html theme={null}
    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>Connect quickstart</title>
      </head>
      <body>
        <button id="connect" type="button">Connect a trading account</button>
        <p id="status" role="status"></p>

        <script type="module">
          import { createConnect } from 'https://trdrsco-connect-sandbox.fly.dev/sdk/connect.js'

          const button = document.querySelector('#connect')
          const status = document.querySelector('#status')

          button.addEventListener('click', async () => {
            status.textContent = 'Opening Connect'
            const response = await fetch('/api/connect-link', { method: 'POST' })
            if (!response.ok) {
              status.textContent = 'Your server could not create a Connect link.'
              return
            }
            const { token } = await response.json()
            const connect = createConnect({ token })
            connect.on((event) => {
              if (event.type === 'ready') status.textContent = ''
              if (event.type === 'connected') status.textContent = 'Connected.'
              if (event.type === 'closed') status.textContent = 'Connect was closed.'
              if (event.type === 'error') status.textContent = `Connect stopped: ${event.code}`
            })
            connect.open()
          })
        </script>
      </body>
    </html>
    ```

    Start the server in the shell that holds your two variables:

    ```bash theme={null}
    node server.mjs
    ```

    It prints `Open http://localhost:8787`.
  </Step>

  <Step title="Open hosted Connect">
    Open `http://localhost:8787`, using exactly that address, and press **Connect a trading account**.
    Hosted Connect opens over your page, with your white-label paper first, under your app's name.
    Pick it and confirm. There's nothing to type: the trader's Demo opens at the balance you set,
    Connect closes, and your page says `Connected.`

    Your server printed the Connect link's id. Put it in your shell:

    ```bash theme={null}
    export LINK_ID=...
    ```

    A `connected` event is a signal for your page, never proof. Confirm on your server by reading the
    link:

    ```bash theme={null}
    curl --fail-with-body "$TRDRS_API_BASE_URL/api/connect/links/$LINK_ID" \
      -H "Authorization: Bearer $TRDRS_API_KEY"
    ```

    The `link` reads `"state": "connected"`, names your trader in `trader`, and carries the Demo's
    `accountId` in `result`. The trader is connected.
  </Step>

  <Step title="Read the trader's accounts">
    From now on your server names the trader on every request: your API key, and your own id for them
    in `x-trdrs-trader`. List the trader's accounts:

    ```bash theme={null}
    curl --fail-with-body "$TRDRS_API_BASE_URL/api/account/list" \
      -H "Authorization: Bearer $TRDRS_API_KEY" \
      -H "x-trdrs-trader: trader-test-1"
    ```

    The response's `accounts` holds the Demo: `provider` is `paper`, `accountNumber` starts `DEMO-`,
    and `balance` is `100000`. Only this trader's accounts appear. Put the two values the trading
    routes name the account by in your shell:

    ```bash theme={null}
    export PROVIDER=paper
    export ACCOUNT=DEMO-...
    ```
  </Step>

  <Step title="Route the trader's first order">
    Before an order, ask what the account may do on the instrument. It runs the checks an order runs
    without sending anything, and on the paper book it readies the instrument's prices for the order
    that follows. The examples trade `HYPERLIQUID:BTC`, the Bitcoin perpetual, whose prices come from a
    public feed, so they run in the sandbox.

    ```bash theme={null}
    curl --fail-with-body "$TRDRS_API_BASE_URL/api/trading/actions?provider=$PROVIDER&account=$ACCOUNT&instrument=HYPERLIQUID%3ABTC" \
      -H "Authorization: Bearer $TRDRS_API_KEY" \
      -H "x-trdrs-trader: trader-test-1"
    ```

    `actions.open.allowed` is `true`. Now route the order, as your interface would when the trader
    presses Buy. `clientOrderId` makes it safe to retry: send the same id again and the order is
    never placed twice.

    ```bash theme={null}
    curl --fail-with-body -X POST "$TRDRS_API_BASE_URL/api/trading/order?provider=$PROVIDER&account=$ACCOUNT" \
      -H "Authorization: Bearer $TRDRS_API_KEY" \
      -H "x-trdrs-trader: trader-test-1" \
      -H "Content-Type: application/json" \
      -d '{ "instrument": "HYPERLIQUID:BTC", "side": "buy", "qty": 0.01, "orderType": "market", "clientOrderId": "quickstart-order-1" }'
    ```

    The response names the order and how it filled: its `providerOrderId`, `filledQty` of `0.01` and
    `avgFillPrice`. The trader holds 0.01 bitcoin on their Demo, and your integration works.

    If the order answers `422` `market_data_unavailable` with `params.cause` `mark_missing`, the
    instrument's first price hadn't reached the account yet. Wait a few seconds and send the same
    request again, with the same `clientOrderId`.
  </Step>
</Steps>

## Test it end to end

Everything above ran in the sandbox. Before you build on it, check the parts you'll rely on:

* **Retry the order.** Send the order request again with the same `clientOrderId`. It answers `409`,
  and the trader still holds 0.01, not 0.02.
* **Name a trader trdrs doesn't know.** List the accounts of `trader-test-2`, an id no Connect link has
  named. You get `404` `trader_not_found`: an id becomes one of your traders the first time a Connect
  link names it.
* **Close Connect without connecting.** Open Connect again and close it. Your page hears `closed`, and
  the new link reads `mounted`, not `connected`.
* **Connect a second trader.** Stop the server, start it again with `TRADER=trader-test-2 node server.mjs`,
  and open your white-label paper again. The new trader holds a Demo of their own, and each trader's
  account list shows only their own Demo.
* **Close the position.** Send `POST /api/trading/flatten?provider=$PROVIDER&account=$ACCOUNT` with
  `{ "instrument": "HYPERLIQUID:BTC" }`, your API key and the trader's id. The Demo is flat again.

## Next steps

<CardGroup cols={2}>
  <Card title="Connect SDK" icon="link" href="/sdks/connect">
    Learn every option, event and error of hosted Connect on your page.
  </Card>

  <Card title="Route your traders' orders" icon="chart-line" href="/guides/route-your-traders-orders">
    Read accounts and streams, and route, move and cancel your traders' orders.
  </Card>

  <Card title="Tiles and white-label paper" icon="table-cells" href="/guides/connect-tiles-and-paper">
    Choose the tiles your Connect offers, and run your traders' Demos.
  </Card>

  <Card title="Pass conformance" icon="list-check" href="/guides/connect-conformance">
    Run your app's whole journey through the graded sandbox check.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.