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

# Migrate from Mobula to Zerion API

> Map Mobula wallet and market endpoints to Zerion API equivalents for holdings, net worth, activity, trades, PnL, NFTs, and prices, with code samples.

If you've been calling Mobula for wallet holdings, net worth, activity, trades, PnL, DeFi positions, NFTs, or token prices, the same data is available on Zerion API across [60+ EVM chains and Solana](/supported-blockchains), usually in a single call.

This guide shows the direct mapping for the main Mobula wallet and market endpoints, with copy-pasteable code for each.

What you get with Zerion:

* **Every chain by default:** Mobula's wallet endpoints read a default set of "premium" chains, and reaching the rest means passing `blockchains=` or `fetchAllChains=true`. Zerion returns every supported chain for an address in one call, narrowed with `filter[chain_ids]` when you want less.
* **One call for tokens + DeFi:** Mobula splits wallet tokens (`/1/wallet/portfolio`) and DeFi positions (`/2/wallet/defi-positions`) across two endpoints and two response shapes. Zerion serves both from `/positions/?filter[positions]=no_filter`.
* **One response shape:** Mobula's V1 endpoints are snake\_case, V2 is camelCase, and a token balance is named `token_balance`, `balance`, `transferAmount`, or `baseTokenAmount` depending on which endpoint you called. Every Zerion response is the same [JSON:API](https://jsonapi.org/) envelope: `data[].attributes` with related entities under `data[].relationships`.
* **Interpreted transactions:** Mobula returns an `actions[]` array where each entry's fields are prefixed by its `model` (`transfer*` or `swap*`), so you branch before you can read an amount. Zerion returns one `operation_type` per transaction and one uniformly shaped `transfers[]` array.

## Endpoint parity

Mobula paths below are relative to `https://api.mobula.io/api`.

| Use case                      | Mobula API                                                         | Zerion API                                                                                                                                  |
| ----------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Tokens + DeFi (one call)      | Two endpoints (`/1/wallet/portfolio` + `/2/wallet/defi-positions`) | [`GET /v1/wallets/{address}/positions/?filter[positions]=no_filter`](/api-reference/wallets/get-wallet-fungible-positions)                  |
| Wallet holdings (with prices) | `GET /1/wallet/portfolio` (V2: `/2/wallet/holdings`)               | [`GET /v1/wallets/{address}/positions/?filter[positions]=only_simple`](/api-reference/wallets/get-wallet-fungible-positions)                |
| Net worth                     | `total_wallet_balance` on `/1/wallet/portfolio`                    | [`GET /v1/wallets/{address}/portfolio`](/api-reference/wallets/get-wallet-portfolio)                                                        |
| Net worth over time           | `GET /1/wallet/history`                                            | [`GET /v1/wallets/{address}/charts/{period}`](/api-reference/wallets/get-wallet-balance-chart)                                              |
| Wallet activity               | `GET /2/wallet/activity`                                           | [`GET /v1/wallets/{address}/transactions/`](/api-reference/wallets/get-wallet-transactions)                                                 |
| Transaction history (V1)      | `GET /1/wallet/transactions`                                       | Same `/transactions/` call                                                                                                                  |
| Token / NFT transfers         | `GET /1/wallet/token-transfers`, `GET /1/wallet/nft-transfers`     | `/transactions/` → `transfers[]` (fungible and NFT transfers in one array)                                                                  |
| Trades only                   | `GET /2/wallet/trades`                                             | [`GET /v1/wallets/{address}/transactions/?filter[operation_types]=trade`](/api-reference/wallets/get-wallet-transactions)                   |
| Per-token PnL                 | `GET /2/wallet/positions`                                          | [`GET /v1/wallets/{address}/pnl?filter[fungible_implementations]=…`](/api-reference/wallets/get-wallet-pnl)                                 |
| DeFi positions                | `GET /2/wallet/defi-positions`                                     | [`GET /v1/wallets/{address}/positions/?filter[positions]=only_complex`](/api-reference/wallets/get-wallet-fungible-positions)               |
| NFT holdings                  | `GET /1/wallet/nfts`                                               | [`GET /v1/wallets/{address}/nft-positions/`](/api-reference/wallets/get-wallet-nft-positions)                                               |
| Token price                   | `GET /2/token/price`, `GET /1/market/data`                         | [`GET /v1/fungibles/by-implementation?implementation={chain}:{address}`](/api-reference/fungibles/get-fungible-asset-by-implementation)     |
| Token metadata                | `GET /1/metadata`, `GET /2/token/details`                          | [`GET /v1/fungibles/{id}`](/api-reference/fungibles/get-fungible-asset-by-id)                                                               |
| Price history                 | `GET /1/market/history`, `GET /2/token/price-history`              | [`GET /v1/fungibles/{id}/charts/{period}`](/api-reference/fungibles/get-a-chart-for-a-fungible-asset)                                       |
| Multiple wallets in one call  | `wallets=addr1,addr2` on `/1/wallet/portfolio`                     | [`GET /v1/wallet-sets/positions/?addresses=…`](/api-reference/wallet-sets/get-wallet-set-fungible-positions) (one EVM + one Solana address) |
| Search assets                 | `GET /1/search`, `GET /2/fast-search`                              | [`GET /v1/fungibles/?filter[search_query]=…`](/api-reference/fungibles/get-list-of-fungible-assets)                                         |
| Supported chains              | `GET /1/blockchains`                                               | [`GET /v1/chains/`](/api-reference/chains/get-list-of-all-chains)                                                                           |
| Swap quotes                   | `GET /2/swap/quoting`                                              | [`GET /v1/swap/quotes/`](/api-reference/swap/get-swap-and-bridge-quotes)                                                                    |
| Realtime updates              | Mobula Streams (WebSocket), `/1/webhook`                           | [Transaction webhooks](/webhooks), [Kafka streams](/streaming)                                                                              |

<Tip>
  Prefer not to write code? The [Zerion CLI](/build-with-ai/zerion-cli) wraps the same endpoints with a one-shot `npx @zerion/cli init` flow, useful for quick experiments and AI agents.
</Tip>

## A note on chains

Chain identifiers are the first thing to change:

* **Mobula:** V1 endpoints take chain *names* (`blockchains=ethereum,base,solana`), V2 endpoints take prefixed IDs (`chainIds=evm:1`), and responses label chains with a display name (`"blockchain": "BNB Smart Chain (BEP20)"`) or a prefixed ID (`"chainId": "evm:56"`).
* **Zerion:** one identifier everywhere: a lowercase slug like `ethereum`, `base`, `binance-smart-chain`, `solana`. Pass it as `filter[chain_ids]=ethereum,base`; read it back from `relationships.chain.data.id`. See the [full list](/supported-blockchains).

Coverage defaults also differ. Mobula's wallet endpoints read a default premium-chain set unless you widen it: on a test address, `/1/wallet/portfolio` returned balances on 10 chains by default and 38 with `fetchAllChains=true`. Zerion queries every supported chain by default, so there's no flag to remember and nothing to fan out.

## Wallet holdings

Mobula's `/1/wallet/portfolio` returns `data.assets[]`, where each entry carries a nested `asset` object for metadata, a `token_balance`, an `estimated_balance` in USD, and a `contracts_balances[]` array with the per-chain breakdown. Zerion returns a flat [JSON:API](https://jsonapi.org/) collection, one row per token per chain, with `attributes.fungible_info` for metadata, `attributes.quantity` for amounts, and `attributes.value` for USD. The same endpoint accepts EVM and Solana addresses.

<CodeGroup>
  ```javascript JavaScript (EVM) theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[positions]=only_simple&filter[trash]=only_non_trash&sort=value`,
    {
      headers: {
        accept: "application/json",
        authorization: `Basic ${btoa(API_KEY + ":")}`,
      },
    }
  );
  const { data } = await res.json();

  for (const pos of data.slice(0, 10)) {
    const { fungible_info, quantity, price, value } = pos.attributes;
    const chain = pos.relationships.chain.data.id;
    console.log(`${fungible_info.symbol} on ${chain}: ${quantity.float} @ $${price} = $${value?.toFixed(2) ?? "N/A"}`);
  }
  ```

  ```javascript JavaScript (Solana) theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "6sEk1enayZBGFyNvvJMTP7qs5S3uC7KLrQWaEk38hSHH";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[chain_ids]=solana&filter[trash]=only_non_trash&sort=value`,
    {
      headers: {
        accept: "application/json",
        authorization: `Basic ${btoa(API_KEY + ":")}`,
      },
    }
  );
  const { data } = await res.json();

  for (const pos of data.slice(0, 10)) {
    const { fungible_info, quantity, value } = pos.attributes;
    console.log(`${fungible_info.symbol}: ${quantity.float} = $${value?.toFixed(2) ?? "N/A"}`);
  }
  ```

  ```python Python theme={null}
  import os, requests

  api_key = os.environ["ZERION_API_KEY"]
  address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/positions/",
      params={
          "currency": "usd",
          "filter[positions]": "only_simple",
          "filter[trash]": "only_non_trash",
          "sort": "value",
      },
      auth=(api_key, ""),
  )
  res.raise_for_status()

  for pos in res.json()["data"][:10]:
      info = pos["attributes"]["fungible_info"]
      qty = pos["attributes"]["quantity"]["float"]
      value = pos["attributes"]["value"]
      chain = pos["relationships"]["chain"]["data"]["id"]
      print(f"{info['symbol']} on {chain}: {qty} = ${value:.2f}" if value else f"{info['symbol']} on {chain}: {qty}")
  ```

  ```bash cURL (EVM) theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/positions/?currency=usd&filter[positions]=only_simple&filter[trash]=only_non_trash&sort=value"
  ```

  ```bash cURL (Solana) theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/6sEk1enayZBGFyNvvJMTP7qs5S3uC7KLrQWaEk38hSHH/positions/?currency=usd&filter[chain_ids]=solana&filter[trash]=only_non_trash&sort=value"
  ```
</CodeGroup>

### Field mapping

| Mobula (`data.assets[].…`)                                                                                                                             | Zerion (`data[].attributes.…`)                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `asset.name`, `asset.symbol`                                                                                                                           | `fungible_info.name`, `fungible_info.symbol`                                                                                            |
| `asset.logo`                                                                                                                                           | `fungible_info.icon.url`                                                                                                                |
| `asset.decimals[]` (array, one per chain)                                                                                                              | `fungible_info.implementations[].decimals`                                                                                              |
| `asset.contracts[]` / `asset.blockchains[]` (parallel arrays)                                                                                          | `fungible_info.implementations[]` (one object per chain, with `chain_id`, `address`, `decimals`)                                        |
| `token_balance` (decimal, summed across chains)                                                                                                        | `quantity.float` per row. Also `quantity.numeric` (decimal string) and `quantity.int` (raw integer string).                             |
| `contracts_balances[].balanceRaw`                                                                                                                      | `quantity.int`                                                                                                                          |
| `contracts_balances[].chainId` (`evm:1`)                                                                                                               | `relationships.chain.data.id` (`ethereum`)                                                                                              |
| `contracts_balances[]` / `cross_chain_balances` (per-chain breakdown nested inside one asset, keyed by display name such as `BNB Smart Chain (BEP20)`) | One row per chain. Group by `relationships.chain.data.id`, or by `relationships.fungible.data.id` to reconstruct the cross-chain asset. |
| `estimated_balance`                                                                                                                                    | `value` (USD, precomputed)                                                                                                              |
| `price`                                                                                                                                                | `price`                                                                                                                                 |
| `price_change_24h`                                                                                                                                     | `changes.percent_1d`. Absolute 24h change is in `changes.absolute_1d`.                                                                  |
| `allocation`                                                                                                                                           | Compute from `value` / [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) → `total.positions`                                  |
| `liquidity`                                                                                                                                            | No per-position liquidity figure. Zerion gates pricing on liquidity instead. See [spam filtering](/spam-filtering).                     |
| `filterSpam`, `minliq` (query)                                                                                                                         | `filter[trash]=only_non_trash` (query), `flags.is_trash` on each row                                                                    |
| `data.balances_length`                                                                                                                                 | The number of rows in `data[]`. `/positions/` is not paginated. It returns the full set in one response.                                |
| `data.total_wallet_balance`                                                                                                                            | [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) → `total.positions`                                                         |

<Note>
  `price` and `value` are `null` for tokens without a reliable price. Guard for `null` before summing or formatting.
</Note>

<Note>
  Native gas tokens come back as ordinary rows, already decimal-adjusted and priced, with `address: null` in the matching `fungible_info.implementations[]` entry.
</Note>

## Net worth

Mobula reports net worth as `total_wallet_balance` on the portfolio response, so getting a total means fetching every asset row. Zerion's [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) is a separate, small response: the total, the 24h change, a breakdown by chain, and a breakdown by position type.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/portfolio?currency=usd`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();
  const a = data.attributes;

  console.log(`Net worth: $${a.total.positions.toFixed(2)}`);
  console.log(`24h change: $${a.changes.absolute_1d?.toFixed(2)} (${a.changes.percent_1d?.toFixed(2)}%)`);
  console.log("By chain:", a.positions_distribution_by_chain);
  console.log("By type:", a.positions_distribution_by_type);
  ```

  ```python Python theme={null}
  import os, requests

  api_key = os.environ["ZERION_API_KEY"]
  address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/portfolio",
      params={"currency": "usd"},
      auth=(api_key, ""),
  )
  a = res.json()["data"]["attributes"]
  print(f"Net worth: ${a['total']['positions']:.2f}")
  print(f"24h change: {a['changes']['percent_1d']}%")
  print("By type:", a["positions_distribution_by_type"])
  ```

  ```bash cURL theme={null}
  curl -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/portfolio?currency=usd"
  ```
</CodeGroup>

### Field mapping

| Mobula                      | Zerion (`/portfolio` → `data.attributes.…`)                                              |
| --------------------------- | ---------------------------------------------------------------------------------------- |
| `data.total_wallet_balance` | `total.positions`                                                                        |
| (no equivalent)             | `changes.absolute_1d`, `changes.percent_1d` (24h change)                                 |
| (no equivalent)             | `positions_distribution_by_chain` (keyed by chain ID)                                    |
| (no equivalent)             | `positions_distribution_by_type` (`wallet`, `deposited`, `borrowed`, `locked`, `staked`) |

## Net worth over time

Mobula's `/1/wallet/history` returns `balance_history` as `[timestamp_ms, value]` pairs. Zerion's [`/charts/{period}`](/api-reference/wallets/get-wallet-balance-chart) returns the same shape with the timestamp in seconds, and the period fixes both the window and the point spacing.

```bash cURL theme={null}
curl -u "YOUR_API_KEY:" \
  "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/charts/month?currency=usd"
```

| Mobula (`/1/wallet/history`)                       | Zerion (`/charts/{period}`)                                                                            |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `data.balance_history[]` → `[timestamp_ms, value]` | `data.attributes.points[]` → `[timestamp_seconds, value]`                                              |
| `data.balance_usd` (current total)                 | [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) → `total.positions`                        |
| `from` / `to` (Unix ms)                            | `{period}` path segment: `hour`, `day`, `week`, `month`, `3months`, `6months`, `year`, `5years`, `max` |
| `period` (candle granularity)                      | Implied by the period. Each one samples its window to roughly 290–460 points                           |
| (no equivalent)                                    | `data.attributes.begin_at` / `end_at` (window bounds)                                                  |

## Wallet activity

Mobula's `/2/wallet/activity` returns one entry per transaction with an `actions[]` array, where each action's fields are prefixed by its `model`: a `transfer` action uses `transferAmount` / `transferType` / `transferAsset`, a `swap` action uses `swapAmountIn` / `swapAssetOut` / `swapPlatform`. Zerion's [`/transactions/`](/api-reference/wallets/get-wallet-transactions) gives each transaction a single decoded `operation_type` and one uniformly shaped `transfers[]` array, plus fees and the dApp when Zerion recognizes the contract.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0x42b9dF65B219B3dD36FF330A4dD8f327A6Ada990";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/transactions/?currency=usd&page[size]=20`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();

  for (const tx of data) {
    const { operation_type, mined_at, transfers, fee } = tx.attributes;
    const chain = tx.relationships.chain.data.id;
    const dappId = tx.relationships.dapp?.data?.id;

    console.log(`[${mined_at}] ${operation_type} on ${chain}${dappId ? ` via ${dappId}` : ""}`);
    for (const t of transfers) {
      const sign = t.direction === "out" ? "-" : "+";
      const symbol = t.fungible_info?.symbol ?? "NFT";
      console.log(`  ${sign}${t.quantity.float} ${symbol} ($${t.value?.toFixed(2) ?? "?"})`);
    }
    console.log(`  Fee: $${fee.value?.toFixed(2) ?? "?"}`);
  }
  ```

  ```python Python theme={null}
  import os, requests

  api_key = os.environ["ZERION_API_KEY"]
  address = "0x42b9dF65B219B3dD36FF330A4dD8f327A6Ada990"

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/transactions/",
      params={"currency": "usd", "page[size]": 20},
      auth=(api_key, ""),
  )
  for tx in res.json()["data"]:
      attrs = tx["attributes"]
      chain = tx["relationships"]["chain"]["data"]["id"]
      dapp_id = (tx["relationships"].get("dapp") or {}).get("data", {}).get("id")
      print(f"[{attrs['mined_at']}] {attrs['operation_type']} on {chain}" + (f" via {dapp_id}" if dapp_id else ""))
      for t in attrs["transfers"]:
          sign = "-" if t["direction"] == "out" else "+"
          symbol = (t.get("fungible_info") or {}).get("symbol", "NFT")
          val = t.get("value")
          print(f"  {sign}{t['quantity']['float']} {symbol} (${val:.2f})" if val else f"  {sign}{t['quantity']['float']} {symbol}")
  ```

  ```bash cURL theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0x42b9dF65B219B3dD36FF330A4dD8f327A6Ada990/transactions/?currency=usd&page[size]=20"
  ```
</CodeGroup>

### Field mapping

| Mobula (`data[].…`)                                                           | Zerion (`data[].attributes.…`)                                                                                                                                                                                                                                                      |
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `txHash`                                                                      | `hash`                                                                                                                                                                                                                                                                              |
| `txDateIso` / `txDateMs`                                                      | `mined_at` (ISO 8601)                                                                                                                                                                                                                                                               |
| `txBlockNumber`                                                               | `mined_at_block`                                                                                                                                                                                                                                                                    |
| `chainId` (`evm:56`)                                                          | `relationships.chain.data.id` (`binance-smart-chain`)                                                                                                                                                                                                                               |
| `txAction` (raw method selector, e.g. `0xce8b3ab5`)                           | `operation_type` (`send`, `receive`, `trade`, `approve`, `revoke`, `deposit`, `withdraw`, `mint`, `burn`, `claim`, `execute`, `deploy`, …). The raw selector and decoded method name are in `acts[].application_metadata.method`.                                                   |
| `actions[].model` (`transfer` / `swap`)                                       | Not needed. One `transfers[]` shape covers both. A swap is `operation_type: trade` with `out` and `in` transfers.                                                                                                                                                                   |
| `actions[].transferAmount` / `transferRawAmount`                              | `transfers[].quantity.float` / `transfers[].quantity.int`                                                                                                                                                                                                                           |
| `actions[].transferAmountUsd`                                                 | `transfers[].value`                                                                                                                                                                                                                                                                 |
| `actions[].transferType` (`TOKEN_IN`, `TOKEN_OUT`, `NATIVE_IN`, `NATIVE_OUT`) | `transfers[].direction` (`in`, `out`, `self`). Native vs token: a native gas token carries no contract address in its matching `fungible_info.implementations[]` entry: an empty string on `transfers[]` and `fee`, but `null` on `/positions/`. Test for both rather than for one. |
| `actions[].transferFromAddress` / `transferToAddress`                         | `transfers[].sender` / `transfers[].recipient`                                                                                                                                                                                                                                      |
| `actions[].transferAsset.{name,symbol,decimals,logo,contract}`                | `transfers[].fungible_info.{name,symbol,implementations[].decimals,icon.url,implementations[].address}`                                                                                                                                                                             |
| `actions[].swapAssetIn` / `swapAssetOut`                                      | `transfers[]` entries with `direction: "out"` (spent) and `direction: "in"` (received)                                                                                                                                                                                              |
| `actions[].swapAmountIn` / `swapAmountOut` / `swapAmountUsd`                  | `transfers[].quantity.float` / `transfers[].value` on the matching leg                                                                                                                                                                                                              |
| `actions[].swapPlatform`                                                      | `relationships.dapp.data.id`, plus `acts[].application_metadata`                                                                                                                                                                                                                    |
| `txRawFeesNative` / `txFeesNativeUsd`                                         | `fee.quantity.int` / `fee.value`. `fee.fungible_info` names the gas token.                                                                                                                                                                                                          |
| `actions[].swapGasFeesUsd`, `swapPlatformFeesUsd`, `swapMevFeesUsd`           | `fee.value` is the network fee. Zerion does not break out platform or MEV fees.                                                                                                                                                                                                     |
| (no equivalent)                                                               | `status` (`confirmed`, `failed`, `pending`), `nonce`, `sent_from`, `sent_to`, `approvals[]`, `flags.is_trash`                                                                                                                                                                       |

### Filter mapping

| Mobula param                                   | Zerion equivalent                                                                |
| ---------------------------------------------- | -------------------------------------------------------------------------------- |
| `chainIds=evm:1`                               | `filter[chain_ids]=ethereum` (comma-separated for multiple; omit for all chains) |
| `from` / `to` (Unix ms)                        | `filter[min_mined_at]` / `filter[max_mined_at]` (ms epoch)                       |
| `limit=100`                                    | `page[size]=100` (max 100)                                                       |
| `offset=200`, `cursorHash` / `cursorDirection` | Follow `links.next` from the response                                            |
| `order=desc`                                   | Default (newest first); ascending order is not supported                         |
| `filterSpam`                                   | `filter[trash]=only_non_trash`                                                   |
| `unlistedAssets`                               | No equivalent flag; unpriced assets return `value: null`                         |

<Note>
  If you're on the older `/1/wallet/transactions`, the same `/transactions/` call replaces it. Its per-row fields map as follows: `hash` → `hash`, `timestamp` → `mined_at`, `amount` / `amount_usd` → `transfers[].quantity.float` / `transfers[].value`, `from` / `to` → `transfers[].sender` / `transfers[].recipient`, `contract` → `transfers[].fungible_info.implementations[].address`, `tx_cost` → `fee.value`, `blockchain` → `relationships.chain.data.id`, and `asset` → `transfers[].fungible_info`. Its `type` is not a 1:1 match for `operation_type`: `buy` and `sell` both become `trade` (read `transfers[].direction` to tell them apart), and `native` becomes `send` or `receive`.
</Note>

## Trades

Mobula's `/2/wallet/trades` returns DEX fills in base/quote terms: `type` is `buy` or `sell`, and amounts split across `baseToken*` and `quoteToken*`. Zerion models the same events as transactions with `operation_type: trade`, where the direction of each transfer tells you what was spent and what was received.

```bash cURL theme={null}
curl -g -u "YOUR_API_KEY:" \
  "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/transactions/?currency=usd&filter[operation_types]=trade&page[size]=20"
```

| Mobula (`data[].…`)                          | Zerion (`data[].attributes.…`)                                                                       |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `transactionHash`                            | `hash`                                                                                               |
| `date` (Unix ms)                             | `mined_at` (ISO 8601)                                                                                |
| `type` (`buy` / `sell`)                      | Derive from `transfers[].direction`: the `out` leg is spent, the `in` leg is received                |
| `baseToken` / `quoteToken`                   | `transfers[].fungible_info` on each leg                                                              |
| `baseTokenAmount` / `quoteTokenAmount`       | `transfers[].quantity.float`                                                                         |
| `baseTokenAmountRaw` / `quoteTokenAmountRaw` | `transfers[].quantity.int`                                                                           |
| `baseTokenAmountUSD` / `quoteTokenAmountUSD` | `transfers[].value`                                                                                  |
| `baseTokenPriceUSD` / `quoteTokenPriceUSD`   | `transfers[].price`                                                                                  |
| `blockchain`                                 | `relationships.chain.data.id`                                                                        |
| `transactionSenderAddress`                   | `sent_from`                                                                                          |
| `swapRecipient`                              | `transfers[].recipient`                                                                              |
| `marketAddress` (pool)                       | No per-trade pool address. `relationships.dapp.data.id` identifies the protocol.                     |
| `totalFeesUSD` / `gasFeesUSD`                | `fee.value` (network fee)                                                                            |
| `baseTokenMarketCapUSD`                      | [`/v1/fungibles/{id}`](/api-reference/fungibles/get-fungible-asset-by-id) → `market_data.market_cap` |

Mobula's `operation`, `labels`, `id`, `platformFeesUSD`, and `mevFeesUSD` have no Zerion equivalent.

## Per-token PnL

Mobula's `/2/wallet/positions` returns one row per token with realized and unrealized PnL, average buy and sell prices, buy/sell counts, and fees. Zerion's [`/pnl`](/api-reference/wallets/get-wallet-pnl) returns FIFO-computed PnL across all chains; add `filter[fungible_ids]` or `filter[fungible_implementations]` and the response gains a `breakdown` object with the same per-token statistics.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
  const impl = "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; // USDC

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/pnl?currency=usd&filter[fungible_implementations]=${impl}`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();
  const a = data.attributes;

  console.log(`Realized:   $${a.realized_gain?.toFixed(2)}`);
  console.log(`Unrealized: $${a.unrealized_gain?.toFixed(2)}`);
  console.log(`Fees:       $${a.total_fee?.toFixed(2)}`);

  const perToken = a.breakdown.by_implementation[impl];
  console.log(`Avg buy:  $${perToken.average_buy_price}`);
  console.log(`Avg sell: $${perToken.average_sell_price}`);
  ```

  ```python Python theme={null}
  import os, requests

  api_key = os.environ["ZERION_API_KEY"]
  address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
  impl = "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"  # USDC

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/pnl",
      params={"currency": "usd", "filter[fungible_implementations]": impl},
      auth=(api_key, ""),
  )
  a = res.json()["data"]["attributes"]
  print(f"Realized:   ${a['realized_gain']:.2f}")
  print(f"Unrealized: ${a['unrealized_gain']:.2f}")

  per_token = a["breakdown"]["by_implementation"][impl]
  print(f"Avg buy:  ${per_token['average_buy_price']}")
  print(f"Avg sell: ${per_token['average_sell_price']}")
  ```

  ```bash cURL theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/pnl?currency=usd&filter[fungible_implementations]=ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
  ```
</CodeGroup>

### Field mapping

| Mobula (`/2/wallet/positions` → `data[].…`)                       | Zerion (`/pnl` → `data.attributes.…`)                                                                                                                                                                                                                                                                                                   |
| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `realizedPnlUSD`                                                  | `realized_gain`, or `breakdown.by_implementation[impl].realized_gain`                                                                                                                                                                                                                                                                   |
| `unrealizedPnlUSD`                                                | `unrealized_gain`                                                                                                                                                                                                                                                                                                                       |
| `totalPnlUSD`                                                     | `total_gain`                                                                                                                                                                                                                                                                                                                            |
| `avgBuyPriceUSD` / `avgSellPriceUSD`                              | `breakdown.by_implementation[impl].average_buy_price` / `average_sell_price`                                                                                                                                                                                                                                                            |
| `totalFeesPaidUSD`                                                | `total_fee`                                                                                                                                                                                                                                                                                                                             |
| `buyFeesPaidUSD` / `sellFeesPaidUSD`                              | Not split by side                                                                                                                                                                                                                                                                                                                       |
| `balance` / `rawBalance` / `amountUSD`                            | Current holdings live on [`/positions/`](/api-reference/wallets/get-wallet-fungible-positions) → `quantity.float` / `quantity.int` / `value`                                                                                                                                                                                            |
| `buys` / `sells` (trade counts)                                   | No equivalent. Count `operation_type: trade` transactions with `filter[fungible_implementations]`.                                                                                                                                                                                                                                      |
| `volumeBuy` / `volumeSell` / `volumeBuyToken` / `volumeSellToken` | `total_invested` and `realized_cost_basis` cover the invested side; per-side token volumes are not returned.                                                                                                                                                                                                                            |
| `firstDate` / `lastDate`                                          | No equivalent. Scope a window with `since` / `till` (13-digit ms epoch). PnL is pre-computed at fixed marks: now, 1 day, 1 week, 1 month and 1 year ago, and the start of the year. An arbitrary timestamp is only served if fewer than 3,000 transactions sit between it and the nearest mark; otherwise the request returns an error. |
| `token.…`                                                         | Asset metadata lives on [`/v1/fungibles/{id}`](/api-reference/fungibles/get-fungible-asset-by-id)                                                                                                                                                                                                                                       |
| `wallet.entity` / `wallet.labels` / `wallet.fundingInfo`          | No equivalent. Zerion does not label wallet owners or funding sources.                                                                                                                                                                                                                                                                  |
| (no equivalent)                                                   | `net_invested`, `received_external`, `sent_external`, `sent_for_nfts`, `received_for_nfts`, `relative_*_gain_percentage`                                                                                                                                                                                                                |

<Tip>
  See the [wallet PnL tracker recipe](/recipes/wallet-pnl-tracker) for a worked example.
</Tip>

## DeFi positions

Mobula's `/2/wallet/defi-positions` nests three levels deep: `data.protocols[]`, each with a `positions[]` array, each of those holding `tokens[]`, `rewards[]`, and a `metadata` object whose fields change with the protocol category. Zerion flattens this: each position leg is one row under `/positions/?filter[positions]=only_complex`, tagged with `protocol`, `protocol_module`, and `position_type`, and grouped by `group_id`.

Two things to know before you map this one. Mobula's endpoint requires a `blockchains` parameter, and it is marked beta. Its own docs note the response shape may change without notice. Its protocol coverage is also an allowlist: on EVM, Aave V3, Lido, Ether.Fi and EigenLayer. Zerion has no protocol allowlist and covers every chain marked **DeFi** in the [supported chains list](/supported-blockchains). On one test address the two returned 1 position and 103 respectively, so budget time to check the protocols you care about rather than assuming parity.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[positions]=only_complex&sort=value`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();

  for (const pos of data.slice(0, 10)) {
    const { name, protocol, protocol_module, position_type, quantity, value } = pos.attributes;
    const chain = pos.relationships.chain.data.id;
    console.log(`[${position_type}] ${name} | ${protocol} (${protocol_module}) on ${chain}: ${quantity.float} = $${value?.toFixed(2) ?? "N/A"}`);
  }
  ```

  ```python Python theme={null}
  import os, requests

  api_key = os.environ["ZERION_API_KEY"]
  address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/positions/",
      params={"currency": "usd", "filter[positions]": "only_complex", "sort": "value"},
      auth=(api_key, ""),
  )
  for pos in res.json()["data"][:10]:
      a = pos["attributes"]
      chain = pos["relationships"]["chain"]["data"]["id"]
      print(f"[{a.get('position_type')}] {a['name']} | {a.get('protocol')} ({a.get('protocol_module')}) on {chain}: {a['quantity']['float']} = ${a['value'] or 0:.2f}")
  ```

  ```bash cURL theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/positions/?currency=usd&filter[positions]=only_complex&sort=value"
  ```
</CodeGroup>

### Field mapping

| Mobula (`data.protocols[].…`)                                                         | Zerion (`data[].attributes.…`)                                                                                                               |
| ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol.name` / `protocol.id`                                                       | `protocol` / `relationships.dapp.data.id`                                                                                                    |
| `protocol.logo` / `protocol.url`                                                      | `application_metadata.icon.url` / `application_metadata.url`                                                                                 |
| `protocol.category` (`lp`, lending, liquid staking, native staking, restaking, perps) | `protocol_module` (`lending`, `liquidity_pool`, `staked`, `locked`, `rewards`, `vesting`, `deposit`, `investment`, `yield`)                  |
| `totalValueUSD` (per protocol)                                                        | Sum `value` across the rows sharing a `relationships.dapp.data.id`                                                                           |
| `positions[].id` (e.g. `aave-deposit-usdc`)                                           | `id` on the position row                                                                                                                     |
| `positions[].name`                                                                    | `name`                                                                                                                                       |
| (no per-position chain field)                                                         | `relationships.chain.data.id` on every row                                                                                                   |
| `positions[].type` (`deposit`, `liquidity`, `stake`, `leverage`)                      | `position_type` (`deposit`, `loan`, `staked`, `locked`, `reward`, `investment`)                                                              |
| `positions[].valueUSD`                                                                | Sum `value` across the rows sharing a `group_id`                                                                                             |
| `positions[].tokens[]`                                                                | One row per token leg, grouped by `group_id`                                                                                                 |
| `positions[].tokens[].{name,symbol,address,decimals}`                                 | `fungible_info.{name,symbol,implementations[].address,implementations[].decimals}`                                                           |
| `positions[].tokens[].amountRaw` / `amountFormatted`                                  | `quantity.int` / `quantity.float`                                                                                                            |
| `positions[].tokens[].priceUSD` / `valueUSD`                                          | `price` / `value`                                                                                                                            |
| `positions[].rewards[]`                                                               | Rows with `position_type: reward`                                                                                                            |
| `positions[].metadata.poolAddress` / `poolName`                                       | `pool_address` / `name`                                                                                                                      |
| `positions[].metadata.shareOfPool`, `lstMint`, `sharesRaw`                            | `receipt.fungible_info` carries the LP or receipt token; the pool share itself is not returned                                               |
| `positions[].metadata.healthFactor`, `ltv`, `liquidationThreshold`                    | No equivalent. Derive from supplied value vs the `position_type: loan` rows.                                                                 |
| `positions[].metadata.supplyAPY`, `borrowAPY`, `apy`, `fee24h`, `exchangeRate`        | No equivalent. Zerion returns balances and USD values, not yields.                                                                           |
| `positions[].metadata.tickLower` / `tickUpper` / `inRange` (CLMM range)               | No equivalent                                                                                                                                |
| `data.totalValueUSD` / `totalDepositedUSD` / `totalBorrowedUSD` / `totalRewardsUSD`   | [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) → `positions_distribution_by_type` (`deposited`, `borrowed`, `locked`, `staked`) |
| `data.wallet` / `data.fetchedAt`                                                      | The queried address; freshness is per row in `updated_at` / `updated_at_block`                                                               |

<Warning>
  Despite the name, Mobula's `positions[].tokens[].amountRaw` is not always a raw integer. On a live Aave V3 USDC supply it returned `"10.019356"`, identical to `amountFormatted`, for a 6-decimal token. If you currently divide `amountRaw` by `10 ** decimals`, re-check that logic against Zerion's `quantity.int`, which is always the raw integer string (`10017049` for the same position).
</Warning>

<Note>
  Zerion's `/positions/` serves wallet tokens *and* DeFi positions. If you currently call `/1/wallet/portfolio` and `/2/wallet/defi-positions` back to back, collapse both into one Zerion call with `filter[positions]=no_filter`.
</Note>

## NFTs

Mobula's `/1/wallet/nfts` returns raw holdings with a `token_uri` you resolve yourself. Zerion's [`/nft-positions/`](/api-reference/wallets/get-wallet-nft-positions) returns resolved media, collection metadata, and floor-price valuation.

```bash cURL theme={null}
curl -g -u "YOUR_API_KEY:" \
  "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/nft-positions/?currency=usd&page[size]=20"
```

| Mobula (`data[].…`)                               | Zerion (`data[].attributes.…`)                                                                       |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `token_address`                                   | `nft_info.contract_address`                                                                          |
| `token_id`                                        | `nft_info.token_id`                                                                                  |
| `amount`                                          | `amount`                                                                                             |
| `name` / `symbol` (collection level)              | `collection_info.name` / `nft_info.name` (token level)                                               |
| `token_uri` (unresolved, often a base64 data URI) | `nft_info.content.preview.url` / `nft_info.content.detail.url` (resolved media)                      |
| `blockchain` / `chain_id`                         | `relationships.chain.data.id`                                                                        |
| (no equivalent)                                   | `price` (floor price) and `value` (floor value), returned by default                                 |
| (no equivalent)                                   | `nft_info.interface` (`ERC721` / `ERC1155`), `nft_info.flags.is_spam`, `collection_info.description` |
| `page` / `offset` / `limit`                       | `page[size]` plus `links.next`                                                                       |

## Token prices and metadata

Mobula splits this across `/2/token/price` (pool-derived price for one token), `/1/market/data` (market figures by asset name or contract), and `/1/metadata` (descriptive metadata). Zerion returns one fungible object carrying all three.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const implementation = "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; // USDC

  const res = await fetch(
    `https://api.zerion.io/v1/fungibles/by-implementation?implementation=${implementation}&currency=usd`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();
  const m = data.attributes.market_data;

  console.log(`${data.attributes.symbol}: $${m.price}`);
  console.log(`24h change: ${m.changes?.percent_1d?.toFixed(2)}%`);
  console.log(`Market cap: $${m.market_cap?.toFixed(0)}`);
  ```

  ```python Python theme={null}
  import os, requests

  api_key = os.environ["ZERION_API_KEY"]
  implementation = "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"  # USDC

  res = requests.get(
      "https://api.zerion.io/v1/fungibles/by-implementation",
      params={"implementation": implementation, "currency": "usd"},
      auth=(api_key, ""),
  )
  a = res.json()["data"]["attributes"]
  m = a["market_data"]
  print(f"{a['symbol']}: ${m['price']}")
  print(f"24h change: {m['changes']['percent_1d']}%")
  ```

  ```bash cURL theme={null}
  curl -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/fungibles/by-implementation?implementation=ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&currency=usd"
  ```
</CodeGroup>

### Field mapping

| Mobula                                                                                             | Zerion (`data.attributes.…`)                                                                                                                                                                                                                                                                                      |
| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `priceUSD` (`/2/token/price`), `price` (`/1/market/data`)                                          | `market_data.price`                                                                                                                                                                                                                                                                                               |
| `price_change_1h` / `price_change_24h` / `price_change_7d` / `price_change_1m` / `price_change_1y` | `market_data.changes.percent_1d`, `percent_30d`, `percent_90d`, `percent_365d`. Hourly and weekly changes are not returned.                                                                                                                                                                                       |
| `market_cap` / `marketCapUSD`                                                                      | `market_data.market_cap`                                                                                                                                                                                                                                                                                          |
| `market_cap_diluted` / `marketCapDilutedUSD`                                                       | `market_data.fully_diluted_valuation`                                                                                                                                                                                                                                                                             |
| `total_supply` / `circulating_supply`                                                              | `market_data.total_supply` / `market_data.circulating_supply`                                                                                                                                                                                                                                                     |
| `volume` / `volume_7d` / `off_chain_volume`                                                        | `market_data.trading_volumes.volume_1d`. Weekly and CEX volumes are not returned.                                                                                                                                                                                                                                 |
| `liquidity` / `liquidityMax` / `liquidityUSD`                                                      | No equivalent field. Liquidity gates whether Zerion prices an asset at all. See [spam filtering](/spam-filtering).                                                                                                                                                                                                |
| `name`, `symbol`, `logo`, `decimals`                                                               | `name`, `symbol`, `icon.url`, `implementations[].decimals`                                                                                                                                                                                                                                                        |
| `contracts[]` / `blockchains[]`                                                                    | `implementations[]` (`chain_id`, `address`, `decimals`, `deployment_date`)                                                                                                                                                                                                                                        |
| `description`, `website`, `twitter`, `discord`, `telegram` (`/1/metadata`)                         | `description`, `external_links[]`                                                                                                                                                                                                                                                                                 |
| `is_listed` (indexed by Mobula)                                                                    | Not the same signal. The closest curation flags are `flags.verified` on the asset and `flags.is_trash` on a wallet position.                                                                                                                                                                                      |
| `priceNative`                                                                                      | Request with `currency=eth` (the `currency` param accepts fiat and crypto units)                                                                                                                                                                                                                                  |
| `ath` / `atl` / `athUSD` / `atlUSD`                                                                | No equivalent. The [fungible chart](/api-reference/fungibles/get-a-chart-for-a-fungible-asset) at `period=max` returns `stats.min` / `stats.max`, but the series is sampled to \~400 points, so those are the extremes of the samples rather than true all-time highs and lows. The dates aren't returned either. |
| `GET /1/market/multi-data`, `POST /2/token/price` (batch)                                          | One implementation per call, or page [`/v1/fungibles/`](/api-reference/fungibles/get-list-of-fungible-assets) and read `market_data.price` from each result                                                                                                                                                       |
| `GET /1/market/history` → `price_history[]`                                                        | [`/v1/fungibles/{id}/charts/{period}`](/api-reference/fungibles/get-a-chart-for-a-fungible-asset) → `points[]`                                                                                                                                                                                                    |

Zerion prices are aggregated rather than tied to a single pool, so Mobula's pool-level fields from `/2/token/details` (`poolAddress`, `exchange`, `factory`, `largestPoolQuote`, `bonded`) have no equivalent. Neither do `rank`, `cexs[]`, `tags[]`, `kyc`, `audit`, `investors`, and `release_schedule`.

## Search

Mobula's `/1/search` and `/2/fast-search` return assets, tokens, and pairs. Zerion's fungibles list takes a `filter[search_query]` and returns matching assets with their market data.

```bash cURL theme={null}
curl -g -u "YOUR_API_KEY:" \
  "https://api.zerion.io/v1/fungibles/?filter[search_query]=usdc&currency=usd&page[size]=10"
```

Results are asset-level, not pair-level: Zerion has no equivalent to Mobula's `pairs[]`, pool metadata, or launchpad fields.

## Multiple wallets

Mobula aggregates an arbitrary number of addresses with `wallets=addr1,addr2,…`. Zerion's [wallet sets](/api-reference/wallet-sets/get-wallet-set-fungible-positions) aggregate across chain types rather than across many accounts: a set takes at most one EVM address and one Solana address, and returns a combined portfolio, positions, transactions, and balance chart. For PnL, call the per-wallet [`/pnl`](/api-reference/wallets/get-wallet-pnl) endpoint for each address.

```bash cURL theme={null}
curl -g -u "YOUR_API_KEY:" \
  "https://api.zerion.io/v1/wallet-sets/portfolio?addresses=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045,6sEk1enayZBGFyNvvJMTP7qs5S3uC7KLrQWaEk38hSHH&currency=usd"
```

Passing two addresses of the same chain type returns `400` (`duplicate address type evm at index 1`). To aggregate several EVM accounts, call the per-wallet endpoints in parallel and sum client-side.

## Pagination

Replace Mobula's `offset` / `limit` (and `cursorHash` / `cursorDirection`) with Zerion's `links.next`. Paginated endpoints (transactions, NFT positions, and fungibles) return a fully-formed next-page URL you can fetch as-is, so there's no offset arithmetic and no drift when new rows land mid-scan.

`/positions/` is the exception: it isn't paginated, ignores `page[size]`, and returns the full set in one response, so the loop below simply exits after the first page.

```javascript theme={null}
async function getAll(url) {
  const all = [];
  const headers = {
    accept: "application/json",
    authorization: `Basic ${btoa(process.env.ZERION_API_KEY + ":")}`,
  };

  while (url) {
    const res = await fetch(url, { headers });
    const { data, links } = await res.json();
    all.push(...data);
    url = links?.next ?? null;
  }
  return all;
}
```

## Realtime updates

Mobula pushes realtime data over WebSocket streams (balances, positions, trades, transfers) and manages webhook subscriptions through `/1/webhook`. Zerion offers two equivalents:

* **[Transaction webhooks](/webhooks):** subscribe a callback URL to a list of wallets and receive a POST for every new transaction. This is the closest match to Mobula's balance and wallet-transfer streams. See the [wallet activity alerts recipe](/recipes/wallet-activity-alerts) for a working example.
* **[Kafka streams](/streaming):** a continuous feed of interpreted trade and price events across chains, for indexing and analytics at high throughput. Provisioned per customer on Enterprise plans.

## Differences from Mobula

Most Mobula wallet and market use cases have a direct Zerion equivalent. A few aren't covered, and others behave differently. Worth a scan before you cut over.

**Not supported today:**

* **Trading and market microstructure:** Mobula's token trades, pair trades, OHLCV candles, holder positions, top-trader positions, order books, and Pulse launchpad feeds have no Zerion equivalent. Zerion's market data is asset-level: price, market cap, supply, volume, and charts.
* **Token security and analytics signals:** `/2/token/security`, buy/sell taxes, holder distribution, sniper and bundler detection, and `securityScore`-style fields aren't returned. Zerion's curation surfaces are `flags.is_trash`, `filter[trash]`, and `fungible_info.flags.verified`. See [spam filtering](/spam-filtering).
* **Wallet labelling and funding sources:** Mobula annotates wallets with entity names, labels, and a `fundingInfo` object tracing the first deposit. Zerion identifies dApps (`relationships.dapp`) but does not label wallet owners or trace funding.
* **Perpetuals and prediction markets:** Mobula covers perps and Polymarket positions, quoting, and execution. Zerion's positions model is spot and DeFi-protocol oriented, and does not return perps or prediction-market positions.
* **Execution beyond swaps:** Zerion offers [swap quotes](/api-reference/swap/get-swap-and-bridge-quotes); Mobula's perp order lifecycle, bridge intents, and Safe deployment flows have no equivalent.
* **ATH / ATL and price-at-timestamp:** Mobula returns `ath` / `atl` with dates and a `price-at` snapshot endpoint. Zerion returns live price plus a chart timeseries you can read historical values from.
* **DeFi yields and risk metrics:** Mobula's DeFi positions carry `supplyAPY`, `borrowAPY`, `healthFactor`, `ltv`, `liquidationThreshold`, and pool APY / 24h fees. Zerion returns the position's balance, USD value, protocol and type, but no yield or liquidation figures.
* **Non-EVM chains beyond Solana:** Mobula indexes chains such as TON, Sui, Starknet, and Stellar. Zerion covers EVM chains and Solana. Check the [supported chains list](/supported-blockchains) for the ones you rely on.

If any of these matter for your migration, [let us know](#get-in-touch). Your feedback helps shape our roadmap.

**Worth knowing:**

* **Authentication:** Mobula takes the API key in an `Authorization: <key>` header. Zerion uses [HTTP Basic Auth](/authentication): the key as username, empty password. Get a key at [dashboard.zerion.io](https://dashboard.zerion.io).
* **Chain identifiers:** Mobula mixes names (`ethereum`), prefixed IDs (`evm:1`), and display labels (`BNB Smart Chain (BEP20)`). Zerion uses one lowercase slug everywhere. See [the note above](#a-note-on-chains).
* **Flattened DeFi:** Mobula nests `tokens[]` and `rewards[]` inside each position, inside each protocol. Zerion returns one row per leg tagged with `protocol_module` and `position_type`. Group by `relationships.dapp.data.id` to reconstruct protocols, and by `group_id` to reconstruct pools.
* **Pagination:** Mobula pages with `offset` / `limit`; Zerion returns a fully-formed `links.next` URL on its paginated endpoints, while `/positions/` returns everything in one response. See [pagination](/pagination-and-filtering).
* **Pricing and spam:** Mobula exposes `filterSpam`, `minliq`, and per-asset `liquidity` so you can filter client-side. Zerion applies a liquidity gate before pricing an asset and flags junk with `flags.is_trash`, which you filter server-side with `filter[trash]=only_non_trash`. Low-liquidity tokens that would otherwise carry an unreliable price come back with `price: null` rather than a figure that distorts a portfolio total.

## Get in touch

Have a use case we don't cover or need assistance with the migration? Our team is happy to help! Reach out via the chat widget on [dashboard.zerion.io](https://dashboard.zerion.io), or [email us](mailto:api@zerion.io).
