> For the complete documentation index, see [llms.txt](https://docs.glacislabs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.glacislabs.com/zero-delta/integration-guide/api.md).

# API

### Conventions

* **Auth.** Most endpoints require an API key in the `x-apikey` request header (a `Authorization: Bearer <key>` form is also accepted). The health probes are public. The base URL and your API key are provided during onboarding (separate dev/staging/production hosts); they are not published here.
* **Versioning.** Paths are namespaced under `/api/v1/`. Changes within `v1` are additive; breaking changes ship under a new version.
* **Roles.** Keys are `viewer` (all `/api/v1/*` read and quote endpoints) or `admin` (viewer access plus the `/api/v1/admin/*` management surface, which is not documented here).
* **Envelope.** Every `/api/v1/*` response is wrapped in a standard envelope: the resource lives under `data`, errors under `error`, and list responses carry `meta` and `paging`. The examples below show the `data` payload; assume the envelope around it.
* **Amounts.** Token amounts are strings in the token's smallest unit (e.g. `"1000000"` = 1 USDC at 6 decimals). Chain IDs are serialized as strings in responses. Native fees are wei strings.
* **Pagination.** List endpoints accept `limit` (max 500; default 50 for transactions, 100 elsewhere) and `offset` (default 0).

### Endpoints at a glance

<table><thead><tr><th>Method</th><th width="274">Path</th><th>Purpose</th></tr></thead><tbody><tr><td><code>POST</code></td><td><code>/api/v1/quote</code></td><td>Cross-chain swap quote</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/transactions</code></td><td>List tracked transactions</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/transactions/{txHash}</code></td><td>One transaction, with steps</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/transactions/{txHash}/orders</code></td><td>Batch orders in single transaction</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/transactions/stats</code></td><td>Aggregated transaction statistics</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/chains</code></td><td>List supported chains</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/chains/{chainId}</code></td><td>One chain</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/tokens</code></td><td>List supported tokens</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/tokens/{tokenId}</code></td><td>One token by ID</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/tokens/lookup</code></td><td>One token by address + chain</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/tokens/configs</code></td><td>Token bridge configuration documents</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/routes</code></td><td>List supported routes</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/routes/{routeId}</code></td><td>One route</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/protocols</code></td><td>List supported GMP protocols</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/solvers</code></td><td>Solvers grouped by chain</td></tr><tr><td><code>GET</code></td><td><code>/api/v1/solvers/{chainId}</code></td><td>Solvers on one chain</td></tr><tr><td><code>GET</code></td><td><code>/health</code></td><td>Liveness probe (no auth)</td></tr><tr><td><code>GET</code></td><td><code>/health/ready</code></td><td>Readiness probe (no auth)</td></tr></tbody></table>

***

### Quotes

#### `POST /api/v1/quote`

> **Wallet screening.** ZeroDelta screens wallets at two points. (1) **At quote time** — when you supply `owner` (and optionally `destinationReceiver`), both are screened by the compliance engine *before* a quote is returned; a blocked pair receives no quote or deposit instructions (`422 COMPLIANCE_BLOCKED`). (2) **At execution time** — the solver re-screens the pair through its pre-fill compliance gate *before filling* the order, so a pair that becomes blocked after quoting is still stopped before settlement. When `owner` is omitted the quote returns `complianceStatus: UNSCREENED` and the execution-time gate remains the enforcement point.

Calculates a firm cross-chain swap quote between two supported tokens. The quote is computed in-process: the engine races every configured liquidity provider in parallel, picks the best output, adds LayerZero/CCTP fees and a bridge-time estimate, and returns a prepared `orderRequest` struct ready for `submitOrder`. The response carries a masked `provider` label (the specific market maker is not disclosed). `fromToken` and `toToken` must differ — same-token bridge-only quotes are not supported. This endpoint is per-key rate limited; max request body 1 MB.

**Request body** (QuoteRequest):

| Field                 | Required | Description                                                                                             |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `fromChainId`         | yes      | Source chain ID (string or number).                                                                     |
| `toChainId`           | yes      | Destination chain ID.                                                                                   |
| `fromToken`           | yes      | Source token symbol or contract address.                                                                |
| `toToken`             | yes      | Destination token symbol or contract address. Must differ from `fromToken`.                             |
| `fromAmount`          | yes      | Amount to sell, in the source token's smallest unit.                                                    |
| `owner`               | no       | Order owner address. When provided, the response also populates `escrowAddress` and `executionChainId`. |
| `destinationReceiver` | no       | Recipient wallet; screened alongside `owner` when present.                                              |

The request does **not** accept `destinationReceiver`; the returned `orderRequest.destinationReceiver` is `null` and is filled client-side before submitting (see the Integration Guide).

```json
{
  "fromChainId": "130",
  "toChainId": "42161",
  "fromToken": "USDC",
  "toToken": "USDT",
  "fromAmount": "1000000000000"
}
```

**Response** (`200`, `data` is a Quote; illustrative values):

<pre class="language-json"><code class="lang-json"><strong>{
</strong>  "data": {
    "fromChainId": "130",
    "toChainId": "42161",
    "fromToken": "USDC",
    "toToken": "USDT",
    "fromAmount": "1000000000000",
    "toAmount": "999850000000",
    "finalAmount": "999850000000",
    "bridge": "Circle",
    "feeBps": 0,
    "estimatedDuration": 600,
    "lifespanSeconds": 30,
    "swapTimeSeconds": 12,
    "gasCostSource": "0",
    "gasCostDest": "0",
    "swapCost": "",
    "provider": "zerodelta",
    "route": { "...": "see Route" },
    "orderRequest": {
      "owner": null,
      "bidToken": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
      "bidTokenAmount": "1000000000000",
      "askToken": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
      "askTokenAmount": "999850000000",
      "destinationReceiver": null,
      "destinationChainId": "42161",
      "deadline": "0",
      "data": "0x"
    },
    "complianceStatus": "SCREENED",
      "compliance": {
      "status": "SCREENED",
      "decision": "ALLOW",
      "screenedWallets": [
        "0x1111111111111111111111111111111111111111",
        "0x2222222222222222222222222222222222222222"
      ],
      "sourceJurisdiction": "US_EX_NY",
      "appliedPolicyIds": ["pol_01H..."],
      "appliedPolicyNames": ["Default OFAC + Jurisdiction"],
      "appliedLegislation": ["MICA_EU"],
      "markersRequired": ["KYC_ATTESTATION"],
      "riskCategories": [],
      "flaggedCategories": [],
      "blockingCategories": [],
      "reasons": [],
      "policyVersion": "2026-07-01",
      "checkId": "chk_01H...",
      "expiresAt": "2026-07-14T12:30:00Z"
    }
  }
}
</code></pre>

When `owner` is supplied in the request, the response additionally includes `escrowAddress`, `executionChainId`, and `executionEscrowAddress`.

**Fee and amount semantics:**

* `toAmount` is the on-chain `order.askTokenAmount` — the swap output threshold the solver must meet to fill the order, used to construct `orderRequest.askTokenAmount`. It is not what the destination receiver gets when the outbound bridge charges a protocol fee — see `finalAmount`.
* `finalAmount` is what the destination receiver actually gets: `toAmount` minus the outbound bridge protocol fee (CCTP fast-track maxFee). It equals `toAmount` for LayerZero outbound and CCTP slow-track outbound, and is otherwise slightly lower. `finalAmount <= toAmount` always; always populated.
* `feeBps` is the route protocol fee in basis points (CCTP fast-track fee for Circle lanes; `0` for LayerZero). Metadata only — it is **not** subtracted from `toAmount`. The actual fee shows up as the gap between `toAmount` and `finalAmount`.
* `estimatedDuration` is the end-to-end transfer time in seconds, composed of inbound bridge finality + execution-chain swap window + outbound bridge finality, plus a per-leg fill-latency buffer that covers tx inclusion on each hop. CCTP legs use fast vs standard finality depending on the deployed adapter's slow-track setting.
* `lifespanSeconds` is how long the quote stays valid, in seconds, derived from the winning provider's signed expiry (expiry minus generation time, floored at 0). It is `0` when the provider does not sign an expiry or the quote has already lapsed. The on-chain fill threshold itself does **not** expire — this is the provider's price-validity window.
* `swapTimeSeconds` is the estimated time for the on-chain swap to settle on the execution chain. The swap executes in a single transaction, so this is one block of the execution chain (its average block time, which differs per chain); `0` when block-time data is unavailable.
* `gasCostSource` / `gasCostDest` are LayerZero native messaging fees in wei (inbound and outbound respectively), and are `0` for CCTP (USDC). Pass `gasCostSource` as `msg.value` on `submitOrder`.
* `swapCost` is a gas hint from the winning liquidity provider (decimal string; empty when the provider does not return one).
* `provider` is a masked liquidity-provider label, always `"zerodelta"` on a successful quote — the specific market maker the engine routes through is not disclosed. Set on every successful quote.

**Errors:**

<table data-header-hidden data-search="false"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td>Status</td><td><code>error.code</code></td><td>Cause</td></tr><tr><td><code>400</code></td><td><code>BAD_REQUEST</code></td><td>Malformed body, missing required field, non-positive <code>fromAmount</code>, or <code>fromToken</code> equal to <code>toToken</code>.</td></tr><tr><td><code>401</code></td><td><code>UNAUTHORIZED</code></td><td>Missing or invalid API key.</td></tr><tr><td><code>422</code></td><td><code>BELOW_MIN_TRADE_SIZE</code></td><td>Bid amount is under the per-pair minimum (about $0.02 globally, about $0.11 for USDe pairs). <code>error.fieldErrors</code> carries a <code>fromAmount</code> entry with the exact minimum.</td></tr><tr><td><code>422</code></td><td><code>QUOTE_UNAVAILABLE</code></td><td>No registered route for the requested (token, source chain, destination chain) — e.g. the token is not enabled on that chain.</td></tr><tr><td><code>422</code></td><td><code>VALIDATION_ERROR</code></td><td>Semantic validation failure, with per-field details.</td></tr><tr><td><code>422</code></td><td><code>COMPLIANCE_BLOCKED</code></td><td>Supplied <code>owner</code>/<code>destinationReceiver</code> pair failed screening; no quote or deposit instructions returned. The error <code>reasons[]</code> array is populated per the disclosure policy.</td></tr><tr><td><code>429</code></td><td><code>RATE_LIMITED</code></td><td>Rate limit exceeded; a <code>Retry-After</code> header (seconds) is returned.</td></tr><tr><td><code>502</code></td><td><code>UPSTREAM_ERROR</code></td><td>The quoting engine could not produce a quote (all liquidity providers failed, or bridge-fee estimation failed). Transient; retry with backoff.</td></tr><tr><td><code>503</code></td><td><code>SERVICE_UNAVAILABLE</code></td><td>Compliance engine unreachable while screening — the quote gate <strong>fails closed</strong> (only when <code>owner</code> is supplied and the gate is configured).</td></tr></tbody></table>

***

### Transactions

#### `GET /api/v1/transactions`

Lists tracked cross-chain transactions, most recent first. Returns an array of TrackedTransaction (without the per-transaction `steps` array — fetch a single transaction for steps).

**Query parameters:**

<table data-search="false"><thead><tr><th>Parameter</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td>Filter by TrackingStatus.</td></tr><tr><td><code>protocol</code></td><td>Filter by GMP protocol (e.g. <code>layerzero</code>).</td></tr><tr><td><code>chainId</code></td><td>Matches source <strong>or</strong> destination chain.</td></tr><tr><td><code>fromChainId</code></td><td>Filter by source chain.</td></tr><tr><td><code>toChainId</code></td><td>Filter by destination chain.</td></tr><tr><td><code>txHash</code></td><td>Filter by transaction hash (list filter, not the path lookup).</td></tr><tr><td><code>wallet</code></td><td>Filter by wallet address.</td></tr><tr><td><code>createdAfter</code> / <code>createdBefore</code></td><td>RFC 3339 time-window bounds.</td></tr><tr><td><code>sortBy</code></td><td><code>created_at</code> (default) or <code>updated_at</code>.</td></tr><tr><td><code>sortOrder</code></td><td><code>desc</code> (default) or <code>asc</code>.</td></tr><tr><td><code>limit</code> / <code>offset</code></td><td>Pagination (default limit 50, max 500).</td></tr></tbody></table>

**Errors:** `400 BAD_REQUEST`, `401 UNAUTHORIZED`.

#### `GET /api/v1/transactions/{txHash}`

Returns one TrackedTransaction keyed by **source transaction hash**, including the `steps` array. This is the endpoint the status poller calls (see the Integration Guide). Before the source tx is mined and indexed, this returns `404` — treat that as "not indexed yet" and keep polling.

**Path parameter:** `txHash` — the source-chain submit transaction hash.

**Errors:** `401 UNAUTHORIZED`, `404 NOT_FOUND`.

#### `GET /api/v1/transactions/{txHash}/orders`

Returns **all** orders sharing a submit transaction hash, most-recent first. A single submit tx can create more than one order (a batch); the single-object `GET /api/v1/transactions/{txHash}` collapses to the most-recent, while this returns the full set — each enriched identically to the single GET. Requires an API key.

**Path parameter:** `txHash` — the source-chain submit transaction hash.

**Response (200):** `data` = array of `TrackedTransaction` (each with its `steps`).

**Errors:** `401 UNAUTHORIZED`, `404 NOT_FOUND`.

#### `GET /api/v1/transactions/stats`

Returns aggregated statistics over the transactions matching the filters. The `data` payload is a TransactionStats object.

**Query parameters:** `status`, `protocol`, `chainId` (matches source **or** destination chain), `fromChainId`, `toChainId`, `wallet`, `createdAfter`, `createdBefore` (same semantics as the list endpoint).

```json
{
  "data": {
    "total": 1234,
    "byStatus": { "delivered": 1100, "in_progress": 30 },
    "byProtocol": { "layerzero": 800, "cctp": 434 },
    "avgDeliveryMs": 412000.0,
    "bySourceChain": { "1": 600, "42161": 300 },
    "byDestChain": { "42161": 500, "8453": 200 }
  }
}
```

**Errors:** `401 UNAUTHORIZED`.

***

### Chains

#### `GET /api/v1/chains`

Lists supported chains. Returns an array of Chain. Each chain is enriched at response time with its contract addresses and execution-chain flag.

**Query parameters:** `enabledOnly` (string; set to `false` to include disabled chains, default `true`), `limit`, `offset`.

**Errors:** `401 UNAUTHORIZED`.

#### `GET /api/v1/chains/{chainId}`

Returns one Chain.

**Path parameter:** `chainId` (integer).

**Errors:** `401 UNAUTHORIZED`, `404 NOT_FOUND`.

***

### Tokens

#### `GET /api/v1/tokens`

Lists supported tokens as per-chain rows. Returns an array of Token, each enriched with bridge metadata and its enabled outbound `routes`. This is the canonical source for per-(chain, token) contract addresses — do not hardcode them.

**Query parameters:** `chainId`, `symbol` (e.g. `USDC`), `isEnabled` (string; `false` includes disabled tokens, default `true`), `limit`, `offset`.

```json
{
  "data": [
    { "symbol": "USDC", "chainId": "42161", "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "decimals": 6 },
    { "symbol": "USDT", "chainId": "42161", "address": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", "decimals": 6 }
  ]
}
```

**Errors:** `401 UNAUTHORIZED`.

#### `GET /api/v1/tokens/{tokenId}`

Returns one Token by its ID.

**Path parameter:** `tokenId` (string).

**Errors:** `401 UNAUTHORIZED`, `404 NOT_FOUND`.

#### `GET /api/v1/tokens/lookup`

Resolves a single Token by contract address and chain.

**Query parameters (both required):** `address`, `chainId`.

**Errors:** `400 BAD_REQUEST`, `401 UNAUTHORIZED`, `404 NOT_FOUND`.

#### `GET /api/v1/tokens/configs`

Returns the per-symbol token bridge configs (loaded from the token config directory), as an array of typed `TokenConfig` objects — one per token, sorted by `name`. `contracts` is keyed by chain ID. Useful for discovering bridge/standard metadata across chains in one call.

```json
{
  "data": [
    {
      "name": "USD Coin",
      "bridge": "Circle",
      "facet": "CCTPV2",
      "standard": "CCTPV2",
      "homeChainId": "1",
      "logoUrl": "https://...",
      "contracts": {
        "42161": { "impl": "0x…", "token": "0x…", "name": "USD Coin", "symbol": "USDC", "chainName": "arbitrum", "decimals": 6, "gmpParameters": "0x…" }
      }
    }
  ]
}
```

**Errors:** `401 UNAUTHORIZED`.

***

### Routes

#### `GET /api/v1/routes`

Lists supported routes. Returns an array of Route. A route is one enabled `(fromChain, fromToken) → (toChain, toToken)` lane with its bridge, fee, and duration metadata. Treat this as the source of truth for which lanes are live before building a route picker.

**Query parameters:**

<table data-search="false"><thead><tr><th>Parameter</th><th>Description</th></tr></thead><tbody><tr><td><code>fromChainId</code></td><td>Filter by source chain.</td></tr><tr><td><code>toChainId</code></td><td>Filter by destination chain.</td></tr><tr><td><code>token</code></td><td>Filter by token symbol, token name, token ID, or source/destination token address.</td></tr><tr><td><code>tokenId</code></td><td>Filter by token ID.</td></tr><tr><td><code>bridge</code></td><td>Filter by bridge provider (e.g. <code>layerzero</code>, <code>Circle</code>).</td></tr><tr><td><code>isEnabled</code></td><td>String; <code>false</code> includes disabled routes, default <code>true</code>.</td></tr><tr><td><code>limit</code> / <code>offset</code></td><td>Pagination.</td></tr></tbody></table>

**Errors:** `401 UNAUTHORIZED`.

#### `GET /api/v1/routes/{routeId}`

Returns one Route.

**Path parameter:** `routeId` (string).

**Errors:** `401 UNAUTHORIZED`, `404 NOT_FOUND`.

***

### Protocols

#### `GET /api/v1/protocols`

Lists the supported cross-chain messaging (GMP) protocols and their per-chain state. The `data` payload has `protocols` (a string array) and `states` (an array of ProtocolState).

```json
{
  "data": {
    "protocols": ["cctp", "layerzero"],
    "states": [
      { "chainId": "1", "executionChainId": "1", "isPaused": false, "updatedAt": "2026-04-13T12:00:00Z" }
    ]
  }
}
```

**Errors:** `401 UNAUTHORIZED`.

***

### Solvers

A solver is a wallet authorized to fill orders on a given chain. These endpoints expose the public solver registry (read-only).

#### `GET /api/v1/solvers`

Returns active solvers grouped by chain, as an array of ChainSolvers.

**Query parameter:** `enabledOnly` (boolean; set to `false` to include soft-deleted rows, default `true`).

**Errors:** `401 UNAUTHORIZED`.

#### `GET /api/v1/solvers/{chainId}`

Returns one ChainSolvers group for a single chain. Returns `404` for chain IDs not in the chain registry.

**Path parameter:** `chainId` (integer). **Query parameter:** `enabledOnly` (boolean, default `true`).

**Errors:** `400 BAD_REQUEST`, `401 UNAUTHORIZED`, `404 NOT_FOUND`.

***

### Health

These probes require no API key.

#### `GET /health`

Liveness probe. Always returns `200`.

```json
{ "status": "ok", "time": "2026-04-13T12:00:00Z" }
```

#### `GET /health/ready`&#x20;

Readiness probe. Probes downstream dependencies in parallel; only the database pools are critical. Returns `503` when a critical DB check fails, otherwise `200` — with `status: degraded` when a non-critical dependency (`solver`, `gmp`, `drpc`, `bebop`) is down. No auth.

```json
{
  "status": "ok",
  "time": "2026-04-13T12:00:00Z",
  "checks": {
    "database":      { "status": "ok", "critical": true, "latencyMs": 3 },
    "read_database": { "status": "ok", "critical": true, "latencyMs": 4 },
    "cache":         { "status": "ok", "critical": true }
  },
  "providers": {
    "a1b2c3d4e5f6": { "status": "ok", "critical": false, "latencyMs": 40, "ageSeconds": 5 },
    "9c0d1e2f3a4b": { "status": "ok", "critical": false }
  },
  "failures": []
}
```

> * `status` is `ok` or `degraded`.
> * `checks` holds infrastructure dependencies under generic role labels (`database`, `read_database`, `cache`).
> * `providers` holds third-party / internal-service dependencies under **opaque hashed keys** — the specific vendor is never disclosed.
> * Each entry has `status` (`ok | unavailable | stub | not_configured`), a `critical` flag, and optional `latencyMs` / `ageSeconds`. Per-check error detail is intentionally omitted.
> * `failures` lists the masked keys of non-ok checks (omitted when empty).

***

### Internal endpoints (not for integrators)

The following endpoints exist on the API but are not part of the integration surface and are not documented here:

* `GET /api/v1/transactions/{txHash}/gmp` — on-demand GMP status enrichment, IP-whitelisted to internal services (Listener/Executor).
* `GET /metrics` — Prometheus metrics, optionally IP-whitelisted.
* `POST /api/v1/admin/genesis`, `GET|POST|DELETE /api/v1/admin/api-keys`, and all `/api/v1/admin/solvers` CRUD plus other `/api/v1/admin/*` routes — operator-only key, solver, and chain management requiring the admin role.

***

### Models

#### Envelope

Every `/api/v1/*` response uses this wrapper. `data` carries the result (an object or array); `error` is present only on failure; `meta` and `paging` appear on list responses.

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>data</code></td><td>object or array</td><td>The resource payload.</td></tr><tr><td><code>error.code</code></td><td>ErrorCode</td><td>Machine-readable error code.</td></tr><tr><td><code>error.message</code></td><td>string</td><td>Human-readable message.</td></tr><tr><td><code>error.fieldErrors[]</code></td><td>array</td><td>Per-field errors, each <code>{ field, message }</code>.</td></tr><tr><td><code>error.reasons[]</code></td><td>array</td><td>Machine-readable decision reason codes, populated on <code>COMPLIANCE_BLOCKED</code> per the server's disclosure policy (<code>POLICY_GUARD_REASON_DISCLOSURE</code>).</td></tr><tr><td><code>meta.page</code></td><td>integer</td><td>Current page.</td></tr><tr><td><code>meta.perPage</code></td><td>integer</td><td>Items per page.</td></tr><tr><td><code>meta.totalCount</code></td><td>integer</td><td>Total matching items.</td></tr><tr><td><code>paging.self</code></td><td>string</td><td>URL of the current page.</td></tr><tr><td><code>paging.next</code></td><td>string or null</td><td>URL of the next page, or null.</td></tr><tr><td><code>paging.prev</code></td><td>string or null</td><td>URL of the previous page, or null.</td></tr></tbody></table>

On error, only the `error` object is populated:

```json
{ "error": { "code": "UNAUTHORIZED", "message": "missing or invalid API key" } }
```

Branch on `error.code` (machine-readable), not on `error.message`.

#### ErrorCode

<table data-search="false"><thead><tr><th>Value</th><th>Typical status</th></tr></thead><tbody><tr><td><code>INTERNAL_ERROR</code></td><td>500</td></tr><tr><td><code>BAD_REQUEST</code></td><td>400</td></tr><tr><td><code>NOT_FOUND</code></td><td>404</td></tr><tr><td><code>CONFLICT</code></td><td>409</td></tr><tr><td><code>VALIDATION_ERROR</code></td><td>422</td></tr><tr><td><code>UNAUTHORIZED</code></td><td>401</td></tr><tr><td><code>FORBIDDEN</code></td><td>403</td></tr><tr><td><code>RATE_LIMITED</code></td><td>429</td></tr><tr><td><code>SERVICE_UNAVAILABLE</code></td><td>503</td></tr><tr><td><code>QUOTE_UNAVAILABLE</code></td><td>422</td></tr><tr><td><code>UPSTREAM_ERROR</code></td><td>502</td></tr><tr><td><code>BELOW_MIN_TRADE_SIZE</code></td><td>422</td></tr><tr><td><code>COMPLIANCE_BLOCKED</code></td><td>422</td></tr></tbody></table>

#### TrackingStatus

Lifecycle status of a tracked cross-chain transaction. Returned as a lowercase string (not the on-chain `OrderStatus` enum — see the Smart Contract Reference). When polling, treat any value outside the non-terminal set as terminal so a newly added status cannot trap your loop.

<table data-search="false"><thead><tr><th>Status</th><th>Terminal?</th><th>Meaning</th></tr></thead><tbody><tr><td><code>pending</code></td><td>no</td><td>Submitted; awaiting arrival on the hub.</td></tr><tr><td><code>in_progress</code></td><td>no</td><td>Arrived on the hub; awaiting fill.</td></tr><tr><td><code>outbound_bridging</code></td><td>no</td><td>Filled; bridging out to the destination chain.</td></tr><tr><td><code>cancel_pending</code></td><td>no</td><td>User pre-cancelled; awaiting arrival to claim.</td></tr><tr><td><code>awaiting_claim</code></td><td>no</td><td>Arrived after a pre-cancel; user can claim funds.</td></tr><tr><td><code>delivered</code></td><td>yes</td><td>Success — receiver got the asset.</td></tr><tr><td><code>expired</code></td><td>yes</td><td>Deadline elapsed in transit (claim required).</td></tr><tr><td><code>cancelled</code></td><td>yes</td><td>Order cancelled; funds returned.</td></tr><tr><td><code>fill_failed</code></td><td>yes</td><td>Solver gave up before filling.</td></tr><tr><td><code>delivery_failed</code></td><td>yes</td><td>Outbound bridge permanently failed.</td></tr><tr><td><code>failed</code></td><td>yes</td><td>Generic unrecoverable failure.</td></tr></tbody></table>

Happy path: `pending → in_progress → outbound_bridging → delivered`.

#### QuoteRequest

| Field         | Type   | Required | Description                                                                                  |
| ------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `fromChainId` | string | yes      | Source chain ID (string or number accepted).                                                 |
| `toChainId`   | string | yes      | Destination chain ID.                                                                        |
| `fromToken`   | string | yes      | Source token symbol or contract address.                                                     |
| `toToken`     | string | yes      | Destination token symbol or address; must differ from `fromToken`.                           |
| `fromAmount`  | string | yes      | Amount to sell, in smallest unit.                                                            |
| `owner`       | string | no       | Order owner address; when set, the response includes `escrowAddress` and `executionChainId`. |

Does not accept `destinationReceiver`.

#### Quote

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>fromChainId</code></td><td>string</td><td>Source chain ID.</td></tr><tr><td><code>toChainId</code></td><td>string</td><td>Destination chain ID.</td></tr><tr><td><code>fromToken</code></td><td>string</td><td>Source token.</td></tr><tr><td><code>toToken</code></td><td>string</td><td>Destination token.</td></tr><tr><td><code>fromAmount</code></td><td>string</td><td>Amount sold, smallest unit.</td></tr><tr><td><code>toAmount</code></td><td>string</td><td>On-chain <code>order.askTokenAmount</code> — the swap output threshold the solver must meet to fill the order; used to construct <code>orderRequest.askTokenAmount</code>. NOT what the receiver gets when the outbound bridge charges a protocol fee — see <code>finalAmount</code>.</td></tr><tr><td><code>finalAmount</code></td><td>string</td><td>What the receiver gets: <code>toAmount</code> minus the outbound CCTP fast-track maxFee. <code>&#x3C;= toAmount</code> (equal for LayerZero outbound / CCTP slow-track). Always populated.</td></tr><tr><td><code>bridge</code></td><td>string</td><td>Bridge provider for the route (e.g. <code>Circle</code>).</td></tr><tr><td><code>feeBps</code></td><td>integer</td><td>Route protocol fee in basis points (CCTP fast-track fee for Circle lanes; <code>0</code> for LayerZero). Metadata only — NOT subtracted from <code>toAmount</code>; the actual fee is the gap between <code>toAmount</code> and <code>finalAmount</code>.</td></tr><tr><td><code>estimatedDuration</code></td><td>integer</td><td>End-to-end transfer time in seconds: inbound bridge finality + execution-chain swap window + outbound bridge finality, plus a per-leg fill-latency buffer for tx inclusion.</td></tr><tr><td><code>lifespanSeconds</code></td><td>integer</td><td>Quote validity in seconds, from the winning provider's signed expiry (floored at 0). <code>0</code> when the provider does not sign an expiry or the quote has lapsed. The on-chain fill threshold itself does not expire.</td></tr><tr><td><code>swapTimeSeconds</code></td><td>integer</td><td>Estimated time for the on-chain swap to settle on the execution chain — one block of the execution chain (average block time, differs per chain). <code>0</code> when block-time data is unavailable.</td></tr><tr><td><code>route</code></td><td>Route</td><td>The route this quote priced.</td></tr><tr><td><code>gasCostSource</code></td><td>string</td><td>LayerZero inbound native fee in wei; <code>0</code> for CCTP. Pass as <code>msg.value</code>.</td></tr><tr><td><code>gasCostDest</code></td><td>string</td><td>LayerZero outbound native fee in wei; <code>0</code> for CCTP.</td></tr><tr><td><code>swapCost</code></td><td>string</td><td>Gas hint from the winning liquidity provider (decimal string; empty when not provided).</td></tr><tr><td><code>provider</code></td><td>string</td><td>Masked liquidity-provider label — always <code>"zerodelta"</code> on a successful quote; the specific market maker is not disclosed.</td></tr><tr><td><code>orderRequest</code></td><td>OrderRequest</td><td>Pre-built struct for <code>submitOrder</code>.</td></tr><tr><td><code>escrowAddress</code></td><td>string</td><td>Source-chain escrow address (present when <code>owner</code> is supplied).</td></tr><tr><td><code>executionChainId</code></td><td>string</td><td>Chain ID where settlement executes (present when <code>owner</code> is supplied).</td></tr><tr><td><code>executionEscrowAddress</code></td><td>string</td><td>Escrow address on the execution chain.</td></tr></tbody></table>

#### OrderRequest

Pre-built Solidity `OrderRequest` struct for `submitOrder()`. `owner` and `destinationReceiver` come back `null` — the frontend must fill both with real, non-zero addresses before calling the contract (see the Integration Guide).

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>owner</code></td><td>string or null</td><td><code>null</code> — fill with the user's wallet address.</td></tr><tr><td><code>bidToken</code></td><td>string</td><td>Resolved source token contract address.</td></tr><tr><td><code>bidTokenAmount</code></td><td>string</td><td>Amount in smallest unit.</td></tr><tr><td><code>askToken</code></td><td>string</td><td>Resolved destination token contract address.</td></tr><tr><td><code>askTokenAmount</code></td><td>string</td><td>On-chain fill threshold — the swap output the solver must meet to fill the order. Always equals <code>Quote.toAmount</code>. NOT what the destination receiver gets when the outbound bridge charges a protocol fee — see <code>Quote.finalAmount</code>.</td></tr><tr><td><code>destinationReceiver</code></td><td>string or null</td><td><code>null</code> — fill with the recipient address.</td></tr><tr><td><code>destinationChainId</code></td><td>string</td><td>Target chain ID.</td></tr><tr><td><code>deadline</code></td><td>string</td><td>Unix seconds; <code>"0"</code> means no deadline.</td></tr><tr><td><code>data</code></td><td>string</td><td>Arbitrary bytes (currently always <code>"0x"</code>).</td></tr></tbody></table>

#### Chain

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>chainId</code></td><td>string</td><td>Chain ID (serialized as a string).</td></tr><tr><td><code>name</code></td><td>string</td><td>Internal name (e.g. <code>arbitrum</code>).</td></tr><tr><td><code>displayName</code></td><td>string</td><td>Human label (e.g. <code>Arbitrum One</code>).</td></tr><tr><td><code>explorerUrl</code></td><td>string</td><td>Block explorer base URL.</td></tr><tr><td><code>nativeCurrency</code></td><td>string</td><td>Native currency symbol.</td></tr><tr><td><code>isTestnet</code></td><td>boolean</td><td>Testnet flag.</td></tr><tr><td><code>isEnabled</code></td><td>boolean</td><td>Whether the chain is enabled.</td></tr><tr><td><code>isExecutionChain</code></td><td>boolean</td><td>True when this is the configured execution (hub) chain.</td></tr><tr><td><code>contracts</code></td><td>object</td><td>Contract addresses for this chain (string → address).</td></tr><tr><td><code>escrowContract</code></td><td>string</td><td>Escrow contract address.</td></tr><tr><td><code>rolesContract</code></td><td>string</td><td>Roles contract address.</td></tr><tr><td><code>providerIds</code></td><td>integer[]</td><td>Provider IDs available on this chain.</td></tr><tr><td><code>slug</code></td><td>string</td><td>Chain slug.</td></tr><tr><td><code>createdAt</code> / <code>updatedAt</code></td><td>string</td><td>RFC 3339 timestamps.</td></tr></tbody></table>

#### Token

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>string</td><td>Token ID.</td></tr><tr><td><code>symbol</code></td><td>string</td><td>Symbol (e.g. <code>USDC</code>).</td></tr><tr><td><code>name</code></td><td>string</td><td>Token name.</td></tr><tr><td><code>chainId</code></td><td>string</td><td>Chain ID.</td></tr><tr><td><code>chainName</code></td><td>string</td><td>Chain name.</td></tr><tr><td><code>address</code></td><td>string</td><td>Contract address on this chain.</td></tr><tr><td><code>decimals</code></td><td>integer</td><td>Token decimals.</td></tr><tr><td><code>logoUrl</code></td><td>string</td><td>Logo URL.</td></tr><tr><td><code>isEnabled</code></td><td>boolean</td><td>Whether the token is enabled.</td></tr><tr><td><code>implAddress</code></td><td>string</td><td>Per-chain implementation address (e.g. OFT impl).</td></tr><tr><td><code>bridge</code></td><td>string</td><td>Bridge provider (e.g. <code>Circle</code>, <code>LayerZero</code>).</td></tr><tr><td><code>standard</code></td><td>string</td><td>Token standard (e.g. <code>CCTPV2</code>, <code>LayerZeroV2OFT</code>).</td></tr><tr><td><code>facet</code></td><td>string</td><td>Diamond facet name.</td></tr><tr><td><code>homeChainId</code></td><td>string</td><td>Home chain ID of the canonical token.</td></tr><tr><td><code>homeTokenAddress</code></td><td>string</td><td>Canonical token address on the home chain.</td></tr><tr><td><code>gmpParameters</code></td><td>string</td><td>GMP-encoded parameters for this token (hex).</td></tr><tr><td><code>routes</code></td><td>Route[]</td><td>Enabled outbound routes from this token.</td></tr></tbody></table>

#### Route

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>string</td><td>Route ID.</td></tr><tr><td><code>tokenId</code></td><td>string</td><td>Token ID.</td></tr><tr><td><code>tokenName</code></td><td>string</td><td>Token name.</td></tr><tr><td><code>fromChainId</code> / <code>fromChainName</code></td><td>string</td><td>Source chain.</td></tr><tr><td><code>toChainId</code> / <code>toChainName</code></td><td>string</td><td>Destination chain.</td></tr><tr><td><code>fromAddress</code> / <code>toAddress</code></td><td>string</td><td>Source / destination token addresses.</td></tr><tr><td><code>homeChainId</code></td><td>string</td><td>Home chain ID of the canonical token.</td></tr><tr><td><code>homeTokenAddress</code></td><td>string</td><td>Canonical token address on the home chain.</td></tr><tr><td><code>decimals</code></td><td>integer</td><td>Token decimals.</td></tr><tr><td><code>bridge</code></td><td>string</td><td>Bridge provider.</td></tr><tr><td><code>adapter</code></td><td>string</td><td>Bridge adapter address.</td></tr><tr><td><code>standard</code></td><td>string</td><td>Token standard (e.g. <code>CCTPV2</code>).</td></tr><tr><td><code>facet</code></td><td>string</td><td>Diamond facet name.</td></tr><tr><td><code>estimatedDuration</code></td><td>integer</td><td>Estimated settlement time, seconds.</td></tr><tr><td><code>minAmount</code> / <code>maxAmount</code></td><td>string</td><td>Route amount bounds.</td></tr><tr><td><code>feeBps</code></td><td>integer</td><td>Route protocol fee in basis points.</td></tr><tr><td><code>isEnabled</code></td><td>boolean</td><td>Whether the route is enabled.</td></tr></tbody></table>

#### Solver

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>string (uuid)</td><td>Solver ID.</td></tr><tr><td><code>chainId</code></td><td>integer</td><td>Chain the solver operates on.</td></tr><tr><td><code>chainName</code></td><td>string</td><td>Resolved chain name.</td></tr><tr><td><code>address</code></td><td>string</td><td>0x-prefixed EVM address.</td></tr><tr><td><code>name</code></td><td>string</td><td>Solver name.</td></tr><tr><td><code>operator</code></td><td>string</td><td>Operator name.</td></tr><tr><td><code>operatorType</code></td><td>string</td><td><code>internal</code> or <code>third-party</code>.</td></tr><tr><td><code>description</code></td><td>string</td><td>Optional description.</td></tr><tr><td><code>website</code></td><td>string</td><td>Optional website.</td></tr><tr><td><code>contact</code></td><td>string</td><td>Optional contact.</td></tr><tr><td><code>isActive</code></td><td>boolean</td><td>Whether the solver is active.</td></tr><tr><td><code>createdBy</code></td><td>string</td><td>Creator.</td></tr><tr><td><code>createdAt</code> / <code>updatedAt</code></td><td>string</td><td>RFC 3339 timestamps.</td></tr></tbody></table>

#### ChainSolvers

Per-chain group in the `/solvers` response.

| Field       | Type      | Description            |
| ----------- | --------- | ---------------------- |
| `chainId`   | integer   | Chain ID.              |
| `chainName` | string    | Chain name.            |
| `solvers`   | Solver\[] | Solvers on this chain. |

#### ProtocolState

| Field              | Type    | Description                                   |
| ------------------ | ------- | --------------------------------------------- |
| `chainId`          | string  | Chain ID.                                     |
| `executionChainId` | string  | Configured execution chain.                   |
| `isPaused`         | boolean | Whether the protocol is paused on this chain. |
| `updatedAt`        | string  | RFC 3339 timestamp.                           |

#### TrackedTransaction

Key fields (a single-transaction GET additionally returns the `steps` array):

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>string</td><td>Internal ID.</td></tr><tr><td><code>txHash</code></td><td>string</td><td>Source transaction hash (the lookup key).</td></tr><tr><td><code>protocol</code></td><td>string[]</td><td>GMP protocol(s) involved.</td></tr><tr><td><code>sourceChainId</code> / <code>destChainId</code></td><td>string</td><td>Source / destination chain IDs.</td></tr><tr><td><code>wallet</code></td><td>string</td><td>Order owner wallet.</td></tr><tr><td><code>status</code></td><td>TrackingStatus</td><td>Lifecycle status.</td></tr><tr><td><code>claimable</code></td><td>boolean</td><td>True iff <code>claimCancellation()</code> on the execution-chain escrow would succeed right now (status is <code>awaiting_claim</code> or <code>expired</code>).</td></tr><tr><td><code>fromTokenAddress</code> / <code>fromTokenSymbol</code> / <code>fromTokenDecimals</code></td><td>—</td><td>Source token metadata.</td></tr><tr><td><code>fromTokenAmount</code> / <code>fromTokenRawAmount</code></td><td>string</td><td>Human-readable / raw source amount.</td></tr><tr><td><code>toTokenAddress</code> / <code>toTokenSymbol</code> / <code>toTokenDecimals</code></td><td>—</td><td>Destination token metadata.</td></tr><tr><td><code>toTokenAmount</code> / <code>toTokenRawAmount</code></td><td>string</td><td>Human-readable / raw destination amount.</td></tr><tr><td><code>completedSteps</code> / <code>totalSteps</code></td><td>integer</td><td>Step progress.</td></tr><tr><td><code>currentStep</code></td><td>string</td><td>Current step name.</td></tr><tr><td><code>submittedAt</code> / <code>arrivedAt</code> / <code>filledAt</code> / <code>deliveredAt</code> / <code>failedAt</code></td><td>string or null</td><td>Lifecycle timestamps.</td></tr><tr><td><code>failedReason</code> / <code>failedStep</code></td><td>string</td><td>Failure detail.</td></tr><tr><td><code>steps</code></td><td>TransactionStep[]</td><td>Step entries (single-transaction GET only).</td></tr><tr><td><code>sourceExplorerLink</code> / <code>destinationExplorerLink</code></td><td>string</td><td>Explorer links.</td></tr><tr><td><code>destinationTx</code></td><td>string</td><td>Destination transaction hash.</td></tr><tr><td><code>gmpStatus</code> / <code>gmpMessageId</code></td><td>string</td><td>GMP status / message ID.</td></tr><tr><td><code>orderNonce</code></td><td>string</td><td>Per-source-chain order nonce (decimal string); needed to rebuild the <code>Order</code> struct for <code>cancel</code> / <code>claimCancellation</code>.</td></tr><tr><td><code>destinationReceiver</code></td><td>string</td><td>Receiver of the ask token on the destination chain.</td></tr><tr><td><code>orderDeadline</code></td><td>string</td><td>Order deadline (unix seconds, decimal string); <code>"0"</code> means none.</td></tr><tr><td><code>executionChainId</code></td><td>string</td><td>Chain where the escrow lives (where <code>cancel</code> / <code>claimCancellation</code> are called).</td></tr><tr><td><code>executionEscrowAddress</code></td><td>string</td><td>Escrow proxy address on the execution chain.</td></tr><tr><td><code>createdAt</code> / <code>updatedAt</code></td><td>string</td><td>RFC 3339 timestamps.</td></tr></tbody></table>

#### TransactionStep

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>stepNumber</code></td><td>integer</td><td>Step index.</td></tr><tr><td><code>stepName</code></td><td>string</td><td>One of <code>submitted</code>, <code>arrived</code>, <code>filled</code>, <code>delivered</code>.</td></tr><tr><td><code>stepStatus</code></td><td>string</td><td>One of <code>completed</code>, <code>pending</code>, <code>failed</code>, <code>recovered</code>.</td></tr><tr><td><code>eventType</code></td><td>string</td><td>Underlying event type.</td></tr><tr><td><code>chainName</code> / <code>chainId</code></td><td>string</td><td>Chain this step occurred on.</td></tr><tr><td><code>txHash</code></td><td>string</td><td>Step transaction hash.</td></tr><tr><td><code>explorerLink</code> / <code>protocolExplorerLink</code></td><td>string</td><td>Explorer links.</td></tr><tr><td><code>blockNumber</code></td><td>integer</td><td>Block number.</td></tr><tr><td><code>blockTimestamp</code></td><td>string or null</td><td>Block timestamp.</td></tr><tr><td><code>gasUsed</code></td><td>integer</td><td>Gas units consumed (omitted when unknown).</td></tr><tr><td><code>gasPriceWei</code></td><td>string</td><td>Effective gas price in wei (omitted when unknown).</td></tr></tbody></table>

#### TransactionStats

| Field                           | Type           | Description                            |
| ------------------------------- | -------------- | -------------------------------------- |
| `total`                         | integer        | Total matching transactions.           |
| `byStatus`                      | object         | Count keyed by TrackingStatus.         |
| `byProtocol`                    | object         | Count keyed by protocol.               |
| `avgDeliveryMs`                 | number or null | Average delivery time in milliseconds. |
| `bySourceChain` / `byDestChain` | object         | Counts keyed by chain ID.              |

***

### Authentication and rate limits

* **Auth.** Send your key in the `x-apikey` header on every `/api/v1/*` request. The base URL and key are provided during onboarding (separate dev/staging/production hosts). A missing or invalid key returns `401 UNAUTHORIZED`. The health probes need no key.
* **Rate limits.** A global per-IP limit applies to every route (on the order of \~100 requests/second, with a short burst allowance roughly double that). A separate, tighter per-key limit applies to the pricing endpoints such as `/api/v1/quote` (on the order of tens of requests/second per key, with a short burst on top). These are order-of-magnitude defaults; your key's exact limit is set at onboarding. On `429 RATE_LIMITED` the API returns a `Retry-After` header (seconds) — honor it, then retry. If you run many concurrent sessions refetching quotes, tell the Glacis team your expected `/quote` rate so your key is sized for it.
