# Merkl Analytics API

API for tracking DeFi positions, analyzing transactions, and computing PnL across protocols

---

# GET /v4/analytics/positions/{userAddress}/{chainId}

_Merkl Analytics API_

> Get user position history

Fetch historical position data for a user across opportunities.

**Auth:** a valid `x-api-key` developer key, or an authenticated JWT identity (cookie or Bearer) — both grant the same read access.

**Response:**
- `data[chainId][identifier][indexingId]`: Wrapped positions grouped by chain ID → identifier → indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId.
- `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed.
- `globalStates` (present when enrichment runs): at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`). The value applied to a snapshot lives on that snapshot's own `globalStates[key] = { blockTimestamp, value }` — the series row at-or-before the snapshot's timestamp (carry-forward) — so each snapshot (`latest` + each `history` entry) is self-describing.
- `pagination.positionCount`: Number of positions returned
- `pagination.totalHistorySnapshots`: Total history snapshots (when > 0)
- `pagination.pageInfo`: Cursor-based pagination info (when applicable)

**Examples:**
- All positions on chain 1: `GET /v4/analytics/positions/0xUserAddress/1`
- With history: `GET /v4/analytics/positions/0xUserAddress/1?includeHistory=true`
- With enrichment: `GET /v4/analytics/positions/0xUserAddress/1?enrichPositions=true`
- With USD display values: `GET /v4/analytics/positions/0xUserAddress/1?includeDisplay=true`
- With Merkl API data: `GET /v4/analytics/positions/0xUserAddress/1?includeMerklAPIData=true`
- Filter by protocol: `GET /v4/analytics/positions/0xUserAddress/1?protocol=uniswap`
- Filter by identifier: `GET /v4/analytics/positions/0xUserAddress/1?identifier=0xPoolAddress`

### Path parameters

- `userAddress` (string) _(required)_
- `chainId` (string | number) _(required)_

### Query parameters

- `enrichPositions` (boolean) _(required)_
- `includeHistory` (boolean) _(required)_
- `includeDisplay` (boolean) _(required)_
- `includeMerklAPIData` (boolean) _(required)_
- `minTimestamp` (string | number)
- `daily` (boolean) _(required)_
- `first` (string | number)
- `protocol` (string)
- `identifier` (string)
- `indexingIds` (array)
- `after` (string)

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/{userAddress}/{chainId}?enrichPositions=<enrichPositions>&includeHistory=<includeHistory>&includeDisplay=<includeDisplay>&includeMerklAPIData=<includeMerklAPIData>&daily=<daily>" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/positions/{userAddress}

_Merkl Analytics API_

> Get user position history

Fetch historical position data for a user across opportunities.

**Auth:** a valid `x-api-key` developer key, or an authenticated JWT identity (cookie or Bearer) — both grant the same read access.

**Response:**
- `data[chainId][identifier][indexingId]`: Wrapped positions grouped by chain ID → identifier → indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId.
- `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed.
- `globalStates` (present when enrichment runs): at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`). The value applied to a snapshot lives on that snapshot's own `globalStates[key] = { blockTimestamp, value }` — the series row at-or-before the snapshot's timestamp (carry-forward) — so each snapshot (`latest` + each `history` entry) is self-describing.
- `pagination.positionCount`: Number of positions returned
- `pagination.totalHistorySnapshots`: Total history snapshots (when > 0)
- `pagination.pageInfo`: Cursor-based pagination info (when applicable)

**Examples:**
- All positions on chain 1: `GET /v4/analytics/positions/0xUserAddress/1`
- With history: `GET /v4/analytics/positions/0xUserAddress/1?includeHistory=true`
- With enrichment: `GET /v4/analytics/positions/0xUserAddress/1?enrichPositions=true`
- With USD display values: `GET /v4/analytics/positions/0xUserAddress/1?includeDisplay=true`
- With Merkl API data: `GET /v4/analytics/positions/0xUserAddress/1?includeMerklAPIData=true`
- Filter by protocol: `GET /v4/analytics/positions/0xUserAddress/1?protocol=uniswap`
- Filter by identifier: `GET /v4/analytics/positions/0xUserAddress/1?identifier=0xPoolAddress`

### Path parameters

- `userAddress` (string) _(required)_
- `chainId` (string | number) _(required)_

### Query parameters

- `enrichPositions` (boolean) _(required)_
- `includeHistory` (boolean) _(required)_
- `includeDisplay` (boolean) _(required)_
- `includeMerklAPIData` (boolean) _(required)_
- `minTimestamp` (string | number)
- `daily` (boolean) _(required)_
- `first` (string | number)
- `protocol` (string)
- `identifier` (string)
- `indexingIds` (array)
- `after` (string)

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/{userAddress}?enrichPositions=<enrichPositions>&includeHistory=<includeHistory>&includeDisplay=<includeDisplay>&includeMerklAPIData=<includeMerklAPIData>&daily=<daily>" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/positions/by-identifier/{chainId}/{identifier}

_Merkl Analytics API_

> Get positions by opportunity identifier

Fetch the latest position for every user in a given opportunity.

**Response:**
- `data[indexingId]`: Wrapped positions keyed by indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId. Each position entry carries its `user` field; multiple users may share the same indexingId positionGroup.
- `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed.
- `globalStates` (present when enrichment runs): at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`). The value applied to a snapshot lives on that snapshot's own `globalStates[key] = { blockTimestamp, value }` — the series row at-or-before the snapshot's timestamp (carry-forward) — so each snapshot is self-describing.
- `pagination.positionCount`: Total number of position entries returned
- `pagination.recipientsCount`: Number of distinct user addresses across all positions

**Examples:**
- Latest positions: `GET /v4/analytics/positions/by-identifier/1/0xPoolAddress`

### Path parameters

- `chainId` (string | number) _(required)_
- `identifier` (string) _(required)_

### Query parameters

- `enrichPositions` (boolean) _(required)_

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/by-identifier/{chainId}/{identifier}?enrichPositions=<enrichPositions>" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/positions/at/{storingId}

_Merkl Analytics API_

> Get closest position states at a timestamp

Return the closest position state(s) to a Unix timestamp for a storingId.

`order=DESC` (default) returns the last state at or before the timestamp; `order=ASC` returns the first state at or after it.

Each entry mirrors the gRPC `StateV2` shape, so the response carries the raw `state` blob unmodified.

**Examples:**
- Latest state ≤ T:    `GET /v4/analytics/positions/at/<storingId>?timestamp=1748635620`
- First state ≥ T:     `GET /v4/analytics/positions/at/<storingId>?timestamp=1748635620&order=ASC`
- One specific user:   `GET /v4/analytics/positions/at/<storingId>?timestamp=1748635620&positionKey=0xUser`

### Path parameters

- `storingId` (string) _(required)_

### Query parameters

- `timestamp` (string | number) _(required)_
- `order` (string | string) _(required)_
- `positionKey` (string)
- `positionGranularityKey` (string)

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/positions/at/{storingId}?timestamp=<timestamp>&order=<order>" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/transactions/{chainId}

_Merkl Analytics API_

> Get states by transaction hashes (V2)

Retrieves position states associated with the given transaction hashes from the V2 position_states table. Returns enriched state data including protocol, action, and token metadata derived from state descriptions.

### Path parameters

- `chainId` (string | number) _(required)_

### Query parameters

- `txHashes` (string) _(required)_
- `positionKeyFilter` (string)
- `timestamps` (string)

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/transactions/{chainId}?txHashes=<txHashes>" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/transactions/by-user/{userAddress}/{chainId}

_Merkl Analytics API_

> Get tx hashes by user, grouped by indexing ID

Returns all transaction hashes recorded for a given user address, grouped by indexing ID (opportunity). Supports optional chain filtering via the chainId path parameter. Results are cursor-paginated: pass `first` to set page size and `after` (from `pageInfo.endCursor`) to fetch the next page. Cursor encodes `(block_number, position_id)` — ordering is block_number DESC.

### Path parameters

- `userAddress` (string) _(required)_
- `chainId` (string | number) _(required)_

### Query parameters

- `first` (string | number)
- `after` (string)

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/transactions/by-user/{userAddress}/{chainId}" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/global-states/{indexingId}

_Merkl Analytics API_

### Path parameters

- `indexingId` (string) _(required)_

### Query parameters

- `fromBlock` (string | number)
- `toBlock` (string | number)

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/global-states/{indexingId}" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/global-states/{indexingId}/latest

_Merkl Analytics API_

### Path parameters

- `indexingId` (string) _(required)_

## Responses

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/global-states/{indexingId}/latest" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/states-meta/find-by-token/{chainId}/{identifier}

_Merkl Analytics API_

> Find storing_ids by token / identifier

Search the `state_set_descriptions` table for every storing_id whose `raw_config` references the given token contract or opportunity identifier.

Pass `chainId=0` to search across all chains.

### Path parameters

- `chainId` (string | number) _(required)_
- `identifier` (string) _(required)_

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/find-by-token/{chainId}/{identifier}" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/states-meta/{storingId}/block-range

_Merkl Analytics API_

> Get indexed block / timestamp range for a storing_id

Return the min/max indexed `block_number` and `block_timestamp` for a storing_id. All four values are 0 when the storing_id has no indexed state yet.

### Path parameters

- `storingId` (string) _(required)_

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/{storingId}/block-range" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/states-meta/{storingId}/setDescription

_Merkl Analytics API_

> Get raw processor config (set description) for a storing_id

Return the raw processor config stored in the `state_set_description` table for the given storing_id.

### Path parameters

- `storingId` (string) _(required)_

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/{storingId}/setDescription" \
  -H "x-api-key: YOUR_API_KEY"
```

---

# GET /v4/analytics/states-meta/address-tags/{chainId}

_Merkl Analytics API_

> Batch resolve address tags

Resolve human-readable labels for up to 100 identifiers on a single chain.

`identifier` may be any resolvable value: owner address, pool address, market id, bytes32 poolId, etc.

Misses return `addressTag: null` — partial resolution is normal for plain EOAs.
Input order is preserved in the response.

**Example:**
```
GET /v4/analytics/states-meta/address-tags/8453?identifiers=0x7Ce5F356a10BBF528618259404543BB1e6412B69,0xRandomEOA
```

### Path parameters

- `chainId` (string | number) _(required)_

### Query parameters

- `identifiers` (string) _(required)_

## Responses

- **200** Response for status 200

## Example request

```bash
curl -X GET "https://analytics.merkl.xyz/v4/analytics/states-meta/address-tags/{chainId}?identifiers=<identifiers>" \
  -H "x-api-key: YOUR_API_KEY"
```