{"openapi":"3.1.0","info":{"title":"Quicknode Earn Public API","version":"1.12.0","summary":"HTTP API to drive Earn strategies end to end.","description":"The Earn public API (`/v1`) drives the full strategy lifecycle over HTTP:\nbrowse vaults, create a strategy, fetch ready-to-sign approval and\ndeposit/withdraw calldata, broadcast it, and poll to completion.\n\n## Lifecycle\n\n1. `GET /v1/config` — chains, minimum sizes, contract address.\n2. `GET /v1/wallets/{addr}/balances` — where the USDC is.\n3. `GET /v1/vaults` / `GET /v1/vaults/rankings` — pick vaults or preview an allocation.\n4. `POST /v1/wallets/{addr}/tos` then `GET /v1/wallets/{addr}/approvals` — one-time consent, then sign any missing approvals.\n5. `POST /v1/strategies` — create.\n6. `POST /v1/strategies/{id}/calldata/deposit` — broadcast the returned transaction.\n7. `GET /v1/strategies/{id}` (+ `/performance`, `/history`, `/bridges`, `/intents/{intentId}`) — monitor.\n8. `POST /v1/strategies/{id}/calldata/withdraw` or `DELETE /v1/strategies/{id}` — close.\n\n## Auth model\n\n- **Reads are public**, rate-limited per IP.\n- **Writes are SIWE-signed.** Create/update/delete and wallet-pref writes\n  carry a per-action Sign-In-With-Ethereum proof in the request body; the\n  recovered wallet (not any body field) is the authoritative owner. The\n  `SignedRequest` component documents the exact message format.\n- **Calldata endpoints are public (no SIWE).** The on-chain signature is\n  the real authorization boundary; rate limiting (per strategy + IP) is\n  the abuse control.\n- **An `apikey` header is required** on every request. The key is public\n  by design; see `components.securitySchemes`.\n\n## Units\n\n- **Read models** (`/v1/vaults`, `/v1/strategies`, detail, history,\n  bridges) return decimal-dollar JSON numbers for USDC values:\n  `total_value_usdc: 1234.56` means $1,234.56.\n- **APY fields are percentage numbers**, not fractions: `apy: 5.423`\n  means 5.423% APY. Realized APY is unbounded and may be negative.\n- **Transaction and approval templates** (`transactions[]`,\n  `approvalsNeeded[]`, balances, approvals) carry base-unit strings:\n  USDC is a 6-decimal integer string (`\"1000000\"` = 1 USDC), native gas\n  is wei, shares and allowances are raw uint256 decimal strings. Strings\n  so values above 2^53 never lose precision.\n\n## Stability\n\n`v1` is additive-only: new optional fields and endpoints may appear;\nexisting fields will not change type or disappear without a version bump.\nOne launch-week exception (2026-07-20): vault rankings moved to\n`GET /v1/vaults/rankings`, wallet approvals became multi-chain, and live\nvault liquidity folded into the vault detail read.\n\nSIWE hardening (2026-07-21, v1.2.0): signed messages carry a `- path:`\nResources entry binding the proof to the request path, and nonces are\nsingle-use. A proof without a matching `- path:` entry is rejected with\n`siwe_payload`, and nonce reuse is rejected with `siwe_replay`.\n`SignedRequest` has the rules.\n\nAPY-gap floors (2026-07-28, v1.3.0): both threshold fields now carry\nhard minimums — same-network >= 3 and effective cross-network >= 5,\nrising to 5/7 when the strategy's networks include Ethereum. Values\nbelow a floor are rejected with a 400. An omitted `delta_pct` on create\nnow defaults to the same-network floor (previously a legacy 0.1);\nstrategies created before the floors keep running unchanged until an\nupdate touches a threshold field.\n\nStrategy leaderboard (2026-08-05, v1.6.0): `GET /v1/strategies/leaderboard`\nreturns every eligible strategy ranked by realized APY computed over\nTOTAL contributed capital (initial deposit plus later top-ups), so a\nmid-life deposit can never masquerade as yield. One dataset — time-span,\nnetwork, and duration filtering is client-side. Wallets are server-side\ntruncated in the board response.\n\nAPY-gap guardrails relaxed (2026-08-06, v1.7.0): the v1.3.0 chain-tier\nfloors are gone. Both threshold fields accept any value >= 0.25\n(cross_chain_delta_pct also keeps its 0 inherit sentinel); the old\nrecommended ranges are UI-side warnings only. An omitted `delta_pct`\non create still defaults to the recommended bar (3, or 5 with\nEthereum). PATCH validates only the threshold fields present in the\npayload; stored values are never re-validated.\n\nClone tracking (2026-08-06, v1.8.0): `POST /v1/strategies` accepts an\noptional `cloned_from` strategy id. Lineage is recorded only when the\ncreated strategy's settings match the source's (the capital amount,\nname, and funding chain are the cloner's own and never count as edits),\nand a clone of a clone is attributed to the original; an edited clone\nstores nothing and is a normal strategy. A later PATCH that changes any\ncompared setting severs lineage on the edited strategy and on its\nclones. Leaderboard rows carry the new `clone_count` (how many\nstrategies currently record that row as their clone source) and\n`cloned_from` (the original a clone row points at, null for\noriginals).\n\nPer-strategy TVL exit ratio (2026-08-11, v1.9.0): `min_tvl_exit_ratio`\n(create/update, default 0.75) sets the held-vault TVL exit floor as\n`min_tvl_usd × ratio` — fresh entries still gate at the full 1x\n`min_tvl_usd`. 0 disables the TVL exit. Participates in the clone-lineage\ncomparison alongside the other config fields.\n\nDollar config levels (2026-08-13, v1.10.0): `min_tvl_exit_usd` and\n`min_liquidity_entry_usd` are the new absolute-dollar source of truth for\nthe TVL exit floor and the liquidity entry level. `min_tvl_exit_ratio`\nand `min_liquidity_entry_multiplier` become deprecated aliases: still\naccepted on writes and returned on reads, always kept consistent with the\ndollar fields. A request carrying a dollar field AND its alias is a 400.\nPATCH semantics changed with the dollar truth: moving a base value\n(`min_tvl_usd` / `min_liquidity_usd`) alone no longer moves the derived\ndollar level; the alias is re-derived instead (clamped/normalized where\nthe pair would invert). Legacy clients that send the alias on every save\nkeep their old behaviour, because a sent alias resolves against the\neffective post-patch base.\n\nLonger time windows (2026-08-15, v1.11.0): two precomputed APY windows\njoin the ladder — 12h and 24h (`windows.h12`/`h24` on vault reads,\n`apy12h`/`apy24h` on the series). The `window` and `apySmoothingMinutes`\nparams accept up to 1440. Create defaults for an omitting client move:\n`delta_confirmations` 10 → 12 (60 min) and `apy_smoothing_minutes`\n30 → 360. Stored values on existing strategies are unchanged.\n\nForce-exit reason (2026-08-16, v1.12.0): history entries in the force\ncategories carry a nullable `force_reason` (`liquidity_exit` |\n`tvl_exit`) naming which floor drove the exit. NULL on rows written\nbefore this version and on non-floor force exits; no backfill.\n","contact":{"name":"Quicknode Earn","url":"https://earn.quicknode.com"},"license":{"name":"Proprietary","url":"https://www.quicknode.com/terms-of-service"}},"servers":[{"url":"https://earn-api.quicknode.dev/functions/v1/api","description":"Production"}],"security":[{"apikey":[]}],"tags":[{"name":"Discovery","description":"Machine-readable API index and the self-served OpenAPI document."},{"name":"Platform","description":"Deployment config, platform-wide stats, and native gas-token USD prices."},{"name":"Vaults","description":"Vault browse/detail/APY-history reads and the ranked allocation preview."},{"name":"Wallets","description":"Per-wallet balances, approval templates, preferences, and terms acceptance."},{"name":"Strategies","description":"Strategy list/detail/performance reads and SIWE-signed lifecycle mutations."},{"name":"Calldata","description":"Ready-to-sign deposit/withdraw/emergency-claim transaction generation."}],"paths":{"/v1":{"get":{"operationId":"getApiIndex","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Discovery"],"summary":"Machine-readable API index","description":"The API name, version, a link to the OpenAPI document, and a one-line\nlist of every operation.\n","responses":{"200":{"description":"The API index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndexResponse"},"examples":{"index":{"summary":"Index with two sample endpoints","value":{"name":"Quicknode Earn Public API","description":"The Earn public API (`/v1`) drives the full strategy lifecycle over HTTP ...","version":"1.1.0","openapi":"https://earn-api.quicknode.dev/functions/v1/api/v1/openapi.json","endpoints":[{"method":"GET","path":"/v1/vaults","summary":"Vault list (browse)"},{"method":"POST","path":"/v1/strategies","summary":"Create a strategy (SIWE `strategy.create`)"}]}}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/openapi.json":{"get":{"operationId":"getOpenapiDocument","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Discovery"],"summary":"This OpenAPI 3.1 document","description":"This document as JSON, so the full contract can be fetched from the\nAPI itself.\n","responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"An OpenAPI 3.1 document (this one)."}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/stats":{"get":{"operationId":"getPlatformStats","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Platform"],"summary":"Platform-wide aggregate stats","description":"Total USDC under management, active strategy count, and total\nrebalances executed. `total_usdc` is a best-effort live figure and may\nfall back to the last-known aggregate.\n","responses":{"200":{"description":"Platform aggregate stats.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformStatsResponse"},"examples":{"stats":{"value":{"total_usdc":2148302.55,"active_strategies":214,"total_rebalances":8831,"updated_at":"2026-07-13T18:00:00.000Z"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/prices":{"get":{"operationId":"getNativeGasPrices","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Platform"],"summary":"Native gas-token USD prices","description":"Latest USD price for each supported native gas token (ETH, POL, MON),\nupdated hourly. `chains` maps each supported chain id to the USD price\nof that chain's native gas token.\n","responses":{"200":{"description":"Latest native gas-token prices.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricesResponse"},"examples":{"prices":{"value":{"ETH":3421.07,"POL":0.412,"MON":1.85,"updated_at":"2026-07-13T17:00:00.000Z","chains":{"1":3421.07,"10":3421.07,"130":3421.07,"137":0.412,"143":1.85,"8453":3421.07,"42161":3421.07}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Price data temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/config":{"get":{"operationId":"getDeploymentConfig","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Platform"],"summary":"Public deployment config","description":"The Earn contract address (the same on every chain) and per-chain\ndisplay names and minimum strategy sizes. A chain missing from\n`chains` is not currently served. `minStrategyUsdc` is sizing\nguidance only, not enforced by `POST /v1/strategies`. `banner` is the\nfirst-party announcement strip the Earn app renders above its nav, and\nis null whenever none is active. `coveredStrategiesEnabled` is the\noperator kill switch for covered strategies (see the schema); it is\ncached with the rest of the response, while `POST /v1/strategies`\nre-checks it live. Responses carry `Cache-Control: public, max-age=300`.\n","responses":{"200":{"description":"The deployment config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigResponse"},"examples":{"config":{"value":{"earnContract":"0x0ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","chains":[{"chainId":8453,"name":"Base","minStrategyUsdc":500},{"chainId":42161,"name":"Arbitrum","minStrategyUsdc":null}],"banner":{"text":"Join the Quicknode Earn Telegram Group To Chat And Stay Up To Date","buttonCta":"Telegram","buttonIcon":"/banner/telegram.svg","buttonUrl":"https://t.me/quicknode_earn"},"coveredStrategiesEnabled":true}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Deployment config unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"unavailable":{"value":{"error":{"code":"internal_error","message":"Deployment config is unavailable"}}}}}}}}}},"/v1/vaults":{"get":{"operationId":"listVaults","x-rate-limit":{"bucket":"vaults","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Vaults"],"summary":"Vault list (browse)","description":"Every approved vault with the nine windowed APYs, TVL, liquidity and\ncapacity from the latest snapshot. For a capital-aware allocation\npreview, use `GET /v1/vaults/rankings`.\n","parameters":[{"name":"chains","in":"query","required":false,"description":"CSV of chain ids. Defaults to all enabled chains.","schema":{"type":"string","examples":["8453,42161"]}},{"name":"window","in":"query","required":false,"description":"Minutes for the headline `apy`, snapped to the nearest precomputed\ncolumn. The nine windowed APYs are always returned in `windows`.\n","schema":{"type":"integer","minimum":1,"maximum":1440,"default":5}},{"name":"minTvl","in":"query","required":false,"description":"Minimum vault TVL in decimal dollars.","schema":{"type":"number","minimum":0,"default":0}},{"name":"minLiquidity","in":"query","required":false,"description":"Minimum withdrawable liquidity in decimal dollars.","schema":{"type":"number","minimum":0,"default":0}},{"name":"includeCovered","in":"query","required":false,"description":"Opt in to OpenCover covered fee-wrapper rows (`covered: true`).\nOmitted or any value other than `true`, the list contains plain\nvaults only, so existing integrations never see wrapper rows\nunexpectedly.\n","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The browse vault list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultsBrowseResponse"},"examples":{"browse":{"summary":"One approved vault with all nine windows","value":{"mode":"browse","window":5,"vaults":[{"chainId":8453,"vaultAddress":"0x616a4e1db48e22028f6bbf20444cd3b8e3273738","name":"Seamless USDC Vault","apy":6.48,"windows":{"m5":6.48,"m10":6.51,"m30":6.44,"h1":6.39,"h2":6.4,"h4":6.35,"h6":6.3,"h12":6.28,"h24":6.25},"tvlUsd":12500000,"availableLiquidityUsd":3400000,"maxDepositUsd":null,"atCapacity":false,"latestAt":"2026-07-07T18:55:00.000Z"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Vault data is temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/vaults/cover-capacity":{"get":{"operationId":"getCoverCapacity","x-rate-limit":{"bucket":"vaults","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Vaults"],"summary":"Live remaining OpenCover coverage capacity","description":"The LIVE remaining OpenCover underwriting capacity, read from\nOpenCover's endpoint at request time.\n\nCapacity is ONE POOL shared by every covered wrapper, so this is a\nsingle figure, not a per-vault list.\n\nPrefer this over the cached `coverCapacity` on vault rows whenever you\nare sizing an amount a user will actually deposit: the covered deposit\ngate re-reads the same endpoint when it builds the plan and requires\n`remaining - deposit >= 0`, so a figure from the ~5-minute sweep cache\ncan disagree with the verdict. The cached field remains fine for\ndisplay.\n\n`remainingUsd: null` means the capacity feed is not configured or no\nwrapper answered. Render it as unknown; do NOT treat it as zero (the\ndeposit gate fails closed on its own).\n","responses":{"200":{"description":"Live shared-pool capacity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveCoverCapacity"},"example":{"remainingUsd":999993.47,"asOf":"2026-08-24T14:27:14Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/vaults/rankings":{"get":{"operationId":"getVaultRankings","x-rate-limit":{"bucket":"vaults","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Vaults"],"summary":"Ranked allocation preview","description":"The vaults a strategy with these settings would target, ranked by the\nsame pipeline that plans deposits, enriched with liquidity/capacity\nfrom the latest snapshot. `capital` and `maxPositions` drive the\nranking; the remaining params filter.\n","parameters":[{"name":"capital","in":"query","required":true,"description":"Capital to allocate, in USDC decimal dollars (> 0).","schema":{"type":"number","exclusiveMinimum":0,"examples":[1000]}},{"name":"maxPositions","in":"query","required":true,"description":"Maximum number of vault positions (positive integer).","schema":{"type":"integer","minimum":1,"examples":[5]}},{"name":"chains","in":"query","required":false,"description":"CSV of chain ids. Defaults to all enabled chains.","schema":{"type":"string","examples":["8453,42161"]}},{"name":"minTvl","in":"query","required":false,"description":"Minimum vault TVL in decimal dollars.","schema":{"type":"number","minimum":0,"default":0}},{"name":"minLiquidity","in":"query","required":false,"description":"Minimum withdrawable liquidity in decimal dollars.","schema":{"type":"number","minimum":0,"default":0}},{"name":"minLiquidityMultiplier","in":"query","required":false,"description":"Entry liquidity threshold multiplier: a vault must have this\nmultiple of the intended position size in withdrawable liquidity\nto qualify. The deposit calldata path uses a looser default of 1.\n","schema":{"type":"number","minimum":0,"default":2}},{"name":"apySmoothingMinutes","in":"query","required":false,"description":"APY smoothing window in minutes; snaps to the nearest precomputed column (5/10/30/60/120/240/360/720/1440).","schema":{"type":"integer","default":360}},{"name":"hiddenVaultKeys","in":"query","required":false,"description":"CSV of `chainId:0xaddress` vault keys to exclude.","schema":{"type":"string","examples":["8453:0xabc...,42161:0xdef..."]}},{"name":"wallet","in":"query","required":false,"description":"If present, merges this wallet's saved global hide list into the exclusions.","schema":{"$ref":"#/components/schemas/Address"}},{"name":"covered","in":"query","required":false,"description":"RANKED mode only. Coverage partition to rank within: `true` = OpenCover\nfee-wrappers only, `false` = plain vaults only. Omit for an unscoped\nranking across both partitions. Any other value is a 400. Threaded into\nthe ranking pipeline's `strategyCovered` (the covered/uncovered wizard\ntoggle drives it).\n","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The ranked vault list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultRankingsResponse"},"examples":{"ranked":{"summary":"Two eligible vaults with one chain excluded","value":{"mode":"ranked","vaults":[{"chainId":8453,"vaultAddress":"0x616a4e1db48e22028f6bbf20444cd3b8e3273738","name":"Seamless USDC Vault","apy":6.482,"tvlUsd":12500000,"availableLiquidityUsd":3400000,"maxDepositUsd":null,"atCapacity":false},{"chainId":42161,"vaultAddress":"0x1a996cb54bb95462040408c06122d45d6cdb6096","name":"Gauntlet USDC Core","apy":5.117,"tvlUsd":8800000,"availableLiquidityUsd":2100000,"maxDepositUsd":5000000,"atCapacity":false}],"excludedChains":[{"chainId":137,"reason":"no_eligible_vaults"}],"eligibleCount":2,"estimatedApy":5.7995}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Vault ranking data is temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Wallet hide-list lookup failed (only when `wallet` is supplied). Fails closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"hide":{"value":{"error":{"code":"hide_lookup_failed","message":"Could not load hide list — try again."}}}}}}}}}},"/v1/vaults/{chainId}/{address}":{"parameters":[{"name":"chainId","in":"path","required":true,"description":"Chain id the vault lives on.","schema":{"type":"integer","minimum":1,"examples":[8453]}},{"name":"address","in":"path","required":true,"description":"Vault (ERC-4626 share token) address.","schema":{"$ref":"#/components/schemas/Address"}}],"get":{"operationId":"getVaultDetail","x-rate-limit":{"bucket":"vaults","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Vaults"],"summary":"Single approved-vault detail","description":"The latest snapshot for one approved vault: the nine windowed APYs, a\nheadline `apy` at the requested `window`, TVL, liquidity and capacity.\nSame row shape as one element of the browse list. An unknown or\ndelisted vault is a 404.\n\n`include=liveLiquidity` adds a fresh on-chain read of the vault's\nwithdrawable liquidity (`liveLiquidity`). This is a live number and\ncan differ from the snapshot `availableLiquidityUsd`. If the on-chain\nread fails, the snapshot detail still returns 200 with\n`liveLiquidity: null` and `liveLiquidityError: \"rpc_error\"`.\n","parameters":[{"name":"window","in":"query","required":false,"description":"Minutes for the headline `apy`, snapped to the nearest precomputed column. The nine windows are returned regardless.","schema":{"type":"integer","minimum":1,"maximum":1440,"default":5}},{"name":"include","in":"query","required":false,"description":"CSV of extras. Supported value — `liveLiquidity`.","schema":{"type":"string","enum":["liveLiquidity"]}}],"responses":{"200":{"description":"The vault's latest snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultDetailResponse"},"examples":{"vault":{"value":{"chainId":8453,"vaultAddress":"0x616a4e1db48e22028f6bbf20444cd3b8e3273738","name":"Seamless USDC Vault","apy":6.48,"windows":{"m5":6.48,"m10":6.51,"m30":6.44,"h1":6.39,"h2":6.4,"h4":6.35,"h6":6.3,"h12":6.28,"h24":6.25},"tvlUsd":12500000,"availableLiquidityUsd":3400000,"maxDepositUsd":null,"atCapacity":false,"latestAt":"2026-07-07T18:55:00.000Z"}},"withLiveLiquidity":{"summary":"With include=liveLiquidity","value":{"chainId":8453,"vaultAddress":"0x616a4e1db48e22028f6bbf20444cd3b8e3273738","name":"Seamless USDC Vault","apy":6.48,"windows":{"m5":6.48,"m10":6.51,"m30":6.44,"h1":6.39,"h2":6.4,"h4":6.35,"h6":6.3,"h12":6.28,"h24":6.25},"tvlUsd":12500000,"availableLiquidityUsd":3400000,"maxDepositUsd":null,"atCapacity":false,"latestAt":"2026-07-07T18:55:00.000Z","liveLiquidity":{"morphoVersion":"v2","withdrawableUsdc":3392041.11,"forceDeallocatableUsdc":812000.5}}}}}}},"400":{"description":"Bad path (chain id not a positive integer, or address not a 0x address) or a bad `window`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"badAddr":{"value":{"error":{"code":"invalid_request","message":"Vault address must be a 0x address"}}}}}}},"404":{"description":"No such approved vault (unknown or delisted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"nf":{"value":{"error":{"code":"not_found","message":"Vault not found (not an approved vault)"}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/vaults/{chainId}/{address}/apy":{"parameters":[{"name":"chainId","in":"path","required":true,"description":"Chain id the vault lives on.","schema":{"type":"integer","minimum":1,"examples":[8453]}},{"name":"address","in":"path","required":true,"description":"Vault (ERC-4626 share token) address.","schema":{"$ref":"#/components/schemas/Address"}}],"get":{"operationId":"getVaultApySeries","x-rate-limit":{"bucket":"vaults","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Vaults"],"summary":"Vault APY over a caller-chosen time interval","description":"The 5-minute snapshot series over `[from, to]`, plus `intervalApy`: the\nannualized share-price return between the first and last snapshot in the\nrange, `(last/first)^(year/elapsed) - 1`, in percent.\n\nRetention is 7 days: an earlier `from` is pulled forward and `clamped` is\nset true. `resolution` downsamples by keeping the last snapshot per\nbucket (real snapshots, never averages) and never changes `intervalApy`.\n","parameters":[{"name":"from","in":"query","required":false,"description":"Start instant (ISO-8601). Default the trailing 24h before `to`. Clamped forward to the 7-day retention floor.","schema":{"type":"string","format":"date-time","examples":["2026-07-05T00:00:00.000Z"]}},{"name":"to","in":"query","required":false,"description":"End instant (ISO-8601). Defaults to now.","schema":{"type":"string","format":"date-time","examples":["2026-07-07T00:00:00.000Z"]}},{"name":"resolution","in":"query","required":false,"description":"Downsample bucket in minutes (5-1440). Omit for the full 5-minute series.","schema":{"type":"integer","minimum":5,"maximum":1440,"examples":[60]}}],"responses":{"200":{"description":"The (optionally downsampled) snapshot series and the interval APY.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultApySeriesResponse"},"examples":{"series":{"value":{"chainId":8453,"vaultAddress":"0x616a4e1db48e22028f6bbf20444cd3b8e3273738","from":"2026-07-06T18:00:00.000Z","to":"2026-07-07T18:00:00.000Z","clamped":false,"retentionDays":7,"resolutionMinutes":60,"intervalApy":6.4213,"snapshots":[{"snapshotAt":"2026-07-06T18:00:00.000Z","apy5m":6.48,"apy10m":6.51,"apy30m":6.44,"apy1h":6.39,"apy2h":6.4,"apy4h":6.35,"apy6h":6.3,"apy12h":6.28,"apy24h":6.25,"assetsPerShare":101.234567,"tvlUsd":12500000,"availableLiquidityUsd":3400000}]}}}}}},"400":{"description":"A malformed `from`/`to`, `from >= to`, a range entirely outside the\n7-day retention, a bad `resolution`, or a bad path\n(`invalid_request`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"order":{"value":{"error":{"code":"invalid_request","message":"`from` must be earlier than `to`"}}},"outside":{"value":{"error":{"code":"invalid_request","message":"The requested range is entirely outside the 7-day snapshot retention"}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/strategies":{"get":{"operationId":"listStrategies","x-rate-limit":{"bucket":"strategies_list","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Strategies"],"summary":"List a wallet's strategies with positions summary","description":"Active and closed strategies for a wallet, each with a live valuation,\nrealized APY, fee/rebalance rollups, and a `positions[]` array. USDC\nvalues are decimal dollars.\n","parameters":[{"name":"wallet","in":"query","required":true,"description":"Owner wallet address.","schema":{"$ref":"#/components/schemas/Address"}}],"responses":{"200":{"description":"The wallet's active and closed strategies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createStrategy","x-rate-limit":{"bucket":"create","limit":10,"windowSeconds":60,"keyedBy":"wallet+ip","note":"A coarse per-IP limit (~30/60s) also applies."},"tags":["Strategies"],"summary":"Create a strategy (SIWE `strategy.create`)","description":"Creates a strategy in `pending_setup` and returns it wrapped in a\n`strategy` object (201). Body is a `SignedRequest`, action\n`strategy.create`; the signed payload is the body minus `siwe`. The\nrecovered wallet owns the strategy; any `wallet` field in the body is\nignored.\n\nOptional `idempotencyKey` (string, 1-128 chars, else 400) rides inside\nthe signed payload and is scoped per wallet. A repeated key returns the\noriginal stored response verbatim, so do not reuse a key across different\nrequests.\n","security":[{"apikey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStrategyRequest"},"examples":{"create":{"$ref":"#/components/examples/CreateStrategyBody"}}}}},"responses":{"201":{"description":"The created strategy, wrapped in a `strategy` object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyMutationResponse"}}}},"400":{"$ref":"#/components/responses/SiweOrBadRequest"},"401":{"$ref":"#/components/responses/SiweUnauthorized"},"403":{"description":"Covered gate (only when the signed payload has `covered: true`).\n`covered_disabled`: the operator kill switch (app_config\n`ENABLE_COVERED_STRATEGIES`) is off. `covered_terms_required`: the\nwallet has not acknowledged OpenCover's terms\n(`POST /v1/wallets/{addr}/opencover-terms`). Both are checked\nBEFORE any insert; uncovered creates never hit this gate. A failed\nlookup during the check is a 503 `covered_access_lookup_failed`\n(fails closed — the boundary never falls through to an insert).\nThere is no wallet allowlist: any wallet may create a covered\nstrategy while the switch is on.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"`hide_floor_violated`: the supplied `hidden_vault_keys` would leave\nfewer than `max_positions + 1` eligible vaults. Hiding vaults that\nwere never eligible passes. A failed read of the wallet's global\nhide list is a 503 `hide_lookup_failed`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/SiweRpc"}}}},"/v1/strategies/leaderboard":{"get":{"operationId":"getStrategiesLeaderboard","x-rate-limit":{"bucket":"vaults","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Strategies"],"summary":"Public strategy leaderboard (eligible strategies by realized APY)","description":"Every eligible strategy ranked by realized APY, computed over TOTAL\ncontributed capital (initial deposit plus any later top-ups) with\nmid-life withdrawals credited back — a mid-life deposit can never\nmasquerade as yield. Live rows are valued from their latest yield\nsnapshot (roughly hourly) and annualized to that snapshot's own\ntimestamp; closed rows use their settled final value annualized to\ntheir close time.\n\nOne dataset, no parameters: clients filter (creation span, networks,\nduration) and number their own filtered views. Rows are sorted by\n`realized_apy_pct` descending with null figures last, capped at 500.\n\nThe board shows every strategy that ever took a deposit — running\n(`live`, including paused and closing) or fully closed (`closed`),\nhowever short-lived. A row measured under 30 minutes carries a null\n`realized_apy_pct` (annualizing minutes is noise) but still appears.\nOne timing caveat: a live row appears once its first yield snapshot\nlands (within roughly an hour of funding) — before that there is no\nvalue to list it by. Closed rows appear immediately.\n\nClone tracking (v1.8.0): each row carries `clone_count` — how many\nstrategies currently record it as their clone source. Lineage is\nstamped at create only when the clone's settings match the source,\nso the figure means \"cloned and left unchanged\". Rows are never\nfolded or hidden; every figure describes the row's own strategy.\n\nPrivacy: `wallet` is truncated server-side (`0x1234…abcd`) as a\ndisplay convention, not anonymity — rows carry the public strategy\nid, which the public strategy-detail read resolves like any other id.\nThe board is recomputed at most every ~5 minutes (`updated_at` marks\nthe compute time).\n","responses":{"200":{"description":"The ranked board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeaderboardResponse"},"examples":{"board":{"summary":"A two-row board","value":{"updated_at":"2026-08-05T12:00:00.000Z","rows":[{"strategy_id":"6f0f5cfa-92ab-4b7e-9e26-0a89f8f0a111","name":"Base yield","wallet":"0x054e…a0f3","first_deposit_at":"2026-07-01T09:30:00.000Z","ended_at":null,"measured_at":"2026-08-05T11:22:00.000Z","status":"live","value_usdc":5103.22,"realized_apy_pct":7.4312,"gross_apy_pct":7.9123,"fees_usdc":3.14,"rebalance_count":12,"covered":false,"clone_count":2,"cloned_from":null,"config":{"delta_pct":3,"cross_chain_delta_pct":5,"delta_confirmations":2,"max_positions":3,"min_tvl_usd":1000000,"min_liquidity_usd":50000,"min_liquidity_entry_multiplier":2,"min_tvl_exit_ratio":0.75,"min_tvl_exit_usd":750000,"min_liquidity_entry_usd":100000,"apy_smoothing_minutes":30,"chain_id":8453,"chain_ids":[8453,42161]}},{"strategy_id":"9c2a1a34-5f2e-47bb-8a01-b7bfae0f2222","name":"Steady USDC","wallet":"0x1234…abcd","first_deposit_at":"2026-06-12T18:05:00.000Z","ended_at":"2026-07-19T04:12:00.000Z","measured_at":"2026-07-19T04:12:00.000Z","status":"closed","value_usdc":10087.55,"realized_apy_pct":6.902,"gross_apy_pct":7.31,"fees_usdc":11.02,"rebalance_count":31,"covered":true,"clone_count":0,"cloned_from":null,"config":{"delta_pct":3,"cross_chain_delta_pct":0,"delta_confirmations":3,"max_positions":1,"min_tvl_usd":500000,"min_liquidity_usd":0,"min_liquidity_entry_multiplier":1,"min_tvl_exit_ratio":0.9,"min_tvl_exit_usd":450000,"min_liquidity_entry_usd":0,"apy_smoothing_minutes":60,"chain_id":8453,"chain_ids":[8453]}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Leaderboard data is temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/strategies/{id}":{"parameters":[{"$ref":"#/components/parameters/StrategyId"}],"get":{"operationId":"getStrategy","x-rate-limit":{"bucket":"strategies_detail","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Strategies"],"summary":"Strategy detail","description":"Single-strategy detail: config, live-valued positions, cycle/gauge fields\n(`cycle_state`, `swap_signals`, `last_skip`, …), closed positions, and the\nsame computed rollups as the list. Poll this endpoint to watch a status\ntransition (e.g. `pending_setup` → `active`, `closing` → `closed`).\n","responses":{"200":{"description":"Strategy detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyDetailResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"patch":{"operationId":"updateStrategy","x-rate-limit":{"bucket":"mutations","limit":20,"windowSeconds":60,"keyedBy":"wallet+ip","note":"A coarse per-IP limit (~30/60s) also applies."},"tags":["Strategies"],"summary":"Update strategy config (SIWE `strategy.update`)","description":"Updates config fields (delta, min TVL/liquidity, hide list, name, and\n`status` for pause/resume — only `active` and `paused` are accepted).\n`SignedRequest`, action `strategy.update`; the `id` rides in the path\nand the proof's `- path:` entry binds it. Only fields present in the\nbody are written; explicit `null` writes null.\n\nHide-list toggles: `hide_vault_key` / `unhide_vault_key` merge a single\nkey server-side, so a toggle cannot clobber concurrent edits. Exactly\none op per request; combining an op with a `hidden_vault_keys` array\nreplace is a 400 `invalid_request`.\n\nA hide op or array replace that would leave fewer than\n`max_positions + 1` eligible vaults is a 409 `hide_floor_violated`.\nHiding a never-eligible vault passes; `unhide_vault_key` is not checked.\n","security":[{"apikey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStrategyRequest"}}}},"responses":{"200":{"description":"The updated strategy, wrapped in a `strategy` object. A missing or\nunowned strategy returns 500 `internal_error`, not 404.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyMutationResponse"}}}},"400":{"$ref":"#/components/responses/SiweOrBadRequest"},"401":{"$ref":"#/components/responses/SiweUnauthorized"},"409":{"description":"`hide_floor_violated`: the hide change would shrink the eligible\nvault pool below `max_positions + 1`. See the description above.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Transient failure, retry: `strategy_read_failed` (hide-list merge\nread), `hide_lookup_failed` (hide-floor check read), or `siwe_rpc`\n(smart-wallet signature verification RPC).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"readFailed":{"value":{"error":{"code":"strategy_read_failed","message":"Could not load the current hide list, try again."}}}}}}}}},"delete":{"operationId":"deleteStrategy","x-rate-limit":{"bucket":"mutations","limit":20,"windowSeconds":60,"keyedBy":"wallet+ip","note":"A coarse per-IP limit (~30/60s) also applies."},"tags":["Strategies"],"summary":"Delete or close a strategy (SIWE `strategy.delete`)","description":"Deletes a pre-deposit strategy; closes a funded strategy that needs no\non-chain withdrawal (the `fallback: \"delete\"` target of\n`POST .../calldata/withdraw`). `SignedRequest`, action `strategy.delete`,\nempty signed payload (`{}`); the `id` rides in the path and the proof's\n`- path:` entry binds it.\n","security":[{"apikey":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteStrategyRequest"}}}},"responses":{"200":{"description":"Deletion or close result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteStrategyResponse"},"examples":{"deleted":{"value":{"success":true,"action":"deleted"}},"closed":{"value":{"success":true,"action":"closed"}},"closing":{"value":{"success":true,"action":"closing"}}}}}},"400":{"description":"Malformed request/SIWE, OR active positions remain on chains not\ncovered by a per-chain close (`invalid_request`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"coverage":{"value":{"error":{"code":"invalid_request","message":"Active positions remain on chain(s) 42161 -- close all chains first"}}}}}}},"401":{"$ref":"#/components/responses/SiweUnauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/SiweRpc"}}}},"/v1/strategies/{id}/history":{"parameters":[{"$ref":"#/components/parameters/StrategyId"}],"get":{"operationId":"getStrategyHistory","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Strategies"],"summary":"Strategy event log","description":"Up to 100 events (deposits, rebalances, withdrawals, cross-chain\nbridge-backs), newest-first. `forced` marks a floor-driven\n(liquidity/TVL) or hide-driven exit from a higher-APY vault;\n`force_reason` names which floor when recorded.\n\n`?format=entries` merges the same events with the strategy's CCTP\ntransfers (limit 50) into one entry per action, each with per-chain\nphases.\n","parameters":[{"name":"format","in":"query","required":false,"description":"Response shape. `events` (default) is the flat event log\n(`HistoryResponse`); `entries` is the aggregated accordion view\n(`HistoryEntriesResponse`).\n","schema":{"type":"string","enum":["events","entries"],"default":"events"}}],"responses":{"200":{"description":"The flat event log (default) or the aggregated entries view\n(`format=entries`). Branch on the `format` you requested.\n","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/HistoryResponse"},{"$ref":"#/components/schemas/HistoryEntriesResponse"}]}}}},"400":{"description":"Invalid strategy id (a UUID is required) or unknown `format`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"bad":{"value":{"error":{"code":"invalid_request","message":"Invalid strategy id (uuid required)"}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/strategies/{id}/bridges":{"parameters":[{"$ref":"#/components/parameters/StrategyId"}],"get":{"operationId":"getStrategyBridges","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Strategies"],"summary":"CCTP bridge transfers for a strategy","description":"The strategy's cross-chain (CCTP) transfers, newest-first, limit 50.\n`amount_usdc` is a string; `fee_usdc` is a number or null.\n","responses":{"200":{"description":"Bridge transfer rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BridgesResponse"}}}},"400":{"description":"Invalid strategy id (a UUID is required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"bad":{"value":{"error":{"code":"invalid_request","message":"Strategy id must be a UUID"}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/strategies/{id}/performance":{"parameters":[{"$ref":"#/components/parameters/StrategyId"}],"get":{"operationId":"getStrategyPerformance","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Strategies"],"summary":"Strategy value/yield time series","description":"Yield snapshots (~55-min cadence) for the trailing `hours`, oldest-first,\nplus `period_fee_usdc` and `net_yield` (see `YieldSnapshot`). USDC values\nare decimal dollars; `weighted_apy` is a percentage. Unknown strategy:\n404. Capped at 2000 rows keeping the newest; `truncated: true` flags a\ncapped response.\n","parameters":[{"name":"hours","in":"query","required":false,"description":"Trailing window in hours. Defaults to 168 (7d); clamped to [1, 2160] (90d).","schema":{"type":"integer","minimum":1,"maximum":2160,"default":168}}],"responses":{"200":{"description":"The clamped window and the yield snapshot series.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerformanceResponse"},"examples":{"series":{"value":{"hours":168,"truncated":false,"snapshots":[{"id":"b7c1e2d3-0000-4000-8000-000000000001","total_value_usdc":1012.34,"period_yield":1.21,"cumulative_yield":12.34,"cumulative_fees":0.87,"period_fee_usdc":0.05,"net_yield":1.16,"weighted_apy":6.42,"snapshot_at":"2026-07-07T18:00:00.000Z"}]}}}}}},"400":{"description":"Invalid strategy id (a UUID is required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"bad":{"value":{"error":{"code":"invalid_request","message":"Strategy id must be a UUID"}}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/strategies/{id}/intents/{intentId}":{"parameters":[{"$ref":"#/components/parameters/StrategyId"},{"name":"intentId","in":"path","required":true,"description":"Intent id, returned as `intentId` by the calldata endpoints.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getIntentStatus","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Strategies"],"summary":"Single intent lifecycle status","description":"Poll the lifecycle of an intent returned by the calldata endpoints.\n\n`status` is derived:\n- `failed`: a failure reason was recorded (including pre-transaction\n  failures); `fulfilled_tx_hash` is always null.\n- `fulfilled`: `fulfilled_at` is set and the intent did not fail.\n- `expired`: unfulfilled and past `expires_at`.\n- `pending`: everything else.\n\n404 `intent_not_found` covers both a missing intent and an intent\nbelonging to another strategy. Raw calldata is never returned.\n","responses":{"200":{"description":"The intent row with its derived status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntentStatusResponse"},"examples":{"fulfilled":{"value":{"id":"7f3a1c2b-0000-4000-8000-000000000abc","strategy_id":"b7c1e2d3-0000-4000-8000-000000000001","chain_id":8453,"kind":"deposit","created_at":"2026-07-07T18:00:00.000Z","expires_at":"2026-08-06T18:00:00.000Z","status":"fulfilled","fulfilled_at":"2026-07-07T18:04:12.000Z","fulfilled_tx_hash":"0x9c1f…","submitted_tx_hash":null,"failure_reason":null}}}}}},"400":{"description":"The strategy id or intent id is not a UUID (`invalid_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"bad":{"value":{"error":{"code":"invalid_request","message":"Intent id must be a UUID"}}}}}}},"404":{"description":"No such intent for THIS strategy (`intent_not_found`; also returned when the intent belongs to another strategy).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"nf":{"value":{"error":{"code":"intent_not_found","message":"Intent not found for this strategy"}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/strategies/{id}/calldata/deposit":{"parameters":[{"$ref":"#/components/parameters/StrategyId"}],"post":{"operationId":"depositCalldata","x-rate-limit":{"bucket":"calldata","limit":10,"windowSeconds":60,"keyedBy":"strategy+ip","note":"A coarser ~30 requests/60s per-IP limit also applies."},"tags":["Calldata"],"summary":"Generate deposit calldata (public, no SIWE)","description":"Returns one ready-to-sign `selfBatchDeposit` transaction on the\nstrategy's chain, covering local vault deposits plus per-remote-chain\nCCTP burns. The matching intent (kind `deposit`) is written server-side;\ndo not post an intent separately. No auth; no request body (the `id` is\nin the path).\n\nStrategy must be `pending_setup` (top-ups are not supported).\n\nTiming: `expiresAt` is an advisory ~1h plan-freshness bound. The intent\nitself has a 30-day TTL; regenerating an unchanged plan within it returns\nthe same `intentId`, and regenerating after expiry or fulfillment mints a\nnew one. A strategy that has never received a deposit is purged about one\nhour after it was last touched, taking its intent with it; calling this\nendpoint refreshes that hour. If signing takes longer than an hour (e.g.\na multisig), regenerate at least hourly until the transaction is\nbroadcast.\n\nApprove vault share tokens and USDC before calling: live allowances\naffect vault selection, and `approvalsNeeded` reflects what is still\nmissing.\n\nSubmit the returned `data` verbatim (re-encoding breaks intent\nattribution) and use `gasHint` as the gas limit (wallet re-estimates can\nrun out of gas).\n","security":[{"apikey":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalldataRequest"},"examples":{"empty":{"summary":"No body required (deposit takes no parameters)","value":{}}}}}},"responses":{"200":{"description":"One deposit transaction, approvals needed, plan transparency, and an advisory expiry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositCalldataResponse"},"examples":{"bridged":{"$ref":"#/components/examples/DepositCalldataBridged"}}}}},"400":{"description":"One of: the strategy is not `pending_setup` (`invalid_state`); no\neligible vaults (`no_eligible_vaults`); gas estimation failed\n(`gas_estimation_failed`, the tx would revert, usually unfunded or\nunapproved); or a malformed JSON body (`invalid_request`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"notPending":{"value":{"error":{"code":"invalid_state","message":"Strategy status is 'active', expected 'pending_setup'"}}},"noVaults":{"value":{"error":{"code":"no_eligible_vaults","message":"No vaults available with APY data"}}},"gas":{"value":{"error":{"code":"gas_estimation_failed","message":"Couldn't estimate gas on chain 8453 — the transaction would likely revert. Retry after funding/approving."}}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Internal error, or `burn_route_unavailable` when a required remote\nchain has no CCTP burn route configured.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"burnRoute":{"value":{"error":{"code":"burn_route_unavailable","message":"No CCTP burn route for chain 42161 (missing_proxy)"}}}}}}},"503":{"description":"The hide list could not be loaded (`hide_lookup_failed`, fails\nclosed).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"hide":{"value":{"error":{"code":"hide_lookup_failed","message":"Could not load hide list — try again."}}}}}}}}}},"/v1/strategies/{id}/calldata/withdraw":{"parameters":[{"$ref":"#/components/parameters/StrategyId"}],"post":{"operationId":"withdrawCalldata","x-rate-limit":{"bucket":"calldata","limit":10,"windowSeconds":60,"keyedBy":"strategy+ip","note":"A coarser ~30 requests/60s per-IP limit also applies."},"tags":["Calldata"],"summary":"Generate withdraw/close calldata (public, no SIWE)","description":"Returns one ready-to-sign `selfBatchWithdraw` transaction per chain,\nsource chain first then ascending chain id. Sign and submit in that\norder, `data` verbatim. Paused and zero-share positions are excluded.\nEach transaction has its own `intentId` (kind `close`), written\nserver-side. No auth; no status gate. Optional `chainIds` in the JSON\nbody restricts the plan to those chains.\n\nIf nothing is withdrawable, `transactions` is `[]` and `fallback` is\n`\"delete\"`: close via `DELETE /v1/strategies/{id}` instead.\n\n`expiresAt` and intent TTL semantics match the deposit endpoint (the\npre-deposit purge does not apply).\n","security":[{"apikey":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawCalldataRequest"},"examples":{"full":{"summary":"Withdraw all chains (no body)","value":{}},"subset":{"summary":"Withdraw only Base","value":{"chainIds":[8453]}}}}}},"responses":{"200":{"description":"Per-chain withdraw transactions (source-first), approvals needed, plan\ntransparency, and an advisory expiry. May be an empty plan with a\n`fallback` hint.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawCalldataResponse"},"examples":{"multichain":{"$ref":"#/components/examples/WithdrawCalldataMulti"},"empty":{"summary":"Nothing to withdraw — use DELETE (approvalsNeeded omitted on the empty plan)","value":{"kind":"withdraw","transactions":[],"plan":{"chains":{}},"expiresAt":"2026-07-07T19:00:00.000Z","fallback":"delete"}}}}}},"400":{"description":"A malformed JSON body or a bad `chainIds` value (`invalid_request`),\nor a `chainIds` subset that matches no withdrawable position\n(`no_positions_for_chains`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"badChains":{"value":{"error":{"code":"invalid_request","message":"`chainIds` must be an array of chain ids"}}},"noPositions":{"value":{"error":{"code":"no_positions_for_chains","message":"No withdrawable positions on the requested chain(s)"}}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/strategies/{id}/calldata/claim":{"parameters":[{"$ref":"#/components/parameters/StrategyId"}],"post":{"operationId":"claimCalldata","x-rate-limit":{"bucket":"calldata","limit":10,"windowSeconds":60,"keyedBy":"strategy+ip","note":"Shares the calldata bucket with deposit/withdraw. A coarser ~30 requests/60s per-IP limit also applies."},"tags":["Calldata"],"summary":"Generate emergency CCTP claim calldata (public, no SIWE)","description":"Escape hatch for a stalled deposit bridge leg: fetches the finalized\nCircle attestation and returns one ready-to-sign `emergencyClaimBridge`\ntransaction on the destination chain. Minted USDC goes directly to the\nburn-time beneficiary's wallet, bypassing the destination vault deposit.\nThe matching intent (kind `emergency_claim`) is written server-side. No\nauth.\n\nEligibility (each failure is a 400 `invalid_state`): the transfer must be\na deposit leg, not already `confirmed` or `user_claimed`, at least 30\nminutes old, and its recorded beneficiary must be the strategy owner. If\nCircle has not finalized the attestation, the response is a 409\n`attestation_pending`; poll on that status code.\n\n`approvalsNeeded` is always `[]`. `expiresAt` is nominal: the claim stays\nvalid until the message nonce is consumed on the destination chain. A\nnull `gasHint` with `estimation_failed` can mean the nonce was already\nconsumed (the transfer completed normally); the on-chain result is\nauthoritative.\n","security":[{"apikey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimCalldataRequest"},"examples":{"claim":{"value":{"transferId":"9a8b7c6d-0000-4000-8000-000000000123"}}}}}},"responses":{"200":{"description":"One emergency-claim transaction on the destination chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimCalldataResponse"},"examples":{"claim":{"value":{"kind":"claim","transactions":[{"chainId":42161,"to":"0x0ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","data":"0x0f2b1a3c0000000000000000000000000000000000000000000000000000000000000040","value":"0","intentId":"7f3a1c2b-0000-4000-8000-000000000abc","kind":"emergency_claim","description":"Claim bridged USDC on chain 42161 directly to your wallet, bypassing the relayer and the destination vault deposit","gasHint":"220000"}],"approvalsNeeded":[],"expiresAt":"2026-07-07T19:00:00.000Z"}}}}}},"400":{"description":"A malformed JSON body or `transferId` (`invalid_request`), or the\ntransfer is not claimable (`invalid_state`: already terminal, not\ndeposit-shaped, not 30 minutes old yet, or the beneficiary does\nnot match the strategy owner).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"badId":{"value":{"error":{"code":"invalid_request","message":"`transferId` must be a UUID"}}},"notStuck":{"value":{"error":{"code":"invalid_state","message":"Transfer is not considered stuck yet. The relayer normally lands the deposit within minutes; retry once the transfer is 30 minutes old."}}}}}}},"404":{"description":"Strategy not found (`not_found`), or no such CCTP transfer for this\nstrategy (`transfer_not_found`; also returned when the transfer\nbelongs to another strategy).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"xfer":{"value":{"error":{"code":"transfer_not_found","message":"CCTP transfer not found for this strategy"}}}}}}},"409":{"description":"Attestation not finalized yet (`attestation_pending`). Retry after\n`Retry-After` seconds.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying (60).","schema":{"type":"integer","examples":[60]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"pending":{"value":{"error":{"code":"attestation_pending","message":"Circle has not finalised the attestation yet. Retry shortly."}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Internal error (including a failed intent write), or\n`unsupported_chain_pair` when the transfer's chain pair has no CCTP\nroute configured.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"pair":{"value":{"error":{"code":"unsupported_chain_pair","message":"No CCTP domain mapped for chain pair 130 to 143"}}}}}}}}}},"/v1/wallets/{addr}/balances":{"parameters":[{"$ref":"#/components/parameters/WalletAddr"}],"get":{"operationId":"getWalletBalances","x-rate-limit":{"bucket":"balances","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Wallets"],"summary":"USDC + native gas balance per chain","description":"One entry per requested chain (default: all supported). Amounts are\nbase-unit strings (USDC 6dp integer string, native in wei);\n`nativeUsdPrice` is the chain's native gas-token USD price in decimal\ndollars (null when unavailable). Per-chain error isolation: a failed\nRPC marks that chain `error: \"rpc_error\"` and never fails the whole\nresponse.\n","parameters":[{"name":"chains","in":"query","required":false,"description":"CSV of supported chain ids. Defaults to all.","schema":{"type":"string","examples":["8453"]}}],"responses":{"200":{"description":"Per-chain balances.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalancesResponse"},"examples":{"two":{"value":{"address":"0x1234567890abcdef1234567890abcdef12345678","chains":[{"chainId":8453,"nativeSymbol":"ETH","nativeBalanceWei":"18240000000000000","nativeUsdPrice":3421.07,"usdc":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","usdcBalance":"1000000000","error":null},{"chainId":42161,"nativeSymbol":"ETH","nativeBalanceWei":null,"nativeUsdPrice":3421.07,"usdc":"0xaf88d065e77c8cc2239327c5edb3a432268e5831","usdcBalance":null,"error":"rpc_error"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/wallets/{addr}/approvals":{"parameters":[{"$ref":"#/components/parameters/WalletAddr"}],"get":{"operationId":"getWalletApprovals","x-rate-limit":{"bucket":"approvals","limit":30,"windowSeconds":60,"keyedBy":"ip"},"tags":["Wallets"],"summary":"Allowances toward the proxy + ready-to-sign approve templates","description":"Current USDC and per-vault share-token allowances toward the Earn\ncontract (the spender), grouped per chain. Each entry carries an\n`approved` flag and, when not approved, a ready-to-sign\n`approve(spender, amount)` template. A share token is approved when\nallowance > 0; USDC is approved when allowance >= `requiredUsdc`.\nAllowances and amounts are base-unit strings. Per-chain error\nisolation: a failed RPC marks that chain `error: \"rpc_error\"` and\nnever fails the whole response.\n","parameters":[{"name":"chains","in":"query","required":false,"description":"CSV of supported chain ids. Defaults to all.","schema":{"type":"string","examples":["8453,42161"]}},{"name":"requiredUsdc","in":"query","required":false,"description":"USDC the pending deposit needs, in 6-decimal base units, evaluated\nagainst each requested chain's USDC allowance. Default 0\n(report-only).\n","schema":{"type":"string","pattern":"^[0-9]+$","default":"0","examples":["1000000000"]}},{"name":"vaults","in":"query","required":false,"description":"CSV of `chainId:0xaddress` vault keys to report. Each key applies\nonly to its own chain; a requested chain with no listed vaults\nstill returns its USDC entry. A key targeting a chain outside the\nrequested set is a 400. Defaults to every approved vault per\nchain.\n","schema":{"type":"string","examples":["8453:0x616a...,42161:0x1a99..."]}}],"responses":{"200":{"description":"Approval state and templates, grouped per chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalsResponse"},"examples":{"mixed":{"value":{"address":"0x1234567890abcdef1234567890abcdef12345678","chains":[{"chainId":8453,"spender":"0x0ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","usdc":{"token":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","currentAllowance":"0","requiredAllowance":"1000000000","approved":false,"tx":{"to":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","data":"0x095ea7b30000000000000000000000000ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","value":"0"}},"vaults":[{"token":"0x616a4e1dB48E22028f6bbf20444Cd3b8e3273738","vaultAddress":"0x616a4e1dB48E22028f6bbf20444Cd3b8e3273738","currentAllowance":"115792089237316195423570985008687907853269984665640564039457584007913129639935","requiredAllowance":"115792089237316195423570985008687907853269984665640564039457584007913129639935","approved":true}],"error":null},{"chainId":42161,"spender":null,"usdc":null,"vaults":[],"error":"rpc_error"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/wallets/{addr}/prefs":{"parameters":[{"$ref":"#/components/parameters/WalletAddr"}],"get":{"operationId":"getWalletPrefs","x-rate-limit":{"bucket":"reads","limit":60,"windowSeconds":60,"keyedBy":"ip"},"tags":["Wallets"],"summary":"Read a wallet's preferences (public)","description":"ToS state, global hide list, the approval-banner flag, and the\nOpenCover terms signal. Unknown wallets return defaults (empty hide\nlist, `opencover_terms_accepted` false). `approved` is a legacy field\nand always `true`; so is `covered_access` (covered strategies are open\nto every wallet). `opencover_terms_accepted` is a SOFT wizard signal\nonly; the hard covered boundary is enforced at strategy create.\n","responses":{"200":{"description":"The wallet's preferences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletPrefsResponse"},"examples":{"prefs":{"value":{"address":"0x1234567890abcdef1234567890abcdef12345678","has_agreement":true,"hidden_vault_keys":["8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738"],"hide_approval":false,"covered_access":true,"opencover_terms_accepted":false}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"patch":{"operationId":"updateWalletPrefs","x-rate-limit":{"bucket":"mutations","limit":20,"windowSeconds":60,"keyedBy":"wallet+ip","note":"A coarser per-IP limit (~30 requests/60s) also applies."},"tags":["Wallets"],"summary":"Update a wallet's preferences (SIWE `prefs.update`)","description":"Updates the global hide list and/or the approval-banner flag in one\nsigned payload. `SignedRequest`, action `prefs.update`. The recovered\nwallet must equal the path `addr` or the call fails 403 `owner_mismatch`.\nAt least one of `hide_vault_key`, `unhide_vault_key`, `hide_approval` is\nrequired. Hides and unhides merge into the stored list; the full\nresulting list is returned.\n\nHiding a held vault is allowed (it can force-exit positions). The\nresponse then carries an advisory `warnings` array listing each active\nstrategy whose eligible pool drops below its `max_positions + 1` floor.\n","security":[{"apikey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrefsUpdateRequest"},"examples":{"hide":{"summary":"Hide one vault and dismiss the approval banner","value":{"hide_vault_key":"8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738","hide_approval":true,"siwe":{"message":"earn.quicknode.com wants you to sign in with your Ethereum account:\n0x1234...\n\nAuthorize prefs.update\n\nURI: https://earn.quicknode.com\nVersion: 1\nChain ID: 8453\nNonce: 5c6d...\nIssued At: 2026-07-07T18:00:00.000Z\nExpiration Time: 2026-07-07T18:05:00.000Z\nResources:\n- sha256:<hex>\n- path:/wallets/0x1234.../prefs","signature":"0xabcd..."}}}}}}},"responses":{"200":{"description":"The merged hide list and the current approval-banner flag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrefsUpdateResponse"},"examples":{"updated":{"value":{"ok":true,"hidden_vault_keys":["8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738"],"hide_approval":true}}}}}},"400":{"$ref":"#/components/responses/SiweOrBadRequest"},"401":{"$ref":"#/components/responses/SiweUnauthorized"},"403":{"$ref":"#/components/responses/OwnerMismatch"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Transient: `prefs_read_failed` (current prefs could not be read;\nnothing was written) or `siwe_rpc` (signature-verification RPC\nfailed). Retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"prefsRead":{"value":{"error":{"code":"prefs_read_failed","message":"Could not load current preferences, try again."}}}}}}}}}},"/v1/wallets/{addr}/tos":{"parameters":[{"$ref":"#/components/parameters/WalletAddr"}],"post":{"operationId":"recordTosAgreement","x-rate-limit":{"bucket":"mutations","limit":20,"windowSeconds":60,"keyedBy":"wallet+ip","note":"A coarser per-IP limit (~30 requests/60s) also applies."},"tags":["Wallets"],"summary":"Record a wallet's ToS agreement (SIWE `tos_agreement`)","description":"Records the wallet's ToS acceptance. `SignedRequest`, action\n`tos_agreement`; the signature binds `terms_url`, which is echoed back.\nThe recovered wallet must equal the path `addr` or the call fails 403\n`owner_mismatch`.\n","security":[{"apikey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TosRequest"},"examples":{"agree":{"value":{"terms_url":"https://www.quicknode.com/terms-of-service","siwe":{"message":"earn.quicknode.com wants you to sign in with your Ethereum account:\n0x1234...\n\nAuthorize tos_agreement\n\nURI: https://earn.quicknode.com\nVersion: 1\nChain ID: 8453\nNonce: 5c6d...\nIssued At: 2026-07-07T18:00:00.000Z\nExpiration Time: 2026-07-07T18:05:00.000Z\nResources:\n- sha256:<hex>\n- path:/wallets/0x1234.../tos","signature":"0xabcd..."}}}}}}},"responses":{"200":{"description":"The recorded agreement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TosResponse"},"examples":{"recorded":{"value":{"ok":true,"address":"0x1234567890abcdef1234567890abcdef12345678","has_agreement":true,"terms_url":"https://www.quicknode.com/terms-of-service"}}}}}},"400":{"$ref":"#/components/responses/SiweOrBadRequest"},"401":{"$ref":"#/components/responses/SiweUnauthorized"},"403":{"$ref":"#/components/responses/OwnerMismatch"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/SiweRpc"}}}},"/v1/wallets/{addr}/opencover-terms":{"parameters":[{"$ref":"#/components/parameters/WalletAddr"}],"post":{"operationId":"recordOpencoverTerms","x-rate-limit":{"bucket":"mutations","limit":20,"windowSeconds":60,"keyedBy":"wallet+ip","note":"A coarse per-IP pre-guard (~30/60s) also runs before SIWE recovery."},"tags":["Wallets"],"summary":"Record a wallet's OpenCover terms acknowledgment (SIWE `opencover_terms`)","description":"Stores the wallet's acknowledgment that it viewed OpenCover's terms\nbefore selecting COVERED in the strategy wizard. `SignedRequest`; action\n`opencover_terms` (statement `Authorize opencover_terms`). Unlike the\nplatform ToS, the FULL signed SIWE message and the raw signature are\nPERSISTED as proof (a third party's terms), keyed one-row-per-wallet with\nlast-write-wins semantics. The `addr` is in the PATH; the recovered\nwallet MUST equal it or the call is a 403 `owner_mismatch`. The signed\n`terms_url` binds which terms version was acknowledged and is echoed\nback. This ack is ENFORCED at covered strategy create (403\n`covered_terms_required`), not merely client-gated.\n","security":[{"apikey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpencoverTermsRequest"},"examples":{"acknowledge":{"value":{"terms_url":"https://opencover.com/quicknode-earn-cover-terms/","siwe":{"message":"earn.quicknode.com wants you to sign in with your Ethereum account:\n0x1234...\n\nAuthorize opencover_terms\n\nURI: https://earn.quicknode.com\nVersion: 1\nChain ID: 8453\nNonce: 5c6d...\nIssued At: 2026-07-07T18:00:00.000Z\nExpiration Time: 2026-07-07T18:05:00.000Z\nResources:\n- sha256:<hex>\n- path:/wallets/0x1234.../opencover-terms","signature":"0xabcd..."}}}}}}},"responses":{"200":{"description":"The recorded acknowledgment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpencoverTermsResponse"},"examples":{"recorded":{"value":{"ok":true,"address":"0x1234567890abcdef1234567890abcdef12345678","opencover_terms_accepted":true,"terms_url":"https://opencover.com/quicknode-earn-cover-terms/"}}}}}},"400":{"$ref":"#/components/responses/SiweOrBadRequest"},"401":{"$ref":"#/components/responses/SiweUnauthorized"},"403":{"$ref":"#/components/responses/OwnerMismatch"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/SiweRpc"}}}}},"components":{"parameters":{"StrategyId":{"name":"id","in":"path","required":true,"description":"Strategy UUID.","schema":{"type":"string","format":"uuid"}},"WalletAddr":{"name":"addr","in":"path","required":true,"description":"Wallet address (`0x` + 40 hex).","schema":{"$ref":"#/components/schemas/Address"}}},"responses":{"BadRequest":{"description":"Malformed request (bad/missing params).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"missing":{"value":{"error":{"code":"invalid_request","message":"`wallet` query param is required"}}}}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"nf":{"value":{"error":{"code":"not_found","message":"Strategy not found"}}}}}}},"InternalError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"ie":{"value":{"error":{"code":"internal_error","message":"Internal server error"}}}}}}},"RateLimited":{"description":"Rate limit exceeded. `Retry-After` (seconds) tells you when to retry.\nReads, feedback, and push writes are keyed per IP; create/update/delete\nare keyed per wallet+IP; deposit/withdraw/claim calldata is keyed per\nstrategy+IP.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","examples":[42]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"rl":{"value":{"error":{"code":"rate_limited","message":"Rate limit exceeded. Retry later."}}}}}}},"SiweUnauthorized":{"description":"SIWE verification failed. `code` is one of: `siwe_domain` (message domain\nnot allowlisted), `siwe_action` (statement/action mismatch), `siwe_stale`\n(expired or validity window out of bounds), `siwe_payload` (payload hash\nor resource mismatch — canonicalization drift, a re-serialized body, or\na `- path:` entry that does not match the request path), `siwe_chain`\n(bad chain id), `siwe_recover` (signature does not match the claimed\naddress), `siwe_replay` (nonce already used — sign a fresh request).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"payload":{"value":{"error":{"code":"siwe_payload","message":"Payload hash mismatch"}}},"domain":{"value":{"error":{"code":"siwe_domain","message":"Domain not allowed: example.com"}}},"stale":{"value":{"error":{"code":"siwe_stale","message":"Signature expired"}}}}}}},"SiweOrBadRequest":{"description":"Malformed request or SIWE block. `code` is one of: `invalid_request`,\n`siwe_missing` (no `siwe` block), `siwe_shape` (message/signature not\nstrings), `siwe_parse` (message does not match the documented line format).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"siweMissing":{"value":{"error":{"code":"siwe_missing","message":"Missing SIWE block"}}},"parse":{"value":{"error":{"code":"siwe_parse","message":"Malformed SIWE message"}}}}}}},"SiweRpc":{"description":"Signature verification could not reach the chain to validate a contract\n(ERC-1271) wallet. Transient — retry. Only occurs for smart-wallet\nsigners.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"rpc":{"value":{"error":{"code":"siwe_rpc","message":"Signature verification RPC error: <detail>"}}}}}}},"OwnerMismatch":{"description":"The signing wallet does not match the `{addr}`\nin the path. A wallet may only write its own preferences / ToS state.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"examples":{"mismatch":{"value":{"error":{"code":"owner_mismatch","message":"The signing wallet does not match the path address"}}}}}}}},"examples":{"CreateStrategyBody":{"summary":"Create a 2-position Base + Arbitrum strategy","value":{"name":"My USDC auto-pilot","capital_usdc":1000,"delta_pct":3,"cross_chain_delta_pct":5,"delta_confirmations":2,"max_positions":2,"min_tvl_usd":1000000,"min_liquidity_usd":50000,"chain_ids":[8453,42161],"apy_smoothing_minutes":30,"hidden_vault_keys":[],"idempotencyKey":"b3d1c2e4-0000-4000-8000-000000000001","siwe":{"message":"earn.quicknode.com wants you to sign in with your Ethereum account:\n0x1234...\n\nAuthorize strategy.create\n\nURI: https://earn.quicknode.com\nVersion: 1\nChain ID: 8453\nNonce: 5c6d...\nIssued At: 2026-07-07T18:00:00.000Z\nExpiration Time: 2026-07-07T18:05:00.000Z\nResources:\n- sha256:<hex>\n- path:/strategies","signature":"0xabcd..."}}},"DepositCalldataBridged":{"summary":"One deposit tx spanning Base (local) + an Arbitrum burn","value":{"kind":"deposit","transactions":[{"chainId":8453,"to":"0x0ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","data":"0x5a6b7c8d0000000000000000000000000000000000000000000000000000000000000060","value":"0","intentId":"7f3a1c2b-0000-4000-8000-000000000abc","kind":"deposit","description":"Deposit $1000 into 2 vault(s), bridging to 1 chain(s)","gasHint":"480000"}],"approvalsNeeded":[],"plan":{"totalUsdc":"1000000000","vaults":["0x616a4e1db48e22028f6bbf20444cd3b8e3273738"],"amounts":["600000000"],"burns":[{"destDomain":3,"mintRecipient":"0x0000000000000000000000000ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","destinationCaller":"0x0000000000000000000000000ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","amount":"400000000","maxFee":"0","minFinalityThreshold":2000}],"burnVaultBreakdown":[[{"vault":"0x1a996cb54bb95462040408c06122d45d6cdb6096","amount":"400000000","name":"Gauntlet USDC Core","apy":5.117}]],"apys":{"8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738":6.482,"42161:0x1a996cb54bb95462040408c06122d45d6cdb6096":5.117},"vaultNames":{"8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738":"Seamless USDC Vault","42161:0x1a996cb54bb95462040408c06122d45d6cdb6096":"Gauntlet USDC Core"},"chainIds":{"8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738":8453,"42161:0x1a996cb54bb95462040408c06122d45d6cdb6096":42161}},"expiresAt":"2026-07-07T19:00:00.000Z"}},"WithdrawCalldataMulti":{"summary":"Close spanning Base (source) then Arbitrum","value":{"kind":"withdraw","transactions":[{"chainId":8453,"to":"0x0ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","data":"0xaabbccdd0000000000000000000000000000000000000000000000000000000000000060","value":"0","intentId":"1111aaaa-0000-4000-8000-000000000001","kind":"close","description":"Close 1 position(s) on chain 8453","gasHint":"310000"},{"chainId":42161,"to":"0x0ea99e5e2f70e6ad0aa1a2b1a0b0d0e0f0a0b0c0","data":"0xeeff00110000000000000000000000000000000000000000000000000000000000000060","value":"0","intentId":"2222bbbb-0000-4000-8000-000000000002","kind":"close","description":"Close 1 position(s) on chain 42161, bridging USDC to the source chain","gasHint":"410000"}],"approvalsNeeded":[],"plan":{"chains":{"8453":{"vaults":["0x616a4e1db48e22028f6bbf20444cd3b8e3273738"],"shares":["599430000000000000000"],"feeAmounts":["0"],"totalFeeUsdc":0,"burns":[]},"42161":{"vaults":["0x1a996cb54bb95462040408c06122d45d6cdb6096"],"shares":["399120000000000000000"],"feeAmounts":["0"],"totalFeeUsdc":0,"burns":[{"mintRecipient":"0x0000000000000000000000001234567890abcdef1234567890abcdef12345678","amount":"115792089237316195423570985008687907853269984665640564039457584007913129639935"}]}}},"expiresAt":"2026-07-07T19:00:00.000Z"}}},"schemas":{"Address":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","description":"EVM address, `0x` + 40 hex.","examples":["0x1234567890abcdef1234567890abcdef12345678"]},"HexData":{"type":"string","pattern":"^0x[0-9a-f]*$","description":"Lowercase hex-encoded transaction calldata. Submit VERBATIM.","examples":["0x5a6b7c8d"]},"BaseUnitString":{"type":"string","pattern":"^[0-9]+$","description":"Non-negative integer as a decimal string (base units — USDC 6dp, wei, or raw uint256). String to preserve precision above 2^53.","examples":["1000000"]},"ApiError":{"type":"object","required":["error"],"additionalProperties":false,"description":"Error envelope. Success bodies are plain resources with no wrapper.","properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine code. Gateway-wide: `invalid_request`,\n`not_found`, `method_not_allowed`, `not_implemented`,\n`rate_limited`, `internal_error`, `hide_lookup_failed`. Mutations\n/ calldata: `invalid_state`, `no_eligible_vaults`,\n`gas_estimation_failed`, `burn_route_unavailable`,\n`no_positions_for_chains`. Emergency claim:\n`transfer_not_found` (404), `attestation_pending` (409),\n`unsupported_chain_pair` (500). Intent status:\n`intent_not_found` (404). Strategy hide-list ops:\n`strategy_read_failed` (503).\nPlatform: `price_data_unavailable` (503). Wallet-pref writes:\n`owner_mismatch` (403), `prefs_read_failed` (503).\nCovered create gate: `covered_disabled` (403),\n`covered_terms_required` (403),\n`covered_access_lookup_failed` (503).\nSIWE (create/update/delete, prefs.update, tos_agreement,\nopencover_terms):\n`siwe_missing`, `siwe_shape`, `siwe_parse`, `siwe_domain`,\n`siwe_action`, `siwe_stale`, `siwe_payload`, `siwe_chain`,\n`siwe_recover`, `siwe_rpc`.\n"},"message":{"type":"string","description":"Human-readable detail. Do not parse; branch on `code`."}}}}},"SiweBlock":{"type":"object","required":["message","signature"],"description":"A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime).","properties":{"message":{"type":"string","description":"The exact 14-line SIWE message that was signed, lines joined with\n`\\n`:\n```\n<domain> wants you to sign in with your Ethereum account:\n<address>\n\nAuthorize <action>\n\nURI: <uri>\nVersion: 1\nChain ID: <chainId>\nNonce: <nonce>\nIssued At: <ISO-8601>\nExpiration Time: <ISO-8601>\nResources:\n- sha256:<payloadHash>\n- path:<request path>\n```\n`<request path>` is the `/v1`-relative request path, lowercased,\nwithout query — e.g. `- path:/strategies/{id}` for an update or\ndelete. The `- path:` entry is required: a legacy 13-line message\n(no `- path:` entry) is rejected with `siwe_payload`.\n"},"signature":{"type":"string","description":"The signature over `message` (EOA ecrecover, or ERC-1271/6492 for smart wallets). `\"0x\"` is accepted for Safe pre-signed flows."}}},"SignedRequest":{"type":"object","required":["siwe"],"description":"Mutation body: a `siwe` block plus the action's payload fields. Signed\npayload = body minus `siwe`, canonicalized (keys sorted at every depth,\n`undefined` dropped) and sha256-hashed into the message `Resources` line;\nan empty payload signs `{}`. The message's `- path:` entry binds the\nproof to the request path (so a proof for one strategy can never act on\nanother), and the nonce is single-use per wallet — replaying a proof\nreturns `siwe_replay`. Actions: `strategy.create`, `strategy.update`,\n`strategy.delete`, `prefs.update`, `tos_agreement`, `opencover_terms`.\nValidity: 5 min (EOA), up to 24h (smart wallet), 25h server cap. Allowed\ndomains: the API host or `earn.quicknode.com`.\n","properties":{"siwe":{"$ref":"#/components/schemas/SiweBlock"}}},"CalldataRequest":{"type":"object","description":"Deposit calldata request body. Public (no SIWE). The deposit endpoint\ntakes no parameters, so send no body or an empty JSON object (`{}`).\n"},"WithdrawCalldataRequest":{"type":"object","description":"Withdraw/close calldata request body. Public (no SIWE).","properties":{"chainIds":{"type":"array","items":{"type":"integer"},"description":"Optional subset of chain ids to withdraw from. Omit to close all chains.","examples":[[8453]]}}},"Vault":{"type":"object","required":["chainId","vaultAddress","name","apy","tvlUsd","availableLiquidityUsd","maxDepositUsd","atCapacity"],"description":"A ranked, eligible vault with liquidity/capacity.","properties":{"chainId":{"type":"integer"},"vaultAddress":{"type":"string","description":"Lowercased vault (ERC-4626 share token) address."},"name":{"type":"string","description":"Human-readable vault name (empty string if unknown)."},"apy":{"type":"number","description":"Ranking-grade APY as a PERCENTAGE (3dp): 6.482 = 6.482%."},"tvlUsd":{"type":"integer","description":"Vault TVL in whole dollars (rounded)."},"availableLiquidityUsd":{"type":["integer","null"],"description":"Withdrawable liquidity in whole dollars, or null if unknown."},"maxDepositUsd":{"type":["integer","null"],"description":"Deposit cap in whole dollars; null means uncapped."},"atCapacity":{"type":"boolean","description":"True when the vault's max deposit is 0 (full)."}}},"ExcludedChain":{"type":"object","required":["chainId","reason"],"properties":{"chainId":{"type":"integer"},"reason":{"type":"string","description":"Why the chain produced no eligible vaults (e.g. `no_eligible_vaults`, `min_strategy_usdc`)."}}},"VaultRankingsResponse":{"type":"object","required":["mode","vaults","excludedChains","eligibleCount","estimatedApy"],"description":"The response of `GET /v1/vaults/rankings`.","properties":{"mode":{"type":"string","enum":["ranked"]},"vaults":{"type":"array","items":{"$ref":"#/components/schemas/Vault"}},"excludedChains":{"type":"array","items":{"$ref":"#/components/schemas/ExcludedChain"}},"eligibleCount":{"type":"integer","description":"Number of eligible vaults before the maxPositions cut."},"estimatedApy":{"type":["number","null"],"description":"Mean of the selected vaults' positive APYs, as a percentage; null when none."}}},"VaultWindows":{"type":"object","required":["m5","m10","m30","h1","h2","h4","h6","h12","h24"],"description":"The nine precomputed APY windows for a vault, as percentages (2dp).\nA null window means insufficient snapshot history for that lookback\n(e.g. a freshly launched vault), NOT zero.\n","properties":{"m5":{"type":["number","null"]},"m10":{"type":["number","null"]},"m30":{"type":["number","null"]},"h1":{"type":["number","null"]},"h2":{"type":["number","null"]},"h4":{"type":["number","null"]},"h6":{"type":["number","null"]},"h12":{"type":["number","null"]},"h24":{"type":["number","null"]}}},"BrowseVault":{"type":"object","required":["chainId","vaultAddress","name","apy","windows","tvlUsd","availableLiquidityUsd","maxDepositUsd","atCapacity","latestAt"],"description":"A single approved vault's latest snapshot, unranked. Returned as an\nelement of the browse list AND as the `GET /v1/vaults/{chainId}/{address}`\ndetail body.\n","properties":{"chainId":{"type":"integer"},"vaultAddress":{"type":"string","description":"Lowercased vault (ERC-4626 share token) address."},"name":{"type":["string","null"],"description":"Human-readable vault name."},"apy":{"type":["number","null"],"description":"Headline APY (%) at the requested `window`, 2dp. Null means\ninsufficient snapshot history at that window (the vault is still\nlisted).\n"},"windows":{"$ref":"#/components/schemas/VaultWindows"},"tvlUsd":{"type":["integer","null"],"description":"Vault TVL in whole dollars (rounded), or null."},"availableLiquidityUsd":{"type":["integer","null"],"description":"Withdrawable liquidity in whole dollars, or null."},"maxDepositUsd":{"type":["integer","null"],"description":"Deposit cap in whole dollars; null means uncapped."},"atCapacity":{"type":"boolean","description":"True when the vault's max deposit is 0 (full)."},"latestAt":{"type":"string","format":"date-time"},"covered":{"type":"boolean","description":"Present and `true` ONLY when this vault is an OpenCover covered fee\nwrapper. OMITTED entirely for plain vaults (not `false`), so\nuncovered rows stay byte-identical; treat absence as uncovered.\n"},"coverCapacity":{"$ref":"#/components/schemas/CoverCapacity"}}},"LiveCoverCapacity":{"type":"object","required":["remainingUsd","asOf"],"description":"The LIVE remaining OpenCover shared-pool capacity, read from\nOpenCover's endpoint at request time. ONE figure for every covered\nwrapper. `remainingUsd: null` means the feed is not configured or no\nwrapper answered: render as unknown, never as zero.\n","properties":{"remainingUsd":{"type":"number","nullable":true,"description":"Remaining shared-pool capacity in decimal dollars, or null when unknown."},"asOf":{"type":"string","format":"date-time","description":"When this reading was taken (request time)."}}},"CoverCapacity":{"type":"object","required":["remainingUsd","checkedAt"],"description":"Remaining OpenCover underwriting capacity, as seen from a covered fee\nwrapper. This is ONE POOL shared by every covered wrapper: each covered\nwrapper reports the same figure, so do not sum it across rows.\nPresent ONLY on covered wrappers (omitted entirely for plain vaults, so\nuncovered payloads stay byte-identical); on the browse list it appears\nonly when the caller passed `includeCovered=true`.\n\nThis is a CACHED display figure refreshed by the capacity sweep every\n~5 minutes. It is NOT the allocation gate: capacity is enforced in one\nplace only — the covered deposit plan — where the server reads\nOpenCover's capacity endpoint live and requires\n`remaining - deposit >= 0` (a deposit of exactly the remaining pool\npasses). Rebalances are not capacity-checked: they move funds between\ncovered wrappers inside the same pool. Clients may use this cached\nfigure to cap a deposit input (e.g. a Max button).\n\n`remainingUsd: null` means the capacity feed has not resolved this\nwrapper (never polled, or the last poll failed) — render it as unknown,\nnever as `$0`.\n","properties":{"remainingUsd":{"type":["number","null"],"description":"Remaining capacity in decimal dollars, or null when unknown."},"checkedAt":{"type":["string","null"],"format":"date-time","description":"When the figure was last refreshed, or null when never polled."}}},"VaultsBrowseResponse":{"type":"object","required":["mode","window","vaults"],"description":"The response of `GET /v1/vaults`.","properties":{"mode":{"type":"string","enum":["browse"]},"window":{"type":"integer","description":"The applied headline-APY window (minutes) after snapping."},"vaults":{"type":"array","items":{"$ref":"#/components/schemas/BrowseVault"}}}},"LiveLiquidity":{"type":"object","required":["morphoVersion","withdrawableUsdc","forceDeallocatableUsdc"],"description":"A fresh on-chain read of a vault's withdrawable liquidity.\n`withdrawableUsdc` is what a plain ERC-4626 `redeem()` can pull right\nnow — the number that bounds an actual withdrawal.\n`forceDeallocatableUsdc` is the upper bound if every Morpho V2 adapter\nwere force-deallocated (a penalty path Earn does not use); equal to\n`withdrawableUsdc` for Morpho V1 vaults. `morphoVersion` is `v1`,\n`v2`, or `v2_with_v1_adapter`.\n","properties":{"morphoVersion":{"type":"string","enum":["v1","v2","v2_with_v1_adapter"]},"withdrawableUsdc":{"type":"number","description":"Decimal dollars."},"forceDeallocatableUsdc":{"type":"number","description":"Decimal dollars."}}},"VaultDetailResponse":{"allOf":[{"$ref":"#/components/schemas/BrowseVault"},{"type":"object","description":"The `GET /v1/vaults/{chainId}/{address}` body. `liveLiquidity` /\n`liveLiquidityError` appear only with `include=liveLiquidity`.\nFor covered fee wrappers, `liveLiquidity` is read from the wrapped\n(underlying) vault — matching the snapshot columns, which mirror\nthe underlying — while `morphoVersion` stays the wrapper's own.\n","properties":{"underlyingVault":{"type":"object","required":["address","name"],"description":"Present ONLY when the vault is a covered fee wrapper: the\nwrapped (underlying) Morpho vault it deposits into. Omitted\nfor plain vaults.\n","properties":{"address":{"type":"string","description":"Lowercased underlying vault address."},"name":{"type":["string","null"]}}},"liveLiquidity":{"oneOf":[{"$ref":"#/components/schemas/LiveLiquidity"},{"type":"null"}]},"liveLiquidityError":{"type":"string","enum":["rpc_error"],"description":"Present when the live read failed (`liveLiquidity` is then null)."}}}]},"VaultApySnapshot":{"type":"object","required":["snapshotAt","apy5m","apy10m","apy30m","apy1h","apy2h","apy4h","apy6h","apy12h","apy24h","assetsPerShare","tvlUsd","availableLiquidityUsd"],"description":"One 5-minute snapshot; downsampling keeps whole rows, never averages.","properties":{"snapshotAt":{"type":"string","format":"date-time"},"apy5m":{"type":["number","null"],"description":"APY % (2dp)."},"apy10m":{"type":["number","null"],"description":"APY % (2dp)."},"apy30m":{"type":["number","null"],"description":"APY % (2dp)."},"apy1h":{"type":["number","null"],"description":"APY % (2dp)."},"apy2h":{"type":["number","null"],"description":"APY % (2dp)."},"apy4h":{"type":["number","null"],"description":"APY % (2dp)."},"apy6h":{"type":["number","null"],"description":"APY % (2dp)."},"apy12h":{"type":["number","null"],"description":"APY % (2dp)."},"apy24h":{"type":["number","null"],"description":"APY % (2dp)."},"assetsPerShare":{"type":["number","null"],"description":"Opaque scaled share price carrying a constant 100x scale. Meaningful\nONLY as a ratio between two snapshots (that is how `intervalApy` is\nderived), never as a per-share dollar value.\n"},"tvlUsd":{"type":["integer","null"],"description":"TVL in whole dollars (rounded), or null."},"availableLiquidityUsd":{"type":["integer","null"],"description":"Withdrawable liquidity in whole dollars, or null."}}},"VaultApySeriesResponse":{"type":"object","required":["chainId","vaultAddress","from","to","clamped","retentionDays","resolutionMinutes","intervalApy","snapshots"],"properties":{"chainId":{"type":"integer"},"vaultAddress":{"type":"string","description":"Lowercased vault address."},"from":{"type":"string","format":"date-time","description":"Effective start (after retention clamp)."},"to":{"type":"string","format":"date-time"},"clamped":{"type":"boolean","description":"True when the requested `from` was pulled forward to the retention floor."},"retentionDays":{"type":"integer","description":"The snapshot retention horizon (7)."},"resolutionMinutes":{"type":["number","null"],"description":"The downsample bucket in minutes, or null for the full 5-minute series."},"intervalApy":{"type":["number","null"],"description":"Annualized share-price return between the first and last snapshot in\nthe FULL range (`(last/first)^(year/elapsed) - 1`, in percent, 4dp).\nNull when there are fewer than two usable share-price snapshots or the\nelapsed span is under one snapshot cadence. Unaffected by `resolution`.\n"},"snapshots":{"type":"array","items":{"$ref":"#/components/schemas/VaultApySnapshot"}}}},"ApiIndexResponse":{"type":"object","required":["name","version","openapi","endpoints"],"description":"The machine-readable discovery index returned by `GET /v1`.","properties":{"name":{"type":"string"},"description":{"type":["string","null"]},"version":{"type":"string"},"openapi":{"type":"string","format":"uri","description":"Absolute URL of the OpenAPI document (`GET /v1/openapi.json`)."},"endpoints":{"type":"array","description":"One entry per documented operation.","items":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","description":"Uppercased HTTP method."},"path":{"type":"string","description":"Templated path (e.g. `/v1/strategies/{id}`)."},"summary":{"type":"string","description":"The operation's summary (present when the operation defines one)."}}}}}},"PlatformStatsResponse":{"type":"object","required":["total_usdc","active_strategies","total_rebalances","updated_at"],"description":"Platform-wide aggregate stats, returned by `GET /v1/stats`.","properties":{"total_usdc":{"type":"number","description":"Total USDC under management across all active/paused strategies, decimal dollars (best-effort live figure)."},"active_strategies":{"type":"integer","description":"Count of strategies with status active or paused."},"total_rebalances":{"type":"integer","description":"Total rebalances executed platform-wide, all time."},"updated_at":{"type":"string","format":"date-time","description":"When this response was computed."}}},"PricesResponse":{"type":"object","required":["ETH","POL","MON","updated_at","chains"],"description":"Latest native gas-token USD prices, returned by `GET /v1/prices`.","properties":{"ETH":{"type":"number","description":"USD price of ETH (used on Ethereum, Optimism, Unichain, Base, Arbitrum)."},"POL":{"type":"number","description":"USD price of POL (used on Polygon)."},"MON":{"type":"number","description":"USD price of MON (used on Monad)."},"updated_at":{"type":"string","format":"date-time","description":"Timestamp of the most recent price update."},"chains":{"type":"object","additionalProperties":{"type":"number"},"description":"USD price of each supported chain's native gas token, keyed by chain id as a string (e.g. `\"8453\": 3421.07`)."}}},"ConfigChain":{"type":"object","required":["chainId","name","minStrategyUsdc"],"description":"One configured chain's deployment entry.","properties":{"chainId":{"type":"integer"},"name":{"type":"string","description":"Display label (e.g. Base)."},"minStrategyUsdc":{"type":["number","null"],"description":"Suggested minimum strategy size in decimal dollars. UI sizing guidance ONLY, not enforced by POST /v1/strategies. Null when unset."}}},"ConfigBanner":{"type":"object","required":["text","buttonCta","buttonIcon","buttonUrl"],"description":"Site-wide announcement strip shown above the app navigation. Operator\ncopy, editable at runtime; `null` on the parent when switched off.\n","properties":{"text":{"type":"string","description":"The message shown on the banner."},"buttonCta":{"type":"string","description":"Label of the banner button."},"buttonIcon":{"type":["string","null"],"description":"Square icon shown left of the label: an https URL or a site-relative path. Null when unset."},"buttonUrl":{"type":"string","description":"https destination the button opens in a new tab."}}},"ConfigResponse":{"type":"object","required":["earnContract","chains","banner","coveredStrategiesEnabled"],"description":"Public deployment config, returned by `GET /v1/config`.","properties":{"earnContract":{"type":"string","description":"The Quicknode Earn contract address (the same on every chain)."},"chains":{"type":"array","items":{"$ref":"#/components/schemas/ConfigChain"},"description":"One entry per served chain; a chain missing here is not currently served."},"banner":{"oneOf":[{"$ref":"#/components/schemas/ConfigBanner"},{"type":"null"}],"description":"Site-wide announcement banner, or null when none is active."},"coveredStrategiesEnabled":{"type":"boolean","description":"The covered-strategies kill switch (app_config\n`ENABLE_COVERED_STRATEGIES`). `false` means the wizard disables the\nCovered option and `POST /v1/strategies` refuses covered creates\n(403 `covered_disabled`). A missing row or a read failure reads as\n`true`. Existing covered strategies keep running either way.\n"}}},"YieldSnapshot":{"type":"object","required":["id","total_value_usdc","period_yield","cumulative_yield","cumulative_fees","period_fee_usdc","net_yield","weighted_apy","snapshot_at"],"description":"One append-only strategy value/yield snapshot. Numeric fields may be\nnull; treat null as 0 when aggregating.\n","properties":{"id":{"type":"string"},"total_value_usdc":{"type":["number","null"],"description":"Total strategy value at the snapshot, decimal dollars."},"period_yield":{"type":["number","null"],"description":"Gross yield accrued since the previous snapshot, decimal dollars."},"cumulative_yield":{"type":["number","null"],"description":"Cumulative gross yield since strategy inception, decimal dollars (not window-scoped; for a window-scoped cumulative, sum `period_yield` across the response rows)."},"cumulative_fees":{"type":["number","null"],"description":"Cumulative fees since strategy INCEPTION, decimal dollars. Same inception-vs-window caveat as `cumulative_yield`."},"period_fee_usdc":{"type":"number","description":"Fees attributed to this snapshot: `max(0, cumulative_fees -\nprevious row's cumulative_fees)`, decimal dollars. The first row of\nany response always reports 0.\n"},"net_yield":{"type":"number","description":"`period_yield - period_fee_usdc`, decimal dollars."},"period_cover_fee_usdc":{"type":"number","description":"OpenCover premium attributed to this snapshot, decimal dollars.\nPresent ONLY for covered strategies (absent entirely otherwise).\nBackward-difference of `cumulative_cover_fees`, clamped at zero;\nfirst row is the 0 baseline. Never folded into `period_fee_usdc`\n(gas-only) and never subtracted from `net_yield` (the premium is\nalready netted out of NAV by the wrapper share price).\n"},"weighted_apy":{"type":["number","null"],"description":"Weighted realized APY at the snapshot, as a percentage."},"snapshot_at":{"type":"string","format":"date-time"}}},"PerformanceResponse":{"type":"object","required":["hours","truncated","snapshots"],"properties":{"hours":{"type":"integer","description":"The clamped trailing window in hours (1..2160)."},"truncated":{"type":"boolean","description":"True when the window held more than 2000 snapshots and the response was capped (see the endpoint description for which end gets dropped)."},"snapshots":{"type":"array","description":"Oldest-first yield snapshots within the window.","items":{"$ref":"#/components/schemas/YieldSnapshot"}}}},"WalletPrefsResponse":{"type":"object","required":["address","has_agreement","hidden_vault_keys","hide_approval"],"properties":{"address":{"type":"string","description":"Lowercased wallet address."},"has_agreement":{"type":"boolean","description":"Whether the wallet has recorded a ToS agreement."},"hidden_vault_keys":{"type":"array","items":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$"},"description":"The wallet's global `chainId:0xaddress` hide list (lowercased)."},"hide_approval":{"type":"boolean","description":"Whether the wallet has dismissed the approval banner."},"covered_access":{"type":"boolean","description":"Always `true`: covered strategies are open to every wallet. Kept on\nthe wire for clients that still read it. Absent on older gateway\nbuilds.\n"},"opencover_terms_accepted":{"type":"boolean","description":"Whether the wallet has a stored OpenCover terms signature\n(`POST /v1/wallets/{addr}/opencover-terms`). A SOFT UI signal; the\ncovered create re-checks it (403 `covered_terms_required`). Defaults\nto false on a lookup hiccup. Absent on older gateway builds.\n"}}},"PrefsUpdateRequest":{"allOf":[{"$ref":"#/components/schemas/SignedRequest"},{"type":"object","description":"At least one of `hide_vault_key`, `unhide_vault_key`, `hide_approval`\nmust be present (all part of the signed payload). `hide_vault_key`\nadds to the global hide list; `unhide_vault_key` removes; both may be\nsent together.\n","properties":{"hide_vault_key":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$","description":"Vault key to add to the hide list."},"unhide_vault_key":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$","description":"Vault key to remove from the hide list."},"hide_approval":{"type":"boolean","description":"Set the approval-banner dismissal flag."}}}]},"PrefsUpdateResponse":{"type":"object","required":["ok","hidden_vault_keys","hide_approval"],"properties":{"ok":{"type":"boolean","description":"Always true on a 2xx."},"hidden_vault_keys":{"type":"array","items":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$"},"description":"The full hide list after the merge."},"hide_approval":{"type":"boolean"},"warnings":{"type":"array","description":"Present only when a `hide_vault_key` request shrank a strategy's\neligible vault pool below its `max_positions + 1` rebalancing floor.\nAdvisory only; the write is always applied. Best-effort: may be\nabsent even when applicable, never an error.\n","items":{"type":"object","required":["code","strategy_id","strategy_name","message"],"properties":{"code":{"type":"string","enum":["hide_floor_violated"]},"strategy_id":{"type":"string"},"strategy_name":{"type":"string"},"message":{"type":"string"}}}}}},"TosRequest":{"allOf":[{"$ref":"#/components/schemas/SignedRequest"},{"type":"object","description":"The signed payload binds the literal `terms_url` for auditability.","properties":{"terms_url":{"type":"string","format":"uri","description":"The ToS URL being agreed to; echoed back in the response."}}}]},"TosResponse":{"type":"object","required":["ok","address","has_agreement"],"properties":{"ok":{"type":"boolean","description":"Always true on a 2xx."},"address":{"type":"string","description":"Lowercased wallet address."},"has_agreement":{"type":"boolean","description":"Always true after recording."},"terms_url":{"type":"string","description":"Echo of the signed `terms_url` (absent when the caller sent none)."}}},"OpencoverTermsRequest":{"allOf":[{"$ref":"#/components/schemas/SignedRequest"},{"type":"object","description":"The signed payload binds the literal `terms_url` (the OpenCover terms\nversion acknowledged). The full SIWE message and raw signature are\npersisted as proof.\n","properties":{"terms_url":{"type":"string","format":"uri","description":"The OpenCover terms URL being acknowledged; bound into the signature and echoed back."}}}]},"OpencoverTermsResponse":{"type":"object","required":["ok","address","opencover_terms_accepted"],"properties":{"ok":{"type":"boolean","description":"Always true on a 2xx."},"address":{"type":"string","description":"Lowercased wallet address."},"opencover_terms_accepted":{"type":"boolean","description":"Always true after recording."},"terms_url":{"type":"string","description":"Echo of the signed `terms_url` (absent when the caller sent none)."}}},"Position":{"type":"object","required":["id","vault_name","vault_address","protocol","shares_raw","usdc_value","entry_apy"],"description":"A live-valued strategy position. Field presence varies by emission\nsite: `strategy_id` appears only on list responses, `current_apy`\nonly on detail responses, `chain_id` and `initial_usd_value` are\nabsent on closed-strategy list positions, and `paused` is absent on\nall closed positions.\n","properties":{"id":{"type":"string"},"strategy_id":{"type":["string","null"],"description":"List responses only; absent on detail positions."},"vault_name":{"type":["string","null"]},"vault_address":{"type":"string"},"protocol":{"type":["string","null"]},"chain_id":{"type":"integer","description":"Absent on closed-strategy list positions; never null when present."},"shares_raw":{"type":["string","null"],"description":"Raw ERC-4626 share balance (uint256 decimal string)."},"usdc_value":{"type":"number","description":"Live `convertToAssets` valuation in decimal dollars."},"entry_apy":{"type":"number","description":"APY percentage at entry (0 when unrecorded, never null)."},"current_apy":{"type":["number","null"],"description":"Detail responses only. The APY the auto-pilot currently attributes to this position, at the strategy's smoothing window. Authoritative for a HELD vault that the vault universe no longer lists (paused, or on the other side of the coverage partition), where a browse lookup would miss."},"initial_usd_value":{"type":["number","null"],"description":"Decimal dollars deposited into this position. Absent on closed-strategy list positions."},"paused":{"type":"boolean","description":"Absent on closed positions."}}},"Strategy":{"type":"object","required":["id","wallet_address","name","capital_usdc","delta_pct","delta_confirmations","max_positions","min_tvl_usd","min_liquidity_usd","min_liquidity_entry_multiplier","min_tvl_exit_ratio","min_tvl_exit_usd","min_liquidity_entry_usd","chain_id","chain_ids","apy_smoothing_minutes","hidden_vault_keys","status","active","created_at","updated_at","first_deposit_at","deactivated_at","final_value_usdc","final_realized_apy","cycle_state","total_value_usdc","pending_bridge_value_usdc","pending_bridges_count","net_value_usdc","realized_apy","positions","rebalance_count","total_fees_usdc"],"description":"A strategy list item: stored config columns plus computed rollups. USDC\nvalues are decimal dollars; APY fields are percentages. Additional\nforward-compatible fields may appear. `live_apy` and `strategy_apy`\nare present on active list items and detail, absent on\n`closedStrategies` items.\n","properties":{"id":{"type":"string"},"wallet_address":{"type":"string"},"name":{"type":"string"},"capital_usdc":{"type":"number","description":"Configured capital in decimal dollars."},"delta_pct":{"type":"number","description":"APY-gap threshold (percentage points) that triggers a SAME-CHAIN rebalance."},"cross_chain_delta_pct":{"type":"number","description":"APY-gap threshold (percentage points) required when the source and destination vaults are on different chains, which costs an extra bridge leg. 0 means inherit delta_pct."},"delta_confirmations":{"type":"integer"},"max_positions":{"type":"integer"},"min_tvl_usd":{"type":"number"},"min_liquidity_usd":{"type":"number","description":"Liquidity auto-exit floor in USD. 0 disables the liquidity filter."},"min_tvl_exit_usd":{"type":"number","description":"Held-vault TVL exit floor in USD (v1.10.0, source of truth). Never above min_tvl_usd. 0 disables the TVL exit."},"min_liquidity_entry_usd":{"type":"number","description":"Liquidity a vault needs before the strategy enters it, in USD (v1.10.0, source of truth). Never below min_liquidity_usd; equal means a vault is enterable right at the exit floor."},"min_liquidity_entry_multiplier":{"type":"number","deprecated":true,"description":"Deprecated alias of min_liquidity_entry_usd (entry = min_liquidity_usd × multiplier). Always consistent with the dollar field."},"min_tvl_exit_ratio":{"type":"number","deprecated":true,"description":"Deprecated alias of min_tvl_exit_usd (exit = min_tvl_usd × ratio). Always consistent with the dollar field. 0 disables the TVL exit."},"chain_id":{"type":"integer","description":"Legacy single-chain id."},"chain_ids":{"type":["string","null"],"description":"Comma-separated authoritative chain id list."},"apy_smoothing_minutes":{"type":"integer"},"hidden_vault_keys":{"type":"array","items":{"type":"string"},"description":"Array of `chainId:0xaddress` keys hidden for this strategy."},"status":{"type":"string","enum":["pending_setup","active","paused","closing","closed"]},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"first_deposit_at":{"type":["string","null"],"format":"date-time"},"deactivated_at":{"type":["string","null"],"format":"date-time"},"final_value_usdc":{"type":["number","null"]},"final_realized_apy":{"type":["number","null"]},"cycle_state":{"type":["string","null"],"enum":["idle","pending","rebalancing","error","action_needed",null]},"covered":{"type":"boolean","description":"True for an OpenCover covered strategy (allocates only to covered\nfee wrappers; Base-only; charged a 1.05%/yr premium inside the\nvault). Set once at create and immutable. Optional/forward-\ncompatible: absent on payloads predating the feature.\n"},"coverage_activated_at":{"type":["string","null"],"format":"date-time","description":"When coverage becomes active for a covered strategy — a FUTURE\ninstant stamped at first deposit (`first_deposit_at + 24h`), NOT a\n\"coverage is active now\" flag. Coverage is in force only once\n`now() >= coverage_activated_at`. Null before first deposit and for\nuncovered strategies.\n"},"total_value_usdc":{"type":"number","description":"Live total value in decimal dollars."},"pending_bridge_value_usdc":{"type":"number"},"pending_bridges_count":{"type":"integer"},"net_value_usdc":{"type":"number","description":"total_value_usdc + pending_bridge_value_usdc."},"realized_apy":{"type":["number","null"],"description":"Annualized realized APY percentage. Null until ~30 min after the deposit flow fully landed (bridges included), and for as long as any deposit bridge is still in flight — do not poll against a fixed 30-minute wait. Unbounded; may be negative."},"live_apy":{"type":["number","null"],"description":"USDC-value-weighted average APY across active positions at a fixed\n5-minute smoothing window. Positions with no snapshot data yet\ncontribute 0% (not excluded). Null when there are no active\npositions.\n"},"strategy_apy":{"type":["number","null"],"description":"Same computation as `live_apy` but at the strategy's own\n`apy_smoothing_minutes` window. Null when that window resolves to 5\n(identical to `live_apy`) or there are no active positions.\n"},"positions":{"type":"array","items":{"$ref":"#/components/schemas/Position"}},"rebalance_count":{"type":"integer"},"total_fees_usdc":{"type":"number"}}},"StrategyRow":{"type":"object","required":["id","wallet_address","name","capital_usdc","delta_pct","delta_confirmations","max_positions","min_tvl_usd","min_liquidity_usd","min_liquidity_entry_multiplier","min_tvl_exit_ratio","min_tvl_exit_usd","min_liquidity_entry_usd","chain_id","chain_ids","apy_smoothing_minutes","hidden_vault_keys","status","active","created_at","updated_at","first_deposit_at","deactivated_at","final_value_usdc","final_realized_apy","last_cycle_at","cycle_state","last_error","swap_signals","unmatched_exits","last_skip"],"description":"The strategy row returned (inside `strategy`) by create and update:\nconfig fields plus cycle/gauge fields (`cycle_state`, `swap_signals`,\n`last_skip`, ...). No computed rollups.\n","additionalProperties":true,"properties":{"id":{"type":"string"},"wallet_address":{"type":"string"},"name":{"type":"string"},"capital_usdc":{"type":"number"},"delta_pct":{"type":"number"},"cross_chain_delta_pct":{"type":"number","description":"Cross-chain APY-gap threshold. 0 means inherit delta_pct."},"delta_confirmations":{"type":"integer"},"max_positions":{"type":"integer"},"min_tvl_usd":{"type":"number"},"min_liquidity_usd":{"type":"number"},"min_tvl_exit_usd":{"type":"number","description":"Held-vault TVL exit floor in USD (v1.10.0, source of truth). Never above min_tvl_usd. 0 disables the TVL exit."},"min_liquidity_entry_usd":{"type":"number","description":"Liquidity a vault needs before the strategy enters it, in USD (v1.10.0, source of truth). Never below min_liquidity_usd."},"min_liquidity_entry_multiplier":{"type":"number","deprecated":true,"description":"Deprecated alias of min_liquidity_entry_usd (entry = min_liquidity_usd × multiplier). Always consistent with the dollar field."},"min_tvl_exit_ratio":{"type":"number","deprecated":true,"description":"Deprecated alias of min_tvl_exit_usd (exit = min_tvl_usd × ratio). Always consistent with the dollar field. 0 disables the TVL exit."},"chain_id":{"type":"integer"},"chain_ids":{"type":["string","null"]},"apy_smoothing_minutes":{"type":"integer"},"hidden_vault_keys":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["pending_setup","active","paused","closing","closed"]},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"first_deposit_at":{"type":["string","null"],"format":"date-time"},"deactivated_at":{"type":["string","null"],"format":"date-time"},"final_value_usdc":{"type":["number","null"]},"final_realized_apy":{"type":["number","null"]},"last_cycle_at":{"type":["string","null"],"format":"date-time"},"cycle_state":{"type":["string","null"],"enum":["idle","pending","rebalancing","error","action_needed",null]},"last_error":{"type":["string","null"]},"swap_signals":{"type":["array","null"],"description":"Opaque to callers; do not depend on element shape.","items":{"type":"object","additionalProperties":true}},"unmatched_exits":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}},"last_skip":{"type":["object","null"],"additionalProperties":true}}},"StrategyMutationResponse":{"type":"object","required":["strategy"],"properties":{"strategy":{"$ref":"#/components/schemas/StrategyRow"}}},"StrategyListResponse":{"type":"object","required":["strategies","closedStrategies"],"properties":{"strategies":{"type":"array","items":{"$ref":"#/components/schemas/Strategy"}},"closedStrategies":{"type":"array","items":{"$ref":"#/components/schemas/Strategy"}}}},"StrategyDetail":{"allOf":[{"$ref":"#/components/schemas/Strategy"},{"type":"object","required":["last_cycle_at","cycle_state","last_error","swap_signals","unmatched_exits","last_skip","has_agreement","vault_names","pending_rebalance","live_apy","strategy_apy"],"description":"`closed_positions` is OMITTED (not an empty array) when the strategy\nhas no deactivated positions; every other detail-only field is\nalways present. `live_apy` and `strategy_apy` are always present on\ndetail.\n","properties":{"last_cycle_at":{"type":["string","null"],"format":"date-time"},"cycle_state":{"type":["string","null"],"enum":["idle","pending","rebalancing","error","action_needed",null]},"last_error":{"type":["string","null"]},"swap_signals":{"type":["array","null"],"description":"Opaque to callers; do not depend on element shape.","items":{"type":"object","additionalProperties":true}},"unmatched_exits":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}},"last_skip":{"type":["object","null"],"additionalProperties":true},"has_agreement":{"type":"boolean","description":"Whether the wallet has signed the ToS."},"closed_positions":{"type":"array","items":{"$ref":"#/components/schemas/Position"}},"vault_names":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of `chainId:0xaddress` → vault name."},"pending_rebalance":{"type":["object","null"],"description":"Snapshot of an in-flight same-chain rebalance; null when none.\n`phase` only distinguishes not-yet-submitted from\nawaiting-confirmation. Cleared once fulfilled; completed\nrebalances appear in `GET /v1/strategies/{id}/history`.\n","required":["chain_id","from_vault","to_vault","submitted_tx_hash","phase","created_at","expires_at"],"properties":{"chain_id":{"type":"integer"},"from_vault":{"type":["string","null"]},"to_vault":{"type":["string","null"]},"submitted_tx_hash":{"type":["string","null"]},"phase":{"type":"string","enum":["submitted","confirming"]},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"}}},"cover":{"type":"object","description":"OpenCover premium summary. Present ONLY on covered strategies —\nOMITTED entirely (not null) for uncovered strategies, so\nuncovered detail payloads carry no cover keys. `premium_rate_bps`\nand `premiums_paid_usdc` come from the `strategy_detail` RPC\n(paid-to-date is the live premium ledger sum while active, the\ndurable `final_cover_fees_usdc` once closed); the three\nprojections are computed at read time as\n`total_value_usdc × 0.0105 × {1/52, 1/12, 1}` (decimal dollars),\nso they track live value and are not byte-stable between polls.\n","required":["premium_rate_bps","premiums_paid_usdc","projected_1w_usdc","projected_1mo_usdc","projected_1y_usdc","coverage_activated_at"],"properties":{"premium_rate_bps":{"type":"number","description":"Flat cover premium in basis points per year (105 = 1.05%/yr)."},"premiums_paid_usdc":{"type":["number","null"],"description":"Estimated premium paid to date, decimal dollars (display-only ledger; chain is the settlement truth). NULL when the RPC has no ledger figure yet (never coerced to 0 — distinct from a genuine $0 paid)."},"projected_1w_usdc":{"type":"number","description":"Projected premium over the next week at current value, decimal dollars."},"projected_1mo_usdc":{"type":"number","description":"Projected premium over the next month at current value, decimal dollars."},"projected_1y_usdc":{"type":"number","description":"Projected premium over the next year at current value, decimal dollars."},"coverage_activated_at":{"type":["string","null"],"format":"date-time","description":"Read-through of the strategy's coverage_activated_at (future instant; coverage in force once now() >= this)."}}}}}]},"StrategyDetailResponse":{"type":"object","required":["strategy"],"properties":{"strategy":{"$ref":"#/components/schemas/StrategyDetail"}}},"LeaderboardRowConfig":{"type":"object","required":["delta_pct","cross_chain_delta_pct","delta_confirmations","max_positions","min_tvl_usd","min_liquidity_usd","min_liquidity_entry_multiplier","min_tvl_exit_ratio","min_tvl_exit_usd","min_liquidity_entry_usd","apy_smoothing_minutes","chain_id","chain_ids"],"description":"The strategy's rebalancer configuration — enough to reproduce the\nsetup, without capital or identity fields.\n","properties":{"delta_pct":{"type":"number","description":"Same-chain APY-gap bar (percentage points)."},"cross_chain_delta_pct":{"type":"number","description":"Cross-chain APY-gap bar. 0 means inherit delta_pct."},"delta_confirmations":{"type":"integer"},"max_positions":{"type":"integer"},"min_tvl_usd":{"type":"number"},"min_liquidity_usd":{"type":"number"},"min_tvl_exit_usd":{"type":"number","description":"Held-vault TVL exit floor in USD (v1.10.0, source of truth). 0 disables the TVL exit."},"min_liquidity_entry_usd":{"type":"number","description":"Liquidity a vault needs before the strategy enters it, in USD (v1.10.0, source of truth)."},"min_liquidity_entry_multiplier":{"type":"number","deprecated":true,"description":"Deprecated alias of min_liquidity_entry_usd (entry = min_liquidity_usd × multiplier)."},"min_tvl_exit_ratio":{"type":"number","deprecated":true,"description":"Deprecated alias of min_tvl_exit_usd (exit = min_tvl_usd × ratio). 0 disables the TVL exit."},"apy_smoothing_minutes":{"type":"integer"},"chain_id":{"type":"integer","description":"Legacy single-chain id."},"chain_ids":{"type":"array","items":{"type":"integer"},"description":"Authoritative chain id list (falls back to `[chain_id]`)."}}},"LeaderboardRow":{"type":"object","required":["strategy_id","name","wallet","first_deposit_at","ended_at","measured_at","status","value_usdc","realized_apy_pct","gross_apy_pct","fees_usdc","rebalance_count","covered","clone_count","cloned_from","config"],"properties":{"strategy_id":{"type":"string"},"name":{"type":"string"},"wallet":{"type":"string","description":"Server-side truncated owner wallet, first-6 + ellipsis + last-4\n(\"0x1234…abcd\"). This response never carries the full address.\n"},"first_deposit_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time","description":"Close time for `closed` rows. Null for `live`.\n"},"measured_at":{"type":"string","format":"date-time","description":"The valuation instant the APY was measured at — the latest yield\nsnapshot for `live` rows, the close time for `closed` rows.\nDerive age/duration displays from `first_deposit_at` → this\ninstant, so a paused strategy (whose snapshots stop) never shows\na growing age against a frozen figure.\n"},"status":{"type":"string","enum":["live","closed"],"description":"live = running (active/paused); closed = fully closed."},"value_usdc":{"type":"number","description":"Strategy value in USD at `measured_at` (latest snapshot for\n`live` rows, settled final value for `closed`).\n"},"realized_apy_pct":{"type":["number","null"],"description":"NET annualized realized APY percentage over TOTAL contributed\ncapital (initial deposit + top-ups, mid-life withdrawals credited\nback); position values are post-fee, so this is net of Earn fees.\nUnbounded; may be negative. Null when the row has under 30\nminutes measured from the time its deposit flow fully landed\n(bridges included), or while a deposit bridge is still in\nflight — the strategy still appears, without a figure.\n"},"gross_apy_pct":{"type":["number","null"],"description":"The same annualization with the Earn fees added back to the\nprofit (pre-fee yield). Null under 30 measured minutes.\n"},"fees_usdc":{"type":"number","description":"Earn fees charged over the measured span, in USD."},"rebalance_count":{"type":"integer","description":"Rebalances executed (same-chain + cross-chain) over the measured span."},"covered":{"type":"boolean","description":"OpenCover covered strategy."},"clone_count":{"type":"integer","description":"How many strategy rows currently record this one as their clone\nsource (`cloned_from`). Lineage is only stamped on a\nsettings-identical clone, so this counts \"cloned and left\nunchanged\". Recomputed per board build: a clone reaped before\ndepositing stops counting.\n"},"cloned_from":{"type":["string","null"],"format":"uuid","description":"The original strategy this row is a settings-identical clone of;\nnull for originals. Always points at a root (a clone of a clone\nis attributed to the original at create), and a settings edit on\neither side clears it. The id is as public as the row's own\n`strategy_id`.\n"},"config":{"$ref":"#/components/schemas/LeaderboardRowConfig"}}},"LeaderboardResponse":{"type":"object","required":["updated_at","rows"],"properties":{"updated_at":{"type":"string","format":"date-time","description":"When this board was computed (cached ~5 minutes)."},"rows":{"type":"array","maxItems":500,"description":"Sorted by `realized_apy_pct` descending.","items":{"$ref":"#/components/schemas/LeaderboardRow"}}}},"HistoryRow":{"type":"object","required":["id","strategy_id","wallet_address","event_type","kind","from_vault","from_vault_name","from_apy","to_vault","to_vault_name","to_apy","usdc_amount","fee_usdc","gas_cost_usdc","tx_hash","burn_tx_hash","timestamp","chain_id","log_index","forced","force_reason"],"description":"One strategy event. Every field is always present (nullable where typed so).","properties":{"id":{"type":"string"},"strategy_id":{"type":"string"},"wallet_address":{"type":["string","null"]},"event_type":{"type":"string","description":"Legacy label: deposit / rebalance / strategy_closed / withdrawal_bridged."},"kind":{"type":"string","enum":["deposit","crosschain_deposit","rebalance","crosschain_rebalance","withdrawal","crosschain_withdrawal"]},"from_vault":{"type":["string","null"]},"from_vault_name":{"type":["string","null"]},"from_apy":{"type":["number","null"],"description":"APY percentage."},"to_vault":{"type":["string","null"]},"to_vault_name":{"type":["string","null"]},"to_apy":{"type":["number","null"],"description":"APY percentage."},"usdc_amount":{"type":["number","null"],"description":"Decimal dollars."},"fee_usdc":{"type":["number","null"],"description":"Decimal dollars."},"gas_cost_usdc":{"type":["number","null"],"description":"Decimal dollars."},"tx_hash":{"type":["string","null"]},"burn_tx_hash":{"type":["string","null"]},"timestamp":{"type":"string","format":"date-time"},"chain_id":{"type":"integer"},"log_index":{"type":["integer","null"]},"forced":{"type":"boolean","description":"True when a rebalance exited a higher-APY vault (liquidity-, TVL-, or hide-driven). See force_reason for which floor, when recorded."},"force_reason":{"type":["string","null"],"enum":["liquidity_exit","tvl_exit",null],"description":"Why the planner force-exited the source vault: minimum-liquidity floor or minimum-TVL floor. Null on non-forced rows, hidden-vault exits, and rows recorded before the reason was tracked."}}},"HistoryResponse":{"type":"object","required":["events"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/HistoryRow"}}}},"HistoryPhaseVault":{"type":"object","required":["name","apy"],"description":"One vault touched by a phase.","properties":{"name":{"type":"string","description":"Vault display name (falls back to a truncated `0x1234…abcd` address when unknown)."},"apy":{"type":["number","null"],"description":"APY percentage at event time, when known."},"amountUsdc":{"type":"number","description":"Per-vault USDC amount in decimal dollars; present when known (always present on multi-vault lists)."}}},"HistoryPhase":{"type":"object","required":["timestamp","chainId","txHash"],"description":"One per-chain step of an aggregated entry.","properties":{"subHeader":{"type":"string","description":"Step label, e.g. `Deposit on Base`, `Withdraw + Bridge Out`, `Bridge In on Arbitrum`. Omitted on single-phase entries."},"timestamp":{"type":["string","null"],"format":"date-time","description":"When the phase confirmed; null while pending (or unknown)."},"chainId":{"type":["integer","null"],"description":"Chain the phase executed on; null only for legacy orphan rows."},"fromVault":{"description":"Source vault, or one entry per vault for multi-vault withdrawals. Omitted when the phase has no source-vault side.","oneOf":[{"$ref":"#/components/schemas/HistoryPhaseVault"},{"type":"array","items":{"$ref":"#/components/schemas/HistoryPhaseVault"}}]},"toVault":{"description":"Destination vault, or one entry per vault for multi-vault deposits/relays. Omitted when the phase has no dest-vault side.","oneOf":[{"$ref":"#/components/schemas/HistoryPhaseVault"},{"type":"array","items":{"$ref":"#/components/schemas/HistoryPhaseVault"}}]},"txHash":{"type":["string","null"],"description":"The phase transaction hash; null while pending."},"pendingLabel":{"type":"string","description":"When present, a null `txHash` means the phase is still pending (`Pending relay…`) rather than unknown."}}},"HistoryEntry":{"type":"object","required":["id","category","timestamp","amountUsdc","feeUsdc","phases"],"description":"One aggregated entry (one user/executor action, possibly spanning\nchains). `id` is stable across refetches and safe to use as a key.\n","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["strategy_enter","strategy_exit","rebalance","crosschain_rebalance","force_rebalance","force_crosschain_rebalance"]},"timestamp":{"type":"string","format":"date-time","description":"Top-level sort key (source-side timestamp for cross-chain flows)."},"amountUsdc":{"type":"number","description":"Total entry amount in decimal dollars."},"feeUsdc":{"type":["number","null"],"description":"Total entry fee in decimal dollars; null when zero or unknown."},"phases":{"type":"array","items":{"$ref":"#/components/schemas/HistoryPhase"},"description":"Length 1 for same-chain entries, 2+ for cross-chain / multi-chain flows."},"pending":{"type":"boolean","description":"True when any phase has not confirmed yet. Absent means false."},"force_reason":{"type":["string","null"],"enum":["liquidity_exit","tvl_exit",null],"description":"Only on force_* categories: why the planner force-exited (minimum-liquidity or minimum-TVL floor). Null/absent keeps the generic force tag (hidden-vault exits and rows recorded before the reason was tracked)."}}},"HistoryEntriesResponse":{"type":"object","required":["entries","truncated"],"properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/HistoryEntry"},"description":"Aggregated entries, newest-first."},"truncated":{"type":"boolean","description":"True when internal read caps were hit; older activity may be missing."}}},"CctpTransfer":{"type":"object","required":["id","strategy_id","source_chain_id","dest_chain_id","amount_usdc","burn_tx_hash","relay_tx_hash","status","user","hooks_completed","source_vault","dest_vault","dest_vaults","transfer_type","fee_usdc","created_at","updated_at"],"description":"A cross-chain (CCTP) transfer.","properties":{"id":{"type":"string"},"strategy_id":{"type":["string","null"]},"source_chain_id":{"type":"integer"},"dest_chain_id":{"type":"integer"},"amount_usdc":{"type":"string","description":"USDC amount as a 6dp base-unit string."},"burn_tx_hash":{"type":"string"},"relay_tx_hash":{"type":["string","null"]},"status":{"type":"string","enum":["burn_submitted","attestation_pending","relay_submitted","confirmed","user_claimed"]},"user":{"type":["string","null"],"description":"bytes32-padded lowercase beneficiary."},"hooks_completed":{"type":"boolean"},"source_vault":{"type":["string","null"]},"dest_vault":{"type":["string","null"]},"dest_vaults":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}},"transfer_type":{"type":["string","null"],"enum":["deposit","withdrawal",null],"description":"Null on legacy rows."},"fee_usdc":{"type":["number","null"],"description":"Decimal number (NOT a base-unit string)."},"batch_id":{"type":["string","null"],"description":"Groups the sibling CCTP legs of one deposit/close action. Null on\nlegacy rows and on automated rebalance/exit legs; group those by a\n`created_at` time window instead.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"BridgesResponse":{"type":"object","required":["transfers"],"properties":{"transfers":{"type":"array","items":{"$ref":"#/components/schemas/CctpTransfer"}}}},"IntentStatusResponse":{"type":"object","required":["id","strategy_id","chain_id","kind","created_at","expires_at","status","fulfilled_at","fulfilled_tx_hash","submitted_tx_hash","failure_reason"],"description":"Intent status with a derived lifecycle `status`. Raw calldata is never\nincluded.\n","properties":{"id":{"type":"string","format":"uuid"},"strategy_id":{"type":"string","format":"uuid"},"chain_id":{"type":"integer"},"kind":{"type":"string","description":"One of `deposit`, `close`, `emergency_claim` (from the calldata endpoints), or system-written `rebalance`, `withdraw_bridge`, `relay_deposit`."},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time","description":"The intent attribution TTL (30 days for user kinds), NOT the calldata plan-freshness bound."},"status":{"type":"string","enum":["pending","fulfilled","failed","expired"],"description":"Derived, not a stored column. `failed`: a failure reason was\nrecorded (including pre-transaction failures; failed wins over\nfulfilled). `fulfilled`: `fulfilled_at` set and not failed.\n`expired`: unfulfilled and past `expires_at`. `pending`:\neverything else.\n"},"fulfilled_at":{"type":["string","null"],"format":"date-time","description":"When the intent was closed. Also set on some FAILED intents, so branch on `status`, never this field alone."},"fulfilled_tx_hash":{"type":["string","null"],"description":"The fulfilling transaction hash; always null on a failed intent."},"submitted_tx_hash":{"type":["string","null"],"description":"Broadcast-time hash set on rebalance intents before fulfillment, when known."},"failure_reason":{"type":["string","null"],"description":"Human-readable failure detail when `status` is `failed`. Sanitized: URLs are redacted and the text is capped at 200 characters."}}},"ChainBalance":{"type":"object","required":["chainId","nativeSymbol","nativeBalanceWei","nativeUsdPrice","usdc","usdcBalance","error"],"properties":{"chainId":{"type":"integer"},"nativeSymbol":{"type":"string","description":"e.g. ETH, POL, MON."},"nativeBalanceWei":{"type":["string","null"],"description":"Native gas balance in wei (base-unit string); null on RPC error."},"nativeUsdPrice":{"type":["number","null"],"description":"USD price of the chain's native gas token (decimal dollars); null when unavailable."},"usdc":{"type":["string","null"],"description":"USDC token address on the chain, or null if none configured."},"usdcBalance":{"type":["string","null"],"description":"USDC balance as a 6dp base-unit string; null on RPC error."},"error":{"type":["string","null"],"enum":["rpc_error",null],"description":"Set to `rpc_error` when that chain's read failed (isolated; other chains still populate)."}}},"BalancesResponse":{"type":"object","required":["address","chains"],"properties":{"address":{"type":"string","description":"Lowercased wallet address."},"chains":{"type":"array","items":{"$ref":"#/components/schemas/ChainBalance"}}}},"ApproveTx":{"type":"object","required":["to","data","value"],"description":"A ready-to-sign `approve(spender, amount)` transaction.","properties":{"to":{"type":"string","description":"The ERC-20 (share token or USDC) to approve."},"data":{"$ref":"#/components/schemas/HexData"},"value":{"type":"string","enum":["0"]}}},"ApprovalEntry":{"type":"object","required":["token","currentAllowance","requiredAllowance","approved"],"properties":{"token":{"type":"string","description":"The ERC-20 that must be approved (share token or USDC)."},"vaultAddress":{"type":"string","description":"Vault this share token belongs to (absent on the USDC entry)."},"currentAllowance":{"$ref":"#/components/schemas/BaseUnitString"},"requiredAllowance":{"type":"string","description":"Required allowance (maxUint256 for share tokens; the deposit amount for USDC). Base-unit string."},"approved":{"type":"boolean","description":"Share token approved ⇔ allowance > 0; USDC approved ⇔ allowance >= required."},"tx":{"description":"Present only when `approved` is false.","allOf":[{"$ref":"#/components/schemas/ApproveTx"}]}}},"ChainApprovals":{"type":"object","required":["chainId","spender","usdc","vaults","error"],"properties":{"chainId":{"type":"integer"},"spender":{"type":["string","null"],"description":"The Earn contract (the approval spender); null on RPC error."},"usdc":{"oneOf":[{"$ref":"#/components/schemas/ApprovalEntry"},{"type":"null"}],"description":"The chain's USDC allowance entry; null on RPC error."},"vaults":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalEntry"}},"error":{"type":["string","null"],"enum":["rpc_error",null],"description":"Set to `rpc_error` when that chain's read failed (isolated; other chains still populate)."}}},"ApprovalsResponse":{"type":"object","required":["address","chains"],"properties":{"address":{"type":"string","description":"Lowercased wallet address."},"chains":{"type":"array","items":{"$ref":"#/components/schemas/ChainApprovals"}}}},"Burn":{"type":"object","required":["destDomain","mintRecipient","destinationCaller","amount","maxFee","minFinalityThreshold"],"description":"A CCTP burn leg in the deposit/withdraw plan.","additionalProperties":true,"properties":{"destDomain":{"type":"integer","description":"CCTP destination domain."},"mintRecipient":{"type":"string","description":"bytes32-padded recipient."},"destinationCaller":{"type":"string","description":"bytes32-padded caller."},"amount":{"type":"string","description":"Base-unit string. For remote withdraw legs the final entry carries the max-uint sentinel (78-digit string) — never round-trip through a JS number."},"maxFee":{"type":"string"},"minFinalityThreshold":{"type":"integer"}}},"Transaction":{"type":"object","description":"A ready-to-sign transaction. Submit `data` VERBATIM.","required":["chainId","to","data","value","intentId","kind","description","gasHint"],"properties":{"chainId":{"type":"integer"},"to":{"type":"string","description":"The Quicknode Earn proxy on `chainId`."},"data":{"$ref":"#/components/schemas/HexData"},"value":{"type":"string","enum":["0"],"description":"Always \"0\" (no native value moved)."},"intentId":{"type":"string","description":"Intent id for tracking this transaction; poll `GET /v1/strategies/{id}/intents/{intentId}`."},"kind":{"type":"string","enum":["deposit","close","emergency_claim"]},"description":{"type":"string"},"gasHint":{"type":["string","null"],"description":"Recommended gas limit (server estimate * 1.10) as a decimal string. null when approvals are still needed (the estimate would revert) — see gasHintReason. Prefer it over re-estimating (which can under-provision and OOG on Base)."},"gasHintReason":{"type":"string","enum":["approvals_required","estimation_failed"],"description":"Present only when `gasHint` is null. `approvals_required`: allowances not yet granted, so the estimate would revert. `estimation_failed`: withdraw/claim gas estimation failed (deposit returns 400 instead); on a claim, may also mean the nonce was already consumed."}}},"ApprovalNeeded":{"type":"object","required":["chainId","token","spender","currentAllowance","requiredAllowance","tx"],"description":"An allowance that must be granted before the transaction will succeed.","properties":{"chainId":{"type":"integer"},"token":{"type":"string"},"spender":{"type":"string","description":"The Quicknode Earn proxy."},"currentAllowance":{"$ref":"#/components/schemas/BaseUnitString"},"requiredAllowance":{"type":"string","description":"Base-unit string (maxUint256 for share tokens)."},"tx":{"$ref":"#/components/schemas/ApproveTx"}}},"DepositPlan":{"type":"object","required":["vaults","amounts","burns","burnVaultBreakdown","totalUsdc","apys","vaultNames","chainIds"],"description":"The deposit allocation plan.","additionalProperties":true,"properties":{"totalUsdc":{"type":"string","description":"Total USDC allocated, 6dp base-unit string."},"vaults":{"type":"array","items":{"type":"string"},"description":"Local-chain vault addresses."},"amounts":{"type":"array","items":{"type":"string"},"description":"Per-vault amounts, base-unit strings, index-aligned with `vaults`."},"burns":{"type":"array","items":{"$ref":"#/components/schemas/Burn"},"description":"Per-remote-chain combined burns."},"burnVaultBreakdown":{"type":"array","description":"Index-aligned with `burns`; each element is the list of destination vaults that burn funds.","items":{"type":"array","items":{"type":"object","required":["vault","amount","name","apy"],"properties":{"vault":{"type":"string"},"amount":{"type":"string","description":"Base-unit string."},"name":{"type":"string"},"apy":{"type":"number","description":"APY percentage (3dp)."}}}}},"apys":{"type":"object","additionalProperties":{"type":"number"},"description":"Map of `chainId:0xaddress` -> APY percentage (3dp) for every selected vault."},"vaultNames":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of `chainId:0xaddress` -> vault name."},"chainIds":{"type":"object","additionalProperties":{"type":"integer"},"description":"Map of `chainId:0xaddress` -> chainId for every selected vault."}}},"CalldataResponseBase":{"type":"object","description":"Shared shape of the three calldata responses. `kind` discriminates\n(`deposit` / `withdraw` / `claim`), so the three responses form one\nfamily a client can type as a discriminated union.\n","required":["kind","transactions","expiresAt"],"properties":{"kind":{"type":"string","enum":["deposit","withdraw","claim"]},"transactions":{"type":"array","items":{"$ref":"#/components/schemas/Transaction"}},"approvalsNeeded":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalNeeded"}},"expiresAt":{"type":"string","format":"date-time","description":"~1h ADVISORY plan-freshness bound. NOT the intent attribution\nwindow (the intent carries a 30-day TTL). Regenerate after this\npasses; regeneration within the intent TTL returns the same\n`intentId`.\n"}}},"DepositCalldataResponse":{"allOf":[{"$ref":"#/components/schemas/CalldataResponseBase"},{"type":"object","required":["approvalsNeeded","plan"],"properties":{"kind":{"type":"string","enum":["deposit"]},"transactions":{"type":"array","description":"Exactly one transaction, on the strategy's chain.","items":{"$ref":"#/components/schemas/Transaction"}},"plan":{"$ref":"#/components/schemas/DepositPlan"}}}]},"WithdrawChainPlan":{"type":"object","required":["vaults","shares","feeAmounts","totalFeeUsdc","burns"],"additionalProperties":true,"properties":{"vaults":{"type":"array","items":{"type":"string"}},"shares":{"type":"array","items":{"type":"string"},"description":"Raw share amounts, base-unit strings."},"feeAmounts":{"type":"array","items":{"type":"string"},"description":"All \"0\" for user withdraws."},"totalFeeUsdc":{"type":"number"},"burns":{"type":"array","items":{"$ref":"#/components/schemas/Burn"}}}},"WithdrawPlan":{"type":"object","required":["chains"],"properties":{"chains":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/WithdrawChainPlan"},"description":"Keyed by chain id (string). A chain whose only positions are zero-share may still appear."}}},"WithdrawCalldataResponse":{"allOf":[{"$ref":"#/components/schemas/CalldataResponseBase"},{"type":"object","required":["plan"],"properties":{"kind":{"type":"string","enum":["withdraw"]},"transactions":{"type":"array","description":"One per chain, source-chain-first. Empty when there is nothing to withdraw.","items":{"$ref":"#/components/schemas/Transaction"}},"approvalsNeeded":{"type":"array","description":"Present on a non-empty plan; omitted on the empty (delete-fallback) plan.","items":{"$ref":"#/components/schemas/ApprovalNeeded"}},"plan":{"$ref":"#/components/schemas/WithdrawPlan"},"fallback":{"type":"string","enum":["delete"],"description":"Present with value `delete` when `transactions` is empty — call `DELETE /v1/strategies/{id}` instead."}}}]},"ClaimCalldataRequest":{"type":"object","required":["transferId"],"description":"Emergency claim request body. Public (no SIWE). Unlike\ndeposit/withdraw an empty body is NOT valid: the route cannot pick a\nstalled transfer for the caller.\n","properties":{"transferId":{"type":"string","format":"uuid","description":"The stalled transfer's `cctp_transfers` row id (find it via `GET /v1/strategies/{id}/bridges`)."}}},"ClaimCalldataResponse":{"allOf":[{"$ref":"#/components/schemas/CalldataResponseBase"},{"type":"object","required":["approvalsNeeded"],"properties":{"kind":{"type":"string","enum":["claim"]},"transactions":{"type":"array","description":"Exactly one `emergencyClaimBridge` transaction, on the transfer's DESTINATION chain (transaction kind `emergency_claim`).","items":{"$ref":"#/components/schemas/Transaction"}},"approvalsNeeded":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalNeeded"},"description":"Always empty (`emergencyClaimBridge` pulls no tokens from the sender)."},"expiresAt":{"type":"string","format":"date-time","description":"Advisory only — a finalized attestation stays valid until the nonce is consumed on the destination chain."}}}]},"CreateStrategyRequest":{"allOf":[{"$ref":"#/components/schemas/SignedRequest"},{"type":"object","required":["name","capital_usdc","max_positions"],"description":"All fields except `siwe` are part of the signed payload. Name regex\n`^[a-zA-Z0-9 _\\-.]{1,50}$`. `chain_ids` is an ARRAY of chain ids on\ninput (stored and returned as a CSV string); a missing/invalid chain\nsilently falls back to the first supported chain.\n","properties":{"name":{"type":"string","maxLength":50},"capital_usdc":{"type":"number","description":"Capital in decimal dollars."},"delta_pct":{"type":"number","description":"Same-network APY-gap threshold (percentage points). Optional: when omitted it defaults to the recommended bar (3, or 5 when the strategy's networks include Ethereum, chain id 1). A present value must be a number >= 0.25 or the request is rejected with a 400; anything above that is accepted, and the recommended ranges are UI-side warnings only (since v1.7.0)."},"cross_chain_delta_pct":{"type":"number","description":"APY-gap threshold (percentage points) required when the source and destination vaults are on different chains. When omitted, defaults to 0 (inherit delta_pct), so integrations that predate this field keep single-threshold behaviour: one bar gates every move, and later delta_pct updates move both. A split bar is always an explicit opt-in. A present value must be 0 (inherit) or a number >= 0.25; a value strictly between 0 and 0.25 is rejected with a 400."},"delta_confirmations":{"type":"integer","description":"Consecutive 5-minute checks the APY gap must hold before a move. Defaults to 12 (60 minutes) when omitted (was 10 before v1.11.0)."},"max_positions":{"type":"integer","minimum":1},"min_tvl_usd":{"type":"number"},"min_liquidity_usd":{"type":"number"},"min_tvl_exit_usd":{"type":"number","minimum":0,"description":"Held-vault TVL exit floor in USD (v1.10.0, preferred); a held vault is force-exited only once its live TVL drops below it. 0 disables the TVL exit. Must not exceed min_tvl_usd (400) and cannot be combined with min_tvl_exit_ratio (400). Defaults to 0.75 × min_tvl_usd when both fields are omitted."},"min_liquidity_entry_usd":{"type":"number","minimum":0,"description":"Liquidity a vault needs before the strategy enters it, in USD (v1.10.0, preferred). Values below min_liquidity_usd normalize up to it, and a min_liquidity_usd of 0 disables the liquidity filter entirely, so the stored entry level is 0 regardless of what is sent. Cannot be combined with min_liquidity_entry_multiplier (400). Defaults to 2 × min_liquidity_usd when both fields are omitted."},"min_liquidity_entry_multiplier":{"type":"number","deprecated":true,"description":"Deprecated alias of min_liquidity_entry_usd: multiplier on min_liquidity_usd for the non-held entry threshold. Defaults to 2 when omitted. The stored multiplier is re-derived from the dollar level."},"min_tvl_exit_ratio":{"type":"number","minimum":0,"maximum":1,"deprecated":true,"description":"Deprecated alias of min_tvl_exit_usd: ratio on min_tvl_usd for the held-vault TVL exit floor. 0 disables the TVL exit. Must be between 0 and 1; values outside the range are rejected. Defaults to 0.75 when omitted. The stored ratio is re-derived from the dollar level."},"chain_id":{"type":"integer","description":"Optional primary chain id; defaults to the first supported chain."},"chain_ids":{"type":"array","items":{"type":"integer"},"description":"Chain ids to span. Omit for a single-chain strategy on `chain_id`."},"apy_smoothing_minutes":{"type":"integer","description":"Defaults to 360 when omitted (was 30 before v1.11.0). Snaps to the nearest precomputed window (5/10/30/60/120/240/360/720/1440)."},"covered":{"type":"boolean","description":"Opt the strategy into OpenCover coverage. Set once at create and\nimmutable thereafter. When true the strategy's invest set is\npinned to Base (`chain_ids` → \"8453\"), where the covered fee\nwrappers live; the funding chain (`chain_id`) is unrestricted,\nand a non-Base funding chain bridges the deposit to Base via\nCCTP. The wallet MUST have acknowledged OpenCover's terms, else\nthe create is a 403 (`covered_terms_required`). Defaults to\nfalse (uncovered).\n"},"hidden_vault_keys":{"type":"array","items":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$"}},"cloned_from":{"type":"string","format":"uuid","description":"Id of the strategy this one was cloned from (part of the\nsigned payload). Optional. A malformed id is rejected with a\n400; an id that no longer references an existing strategy is\nignored (the create succeeds with no lineage — the source\ncan be garbage-collected between fetch and signing). Lineage\nis stored only when the\ncreated strategy's settings canonically match the source's —\ncapital, name, and funding chain are the cloner's own and\nnever count as edits, and a network the new capital cannot\nclear is dropped from the comparison (a smaller clone\nlegitimately spans fewer networks); any other difference\nstores null and the create still succeeds. Lineage is also\nwithheld when either side carries a per-strategy\n`hidden_vault_keys` list (a curated vault set is part of the\nconfiguration but is never carried by the clone flow). A\nclone of a clone is attributed to the ORIGINAL (the stored\nid is always a root). Feeds the leaderboard's `clone_count`\nand `cloned_from`. A later PATCH that changes any compared\nsetting or the hide list clears the stored lineage (see\n`updateStrategy`).\n"},"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Optional dedupe key (part of the signed payload). A repeat\nreturns the ORIGINAL stored response verbatim, so never reuse a\nkey across logically different requests. Malformed values are 400\n`invalid_request`.\n"}}}]},"UpdateStrategyRequest":{"allOf":[{"$ref":"#/components/schemas/SignedRequest"},{"type":"object","description":"Any subset of config fields. Only present fields are written;\nexplicit null writes null. `status` drives pause/resume.\n`hide_vault_key` / `unhide_vault_key`: exactly one per request,\nnever both, never combined with `hidden_vault_keys` (400\n`invalid_request` otherwise).\n\nClone lineage side effect (v1.8.0): when a PATCH changes any\ncompared setting (the threshold bars, confirmations, vault\ncount, TVL/liquidity floors, entry multiplier, TVL exit ratio,\nsmoothing) or changes the strategy's hide list, the strategy's\n`cloned_from` is cleared and so is the `cloned_from` of any\nstrategies cloned from it: the \"identical settings\" claim broke\non both sides.\nName, capital, and status changes never sever. Every severing\nchange is also logged internally.\n","properties":{"name":{"type":"string","maxLength":50},"capital_usdc":{"type":"number","description":"Decimal dollars. Written verbatim when present; no balance check on update (unlike create)."},"delta_pct":{"type":"number","description":"Same-network APY-gap threshold (percentage points). Validated only when present in the payload: must be a number >= 0.25. Stored values are never re-validated, so strategies created under the old chain-tier floors keep running unchanged."},"cross_chain_delta_pct":{"type":"number","description":"Cross-chain APY-gap threshold. 0 means inherit delta_pct: send 0 (not null; the column is non-nullable) to reset an explicit bar back to inherit. Validated only when present in the payload: must be 0 or a number >= 0.25."},"delta_confirmations":{"type":"integer"},"max_positions":{"type":"integer"},"min_tvl_usd":{"type":"number","description":"Minimum vault size (TVL) in USD. Since v1.10.0 changing it alone no longer moves the dollar exit floor: min_tvl_exit_usd is kept and min_tvl_exit_ratio is re-derived (a stored exit floor stranded above a lowered minimum clamps down to it)."},"min_liquidity_usd":{"type":"number","description":"Liquidity auto-exit floor in USD. Since v1.10.0 changing it alone no longer moves the dollar entry level: min_liquidity_entry_usd is kept (normalized up to the new floor) and the multiplier alias is re-derived."},"min_tvl_exit_usd":{"type":"number","minimum":0,"description":"Held-vault TVL exit floor in USD (v1.10.0, preferred). 0 disables the TVL exit. Must not exceed the effective post-patch min_tvl_usd (400) and cannot be combined with min_tvl_exit_ratio (400)."},"min_liquidity_entry_usd":{"type":"number","minimum":0,"description":"Liquidity entry level in USD (v1.10.0, preferred). Values below the effective post-patch min_liquidity_usd normalize up to it, and an effective floor of 0 disables the liquidity filter entirely, so the stored entry level is 0 regardless of what is sent. Cannot be combined with min_liquidity_entry_multiplier (400)."},"min_liquidity_entry_multiplier":{"type":"number","deprecated":true,"description":"Deprecated alias of min_liquidity_entry_usd. When sent, the stored dollar level becomes multiplier × the effective min_liquidity_usd."},"min_tvl_exit_ratio":{"type":"number","minimum":0,"maximum":1,"deprecated":true,"description":"Deprecated alias of min_tvl_exit_usd. When sent, the stored dollar level becomes ratio × the effective min_tvl_usd. Must be between 0 and 1; values outside the range are rejected."},"apy_smoothing_minutes":{"type":"integer"},"hidden_vault_keys":{"type":"array","items":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$"},"description":"Full-array REPLACE of the strategy hide list. Cannot be combined with the single-op toggles below."},"hide_vault_key":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$","description":"Single-op: add this vault key to the strategy's hide list (lowercased, deduped server-side)."},"unhide_vault_key":{"type":"string","pattern":"^\\d+:0x[0-9a-fA-F]{40}$","description":"Single-op: remove this vault key from the strategy's hide list."},"status":{"type":"string","enum":["active","paused"]}}}]},"DeleteStrategyRequest":{"allOf":[{"$ref":"#/components/schemas/SignedRequest"},{"type":"object","description":"A pre-deposit delete is usually just the `siwe` block (empty signed\npayload `{}`). A post-deposit close MAY carry final-value fields (all\npart of the signed payload): a legacy envelope (`txHash`,\n`finalValueUsdc`) or a per-chain map that must cover every\nactive-position chain.\n","properties":{"txHash":{"type":"string","description":"Close tx hash (legacy envelope)."},"finalValueUsdc":{"type":"number","description":"On-chain final value in decimal dollars (legacy envelope), covering the whole close. Withdrawal amounts already recorded from on-chain close events always take precedence: only the excess of this value above the recorded event total is counted (floored at zero) and stands in for the legs those events do not cover, so the recorded events win over a stale or lower total."},"perChain":{"type":"object","additionalProperties":{"type":"object","properties":{"txHash":{"type":"string"},"finalValueUsdc":{"type":"number"},"feeUsdc":{"type":"number"}}},"description":"Per-chain close data keyed by chain id (string)."}}}]},"DeleteStrategyResponse":{"type":"object","required":["success","action"],"properties":{"success":{"type":"boolean","description":"Always true on a 2xx."},"action":{"type":"string","enum":["deleted","closed","closing"],"description":"`deleted` for a pre-deposit hard delete; `closed` for a post-deposit DB close; `closing` when withdrawal bridges are still in flight — the event-processor finalizes the strategy when the last bridge confirms."}}}},"securitySchemes":{"apikey":{"type":"apiKey","in":"header","name":"apikey","description":"API key required on every request. Public by design; it is not an\nauthorization credential (writes require SIWE signatures).\n\n- Production: `sb_publishable_3xcdKa_uMRhK71Izd6BLdg_2vskXZ_h`\n"}}}}