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

# Quickstart

> From zero to a wallet snapshot, then how to read it.

> [!WARNING]
>
> **Private alpha, partial coverage.** The API is in private alpha (`release_status: "alpha_partial"`): a token is
> required and only some protocols are covered. See [Supported protocols](https://docs.lucidblock.xyz/reference/supported-protocols.md).

This page takes you from zero to a wallet snapshot, then shows how to read it and how to handle values that are
unknown. All examples use this base URL and, during the alpha, an access token in an environment variable:

```bash theme={null}
BASE_URL="https://api.lucidblock.xyz"
export LUCIDBLOCK_TOKEN="<your access token>"
```

## Base URL and access token

* Production: `https://api.lucidblock.xyz`. Local development: `http://127.0.0.1:8789` (`pnpm dev`).
* During the private alpha every `/v1/*` route except `/v1/health` needs a token. Request one at
  `<CONTACT_EMAIL>`. Send it as `Authorization: Bearer $LUCIDBLOCK_TOKEN` on every request. Never put it in a URL.
* `/v1/health`, `/openapi.json` and `/` need no token.

## Your first request in 60 seconds

The first request for a wallet nobody has looked up recently returns **`202`** with a scan job: the API has queued a
bounded scan of the supported protocols. Poll the job (it costs 1 rate-limit unit; a wallet read costs 5), wait the
number of seconds in `Retry-After` / `retry_after_seconds`, and when the job reaches `succeeded` or `partial`, request
the wallet again to get the **`200`** snapshot. A wallet with a fresh snapshot returns `200` straight away.

> [!TIP]
>
> **Simplest integration: add `?wait=8`.** The API then holds a cold request open for up to 8 seconds while the scan
> runs and answers with the `200` snapshot in one call in most cases. If the scan takes longer you get the same `202`
> as without `wait`, so keep the polling loop below as the fallback. `wait` is 0 to 10 seconds (default 0) and the call
> costs 5 units once. See [Integrate with ?wait=8](https://docs.lucidblock.xyz/guides/wait.md).

```bash theme={null}
W="G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH"
curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions?wait=8"
# usually 200 with the snapshot; otherwise the 202 below, then poll as shown
```

The full flow with polling as the fallback:

> [tab] curl
>
> ```bash theme={null}
> W="G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH"
> curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions"
> # 202 → {"job_id": "...", "poll_url": "/v1/solana/wallets/.../jobs/...", "retry_after_seconds": 3, ...}
> curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions/jobs/<job_id>"
> # repeat until "status" is "succeeded" or "partial", then request the wallet again:
> curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions"
> ```

> [tab] JavaScript
>
> Node.js 18+ or a browser (CORS is open). The loop honours `Retry-After`, polls the job rather than the wallet, and stops
> on terminal job states.
>
> ```js theme={null}
> const BASE_URL = process.env.BASE_URL ?? "https://api.lucidblock.xyz";
> const TOKEN = process.env.LUCIDBLOCK_TOKEN;
> const WALLET = process.env.WALLET ?? "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH";
> const headers = TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {};
> const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
>
> async function get(path) {
>   const res = await fetch(BASE_URL + path, { headers });
>   if (res.status === 429 || res.status === 503) {
>     await sleep(Number(res.headers.get("Retry-After") ?? 5));
>     return get(path);
>   }
>   return { status: res.status, retryAfter: Number(res.headers.get("Retry-After") ?? 2), body: await res.json() };
> }
>
> async function snapshot(wallet) {
>   const path = `/v1/solana/wallets/${wallet}/defi-positions`;
>   for (let attempt = 0; attempt < 5; attempt++) {
>     const r = await get(path);
>     if (r.status === 200) return r.body;
>     if (r.status !== 202) throw new Error(`${r.status}: ${JSON.stringify(r.body.error)}`);
>     // 202: poll the job until it is terminal, then read the wallet again.
>     let wait = Math.max(r.body.retry_after_seconds, 2);
>     for (let i = 0; i < 30; i++) {
>       await sleep(wait);
>       const job = (await get(r.body.poll_url)).body;
>       if (job.status === "succeeded" || job.status === "partial") break;
>       if (job.status === "failed" || job.status === "expired") throw new Error(`scan ${job.status}`);
>       wait = Math.min(wait * 1.5, 10);
>     }
>   }
>   throw new Error("no snapshot after 5 attempts");
> }
>
> const s = await snapshot(WALLET);
> console.log(`coverage: ${s.coverage.status}`);
> console.log(`total_value_usd: ${s.totals?.total_value_usd ?? "null"} (${s.totals?.total_null_reasons.join(", ") || "complete"})`);
> console.log(`known_value_usd: ${s.totals?.known_value_usd ?? "null"}`);
> for (const p of s.positions) console.log(`- ${p.protocol.id} ${p.position_type}: ${p.net_value_usd ?? "null"}`);
> ```

> [tab] Python
>
> ```python theme={null}
> import os, time, requests
>
> BASE_URL = os.environ.get("BASE_URL", "https://api.lucidblock.xyz")
> TOKEN = os.environ.get("LUCIDBLOCK_TOKEN")
> WALLET = os.environ.get("WALLET", "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH")
> HEADERS = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
>
> def get(path):
>     while True:
>         r = requests.get(BASE_URL + path, headers=HEADERS, timeout=30)
>         if r.status_code in (429, 503):
>             time.sleep(int(r.headers.get("Retry-After", "5")))
>             continue
>         return r
>
> def snapshot(wallet):
>     path = f"/v1/solana/wallets/{wallet}/defi-positions"
>     for _ in range(5):
>         r = get(path)
>         if r.status_code == 200:
>             return r.json()
>         if r.status_code != 202:
>             raise RuntimeError(f"{r.status_code}: {r.json()['error']}")
>         job = r.json()
>         wait = max(job["retry_after_seconds"], 2)
>         for _ in range(30):
>             time.sleep(wait)
>             status = get(job["poll_url"]).json()["status"]
>             if status in ("succeeded", "partial"):
>                 break
>             if status in ("failed", "expired"):
>                 raise RuntimeError(f"scan {status}")
>             wait = min(wait * 1.5, 10)
>     raise RuntimeError("no snapshot after 5 attempts")
>
> s = snapshot(WALLET)
> totals = s["totals"] or {}
> print("coverage:", s["coverage"]["status"])
> print("total_value_usd:", totals.get("total_value_usd"), totals.get("total_null_reasons"))
> print("known_value_usd:", totals.get("known_value_usd"))
> for p in s["positions"]:
>     print("-", p["protocol"]["id"], p["position_type"], p["net_value_usd"])
> ```

A captured `202` (from local development):

> [accordion] Captured example
>
> `GET /v1/solana/wallets/BWJUG6W67qRq7Xbkv3cLo7m4zEr21oJKg93ens3mLmBg/defi-positions` → `202` (captured 2026-11-12 from local `positions-202`)
>
> ```json theme={null}
> {
>   "job_id": "600aa76a-4e8b-4a66-ae70-9410c8eaa601",
>   "status": "queued",
>   "wallet": "BWJUG6W67qRq7Xbkv3cLo7m4zEr21oJKg93ens3mLmBg",
>   "retry_after_seconds": 3,
>   "poll_url": "/v1/solana/wallets/BWJUG6W67qRq7Xbkv3cLo7m4zEr21oJKg93ens3mLmBg/defi-positions/jobs/600aa76a-4e8b-4a66-ae70-9410c8eaa601",
>   "request": {
>     "include": [
>       "underlying",
>       "rewards",
>       "valuation"
>     ],
>     "protocols": null,
>     "freshness_seconds": 120,
>     "include_rewards_in_total": false,
>     "limit": 100
>   }
> }
> ```

## Reading a response

Read a snapshot in this order. The captured response below is a native-stake wallet (two stake accounts the wallet can
withdraw).

1. **`coverage`**: did we look everywhere we support? `coverage.status` is `complete_for_supported_protocols` here, and
   every entry of `coverage.protocols` is `complete`. A missing position means something only for protocols listed
   `complete` ([Concepts: coverage vs valuation](https://docs.lucidblock.xyz/concepts/coverage-and-valuation.md#coverage-vs-valuation-two-independent-axes)).
2. **`totals`**: `total_value_usd` is a real total only when coverage is complete and every counted leg is priced;
   otherwise it is `null` and `total_null_reasons` says why. `known_value_usd` is the sum of the legs we could price
   ([Concepts: totals](https://docs.lucidblock.xyz/concepts/totals.md#totals-semantics)).
3. **`positions[]`**: each position has `assets`, `debts` and `rewards` legs. Each leg has an exact `amount`, a
   `value_usd` (or `null`), its `pricing_source` and `valuation_status`, and whether it counts (`in_totals`)
   ([Concepts: positions, legs and roles](https://docs.lucidblock.xyz/concepts/positions-and-legs.md#positions-legs-and-roles)).

> [accordion] Captured example
>
> `GET /v1/solana/wallets/G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH/defi-positions` → `200` (captured 2026-11-12 from local `positions-native-stake`)
>
> ```json theme={null}
> {
>   "chain": "solana",
>   "network": "mainnet-beta",
>   "wallet": "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH",
>   "snapshot_id": "a19b0669-78d1-4d65-a9f0-60f32c0fbb95",
>   "release_status": "alpha_partial",
>   "as_of": "2026-11-12T20:33:30Z",
>   "observed_slots": {
>     "minimum": 468660578,
>     "maximum": 468660591
>   },
>   "snapshot_consistency": "multi_slot",
>   "refreshing": false,
>   "refresh_job_id": null,
>   "freshness": {
>     "age_seconds": 5,
>     "stale": false,
>     "max_age_seconds": 120
>   },
>   "coverage": {
>     "status": "complete_for_supported_protocols",
>     "universe": "supported_adapters_only",
>     "supported_protocol_count": 7,
>     "completed_protocol_count": 7,
>     "failed_protocol_count": 0,
>     "unsupported_programs_detected": null,
>     "protocols": [
>       {
>         "id": "native_stake",
>         "adapter_version": "1.0.0",
>         "status": "complete",
>         "warnings": [],
>         "error_code": null
>       },
>       {
>         "id": "spl_stake_pool",
>         "adapter_version": "1.0.0",
>         "status": "complete",
>         "warnings": [],
>         "error_code": null
>       },
>       {
>         "id": "jupiter_perps",
>         "adapter_version": "1.0.0",
>         "status": "complete",
>         "warnings": [],
>         "error_code": null
>       },
>       {
>         "id": "kamino_lend",
>         "adapter_version": "1.0.0",
>         "status": "complete",
>         "warnings": [],
>         "error_code": null
>       },
>       {
>         "id": "raydium_cpmm",
>         "adapter_version": "1.0.0",
>         "status": "complete",
>         "warnings": [],
>         "error_code": null
>       },
>       {
>         "id": "raydium_amm_v4",
>         "adapter_version": "1.0.0",
>         "status": "complete",
>         "warnings": [],
>         "error_code": null
>       },
>       {
>         "id": "raydium_clmm",
>         "adapter_version": "1.0.0",
>         "status": "complete",
>         "warnings": [
>           "limit_orders_not_scanned"
>         ],
>         "error_code": null
>       }
>     ],
>     "wallet_token_scan": {
>       "spl_token": "complete",
>       "token_2022": "complete",
>       "token_accounts_found": 1
>     },
>     "warnings": [
>       "program_discovery_not_run"
>     ],
>     "skipped_protocol_count": 0
>   },
>   "positions": [
>     {
>       "id": "solana:native_stake:116vkzEjoTpFEd3x12XL9HbYJ5EpoC4cZ9a1N5A5mUt",
>       "protocol": {
>         "id": "native_stake",
>         "name": "Solana Stake",
>         "program_id": "Stake11111111111111111111111111111111111111"
>       },
>       "position_type": "native_staking",
>       "category": "native_staking",
>       "position_account": "116vkzEjoTpFEd3x12XL9HbYJ5EpoC4cZ9a1N5A5mUt",
>       "owner_role": "withdraw_authority",
>       "market": "BULKEEKf9Hjy4nwCthjzheEk4joH23LLXttAHjqEZmB2",
>       "assets": [
>         {
>           "role": "staked",
>           "mint": "So11111111111111111111111111111111111111112",
>           "symbol": "SOL",
>           "amount_raw": "216875911",
>           "decimals": 9,
>           "amount": "0.216875911",
>           "price_usd": "81.5403431471040927",
>           "value_usd": "17.684136203280807116",
>           "pricing_source": "builtin:raydium-amm-v4",
>           "price_as_of": "2026-11-12T20:31:41.000Z",
>           "valuation_status": "priced",
>           "confidence": "qualified",
>           "claimable": null,
>           "amount_basis": "exact",
>           "informational": false,
>           "in_totals": true,
>           "market_price_usd": null,
>           "protocol_mark_price_usd": null,
>           "price_warnings": [],
>           "exclusion_reason": null,
>           "context": {
>             "account": "116vkzEjoTpFEd3x12XL9HbYJ5EpoC4cZ9a1N5A5mUt",
>             "as_of_slot": null,
>             "status": "deactivating",
>             "reward_index": null
>           }
>         }
>       ],
>       "debts": [],
>       "rewards": [],
>       "net_value_usd": "17.684136203280807116",
>       "known_value_usd": "17.684136203280807116",
>       "valuation": {
>         "status": "complete",
>         "priced_leg_count": 1,
>         "indicative_leg_count": 0,
>         "unpriced_leg_count": 0
>       },
>       "accounting": {
>         "counts_toward_totals": true,
>         "parent_position_id": null,
>         "carried_forward_from_snapshot": null
>       },
>       "flags": [],
>       "evidence": {
>         "slot": 468660586,
>         "adapter_version": "1.0.0",
>         "method": "program_accounts",
>         "accounts": [
>           "116vkzEjoTpFEd3x12XL9HbYJ5EpoC4cZ9a1N5A5mUt",
>           "SysvarC1ock11111111111111111111111111111111",
>           "SysvarStakeHistory1111111111111111111111111"
>         ],
>         "decoder_hash": "8fbf40e8f01970828a97b687ff32048248b48df81c0a3e89f0c1b853e8ef1108"
>       },
>       "details": {
>         "state_tag": 2,
>         "status": "deactivating",
>         "active_lamports": "0",
>         "activating_lamports": "0",
>         "deactivating_lamports": "214582445",
>         "inactive_lamports": "10586",
>         "rent_exempt_reserve": "2282880",
>         "breakdown_reserve_lamports": "2282880",
>         "delegated_stake": "214582445",
>         "activation_epoch": "943",
>         "deactivation_epoch": "1084",
>         "voter": "BULKEEKf9Hjy4nwCthjzheEk4joH23LLXttAHjqEZmB2",
>         "lockup": {
>           "unix_timestamp": "0",
>           "epoch": "0",
>           "custodian": "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH"
>         },
>         "locked": false,
>         "withdrawable_now_lamports": "10586",
>         "wallet_is_staker": true,
>         "staker": "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH",
>         "withdrawer": "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH",
>         "activation_method": "agave_exact",
>         "clock_epoch": "1084"
>       },
>       "warnings": []
>     },
>     {
>       "id": "solana:native_stake:AkKezL7CHq8242RtV4i53gGTmZ13EJC87AinsXXWVnNX",
>       "protocol": {
>         "id": "native_stake",
>         "name": "Solana Stake",
>         "program_id": "Stake11111111111111111111111111111111111111"
>       },
>       "position_type": "native_staking",
>       "category": "native_staking",
>       "position_account": "AkKezL7CHq8242RtV4i53gGTmZ13EJC87AinsXXWVnNX",
>       "owner_role": "withdraw_authority",
>       "market": "2NxEEbhqqj1Qptq5LXLbDTP5tLa9f7PqkU8zNgxbGU9P",
>       "assets": [
>         {
>           "role": "staked",
>           "mint": "So11111111111111111111111111111111111111112",
>           "symbol": "SOL",
>           "amount_raw": "34072977",
>           "decimals": 9,
>           "amount": "0.034072977",
>           "price_usd": "81.5403431471040927",
>           "value_usd": "2.778322236623385367",
>           "pricing_source": "builtin:raydium-amm-v4",
>           "price_as_of": "2026-11-12T20:31:41.000Z",
>           "valuation_status": "priced",
>           "confidence": "qualified",
>           "claimable": null,
>           "amount_basis": "exact",
>           "informational": false,
>           "in_totals": true,
>           "market_price_usd": null,
>           "protocol_mark_price_usd": null,
>           "price_warnings": [],
>           "exclusion_reason": null,
>           "context": {
>             "account": "AkKezL7CHq8242RtV4i53gGTmZ13EJC87AinsXXWVnNX",
>             "as_of_slot": null,
>             "status": "active",
>             "reward_index": null
>           }
>         }
>       ],
>       "debts": [],
>       "rewards": [],
>       "net_value_usd": "2.778322236623385367",
>       "known_value_usd": "2.778322236623385367",
>       "valuation": {
>         "status": "complete",
>         "priced_leg_count": 1,
>         "indicative_leg_count": 0,
>         "unpriced_leg_count": 0
>       },
>       "accounting": {
>         "counts_toward_totals": true,
>         "parent_position_id": null,
>         "carried_forward_from_snapshot": null
>       },
>       "flags": [],
>       "evidence": {
>         "slot": 468660586,
>         "adapter_version": "1.0.0",
>         "method": "program_accounts",
>         "accounts": [
>           "AkKezL7CHq8242RtV4i53gGTmZ13EJC87AinsXXWVnNX",
>           "SysvarC1ock11111111111111111111111111111111",
>           "SysvarStakeHistory1111111111111111111111111"
>         ],
>         "decoder_hash": "8fbf40e8f01970828a97b687ff32048248b48df81c0a3e89f0c1b853e8ef1108"
>       },
>       "details": {
>         "state_tag": 2,
>         "status": "active",
>         "active_lamports": "31535374",
>         "activating_lamports": "0",
>         "deactivating_lamports": "0",
>         "inactive_lamports": "254723",
>         "rent_exempt_reserve": "2282880",
>         "breakdown_reserve_lamports": "2282880",
>         "delegated_stake": "31535374",
>         "activation_epoch": "714",
>         "deactivation_epoch": "max",
>         "voter": "2NxEEbhqqj1Qptq5LXLbDTP5tLa9f7PqkU8zNgxbGU9P",
>         "lockup": {
>           "unix_timestamp": "0",
>           "epoch": "0",
>           "custodian": "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH"
>         },
>         "locked": false,
>         "withdrawable_now_lamports": "254723",
>         "wallet_is_staker": true,
>         "staker": "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH",
>         "withdrawer": "G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH",
>         "activation_method": "agave_exact",
>         "clock_epoch": "1084"
>       },
>       "warnings": []
>     }
>   ],
>   "page": {
>     "limit": 100,
>     "returned": 2,
>     "total_positions": 2,
>     "next_cursor": null,
>     "page_complete": true
>   },
>   "totals": {
>     "total_value_usd": "20.462458439904192483",
>     "known_value_usd": "20.462458439904192483",
>     "total_null_reasons": [],
>     "unpriced_position_count": 0,
>     "priced_leg_count": 2,
>     "indicative_leg_count": 0,
>     "unpriced_leg_count": 0,
>     "excluded": {
>       "indicative_value_usd": null,
>       "stale_value_usd": null,
>       "estimated_rewards_value_usd": null,
>       "claimable_rewards_not_included_usd": null,
>       "staker_only_value_usd": null
>     },
>     "valuation_status": "complete",
>     "include_rewards_in_total": false,
>     "price_sources": [],
>     "note": "Value includes only the explicitly supported scanned DeFi universe; it is not universal wallet net worth."
>   },
>   "request": {
>     "include": [
>       "underlying",
>       "rewards",
>       "valuation"
>     ],
>     "protocols": null,
>     "freshness_seconds": 120,
>     "include_rewards_in_total": false,
>     "limit": 100
>   }
> }
> ```

## Other useful first requests

```bash theme={null}
curl -s "$BASE_URL/v1/health"                                                     # no token needed
curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/protocols"   # what is supported
curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/coverage"    # categories covered
curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions?protocols=native_stake"
curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions?include=underlying,rewards"   # no valuation
curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions?include_rewards_in_total=true"
curl -s -H "Authorization: Bearer $LUCIDBLOCK_TOKEN" "$BASE_URL/v1/solana/wallets/$W/defi-positions?freshness_seconds=600"
```

## Rate limits

60 units per minute per IP: a wallet read costs 5 units, everything else costs 1, and at most 20 requests per hour may
start a new scan. Every response carries `RateLimit-Remaining`; a `429` carries `Retry-After`. Details:
[Rate limits and access](https://docs.lucidblock.xyz/reference/rate-limits.md).

## Next steps

* [Concepts](https://docs.lucidblock.xyz/concepts/coverage-and-valuation.md): coverage, null-not-zero, totals, freshness.
* [Supported protocols](https://docs.lucidblock.xyz/reference/supported-protocols.md) and [API reference](https://docs.lucidblock.xyz/api-reference/overview.md).
* [Errors](https://docs.lucidblock.xyz/reference/errors.md) and the [FAQ](https://docs.lucidblock.xyz/help/faq.md).


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