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

# V2 API Overview

> Conventions, auth, envelope, errors, scopes, and idempotency for the Glider V2 Integrator API.

The Glider V2 API is the canonical REST surface for external integrators. It
lets partners define rebalance strategies, enroll end-users into them, and
monitor or withdraw the resulting portfolios.

## Mental Model

A **strategy** is a reusable template — allocation, schedule, and swap
preferences. A **portfolio** is one end-user's mirror of a strategy with one
smart account per chain. Every portfolio enrolled in a strategy tracks the
same allocation and cadence; publishing a new strategy version re-targets all
enrolled portfolios on their next scheduled rebalance.

Two rebalance triggers exist side-by-side:

* **Scheduled** — runs on the strategy's `frequency` (`hourly`/`daily`/
  `weekly`/`monthly`). Each portfolio's `nextDueAt` and `lastRebalanceAt`
  live on `GET /v2/portfolios/{id}` under `schedule`.
* **Manual** — `POST /v2/portfolios/{id}/rebalance` dispatches a one-off
  run outside the schedule. Useful when an end-user wants to capture or
  hedge market volatility without waiting for the next tick. A short
  per-portfolio cooldown applies; see the `429` + `Retry-After` contract.

## Asset Coverage

Strategy allocations and portfolio positions accept any CAIP-19 asset Glider
supports — ERC-20 tokens, SPL tokens, and tokenized real-world assets
(equities, treasuries) sourced through Glider's upstream data providers.
`GET /v2/portfolios/{id}/positions` returns every asset class in one
uniform row shape; integrators do not need to branch on asset type.

## Chain Abstraction

Glider's wallet infrastructure handles cross-chain deposit routing and
asset migrations between supported chains. Integrators do not need to plan
for client-side migrations when a chain is sunset or a strategy moves
liquidity venues — Glider relocates user assets without integrator
intervention.

## Machine-Readable Spec

For coding agents and SDK code-generators, point your tooling at these URLs
directly — they are the source of truth, not this page.

| Artifact                  | URL                                     | Purpose                                           |
| ------------------------- | --------------------------------------- | ------------------------------------------------- |
| OpenAPI 3.1 JSON          | `https://api.glider.fi/v2/openapi.json` | Generate SDKs, typed clients, Postman collections |
| LLM-friendly markdown     | `https://api.glider.fi/v2/llms.txt`     | Primer for coding agents (Claude, Cursor, etc.)   |
| Interactive docs (Scalar) | `https://api.glider.fi/v2/docs`         | Try-it-out UI with live request builder           |

## Base URL

```
https://api.glider.fi/v2
```

A staging server at `https://staging-api.glider.fi/v2` is available for
integration testing. Contact `developers@glider.fi` for a staging API key.

## Authentication

Every endpoint except `GET /v2/scopes` requires an API key in the `x-api-key`
header. HTTP header names are case-insensitive but the canonical form used
throughout v2 is lowercase.

```bash theme={null}
curl -H "x-api-key: gldr_sk_your_api_key" https://api.glider.fi/v2/whoami
```

`GET /v2/whoami` returns your tenant identity and granted scopes. Call it
first to confirm the key works.

## Response Envelope

V2 uses a consistent envelope. **Tracing identifiers live in response headers,
not in the JSON body.**

### Success

```json theme={null}
{
  "success": true,
  "data": { /* endpoint-specific */ }
}
```

### Paginated success

```json theme={null}
{
  "success": true,
  "data": { /* endpoint-specific */ },
  "nextCursor": "eyJjIjoi..." // null on the last page
}
```

`nextCursor` is a sibling of `data`, never nested inside it. Collections inside
`data` are always wrapped under a named key (e.g. `data.portfolios`, not
`data` directly).

### Error

```json theme={null}
{
  "success": false,
  "error": {
    "code": "API_400",
    "message": "Request validation failed",
    "details": ["allocation.assets: Allocation weights must sum to 100"]
  }
}
```

`details` is optional and omitted when there is nothing to say beyond
`message`.

### Tracing Headers

Every response, success or error, includes:

| Header             | Purpose                                                   |
| ------------------ | --------------------------------------------------------- |
| `X-Correlation-Id` | Cross-service trace id. Quote this when reporting issues. |
| `X-Request-Id`     | Unique per request.                                       |

V2 never places `correlationId`, `requestId`, or `timestamp` inside the body.
Read them from headers only.

## Scopes

Every authenticated endpoint declares the single scope it requires. If your
key lacks the scope you get `403 API_104`.

| Scope                 | Tier     | Grants                                                                    |
| --------------------- | -------- | ------------------------------------------------------------------------- |
| `strategies:read`     | default  | List and view strategies                                                  |
| `strategies:write`    | standard | Create strategies and publish versions                                    |
| `portfolios:read`     | default  | List and view user portfolios, poll operations                            |
| `portfolios:write`    | standard | Start, stop, and trigger rebalances                                       |
| `portfolios:withdraw` | standard | Prepare and submit user-signed withdrawal authorizations                  |
| `enroll:write`        | standard | Enroll new users into a strategy (creates smart accounts and a portfolio) |

Tiers:

* **default** — granted to every new key at issuance.
* **standard** — enabled per-integrator on request. Contact
  `developers@glider.fi` to upgrade.
* **restricted** — gated behind a commercial agreement.

To see the live set call `GET /v2/scopes` (no auth required). To see what
your specific key has, call `GET /v2/whoami`.

## Identifiers (CAIP)

All on-chain identifiers use
[CAIP](https://github.com/ChainAgnostic/CAIPs) so the same request shape
works on every supported chain. Never send bare hex addresses — they are
rejected with `400`.

| Identifier                             | Form                                                 | When to use                                                                                                                                                          |
| -------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| End-user wallet (EOA)                  | **Chain-agnostic** CAIP-10: `eip155:0:0x<addr>`      | Owner addresses in enrollment and portfolio reads. The same EOA works on every EIP-155 chain, so the chain reference is `0` per CAIP-10 §Abstract Account Addresses. |
| Smart account / smart-contract account | **Chain-bound** CAIP-10: `eip155:<chainId>:0x<addr>` | Smart accounts, session-key agents, withdrawal recipients. The contract exists only at that (chain, address) tuple.                                                  |
| Asset (ERC-20)                         | CAIP-19: `eip155:<chainId>/erc20:0x<addr>`           | Strategy allocations, withdrawal assets.                                                                                                                             |
| Asset (SPL, Solana)                    | CAIP-19: `solana:<ref>/spl:<mint>`                   | Same surface, Solana-native assets.                                                                                                                                  |

Withdrawal recipients must be **chain-bound** and must match the chain of
every asset in the request — one withdrawal = one chain.

See [CAIP identifiers](/guides/caip-identifiers) for a worked walk-through.

## Supported Chains

Production today supports:

| Chain            | ID      |
| ---------------- | ------- |
| Ethereum Mainnet | `1`     |
| Optimism         | `10`    |
| Polygon          | `137`   |
| Base             | `8453`  |
| Arbitrum         | `42161` |
| Linea            | `59144` |
| Blast            | `81457` |
| Plume            | `98866` |

If you pass an unsupported chain id, stage-1 enrollment returns `400` with
the allowed list in the error `details`.

## Monetary Values

All money is expressed as **decimal strings**, never numbers, to avoid IEEE
754 precision loss.

| Field                       | Format                                             | Example                        |
| --------------------------- | -------------------------------------------------- | ------------------------------ |
| `totalValueUsd`, `valueUsd` | 6 decimal places                                   | `"1500.500000"`                |
| `balance`                   | Full precision, trailing zeros trimmed             | `"14562044.3028598485"`        |
| `balanceRaw`, `amountRaw`   | Integer string, no decimal, no scientific notation | `"14562044302859848500000000"` |
| `priceUsd`                  | 6 decimal places                                   | `"1.000000"`                   |
| `weight`                    | Percent with up to 2 decimal places                | `"60"` or `"33.33"`            |
| `decimals`                  | Integer (the only numeric financial field)         | `6`                            |

For full withdrawals, read `balanceRaw` from
`GET /v2/portfolios/{portfolioId}/positions` and echo it as `amountRaw` —
do not re-derive from `balance`.

## Pagination

Cursor-based (keyset) pagination on `(createdAt, id)`.

* `limit` query param — min 1, max 200, default 50.
* `cursor` query param — opaque base64url string.
* `nextCursor` response field — `null` when there are no more pages.

**Do not decode or construct cursors.** They are server-owned and may change
format without a version bump. Pass the previous response's `nextCursor`
verbatim.

## Idempotency

Write endpoints that produce irreversible side effects accept an idempotency
anchor supplied by the caller. Replays with the same anchor return the
original response; replays with a conflicting body return `409 API_008`.

| Route                               | Anchor          | Sourced from                                           |
| ----------------------------------- | --------------- | ------------------------------------------------------ |
| `POST /v2/enroll`                   | `flowId`        | `POST /v2/enroll/signature` response                   |
| `POST /v2/portfolios/{id}/withdraw` | `message.nonce` | `POST /v2/portfolios/{id}/withdraw/signature` response |

See [Idempotency](/guides/idempotency) for retry rules and the three 409
sub-codes you may see.

## Async Operations

Write endpoints that dispatch onchain work (e.g. withdraw) return `202` with
an `operationId`. Poll
`GET /v2/portfolios/{portfolioId}/operations/{operationId}` at 2–5s
intervals until the state reaches `completed`, `failed`, or `cancelled`.

## Error Codes

All v2 error codes use the `API_XXX` format. The range prefix signals the
category; specific codes below are the ones you are likely to see from v2
endpoints.

| Code      | HTTP | Meaning                                                          |
| --------- | ---- | ---------------------------------------------------------------- |
| `API_006` | 404  | Resource not found                                               |
| `API_007` | 409  | Idempotency replay in progress — retry after a short wait        |
| `API_008` | 409  | Idempotency key conflict — same anchor, different body           |
| `API_101` | 401  | `x-api-key` header missing                                       |
| `API_102` | 401  | API key invalid                                                  |
| `API_104` | 403  | Missing required scope                                           |
| `API_200` | 404  | Portfolio not found or not owned by tenant                       |
| `API_202` | 409  | Portfolio already exists for this `(strategyId, ownerAccountId)` |
| `API_210` | 400  | Insufficient balance for withdrawal                              |
| `API_211` | 400  | Invalid recipient                                                |
| `API_212` | 400  | Duplicate `assetId` in withdrawal                                |
| `API_213` | 400  | Withdrawal chain mismatch (recipient vs asset chain)             |
| `API_214` | 400  | Withdrawal `message.portfolioId` mismatches the path param       |
| `API_215` | 400  | Portfolio has no smart account on the recipient's chain          |
| `API_216` | 400  | Withdrawal authorization expired (past `message.expiresAt`)      |
| `API_217` | 400  | Withdrawal signature does not recover to the portfolio owner     |
| `API_400` | 400  | Request validation failed — see `details[]`                      |
| `API_506` | 503  | Signature verifier temporarily unavailable — safe to retry       |
| `API_600` | 500  | Internal server error                                            |

Full catalog at [Error codes](/guides/error-codes).

## Tracing and Support

When reporting an issue, include the `X-Correlation-Id` header from the
failing response. It lets us join logs across services without asking you to
reproduce.

## Next Steps

* [Two-stage enrollment](/guides/two-stage-enrollment) — the signable-message round trip that creates a portfolio
* [Two-stage withdrawal](/guides/two-stage-withdrawal) — EIP-712 authorization flow for user-signed withdrawals
* [Idempotency](/guides/idempotency) — retry rules and the three 409 sub-codes
* [CAIP identifiers](/guides/caip-identifiers) — chain-agnostic vs chain-bound, worked examples
