# `Bourse.WS.URLRouting`
[🔗](https://github.com/ZenHive/bourse/blob/main/lib/bourse/ws/url_routing.ex#L1)

Pure URL resolution for WebSocket endpoints.

Given a `%Bourse.Exchange{}`, returns the public or private WS URL appropriate
for the current sandbox flag, with `{hostname}` interpolated from
`exchange.hostname`.

URLs come from `Bourse.WS.Config`, which prefers resolved `websocket.urls`
from the spec and falls back to hand-maintained bases when absent.

Binance USD-M additionally authors a `/market` host; `stream_url/2` picks it
for regular market streams so a `/public` socket cannot silently drop them.

# `authored_usdm_host?`

```elixir
@spec authored_usdm_host?(Bourse.Exchange.t(), String.t()) :: boolean()
```

True when `url` is an authored USD-M public or market host, including the
legacy unrouted `/ws` alias that still behaves like `/public`.

# `group_channels_by_url`

```elixir
@spec group_channels_by_url(Bourse.Exchange.t(), [String.t() | map()]) :: [
  {String.t() | nil, [String.t() | map()]}
]
```

Groups channels by the authored public host that carries them.

The groups retain first-host and channel order so callers can subscribe
deterministically while using one connection per host.

# `market_url`

```elixir
@spec market_url(Bourse.Exchange.t()) :: String.t() | nil
```

USD-M regular-market stream host (`/market/ws`), or nil when the venue has none.

Binance split USD-M market streams off the high-frequency `/public` host.
`watch_ticker` and other `/market` streams resolve here; other venues stay nil.

# `private_url`

```elixir
@spec private_url(Bourse.Exchange.t()) :: String.t() | nil
```

Private WS URL for the given exchange, or nil if the exchange has no WS config
or no private endpoint.

# `public_url`

```elixir
@spec public_url(Bourse.Exchange.t()) :: String.t() | nil
```

Public WS URL for the given exchange, or nil if the exchange has no WS config.

Honors `exchange.sandbox` — returns the sandbox URL when the flag is true
and a sandbox URL is configured.

# `stream_url`

```elixir
@spec stream_url(Bourse.Exchange.t(), String.t()) :: String.t() | nil
```

Public-section URL that actually delivers `channel`.

USD-M `@miniTicker` / `@ticker` / `@aggTrade` live on `/market/ws`; depth,
`@trade`, and `@bookTicker` stay on `/public/ws`. Other venues return
`public_url/1`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
