> For the complete documentation index, see [llms.txt](https://docs.kpk.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kpk.io/funds/infrastructure/onchain-accounting/concepts/position-identity.md).

# Position identity

Every position the NAV Calculator reports carries a **machine-readable identity** so consumers can branch, index, and reconcile without parsing display strings. The stable identity is the typed key **`protocolSubId` + `positionId` + `balanceAsset.asset` + `positionKind`** on each `PositionBalance` / `Position`, scoped per `chainId`.

**Source:** [`INAVCalculator.sol`](https://github.com/karpatkey/onchain-accounting/blob/main/src/nav/INAVCalculator.sol) · [`ProtocolIds.sol`](https://github.com/karpatkey/onchain-accounting/blob/main/src/utils/ProtocolIds.sol)

***

## The identity fields

Each position carries a **3-level protocol taxonomy** (`protocolBrand` ⊃ `protocolId` ⊃ `protocolSubId`). Only the most specific level, `protocolSubId`, is part of the identity key; the two broader levels exist for grouping and roll-up.

| Field                | Type      | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocolBrand`      | `bytes32` | `keccak256` of the **version-stripped brand** slug (e.g. `aave`, `balancer`, `uniswap`). Broadest grouping layer — display/roll-up only, **not** in the identity key.                                                                                                                                                                                                                                                                                                                                               |
| `protocolId`         | `bytes32` | `keccak256` of the **ecosystem** slug, version included (e.g. `aave-v3`, `balancer-v2`). Mid grouping layer — **not** in the identity key (functionally determined by `protocolSubId`).                                                                                                                                                                                                                                                                                                                             |
| `protocolSubId`      | `bytes32` | `keccak256` of the **product** slug (e.g. `aave-v3-lending`, `balancer-v3-pools`). **In the identity key** — it selects the `positionId` decode schema: one slug ⇒ exactly one schema, so no runtime sniffing is needed.                                                                                                                                                                                                                                                                                            |
| `positionId`         | `bytes`   | ABI-encoded **static** position coordinates. How to decode it is fully determined by `protocolSubId`.                                                                                                                                                                                                                                                                                                                                                                                                               |
| `positionKind`       | `enum`    | Semantic action family (below). `isDebt` remains the canonical NAV-sign signal; `positionKind` adds category context.                                                                                                                                                                                                                                                                                                                                                                                               |
| `isLocked`           | `bool`    | `true` when the leg is **not withdrawable right now** — a pending/cooling withdrawal request or cooldown. `false` for every other leg. A **mutable per-leg attribute, NOT part of the identity key** — a leg flips `true→false` as its portion finalizes, so keying on it would split one economic position. A withdrawal queue's claimable (`isLocked=false`) and locked (`isLocked=true`) legs share one key and are **summed** in reconciliation; read `isLocked` per leg for the claimable-vs-locked breakdown. |
| `positionInstanceId` | `bytes32` | **Ephemeral** per-item handle — an NFT `tokenId`, a withdrawal-request id, or a StakeWise exit ticket; `bytes32(0)` when there is no per-item handle. **Excluded** from the stable identity key by design, so the legs of one anchor sum and identity survives item churn.                                                                                                                                                                                                                                          |

{% hint style="info" %}
`balanceAdapter` (the address that reported a position) is **provenance, not identity** — the same logical position can move to a different adapter across a redeploy. `protocolBrand` and `protocolId` are **grouping only**. The stable identity is `protocolSubId` + `positionId` + `balanceAsset.asset` + `positionKind` (per chain).
{% endhint %}

***

## PositionKind

```solidity
enum PositionKind {
    Wallet,      // plain ERC20 / native balance held directly
    Supplied,    // credit deployed into a protocol: lending supply, vault shares, unstaked LP principal
    Borrowed,    // debt owed to a protocol (isDebt = true)
    Collateral,  // credit locked as collateral
    Staking,     // principal locked behind an unstake action (Safety Module, gauge/Convex-staked LP, …)
    Rewards,     // accrued but unclaimed reward tokens (CRV, CVX, BAL, COMP, …)
    Fees         // uncollected protocol / LP fees (e.g. Uniswap V3 position fees)
}
```

The kind reflects the **outermost** action family — what the account does to enter/exit the position. Gauge-staked LP is `Staking` regardless of the LP wrapped inside; the inner exposure stays recoverable from `protocolId` / `positionId`. Unstaked LP sitting in the wallet is `Supplied`.

***

## Protocol taxonomy catalogue

Slugs are defined in [`src/utils/ProtocolIds.sol`](https://github.com/karpatkey/onchain-accounting/blob/main/src/utils/ProtocolIds.sol); each constant is `keccak256` of the slug. The **`protocolSubId`** (product) column is the one that enters the identity key and selects the decode schema; `protocolId` (ecosystem) and `protocolBrand` (brand) group it. Single-product ecosystems collapse — brand = id = subId.

| Family                       | `protocolId` (ecosystem)                          | `protocolSubId` (product)                                                                              |
| ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Wallet                       | `wallet`                                          | `wallet`                                                                                               |
| Aave V3                      | `aave-v3`                                         | `aave-v3-lending` · `aave-v3-safety-module` · `aave-v3-umbrella`                                       |
| Spark                        | `spark`                                           | `spark-lend`                                                                                           |
| Morpho                       | `morpho`                                          | `morpho-vaults` · `morpho-markets`                                                                     |
| Balancer V3                  | `balancer-v3`                                     | `balancer-v3-pools` · `balancer-v3-gauges`                                                             |
| Balancer V2                  | `balancer-v2`                                     | `balancer-v2-pools` · `balancer-v2-gauges`                                                             |
| Curve                        | `curve`                                           | `curve-pools` · `curve-gauges`                                                                         |
| Convex                       | `convex`                                          | `convex-curve-lp-staking`                                                                              |
| Uniswap V3 / V2              | `uniswap-v3` · `uniswap-v2`                       | `uniswap-v3` · `uniswap-v2`                                                                            |
| PancakeSwap V3 / V2          | `pancakeswap-v3` · `pancakeswap-v2`               | `pancakeswap-v3` · `pancakeswap-v2`                                                                    |
| Gearbox V3                   | `gearbox-v3`                                      | `gearbox-v3-markets` · `gearbox-v3-credit-accounts`                                                    |
| Euler                        | `euler`                                           | `euler-vaults`                                                                                         |
| Compound V3                  | `compound-v3`                                     | `compound-v3-comets`                                                                                   |
| Fluid                        | `fluid`                                           | `fluid-ftokens` · `fluid-vaults`                                                                       |
| StakeWise V3                 | `stakewise-v3`                                    | `stakewise-v3-vaults` · `stakewise-v3-exit`                                                            |
| Nexus Mutual staking         | `nexus-mutual`                                    | `nexus-mutual-staking-pools`                                                                           |
| Generic ERC-4626             | `erc4626`                                         | `erc4626`                                                                                              |
| Cap                          | `cap`                                             | `cap`                                                                                                  |
| Withdrawal / cooldown queues | `lido` · `stader` · `kelp` · `etherfi` · `ethena` | `lido-withdrawal` · `stader-withdrawal` · `kelp-withdrawal` · `etherfi-withdrawal` · `ethena-cooldown` |

For grouping, `protocolBrand` strips the version: `aave-v3 → aave`, `balancer-v2`/`balancer-v3 → balancer`, `uniswap-v3`/`uniswap-v2 → uniswap`, `pancakeswap-* → pancakeswap`, `gearbox-v3 → gearbox`, `stakewise-v3 → stakewise`, `compound-v3 → compound`. Single-version protocols reuse their ecosystem id as the brand.

***

## positionId decode schemas

All `positionId` values are `abi.encode(...)`. The schema is keyed by `protocolSubId`:

| `protocolSubId`                                                                                              | `positionId` schema                                                                                                                                                             |
| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wallet`                                                                                                     | `(address asset)`                                                                                                                                                               |
| `aave-v3-lending` · `spark-lend`                                                                             | `(address pool)` — one position per pool; the reserve is carried in `balanceAsset`, not the `positionId`                                                                        |
| `aave-v3-safety-module` · `aave-v3-umbrella`                                                                 | `(address stakeToken)` — the two staking legs are told apart by `isLocked`, the reward leg by `positionKind`                                                                    |
| `morpho-vaults` · `gearbox-v3-markets` · `euler-vaults` · `erc4626` · `stakewise-v3-vaults` · `fluid-vaults` | `(address vault)`                                                                                                                                                               |
| `gearbox-v3-credit-accounts`                                                                                 | `(address creditAccount)` — one anchor per Credit Account; the Borrowed leg and each Collateral leg share it                                                                    |
| `cap`                                                                                                        | `(address token)` — the cUSD leg or the stcUSD leg                                                                                                                              |
| `morpho-markets`                                                                                             | `(bytes32 marketId)`                                                                                                                                                            |
| `balancer-v3-pools` · `curve-pools`                                                                          | `(address pool)`                                                                                                                                                                |
| `balancer-v3-gauges` · `balancer-v2-gauges` · `curve-gauges`                                                 | `(address gauge)`                                                                                                                                                               |
| `convex-curve-lp-staking`                                                                                    | `(address rewarder)` — Convex `BaseRewardPool`                                                                                                                                  |
| `balancer-v2-pools`                                                                                          | `(bytes32 poolId)`                                                                                                                                                              |
| `uniswap-v3` · `pancakeswap-v3`                                                                              | `(address pool)` — one per fee-tier pool; the NFT `tokenId` is in `positionInstanceId`, not the `positionId`                                                                    |
| `uniswap-v2` · `pancakeswap-v2`                                                                              | `(address pair)`                                                                                                                                                                |
| `compound-v3-comets` · `fluid-ftokens`                                                                       | `(address market)`                                                                                                                                                              |
| `nexus-mutual-staking-pools`                                                                                 | `(address stakingPool)` — the NFT `tokenId` is in `positionInstanceId`                                                                                                          |
| `stader-withdrawal`                                                                                          | `(address userWithdrawalManager)` — one anchor; one leg per open request (`positionInstanceId` = request id)                                                                    |
| `kelp-withdrawal`                                                                                            | `(address withdrawalManager)` — one anchor across all payout assets; one leg per open request (`positionInstanceId` = request id), legs told apart also by `balanceAsset.asset` |
| `lido-withdrawal`                                                                                            | `(address withdrawalQueue)` — one anchor; one leg per open request (`positionInstanceId` = request id)                                                                          |
| `ethena-cooldown`                                                                                            | `(address sUSDe)` — single leg, `isLocked` toggles                                                                                                                              |
| `etherfi-withdrawal`                                                                                         | `(address withdrawRequestNFT)` — one anchor; one leg per open request (`positionInstanceId` = request NFT id)                                                                   |
| `stakewise-v3-exit`                                                                                          | `(address vault)` — static, per vault; one leg per exit ticket (`positionInstanceId` = ticket)                                                                                  |

{% hint style="info" %}
**Static positionId + per-item legs.** The withdrawal-queue and exit-queue adapters use a static `positionId` (the anchor contract above) and emit **one leg per open request**, each carrying its request id in `positionInstanceId` and its own `isLocked` (finalized/claimable → `false`, pending → `true`); the owed asset is in `balanceAsset`. The Ethena cooldown is a single leg whose `isLocked` toggles. Example — to decode a Morpho market leg: confirm `protocolSubId == keccak256("morpho-markets")`, then `marketId = abi.decode(positionId, (bytes32))`. To decode an Aave supply leg: `pool = abi.decode(positionId, (address))` — the supplied/borrowed reserve is the position's `balanceAsset.asset`.
{% endhint %}

***

## Stable reconciliation key

To match the same position across reads (and across adapter redeploys), use:

```solidity
keccak256(abi.encode(chainId, protocolSubId, positionId, balanceAsset.asset, positionKind))
```

This keys on **`protocolSubId`** (the product level), and deliberately excludes `protocolBrand`, `protocolId`, `balanceAdapter`, `positionInstanceId`, `isLocked`, `amount`, and all pricing — those either are grouping-only, change between reads, or are mutable/ephemeral, while the position's identity does not. **`isLocked` is excluded** because it is a mutable per-leg attribute (a leg flips `true→false` as its portion finalizes), so keying on it would give one economic position two keys over its lifetime.

{% hint style="info" %}
Because `isLocked` and `positionInstanceId` are **both excluded**, a withdrawal queue's claimable and locked legs — and all its per-request legs — collapse onto a single reconciliation key and **sum to the full holding**. Read `isLocked` per leg if you want the claimable-vs-locked breakdown, and append `positionInstanceId` if you want **per-item** granularity — at the cost of an ephemeral key that blinks in and out as items finalize, mint, claim, or burn. Never use those mutable/ephemeral fields for stable identity.
{% endhint %}

***

## Human-readable labels

Labels are **not** carried on the position. They are produced **lazily, on the verbose read path only**: `getAccountPositionsVerbose` / `getAccountPositionsForAssetVerbose` / `getAccountNavVerbose` call each adapter's `IPositionDescribable` — `protocolName()` and `positionLabels(positionId)` — via fail-open, gas-capped staticcalls, and return `VerbosePosition { position, protocolName, labels }`. Non-verbose reads return positions without labels (lower gas). The full breadcrumb a consumer renders is `[protocolName, ...labels]` — e.g. `["Morpho", "Market", "WETH/USDC"]` or `["Uniswap V3", "AMM Liquidity Pool", "USDC/WETH 0.3%"]`.

```solidity
interface IPositionDescribable {
    /// @notice Human-readable protocol name, e.g. "Morpho", "Uniswap V3".
    function protocolName() external view returns (string memory);

    /// @notice Display hierarchy below the protocol name, leaf last — derived purely from the static positionId.
    function positionLabels(bytes calldata positionId) external view returns (string[] memory);
}
```

Because `positionLabels` receives only the **static `positionId`**, it never includes a per-item id; the ephemeral NFT/request id lives in `positionInstanceId`. An adapter that does not implement `IPositionDescribable` (or whose call reverts) simply yields an empty `protocolName` / empty `labels`; the position's other fields are unaffected.

### Enumerating position shapes

A **configuration view** — a registry page listing what the NAV Calculator is set up to account for — has no account, so it has no `positionId` to pass to `positionLabels`. For meta-adapters the shapes *are* the governed instance set, readable via `getMetaInstances(adapter)`. For other adapters there was no enumeration primitive at all, so the only question a consumer could ask was `positionLabels("")` — which answers only for adapters whose breadcrumb happens to be `positionId`-independent. An adapter that derives labels per `positionId` correctly returns an empty array to that probe, and was therefore indistinguishable from one that was simply broken.

`IPositionEnumerable` closes that gap. It is the companion to `IPositionDescribable` and read the same way — optional, ERC-165 advertised, called through a fail-open staticcall, and **never on the NAV path**:

```solidity
interface IPositionEnumerable {
    /// @notice The positionIds this adapter can report, one per distinct position shape.
    function positionIds() external view returns (bytes[] memory);
}
```

Its one binding guarantee: **each returned id is byte-identical to the `positionId` the adapter emits for that shape**, so a configuration view can be *joined* against real positions. An id that merely labels correctly while differing from the emitted bytes silently breaks that join. The ERC-165 bit is itself the signal that an empty answer was a real answer rather than a failure.

These are **shapes, not positions**: an id being listed says nothing about whether any account holds a balance, and there are no amounts. As with labels, don't treat the enumeration as an identity or indexing mechanism — reconcile on the typed `protocolSubId` / `positionId` / `positionKind` fields.

Adapters implement it only where the shape set is bounded and derivable on-chain — pinned at construction, or a governed instance set. That is decided per adapter rather than per family: an adapter whose shapes are discovered per account (a coordinate cache fed by the position owner) has no account-independent answer and must not implement it, while one that pins its shapes at construction does, even if it is assistant-fed. Neither `positionLabels("")` nor the `IAssistedBalanceAdapter` marker discriminates — check the ERC-165 bit.

**The whole meta family implements it.** A meta-adapter's shape set *is* its governed instance set, so `positionIds()` reads `getMetaInstances(address(this))` off the NAV Calculator and maps each coordinate through the same internal hook that builds the emitted `positionId`. Enumeration therefore tracks governance with **no adapter state and no admin surface** — adding an instance is already a `MANAGER` config transaction — and the adapters stay immutable. A coordinate that cannot be resolved is dropped and the remaining shapes still returned, so one bad instance never blanks the enumeration.

{% hint style="warning" %}
**The trap is identity, not labelling.** Where an adapter's instance coordinate differs from the `positionId` it emits, enumeration must advertise the **emitted** id. [Aave V3](/funds/infrastructure/onchain-accounting/meta-balance-adapters/aave-v3.md) is the live case: its coordinate is a `ProtocolDataProvider`, while its `positionId` is the `Pool` resolved from it — so it advertises the Pool. Labelling this wrong is invisible, because `positionLabels(dataProvider)` and `positionLabels(pool)` return the *same* `["Market", <marketId>]` (both resolve the same addresses provider). Only the byte-exact join fails, and it fails silently, with both sides looking correctly labelled.
{% endhint %}

So a consumer sees three answers, and the ERC-165 bit tells them apart: an **enumerable** adapter returns its shapes from `positionIds()`; a **single-shape** adapter answers the account-less `positionLabels("")` probe directly; and a **per-account** adapter returns an empty array, meaning its shapes only exist once an owner has fed coordinates.

There is no NAV Calculator passthrough for `positionIds()`: a consumer takes the adapter set from `getAllAdapters()` and staticcalls each adapter directly. That is safe here because a position **shape** is not overridable — nothing on the registry can change what `positionIds()` returns.

**Do not extend that pattern to the display surface.** `protocolName()` and `positionLabels()` *do* have NAV passthroughs — the verbose reads on the position path, and `NAVCalculator.getAdapterDisplayInfo(adapters)` on the configuration path — and those are the only reads that apply the registry's [adapter label overrides](/funds/infrastructure/onchain-accounting/concepts/asset-classification.md#adapter-label-overrides). A consumer that staticcalls an adapter directly for its name or labels reads the compiled-in value and never learns an override exists.

### Label catalogue

The breadcrumb each adapter returns from `positionLabels`, keyed by `protocolSubId` (the full rendered breadcrumb is `[protocolName, ...labels]`). Leaf placeholders (`<…>`) are read live on-chain; a read that fails **drops only the leaf**, collapsing to the bare category (e.g. `["Vault"]`, `["Market"]`, `["AMM Liquidity Pool"]`).

That degradation is a guarantee, not best effort: `positionLabels` is display-only and **never reverts**. The metadata readers behind it guard the call target's codesize and validate the ABI head before decoding, rather than relying on `try/catch` — which catches neither a call to a code-less address nor a return-data decode failure, since both revert in the *calling* frame. This matters because the NAV Calculator collapses a reverting labels read into an **empty** array, losing the category as well as the leaf, so the row would render with no breadcrumb at all — strictly worse than a missing leaf.

A **malformed `positionId`** is handled the same fail-open way rather than reverting: an id that is not exactly one 32-byte word returns an empty array, and a word that is not a valid address (non-zero upper 96 bits) degrades to the bare category. The accept window is exactly 32 bytes, not "at least 32" — `abi.decode` silently ignores trailing bytes, so a looser guard would let a foreign multi-word coordinate through and label it from its first word, producing a confident, wrong breadcrumb for a position the adapter never emitted.

Deriving a leaf can also be **capped on gas**. Some leaves are built by reaching further into the chain from the governed coordinate — pool coins, a gauge's LP token, an ERC-20 `symbol()` per token — so those reads sit behind a fixed per-leaf ceiling; the rest need none, because they bottom out in the shared metadata readers, which are already capped per read. A leaf that does not fit its ceiling is **dropped, degrading to the bare category** — the same answer an unreadable symbol has always produced.

{% hint style="info" %}
This is a **liveness** bound, not a correctness one, and it applies only to the display path. Valuation reads are untouched: nothing here can change an amount or a price, and a label that cannot be derived never suppresses a position. Bounding the *valuation* reads the same way would be a different and riskier trade — too tight a bound there would skip a real market rather than a display string.
{% endhint %}

| `protocolSubId`                                              | `positionLabels`                                                                                                                                  |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wallet`                                                     | `["<symbol>"]` (or `["native"]` for the native token)                                                                                             |
| `aave-v3-lending`                                            | `["Market", "<market id>"]`                                                                                                                       |
| `spark-lend`                                                 | `["Market", "SparkLend"]`                                                                                                                         |
| `aave-v3-safety-module`                                      | `["Safety Module"]`                                                                                                                               |
| `aave-v3-umbrella`                                           | `["Umbrella Module"]`                                                                                                                             |
| `morpho-markets`                                             | `["Market", "<collateral>/<loan>"]`                                                                                                               |
| `morpho-vaults` · `euler-vaults`                             | `["Vault", "<vault name>"]`                                                                                                                       |
| `fluid-vaults`                                               | `["Vault", "<collateral>/<debt> #<vaultId>"]` (e.g. `wstETH/USDC #4`) — a Fluid vault is not an ERC20, and the pair alone is not unique           |
| `stakewise-v3-vaults`                                        | `["Vault", "<vault name>"]`, falling back to `["Vault", "<underlying symbol> <vault address>"]` — only the `…Erc20Vault` variants answer `name()` |
| `fluid-ftokens`                                              | `["Lending", "<fToken symbol>"]`                                                                                                                  |
| `compound-v3-comets`                                         | `["Market", "<comet symbol>"]` (e.g. `cUSDCv3`)                                                                                                   |
| `gearbox-v3-markets`                                         | `["Market", "<market name>"]`                                                                                                                     |
| `gearbox-v3-credit-accounts`                                 | `["Credit Account", "<credit-manager name>"]` (→ underlying asset symbol if name is empty)                                                        |
| `cap`                                                        | `["Stablecoin", "cUSD"]` · `["Savings", "stcUSD"]`                                                                                                |
| `curve-pools` · `balancer-v2-pools` · `balancer-v3-pools`    | `["AMM Liquidity Pool", "<sym0>/<sym1>/…"]`                                                                                                       |
| `uniswap-v2` · `pancakeswap-v2`                              | `["AMM Liquidity Pool", "<sym0>/<sym1>"]`                                                                                                         |
| `uniswap-v3` · `pancakeswap-v3`                              | `["AMM Liquidity Pool", "<sym0>/<sym1> <fee>%"]` (e.g. `USDC/WETH 0.3%`)                                                                          |
| `curve-gauges` · `balancer-v2-gauges` · `balancer-v3-gauges` | `["Staking Gauge", "<sym0>/<sym1>/…"]`                                                                                                            |
| `convex-curve-lp-staking`                                    | `["Staking", "Curve LP", "<coins>"]`                                                                                                              |
| `nexus-mutual-staking-pools`                                 | `["Staking Pools", "Pool #<poolId>"]`                                                                                                             |
| `lido-withdrawal`                                            | `["Withdrawal Queue", "stETH"]`                                                                                                                   |
| `stader-withdrawal`                                          | `["Withdrawal Queue", "ETHx"]`                                                                                                                    |
| `kelp-withdrawal`                                            | `["Withdrawal Queue", "rsETH"]`                                                                                                                   |
| `etherfi-withdrawal`                                         | `["Withdrawal Queue", "eETH"]`                                                                                                                    |
| `stakewise-v3-exit`                                          | `["Exit Queue", "<receiptSymbol>"]` (e.g. `osETH`; falls back to `["Exit Queue"]`)                                                                |
| `ethena-cooldown`                                            | `["Unstaking Cooldown", "sUSDe"]`                                                                                                                 |

{% hint style="warning" %}
Labels are **display-only** — never use them for identity, indexing, or reconciliation. Use the typed key above.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kpk.io/funds/infrastructure/onchain-accounting/concepts/position-identity.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
