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

# API versioning

> How the Rhinestone API evolves, how to pin a version, and how to migrate between them.

The Rhinestone API is versioned. Pin a version via the `x-api-version` header and upgrade on your own schedule — new versions are additive and opt-in.

## Why versioning

The API evolves as Rhinestone adds chains, settlement layers, and new intent patterns. Versioning lets us ship those changes without forcing every integrator to migrate in lockstep.

We use dated versions (`YYYY-MM.name`) rather than semver-style major versions. Semver majors tend to hoard changes into rare, big-bang releases — each bump looks like a migration project, so teams avoid shipping them. Dated versions normalise small, frequent bumps, so each one carries a small diff.

The `.name` suffix gives each release a human handle. We name versions after mountains, one per letter — `alps`, `blanc`, `corno`, and so on. It's easier to say "switch to blanc" than "switch to 2026-04".

## Pinning a version

Pin a version by sending `x-api-version` on every request:

```ts {6} theme={null}
const res = await fetch("https://v1.orchestrator.rhinestone.dev/quotes", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": apiKey,
    "x-api-version": "2026-04.blanc",
  },
  body: JSON.stringify(payload),
});
```

The format is `YYYY-MM.name`. Always send the header — requests without it fall back to the deprecated `2026-01.alps` shape, and unknown or malformed values return `400 VALIDATION_ERROR`.

## Compatibility

### What we can change without a new version

Within a version, we treat the response schema as open. These changes ship freely:

* New optional request fields
* New response fields
* New enum variants in responses (e.g. a new `settlementLayer`)
* New endpoints
* Widened request validation
* Changed default values

### What your client must do

To consume those changes safely:

* **Ignore unknown fields.** If you use a strict JSON parser (Go's `DisallowUnknownFields`, Rust `serde(deny_unknown_fields)`, Jackson's `FAIL_ON_UNKNOWN_PROPERTIES`), disable it for our responses.
* **Handle unknown enum values gracefully.** A `switch` on `settlementLayer` that throws on default will break the day we add a new one. Treat unknown values as "not supported by this client" and fall back.
* **Don't reconstruct EIP-712 types client-side.** Forward the server's typed data verbatim to `wallet.signTypedData()`. Hand-rolled type libraries break on additive schema changes.
* **Treat quote 404s as normal.** Quoted intents are stored server-side with a short TTL. If a quote expires or the quote store restarts, you'll see 404 on submit. Re-quote — don't retry the submit.
* **Take `routes[0]` unless you have your own criteria.** The array is server-ranked by a cost/speed tradeoff. Re-sorting client-side usually degrades route quality.

### What triggers a new version

* Removing or renaming any field
* Adding a required request field
* Changing a field's type
* Restructuring request or response shape
* Narrowing request validation
* Making a required response field optional or nullable
* Removing an endpoint

### Deprecation lifecycle

Fields deprecated in version N remain present and documented as deprecated; they're removed in N+1. Versions themselves are not routinely deprecated — each version is intended to stay live indefinitely so you never have to migrate on our clock.

The one exception is `2026-01.alps`, which will be sunset after integrators migrate to `blanc`. The shape difference between the two is large enough that maintaining both long-term isn't practical. Future version transitions will follow the standard per-field deprecation rule.

## Versions

### `2026-04.blanc`

The current version. Start here if you're integrating from scratch.

**What changed:**

* **Flat `routes[]` array.** Each route carries its own `intentId`, `cost`, `signData`, and `tokenRequirements` (EOAs only — smart accounts handle approvals internally). Pre-ranked; `routes[0]` is recommended.
* **CAIP-2 chain ids.** Every chain id on the wire is now an `eip155:<chainId>` string — `1` becomes `"eip155:1"`. Affects scalar fields, arrays, and chain-keyed maps. Path parameters are unchanged.
* **EIP-712 typed data in the response.** Forward `signData.origin[]` and `signData.destination` directly to `wallet.signTypedData()`. No more client-side type reconstruction.
* **Server-stored intents.** `POST /quotes` returns an `intentId`; the full intent stays server-side. Submit via `POST /intents` with `{ intentId, signatures }` — no more round-tripping `intentOp`.
* **Resource-style endpoints.** `POST /quotes` (was `/intents/route`), `POST /intents` (was `/intent-operations`), `GET /intents/:id` (was `/intent-operation/:id`), `POST /intents/splits` (was `/intents/split`).
* **Flattened cost structure.** One `cost` object per route with `input`, `output`, and `fees: { total, breakdown }`. Replaces scattered `tokensSpent` / `tokensReceived` / `feeBreakdownUSD` / `gasCost` / `sponsorFee`.
* **Submit response collapsed.** `POST /intents` returns just `{ intentId }` (was `{ result: { id, status } }`). The synthesised `status: "FAILED"` is gone — simulation failures now surface as 4xx with the unified error envelope.
* **Field renames.** Notable: `destinationGasUnits` → `destinationGasLimit`, `sponsorSettings.{gas,bridgeFees,swapFees}Sponsored` → `{gas,bridgeFees,swapFees}`, `userAddress` → `accountAddress`. Portfolio token shape uses `symbol` (was `tokenName`), `chains` (was `tokenChainBalance`), and per-chain `address`/`decimals` (were `tokenAddress`/`tokenDecimals`). Timestamps move from `BigIntLike` strings to integer Unix seconds.
* **Unified error envelope.** All non-2xx responses share `{ code, message, traceId, details? }`. Switch from message-pattern matching to `code`-based dispatch.
* **Schema trimming.** `Account.mockSignature` is gone — use the per-chain `Account.mockSignatures` map. `AccountType.SMART_ACCOUNT` is gone — use `ERC7579`. The `accountAccessList` flat-array variant is gone — use the structured shape.
* **Compact / resource locks removed.** No blanc equivalent for `topupCompact`, `emissaryConfig`, `MultichainCompact` origin signatures, or the `/withdrawals` endpoints.

#### Migrating from `alps`

**Quote and submit**

```ts theme={null}
// Before (alps) — client round-trips the full intentOp
const { intentOp } = await post("/intents/route", body);
const signatures = await sign(intentOp);
await post("/intent-operations", {
  signedIntentOp: { ...intentOp, ...signatures },
});

// After (blanc) — server stores the intent, client submits signatures by id
const { routes } = await post("/quotes", body);
const { intentId, signData } = routes[0];
const signatures = {
  origin: await Promise.all(signData.origin.map(signTypedData)),
  destination: await signTypedData(signData.destination),
};
await post("/intents", { intentId, signatures });
```

**Signing**

```ts theme={null}
// Before (alps) — client hashes intent fields with hand-built EIP-712 types
const hash = getIntentHash(intentOp);
const sig = await signer.signTypedData(types, hash);

// After (blanc) — server provides the typed data directly
const sig = await signer.signTypedData(signData.origin[0]);
```

**Submit response**

```ts theme={null}
// Before (alps) — wrapped, with synthesised status
const { result } = await post("/intent-operations", body);
const id = result.id; // result.status was "PENDING" or synthesised "FAILED"

// After (blanc) — id only; simulation failures surface as 4xx
const { intentId } = await post("/intents", body);
```

**Status polling**

```ts theme={null}
// Before (alps)
const op = await get(`/intent-operation/${id}`);
const status = op.result.status;

// After (blanc)
const op = await get(`/intents/${intentId}`);
const status = op.status;
```

The status enum is narrower in blanc — legacy values that no longer apply are removed. `PRECONFIRMED` is gone; if your client branches on it, drop that case.

**Splits endpoint**

`POST /intents/split` → `POST /intents/splits`. Request and response shapes follow the same blanc-wide rules (CAIP-2 chain ids, unified error envelope).

**Chain ids**

CAIP-2 strings (`eip155:<chainId>`) replace numeric chain ids on every field that carries one — `chainId`, `sourceChainId`, `destinationChainId`, `chainIds`, `allChainIds`, and chain-keyed maps like `auxiliaryFunds`, `preClaimExecutions`, `chainTokens`, and `tokenRequirements`. Path parameters are unchanged.

```ts theme={null}
// Before (alps)
{ "chainId": 42161 }
{ "tokenRequirements": { "1": [...], "42161": [...] } }

// After (blanc)
{ "chainId": "eip155:42161" }
{ "tokenRequirements": { "eip155:1": [...], "eip155:42161": [...] } }
```

The portfolio filter switches from comma-separated lists to repeated query parameters, with `tokens` keyed as `chain:address`:

```
GET /accounts/0xabc/portfolio
  ?chainIds=eip155:1&chainIds=eip155:137
  &tokens=eip155:1:0xa0b...&tokens=eip155:137:0xa0b...
```

**Reading costs**

```ts theme={null}
// Before (alps)
const gasUSD = response.feeBreakdownUSD.gas;
const inputAmount = response.tokensSpent[0].amount;

// After (blanc)
const gasUSD = route.cost.fees.breakdown.gas.usd;
const inputAmount = route.cost.input[0].amount;
```

**Field renames**

| Before (alps)         | After (blanc)         |
| --------------------- | --------------------- |
| `userAddress`         | `accountAddress`      |
| `destinationGasUnits` | `destinationGasLimit` |
| `tokensSpent`         | `cost.input`          |
| `tokensReceived`      | `cost.output`         |
| `feeBreakdownUSD`     | `cost.fees.breakdown` |
| `tokenName`           | `symbol`              |
| `tokenChainBalance`   | `chains`              |
| `tokenAddress`        | `address`             |
| `tokenDecimals`       | `decimals`            |

Timestamps move from `BigIntLike` strings (`"1700000000"`) to integer Unix seconds (`1700000000`).

**Sponsor settings**

```ts theme={null}
// Before (alps)
{
  sponsorSettings: {
    gasSponsored: true,
    bridgeFeesSponsored: false,
    swapFeesSponsored: false,
  }
}

// After (blanc)
{
  sponsorSettings: {
    gas: true,
    bridgeFees: false,
    swapFees: false,
  }
}
```

**Account types and mock signatures**

`AccountType.SMART_ACCOUNT` is gone — use `ERC7579`. The flat `accountAccessList` array shape is gone — use the structured shape. Mock signatures move from a single `Account.mockSignature` to a per-chain `Account.mockSignatures` map.

```ts theme={null}
// Before (alps)
{
  account: {
    accountType: "SMART_ACCOUNT",
    mockSignature: "0x...",
  }
}

// After (blanc)
{
  account: {
    accountType: "ERC7579",
    mockSignatures: {
      "eip155:1": "0x...",
      "eip155:42161": "0x...",
    },
  }
}
```

**Error handling**

```ts theme={null}
// Before (alps) — message-pattern matching
if (err.message === "Insufficient balance") { /* ... */ }
if (err.message.startsWith("Unsupported chain ")) { /* ... */ }

// After (blanc) — code-based dispatch
switch (err.code) {
  case "INSUFFICIENT_LIQUIDITY":
    // err.details: { availableIntents, unfillable }
    break;
  case "VALIDATION_ERROR":
    // err.details: [{ message, context? }]
    break;
}
```

Honour `Retry-After` on `429 TOO_MANY_REQUESTS`.

| Code                         | HTTP |
| ---------------------------- | ---- |
| `VALIDATION_ERROR`           | 400  |
| `UNAUTHORIZED`               | 401  |
| `FORBIDDEN`                  | 403  |
| `NOT_FOUND`                  | 404  |
| `CONFLICT`                   | 409  |
| `UNPROCESSABLE_CONTENT`      | 422  |
| `INSUFFICIENT_LIQUIDITY`     | 422  |
| `TOO_MANY_REQUESTS`          | 429  |
| `INTERNAL_ERROR`             | 500  |
| `SETTLEMENT_QUOTE_ERROR`     | 502  |
| `SETTLEMENT_EXECUTION_ERROR` | 502  |
| `RELAYER_MARKET_UNAVAILABLE` | 503  |
| `EXTERNAL_SERVICE_TIMEOUT`   | 504  |

The list is extensible — add unknown codes to a generic fallback path.

**Things you can't migrate**

The Compact / resource-lock surface has no blanc equivalent. If your integration depends on `topupCompact`, `emissaryConfig`, the `/withdrawals` endpoints, or `MultichainCompact` origin signatures, stay on `alps` until a replacement ships.

### `2026-01.alps`

The original release. Intents are round-tripped through the client, signed via client-reconstructed EIP-712, and submitted with the full `intentOp` attached to `signedIntentOp`. The Compact / resource-lock surface (`topupCompact`, `emissaryConfig`, `/withdrawals`, `MultichainCompact` signatures) is only available on this version — stay on `alps` if you depend on it.

<Warning>
  Deprecated. Will be removed once existing integrations migrate to `blanc`. No new features will land on this version.
</Warning>
