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

# Identity and PnL

> Handle, user id or wallet to a trader and their verified wallets.

![GET /v2/user/handle/\{handle} resolves the fictional handle @tradername to a verified Solana wallet and a verified EVM wallet, shown as fictional shortened sample strings 8qNx...vR4m and 0x7aB2...9cF4](https://mintcdn.com/socialscan/2XBvOEwup27FSPkm/images/art/identity.webp?fit=max\&auto=format\&n=2XBvOEwup27FSPkm\&q=85\&s=cd8b8e06d5d465318d2b44c6641a997a)

| Endpoint | Answers |
| - | - |
| `GET /v2/user/handle/{handle}` | handle to user and verified wallets |
| `GET /v2/user/id/{id}` | user id to user and verified wallets |
| `GET /v2/user/wallet/{address}` | wallet to the trader behind it |
| `POST /v2/user/handle/{handle}/resolve` | resolve this handle now |
| `GET /v2/user/handle/{handle}/pnl` | realized profit over 24h, 7d and 30d |
| `POST /v2/user/handles/pnl` | the same for up to 100 handles in one call |

```json theme={null}
{
  "id": "00000000-0000-0000-0000-000000000000",
  "handle": "tradername",
  "name": "Trader",
  "bio": null,
  "banner": null,
  "profilePicture": "https://<image host>/a/3e0eb557061268eb4e2eddac50ce5671.webp",
  "twitter": "https://x.com/tradername",
  "solanaAddress": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "evmAddress": "0x8ba1f109551bd432803012645ac136ddd64dba72"
}
```

* **Key on `id`, not `handle`.** `id` is the stable user id and never
  changes. Handles get renamed.
* `solanaAddress` and `evmAddress` are `null` when we hold no verified address
  for that chain. Null does not mean the trader has no wallet.
* `banner` and `profilePicture` come from our image host at `/a/<key>.webp`. A
  new picture gets a new URL.
* A response that carries a wallet counts as one of the plan's monthly reveals.

## Not found

A lookup that finds nothing returns `404 NOT_FOUND`. On a wallet lookup it means
we hold no verified link from that address to a trader. Cache the negative
briefly and ask again later. A user we hold without a verified wallet returns
`200` with both addresses `null`.

An address verified by more than one trader returns one record, the trader who
verified it first. A wallet lookup needs a base58 Solana address or a
`0x`-prefixed EVM address. Anything else returns `400`.

## PnL

![Illustrative realized profit for the 24h, 7d and 30d windows (+\$1,430, +\$109,660, +\$104,229) and on-platform cash flow for all time (+\$8,120), on one linear scale](https://mintcdn.com/socialscan/pX0brSzGgXBryxeh/images/art/pnl.webp?fit=max\&auto=format\&n=pX0brSzGgXBryxeh\&q=85\&s=dcfa538180f601ebecfac93004fa98a1)

```bash theme={null}
curl -s -H "Authorization: Bearer $KEY" \
  "https://api.fomoscan.sh/v2/user/handle/tradername/pnl"
```

```json theme={null}
{
  "handle": "tradername",
  "displayName": "Trader",
  "avatarUrl": "https://<image host>/a/3e0eb557061268eb4e2eddac50ce5671.webp",
  "twitter": "tradername",
  "followers": 2481,
  "wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "evmWallet": "0x8ba1f109551bd432803012645ac136ddd64dba72",
  "status": "ready",
  "retryAfterSeconds": null,
  "updatedAt": 1790000000000,
  "windows": {
    "24h": { "netUsd": 1430.12, "returnPct": 5.82, "volumeUsd": 51760.05,
             "trades": 15, "tokensTraded": 5, "winningTokens": 2, "status": "ready" },
    "7d":  { "netUsd": 109660.52, "status": "ready" },
    "30d": { "netUsd": 104229.32, "status": "ready" },
    "all": { "netUsd": 8120.4, "rank": 312, "status": "ready" }
  },
  "unrealized": { "unrealizedPnlUsd": 2210.4, "holdingsUsd": 18400.0, "positions": 6,
                  "excludedPositions": 2, "status": "ready", "updatedAt": 1790000000000 }
}
```

`returnPct` is in percent: `5.82` means 5.82%.

One response carries two scopes:

| Window | Scope | Measures |
| - | - | - |
| `24h`, `7d`, `30d` | The whole wallet: every venue, Solana and EVM together | Realized profit |
| `all` | On-platform trades only | Cash flow (sold minus bought), with the trader's `rank` |

`all` is not profit. A trader who only bought shows a negative `all` whatever
they hold. Use `24h`, `7d` and `30d` for profit. The
[API reference](https://docs.fomoscan.sh/api-reference/introduction.md) lists every field.

All windows arrive in one call. A response or batch entry with `status`
`ready` carries a wallet and counts as a reveal. Only `ready` is charged the hit
price. Every other answer, and a `404`, costs the miss price.

| You get | It means |
| - | - |
| `404` | We do not hold this handle. |
| `status: "ready"` | Whole-wallet figures are served. Each window still carries its own `status`; a window read for the first time can be `pending` with null figures. |
| `status: "pending"` | Nothing could be read in time. Ask again after `retryAfterSeconds`. |
| `status: "failed"` | The last reading failed. Ask again after `retryAfterSeconds`. |
| `status: "no-wallet"` | We know the trader and hold no verified wallet. `wallet` and `evmWallet` are `null`. |
| `status: "unavailable"` | Whole-wallet reading is off or out of capacity. Asking again soon will not help. |

`no-wallet` does not mean the trader has no wallet. `all` is `null` for a
trader with no on-platform trade.

## Resolve on demand

> [!WARNING]
>
> **Live resolution is paused.** A handle we already hold with a verified wallet
> answers `200` at the usual price. Any other handle answers `202` with `reason: "unavailable"` and
> `retryable: false`, and is not charged. The rest of this section describes the
> endpoint when live resolution runs.

The `GET` endpoints answer from what we already hold. `POST .../resolve` asks
for an answer we do not hold yet, by handle only.

```bash theme={null}
curl -s -X POST -H "Authorization: Bearer $KEY" \
  "https://api.fomoscan.sh/v2/user/handle/tradername/resolve"
```

| Status | Meaning |
| - | - |
| `200` | The ordinary user record, the same one the `GET` returns. |
| `202` | `{ "pending": true, "reason": …, "retryable": … }`. Not ready yet. Ask again after `retryAfterSeconds` when it is present. |
| `404` | No such user exists. |

A `200` costs 30,000 CU. A `202` or a `404` costs 200 CU, except the paused
answer above, which is free. The endpoint allows 10 calls a minute per IP.

Asking again joins the resolve already running and does not start a second one.
`retryAfterSeconds` also arrives as a `Retry-After` header. A live answer can
take several seconds, so allow for it in your client timeout.

| `reason` | Meaning |
| - | - |
| `queued` | Accepted. The answer is not ready yet. |
| `timeout` | The answer did not arrive in time. Ask again. |
| `unavailable` | No answer can be produced right now. |
| `rate_limited` | The request was not accepted. Ask again later. |
| `no_preview` | No wallet can be attested for this user. Retrying rarely helps. |
| `not_ingested` | The record is not readable yet. Ask again shortly. |

When live lookup is paused, an unknown handle returns `202` with `reason:
"unavailable"`, `retryable: false` and a `message`. No job is queued and the
call is free. Cached records stay available and return `200`.

The `x-fomoscan-resolve` header reads `cached` when we already held the record
and `live` when this call produced it. The body is the same either way.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.