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

# Portfolio WebSocket

> Unauthenticated Socket.IO feed for public portfolio snapshots.

Unauthenticated Socket.IO feed for public portfolio snapshots. The wire shape mirrors `GET /users/portfolio` — same filters, same `data` / `meta` payload. Real-time updates are pushed after trades on the subscribed wallet or market.

## Connection

|                     | Value                                            |
| ------------------- | ------------------------------------------------ |
| Socket.IO namespace | `/portfolio-public`                              |
| URL                 | `https://api-mainnet.xo.market/portfolio-public` |

No authentication is required. Identity is supplied per subscription via the `user` field (wallet address or username). Private profiles return an empty paginated result — the same rule as the REST endpoint.

```ts theme={null}
import { io } from "socket.io-client";

const socket = io("https://api-mainnet.xo.market/portfolio-public", {
  transports: ["websocket", "polling"],
});
```

## Client events (emit)

### `portfolio_subscribe`

Starts or updates a subscription and emits an immediate snapshot.

Required field:

| Field  | Type   | Description                                                             |
| ------ | ------ | ----------------------------------------------------------------------- |
| `user` | string | Target wallet address (`0x…`, any case) or username (case-insensitive). |

Optional filter fields (same semantics as `GET /users/portfolio`):

| Field              | Type    | Default     | Description                                                                   |
| ------------------ | ------- | ----------- | ----------------------------------------------------------------------------- |
| `page`             | number  | `1`         | Page number (1-based).                                                        |
| `take`             | number  | `10`        | Page size.                                                                    |
| `marketScope`      | string  | `default`   | `default`, `withClob`, `onlyClob`, `onlyClobPulse`, `onlyPulse`, `withPulse`. |
| `status`           | string  | `all`       | `active`, `closed`, `all`, `resolved`.                                        |
| `marketId`         | number  | —           | Internal market id (switches to detail mode).                                 |
| `contractMarketId` | string  | —           | On-chain market key (`0x…`) or numeric contract id.                           |
| `marketStatus`     | string  | —           | Filter by indexer market status (e.g. `ACTIVE`).                              |
| `search`           | string  | —           | Case-insensitive title search.                                                |
| `sortBy`           | string  | `createdAt` | `pnl`, `value`, `expiry`, `createdAt`.                                        |
| `sortOrder`        | string  | `DESC`      | `ASC` or `DESC`.                                                              |
| `refresh`          | boolean | `false`     | Bypass portfolio cache before building the snapshot.                          |

```ts theme={null}
socket.emit("portfolio_subscribe", {
  user: "0x3202b94b4d90A2F5Cc1750334e177FF7Ef95560F",
  page: 1,
  take: 10,
  marketScope: "onlyClob",
  status: "active",
  refresh: true,
});
```

### `portfolio_refresh`

Rebuilds the snapshot using the current subscription filters, or updates filters when a payload is supplied.

```ts theme={null}
socket.emit("portfolio_refresh", {
  page: 1,
  take: 10,
  marketScope: "onlyClobPulse",
  status: "closed",
});
```

Subscribe with `portfolio_subscribe` before calling `portfolio_refresh`.

### `portfolio_unsubscribe`

Clears the subscription and leaves wallet / market rooms.

```ts theme={null}
socket.emit("portfolio_unsubscribe", {});
```

## Server events (listen)

### `portfolio_snapshot`

Main data event. `data` and `meta` match the REST response from `GET /users/portfolio`.

```ts theme={null}
type PortfolioSnapshotEvent = {
  type: "portfolio_snapshot";
  data: unknown[];
  meta: {
    page: number;
    take: number;
    itemCount: number;
    pageCount: number;
    hasPreviousPage: boolean;
    hasNextPage: boolean;
  };
  filters: Record<string, unknown>;
  emittedAt: string; // ISO timestamp
};
```

The server pushes refreshed snapshots when:

* the subscribed wallet executes a trade, or
* a fill moves the price on a market the socket is scoped to (via `marketId` or `contractMarketId`).

### `portfolio_error`

Validation, lookup, or snapshot-build failures.

```ts theme={null}
type PortfolioErrorEvent = {
  message: string;
};
```

Common messages:

| Message                                               | Cause                                 |
| ----------------------------------------------------- | ------------------------------------- |
| ``A `user` (wallet address or username) is required`` | Missing `user` on subscribe.          |
| `User not found`                                      | Unknown wallet / username.            |
| ``Subscribe with a `user` before refreshing``         | `portfolio_refresh` before subscribe. |
| `Failed to build portfolio snapshot`                  | Internal enrichment error.            |

## Recommended flow

1. Connect to `/portfolio-public` (no auth header).
2. On `connect`, emit `portfolio_subscribe` with `user` and active filters.
3. Replace local portfolio state on every `portfolio_snapshot`.
4. On filter changes, emit `portfolio_subscribe` again with updated params.
5. On explicit refresh, emit `portfolio_refresh`.
6. On leaving the view, emit `portfolio_unsubscribe` or disconnect.

Do not poll REST on an interval while this socket is active.

## REST equivalent

```http theme={null}
GET /api/users/portfolio?user=0x3202…&page=1&take=10&marketScope=onlyClob&status=active&refresh=true
```

Equivalent subscribe payload:

```json theme={null}
{
  "user": "0x3202b94b4d90A2F5Cc1750334e177FF7Ef95560F",
  "page": 1,
  "take": 10,
  "marketScope": "onlyClob",
  "status": "active",
  "refresh": true
}
```
