# HTTP — `GET /v1/userFills`

Recorded, confirmed fills for a user over an inclusive time range. Public
endpoint. Early WebSocket notifications can arrive before a fill appears here.

```http
GET /v1/userFills?user=0x0000000000000000000000000000000000000001&startTime=1780000000000&endTime=1780003600000
```

| Param | Required | Meaning |
| --- | --- | --- |
| `user` | yes | EVM address. Missing fails with `invalid_query`; malformed with `bad_request`. |
| `startTime` | yes | Inclusive start, Unix milliseconds. Missing fails with `invalid_query`. |
| `endTime` | no | Inclusive end, Unix milliseconds; defaults to now. `startTime > endTime` fails with `invalid_time_range`. |
| `cursor` | no | Opaque `nextCursor` from a prior response with the same `order`. An unparseable value, or a cursor from the other order, fails with `invalid_cursor`. |
| `order` | no | `asc` (default) returns fills in the order Mosaiq recorded them. `desc` returns the newest fills first. Other values fail with `invalid_query`. |
| `limit` | no | Fills per page, clamped to `1` to `500`, default `500`. A value that is not an integer from `0` to `65535` fails with `invalid_query`. |

```jsonc
{
  "status": "ok",
  "data": {
    "user": "0x0000000000000000000000000000000000000001",
    "userFills": [
      {
        "fillId": "123456",
        "orderId": "2a4d4983-9057-4da9-bcd7-0aa0e4185bb0",
        "cloid": "0x00000000000000000000000000000001",
        "a": "BTC",
        "venue": "lighter",
        "b": "buy",
        "size": "0.01",
        "price": "65000",
        "liquidityRole": "maker",
        "fees": { "exchange": "0.0234", "mosaiq": "0" },
        "createdAtMs": 1780000100000
      }
    ],
    "nextCursor": "uf1_42",
    "hasMore": false
  },
  "server_time_ms": 1780003600100
}
```

With `order=asc`, rows follow the order in which Mosaiq recorded the fills, not
execution time, so `createdAtMs` can decrease between rows. `(venue, fillId)`
identifies a fill; fill ID spaces differ per venue. Treat `fillId` as an opaque
string. On Lighter, a match between two Mosaiq traders has an ID of the form
`xc:{taker order}:{maker order}:{m|t}`, and it matches no Lighter trade. `fees` is
absent until settled fees are available. `fees.priority` is the Hyperliquid
priority fee paid on that fill, in USDC, for an order sent with `priorityFeeBps`;
it is absent when the fill paid none.

## Backfill in recording order

`order=asc` is the default. A page holds at most `limit` fills. Set an explicit
`endTime` for a backfill. While `hasMore` is `true`, pass `nextCursor` as
`cursor` and keep `user`, `startTime`, and `endTime` unchanged. When `hasMore`
is `false`, `nextCursor` covers every fill recorded when the response started.
Reuse it to poll for fills recorded later in the same time window, including
fills with an older `createdAtMs`.

Start without a cursor when changing the user or time window. Reusing a cursor
with a wider window can skip fills that were previously outside the range.

## Read the newest fills first

`order=desc` serves history views that show the newest fills first and load
older fills on demand. The first page holds the newest fills at or before
`endTime`:

```http
GET /v1/userFills?user=0x0000000000000000000000000000000000000001&startTime=0&order=desc&limit=20
```

```jsonc
{
  "status": "ok",
  "data": {
    "user": "0x0000000000000000000000000000000000000001",
    "userFills": [
      // Up to 20 fills, newest createdAtMs first, with the fields shown above.
    ],
    "nextCursor": "ufd1_1780000100_123456000_6f1c2a3e-9b1d-4c8e-a5f2-0d3b7c9e1a24",
    "hasMore": true
  },
  "server_time_ms": 1780003600100
}
```

To read the next older page, pass `nextCursor` as `cursor` and keep `user`,
`startTime`, `endTime`, and `order` unchanged. `limit` can change between pages.
Each page starts strictly after the previous one. When `hasMore` is `false`,
the response has no `nextCursor`.

Rows sort by `createdAtMs`, newest first, then by an internal fill key. For fills
recorded before 2026-08-25, the order can differ from `createdAtMs` by up to 27
hours.

A walk returns each fill at most once, and pages never overlap. New fills sort
at the top, so a walk that already started does not show them. Start again
without a cursor to see fills recorded since the first page. A page can hold
fewer than `limit` fills while `hasMore` is `true` only when the time window
starts or ends before 2026-08-25.

`order=desc` does not replace the `asc` cursor for synchronization. Use
`order=asc` to backfill after a WebSocket gap.

## WebSocket recovery

The `userFills` topic does not replay a gap. After a disconnect, subscribe
again and buffer live fills, query this endpoint with a conservative inclusive
`startTime`, follow `nextCursor` until `hasMore` is `false`, merge, and
deduplicate by `(venue, fillId)`.
