# Leaderboard


Source: https://docs.stxapp.io/concepts/leaderboard/

The leaderboard ranks members by what they did on the exchange: how much they
traded and made, how many contracts and markets they settled, how often they
won, their biggest win, their return and their longest winning streak. It is public
inside the platform (every ranked row shows a **handle** and an **avatar**,
never a name, an email or an account id), and you control whether you appear on
it.

:::note[Not every environment has it]
The leaderboard is a feature STX turns on per environment. Where it is off, every
`/api/v1/leaderboard/*` endpoint answers `404 {"error": "Leaderboard is not enabled"}`.
:::

## Metrics

| `metric` | What is ranked | `value` type |
| --- | --- | --- |
| `volume` | Notional traded: `filled quantity × market max_price`, whatever the trade price | dollar string |
| `profit` | Net profit and loss on markets settled in the period, after fees | dollar string, e.g. `"12.5000"` |
| `predictions` | Contracts settled in the period | integer |
| `markets` | Markets settled in the period, whether won, lost or pushed | integer |
| `win_rate` | Markets won ÷ markets won or lost. Needs 10 decided markets in the period | number, `0`–`1` |
| `biggest_win` | Net profit on your best single market settled in the period | dollar string |
| `return` | Net profit ÷ volume. Needs $500 of volume and a profit in the period | number, `0`–`1` |
| `streak` | Longest run of winning markets in a row in the period; a push does not break it | integer |

## Which boards are shown

STX decides which boards are shown and how many members each lists (10, 25,
50 or 100). Asking for a hidden board is not an error: it comes back empty with
`"shown": false`. The table above gives how `value` is written on each board.

STX can keep some account types (for example market makers or STX's own accounts)
off every board. An account that is self-excluded, in a cool-off period, suspended,
banned, closed, rejected, archived or overdrawn never appears on a board or a
profile page. Only a **positive** value earns a place on a board: a member with nothing settled
or traded in the period is not listed, and neither is a net loss on `profit`.
Ranks are **dense**: two members with the same value share a rank, and the next
distinct value takes the next rank. A board holds at most the configured
number of members per board, never more than **100 rows**.

## Periods

Every period is a range of calendar days in **America/New_York**, ending today.
A settlement at 23:30 New York time on a Sunday counts for Sunday even though it
is already Monday in UTC.

| `period` | Covers | Resets |
| --- | --- | --- |
| `daily` | Today | Midnight New York, every day |
| `weekly` | Monday to today | Midnight New York on Monday |
| `monthly` | The 1st to today | Midnight New York on the 1st |
| `yearly` | 1 January to today | Midnight New York on 1 January |
| `all` | Everything | Never |

Every board response carries `next_reset_at` as a UTC timestamp: local midnight
shifted to UTC, so it moves by an hour across a daylight-saving change while the
board still resets at 00:00 New York. It is `null` for `all`.

Boards are rebuilt on a fixed schedule (every 15 minutes by default, on the clock), not
after each trade or settlement; `refreshed_at` is when the snapshot you are reading was
built. A member who leaves the leaderboard is removed at once.

## Categories

`category` is `all` (every sport combined) or one sport key: the lowercase sport
name, as in a row's `top_sport` (for example `basketball`). A sport has a board once a
market in it has settled or traded in the period.

An unknown category is not an error. The board is simply empty, because a
sport that is between seasons genuinely has nobody on it.

## Endpoints

| Method | Path | Returns |
| --- | --- | --- |
| GET | `/api/v1/leaderboard?period=weekly&category=all&metric=profit&limit=50` | The top rows of one board |
| GET | `/api/v1/leaderboard/me?period=weekly&category=all` | Your own standing, including a rank outside the top 100 |
| GET | `/api/v1/me` | Your identity and public profile |
| PATCH | `/api/v1/me/profile` | Change handle, avatar or opt-in |

All of `period`, `category`, `metric` and `limit` are optional and default to
`weekly`, `all`, the board STX opens on and the number of members per board. An
unknown `period` or `metric` is a `400` naming the field. `limit` is clamped to
the number of members per board: a board is a fixed top-N, so there is no cursor
and never a next page.

A board row:

```json
{
  "rank": 1,
  "handle": "swift.fox12",
  "avatar_url": "/avatars/swift.fox12.svg",
  "top_sport": "basketball",
  "top_sport_icon_url": "/api/images/categories/standard/basketball-6b422c4a.svg",
  "value": "300.0000"
}
```

`top_sport` is the sport the member traded most in the period; on a sport board
it is that sport.

Your own standing:

```json
{
  "profit": {"rank": 2, "value": "12.5000"},
  "volume": {"rank": 1, "value": "400.0000"},
  "predictions": {"rank": 3, "value": 7},
  "markets": {"rank": 4, "value": 12},
  "win_rate_rank": {"rank": 2, "value": 0.7},
  "biggest_win": null,
  "return": null,
  "streak": {"rank": 6, "value": 3},
  "win_rate": 0.7,
  "settled_markets": 10,
  "opted_in": true,
  "period": "weekly",
  "category": "basketball"
}
```

A metric you have nothing positive on is `null` even while you rank on another,
and so is every board STX hides. The Win rate board's rank is
`win_rate_rank`; `win_rate` is your plain share of decided markets.
`win_rate` is the share of decided markets you won, and is `null` until at least
10 markets have been decided in the period. A market you settled at exactly zero
is neither a win nor a loss. When you are unranked (opted out, or no activity
in the period), every metric is `null`, `settled_markets` is `0` and `opted_in`
tells you which of the two it is.

### Authentication

These endpoints, and `GET /api/v1/me` / `PATCH /api/v1/me/profile`, accept
**either** credential:

- The signed Ed25519 headers every other `/api/v1` endpoint takes; see
  [request signing](/api/authentication/). A `read_only` key can
  read every leaderboard endpoint; `PATCH /api/v1/me/profile` needs `read_write`.
- `Authorization: Bearer <token>` with the session token STX's own apps sign in
  with. A session may always update its own profile. An integration signs with
  its API key.

With no credential, or a bad one, the answer is `401 {"error": "Unauthorized"}`.
If any `X-STX-ACCESS-*` header is present the request is treated as a signed
request and the signing rules apply.

## Handles and avatars

Your **handle** is the name other members see. It is separate from your sign-in
email and never derived from your name. Every account starts with a generated
`adjective.animal1234` handle; you can pick your own:

- 3 to 24 characters: lowercase letters, digits, and `.` or `_` between them.
  Case is folded, so `Swift.Fox12` is saved as `swift.fox12`.
- Not already taken, not a reserved word (`admin`, `support`, `stx`, …) and not
  on the blocklist.
- Changeable **once every 30 days**. `GET /api/v1/me` reports
  `handle_changeable_at`: `null` when you may change it now, otherwise when the
  window ends. Sending your current handle again is not a change.

Your **avatar** is generated from three values you choose and rendered on
demand. There is no upload:

```json
{"style": "dots", "seed": "a1b2c3d4", "palette": "ocean"}
```

`style` is one of `dots`, `rings`, `stripes`, `grid`, `ball`, `court`, `stitch`,
`target`, `candles`, `dice`; `palette` one of `ember`,
`forest`, `ocean`, `grape`, `slate`, `mint`, `rose`, `gold`; `seed` any 1–32
characters, and the same three values always render the same image. Every row
carries `avatar_url`, a path relative to the API host (`/avatars/{handle}.svg`)
that serves the SVG publicly with a one-day cache and an `ETag`.

```bash
curl -X PATCH https://demo.stxapp.io/api/v1/me/profile \
  -H "X-STX-ACCESS-KEY: $KEY_ID" -H "X-STX-ACCESS-TIMESTAMP: $TS" \
  -H "X-STX-ACCESS-SIGNATURE: $SIG" -H "Content-Type: application/json" \
  -d '{"handle": "swift.fox12", "avatar": {"style": "rings", "seed": "deadbeef", "palette": "gold"}}'
```

The response is the same object `GET /api/v1/me` returns. A key of the wrong
JSON type is a `400`; a handle that is taken, reserved, malformed or changed
too soon is a `422` whose `error` names the rule.

## Joining and leaving

```json
{"leaderboard_opt_in": false}
```

sent to `PATCH /api/v1/me/profile` removes you from every public board on the
next snapshot. Your own `GET /api/v1/leaderboard/me` then reports every rank as
`null` with `opted_in: false`; your trading is unaffected. Send `true` to join.
Each change is recorded with its time.

A new account starts with the jurisdiction's default. An account opened before
the leaderboard existed is not listed until you join; joining also gives you a
generated handle and avatar if you do not have one yet.
