---
title: Connecting
description: Reach UDX over HTTPS or WebSocket in each environment, and understand what a request or a session is.
---

UDX exposes the same environments over REST and WebSocket. Use UAT to build and test; production carries real money.

| Environment | REST endpoint                             | WebSocket endpoint                        |
| ----------- | ----------------------------------------- | ----------------------------------------- |
| UAT         | `https://api.uat.udx.underdogsports.com/` | `wss://api.uat.udx.underdogsports.com/ws` |
| Production  | `https://api.underdogexchange.com/`       | `wss://api.underdogexchange.com/ws`       |

Create your UAT API key in the [UDX UAT app](https://app.uat.udx.underdogsports.com/).
Both interfaces authenticate with the same API key and select a dated contract version. See [Authentication](/reference/authentication) and [Versioning](/reference/versioning).



## Make a Request

**REST**

Every REST request stands alone. There is no session: each request carries its own credential, its own `UDX-Version`, and, for a signed request, a fresh timestamp and signature. Request and response bodies are JSON, and a request with a body sends `Content-Type: application/json`.

**WebSocket**

Open `GET /ws` and offer a supported `udx.json.<YYYY-MM-DD>` subprotocol; the exchange selects it, and a connection that offers none it supports is refused. Authenticate the upgrade with the three `UDX-Access-*` headers, signing `timestamp + GET + /ws`. The session authenticates once, at connect; there is no per-message signature, and nothing sent afterward changes who you are.

The connection acts for the API key's member. Member-private streams, such as RFQs, return only that member's data. Every frame is JSON text; a binary frame closes the connection.

A command is a JSON object with a client-assigned `id`, a `cmd`, and `params`:

```json
{
  "id": 1,
  "cmd": "subscribe",
  "params": {
    "channels": [
      {
        "channel": "rfqs",
        "tickers": ["UDX-EXAMPLE-MARKET-A"]
      }
    ]
  }
}
```

`id` is your correlation handle: a positive integer, unique among the commands you have in flight. Every direct response echoes it. Streamed data carries no `id`.

## Read Responses

**REST**

Every response carries the applied `UDX-Version`. Collections page through `next_page_url`, described in [Pagination](/reference/pagination). Trading writes answer `202 Accepted` and resolve later, described in [Asynchronous Writes](/reference/asynchronous-writes).

**WebSocket**

- Every command is a JSON object containing a client-assigned `id`, a `cmd`, and `params`. Every direct response echoes that `id`.
- `subscribe` opens one subscription per `(channel, ticker)` pair. Each pair receives its own `subscribed` response with a server-assigned subscription ID (`sid`).
- Each subscription has its own `seq`, which starts at `1` and increments once per frame.
- `unsubscribe` ends a subscription by `sid`. Its `unsubscribed` response echoes the command `id` and is a barrier after which no frame for that `sid` arrives.

See the [WebSocket API reference](/api-reference/websocket) for more details.

## Handle Errors

**REST**

Every non-2xx response is a problem details body as defined by [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457), sent as `application/problem+json`. Every body contains `type`, `title`, `status`, `detail`, and an `instance` that names the request ID. `type` is a stable `urn:udx:problem:` URN that identifies the kind of problem; it is an identifier, not a URL to dereference. Branch on the status code and `type`. `detail` is human-readable and may change. Quote the `instance` value when you contact UDX support.

For example, an invalid request parameter returns:

```json
{
  "type": "urn:udx:problem:invalid-request-parameters",
  "title": "Bad Request",
  "status": 400,
  "detail": "invalid request parameters",
  "instance": "urn:request-id:8f14e45f-ceea-4abc-a743-e89a34d3f6ef"
}
```

**WebSocket**

A rejected command returns `type: "error"` echoing the command's `id` and leaves the connection open:

```json
{
  "id": 1,
  "type": "error",
  "msg": {
    "code": "permission_denied",
    "detail": "..."
  }
}
```

A stream can also end on its own. An `error` frame that names a `sid` and carries no `id` is terminal for that subscription: no further frame arrives for it, the connection and every other subscription stay usable, and you resubscribe when the cause clears. Each operation's reference page lists the codes it can send. A connection-fatal condition is reported with a WebSocket close frame instead; see [Close Codes](/reference/close-codes).

## Stay Connected

**REST**

REST holds no connection state. Each request authenticates itself, so there is nothing to keep alive and nothing to resume.

**WebSocket**

The exchange keeps each session alive with standard [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455#section-5.5.2) ping/pong control frames. There is no application-level heartbeat message.

- The exchange sends the pings. Your client must answer each with a pong. Conformant WebSocket libraries do this automatically.
- You may also send your own pings; the exchange answers each with a pong.
- A ping not answered within the keep-alive interval closes the session with code `1001`. The interval is configuration-derived; do not hard-code it, and always answer pings.

A `sid` and its sequence exist only for the connection. A disconnected session cannot be resumed: reconnect, authenticate again, resubscribe, and rebuild state.
