Download OpenAPI specification:
HTTP API to drive Earn strategies end to end.
The Earn public API (/v1) drives the full strategy lifecycle over HTTP:
browse vaults, create a strategy, fetch ready-to-sign approval and
deposit/withdraw calldata, broadcast it, and poll to completion.
GET /v1/config — chains, minimum sizes, contract address.GET /v1/wallets/{addr}/balances — where the USDC is.GET /v1/vaults / GET /v1/vaults/rankings — pick vaults or preview an allocation.POST /v1/wallets/{addr}/tos then GET /v1/wallets/{addr}/approvals — one-time consent, then sign any missing approvals.POST /v1/strategies — create.POST /v1/strategies/{id}/calldata/deposit — broadcast the returned transaction.GET /v1/strategies/{id} (+ /performance, /history, /bridges, /intents/{intentId}) — monitor.POST /v1/strategies/{id}/calldata/withdraw or DELETE /v1/strategies/{id} — close.SignedRequest component documents the exact message format.apikey header is required on every request. The key is public
by design; see components.securitySchemes./v1/vaults, /v1/strategies, detail, history,
bridges) return decimal-dollar JSON numbers for USDC values:
total_value_usdc: 1234.56 means $1,234.56.apy: 5.423
means 5.423% APY. Realized APY is unbounded and may be negative.transactions[],
approvalsNeeded[], balances, approvals) carry base-unit strings:
USDC is a 6-decimal integer string ("1000000" = 1 USDC), native gas
is wei, shares and allowances are raw uint256 decimal strings. Strings
so values above 2^53 never lose precision.v1 is additive-only: new optional fields and endpoints may appear;
existing fields will not change type or disappear without a version bump.
One launch-week exception (2026-07-20): vault rankings moved to
GET /v1/vaults/rankings, wallet approvals became multi-chain, and live
vault liquidity folded into the vault detail read.
SIWE hardening (2026-07-21, v1.2.0): signed messages carry a - path:
Resources entry binding the proof to the request path, and nonces are
single-use. A proof without a matching - path: entry is rejected with
siwe_payload, and nonce reuse is rejected with siwe_replay.
SignedRequest has the rules.
APY-gap floors (2026-07-28, v1.3.0): both threshold fields now carry
hard minimums — same-network >= 3 and effective cross-network >= 5,
rising to 5/7 when the strategy's networks include Ethereum. Values
below a floor are rejected with a 400. An omitted delta_pct on create
now defaults to the same-network floor (previously a legacy 0.1);
strategies created before the floors keep running unchanged until an
update touches a threshold field.
The API name, version, a link to the OpenAPI document, and a one-line list of every operation.
{- "name": "QuickNode Earn Public API",
- "description": "The Earn public API (`/v1`) drives the full strategy lifecycle over HTTP ...",
- "version": "1.1.0",
- "endpoints": [
- {
- "method": "GET",
- "path": "/v1/vaults",
- "summary": "Vault list (browse)"
}, - {
- "method": "POST",
- "path": "/v1/strategies",
- "summary": "Create a strategy (SIWE `strategy.create`)"
}
]
}Total USDC under management, active strategy count, and total
rebalances executed. total_usdc is a best-effort live figure and may
fall back to the last-known aggregate.
{- "total_usdc": 2148302.55,
- "active_strategies": 214,
- "total_rebalances": 8831,
- "updated_at": "2026-07-13T18:00:00.000Z"
}Latest USD price for each supported native gas token (ETH, POL, MON),
updated hourly. chains maps each supported chain id to the USD price
of that chain's native gas token.
{- "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
}
}The Earn contract address (the same on every chain) and per-chain
display names and minimum strategy sizes. A chain missing from
chains is not currently served. minStrategyUsdc is sizing
guidance only, not enforced by POST /v1/strategies. banner is the
first-party announcement strip the Earn app renders above its nav, and
is null whenever none is active. Responses carry
Cache-Control: public, max-age=300.
{- "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",
}
}Every approved vault with the seven windowed APYs, TVL, liquidity and
capacity from the latest snapshot. For a capital-aware allocation
preview, use GET /v1/vaults/rankings.
| chains | string Examples: chains=8453,42161 CSV of chain ids. Defaults to all enabled chains. |
| window | integer [ 1 .. 360 ] Default: 5 Minutes for the headline |
| minTvl | number >= 0 Default: 0 Minimum vault TVL in decimal dollars. |
| minLiquidity | number >= 0 Default: 0 Minimum withdrawable liquidity in decimal dollars. |
| includeCovered | boolean Default: false Opt in to OpenCover covered fee-wrapper rows ( |
{- "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
}, - "tvlUsd": 12500000,
- "availableLiquidityUsd": 3400000,
- "maxDepositUsd": null,
- "atCapacity": false,
- "latestAt": "2026-07-07T18:55:00.000Z"
}
]
}The vaults a strategy with these settings would target, ranked by the
same pipeline that plans deposits, enriched with liquidity/capacity
from the latest snapshot. capital and maxPositions drive the
ranking; the remaining params filter.
| capital required | number > 0 Examples: capital=1000 Capital to allocate, in USDC decimal dollars (> 0). |
| maxPositions required | integer >= 1 Examples: maxPositions=5 Maximum number of vault positions (positive integer). |
| chains | string Examples: chains=8453,42161 CSV of chain ids. Defaults to all enabled chains. |
| minTvl | number >= 0 Default: 0 Minimum vault TVL in decimal dollars. |
| minLiquidity | number >= 0 Default: 0 Minimum withdrawable liquidity in decimal dollars. |
| minLiquidityMultiplier | number >= 0 Default: 2 Entry liquidity threshold multiplier: a vault must have this multiple of the intended position size in withdrawable liquidity to qualify. The deposit calldata path uses a looser default of 1. |
| apySmoothingMinutes | integer Default: 30 APY smoothing window in minutes; snaps to the nearest precomputed column (5/10/30/60/120/240/360). |
| hiddenVaultKeys | string Examples: hiddenVaultKeys=8453:0xabc...,42161:0xdef... CSV of |
| wallet | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: wallet=0x1234567890abcdef1234567890abcdef12345678 If present, merges this wallet's saved global hide list into the exclusions. |
| covered | string Enum: "true" "false" RANKED mode only. Coverage partition to rank within: |
{- "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
}The latest snapshot for one approved vault: the seven windowed APYs, a
headline apy at the requested window, TVL, liquidity and capacity.
Same row shape as one element of the browse list. An unknown or
delisted vault is a 404.
include=liveLiquidity adds a fresh on-chain read of the vault's
withdrawable liquidity (liveLiquidity). This is a live number and
can differ from the snapshot availableLiquidityUsd. If the on-chain
read fails, the snapshot detail still returns 200 with
liveLiquidity: null and liveLiquidityError: "rpc_error".
| chainId required | integer >= 1 Examples: 8453 Chain id the vault lives on. |
| address required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Vault (ERC-4626 share token) address. |
| window | integer [ 1 .. 360 ] Default: 5 Minutes for the headline |
| include | string Value: "liveLiquidity" CSV of extras. Supported 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
}, - "tvlUsd": 12500000,
- "availableLiquidityUsd": 3400000,
- "maxDepositUsd": null,
- "atCapacity": false,
- "latestAt": "2026-07-07T18:55:00.000Z"
}The 5-minute snapshot series over [from, to], plus intervalApy: the
annualized share-price return between the first and last snapshot in the
range, (last/first)^(year/elapsed) - 1, in percent.
Retention is 7 days: an earlier from is pulled forward and clamped is
set true. resolution downsamples by keeping the last snapshot per
bucket (real snapshots, never averages) and never changes intervalApy.
| chainId required | integer >= 1 Examples: 8453 Chain id the vault lives on. |
| address required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Vault (ERC-4626 share token) address. |
| from | string <date-time> Examples: from=2026-07-05T00:00:00.000Z Start instant (ISO-8601). Default the trailing 24h before |
| to | string <date-time> Examples: to=2026-07-07T00:00:00.000Z End instant (ISO-8601). Defaults to now. |
| resolution | integer [ 5 .. 1440 ] Examples: resolution=60 Downsample bucket in minutes (5-1440). Omit for the full 5-minute series. |
{- "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,
- "assetsPerShare": 101.234567,
- "tvlUsd": 12500000,
- "availableLiquidityUsd": 3400000
}
]
}One entry per requested chain (default: all supported). Amounts are
base-unit strings (USDC 6dp integer string, native in wei);
nativeUsdPrice is the chain's native gas-token USD price in decimal
dollars (null when unavailable). Per-chain error isolation: a failed
RPC marks that chain error: "rpc_error" and never fails the whole
response.
| addr required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Wallet address ( |
| chains | string Examples: chains=8453 CSV of supported chain ids. Defaults to all. |
{- "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"
}
]
}Current USDC and per-vault share-token allowances toward the Earn
contract (the spender), grouped per chain. Each entry carries an
approved flag and, when not approved, a ready-to-sign
approve(spender, amount) template. A share token is approved when
allowance > 0; USDC is approved when allowance >= requiredUsdc.
Allowances and amounts are base-unit strings. Per-chain error
isolation: a failed RPC marks that chain error: "rpc_error" and
never fails the whole response.
| addr required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Wallet address ( |
| chains | string Examples: chains=8453,42161 CSV of supported chain ids. Defaults to all. |
| requiredUsdc | string^[0-9]+$ Default: "0" Examples: requiredUsdc=1000000000 USDC the pending deposit needs, in 6-decimal base units, evaluated against each requested chain's USDC allowance. Default 0 (report-only). |
| vaults | string Examples: vaults=8453:0x616a...,42161:0x1a99... CSV of |
{- "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"
}
]
}ToS state, global hide list, the approval-banner flag, and the
covered-access signals. Unknown wallets return defaults (empty hide
list, both covered flags false). approved is a legacy field and
always true. covered_access / opencover_terms_accepted are SOFT
wizard signals only; the hard covered boundary is enforced at strategy
create.
| addr required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Wallet address ( |
{- "address": "0x1234567890abcdef1234567890abcdef12345678",
- "has_agreement": true,
- "hidden_vault_keys": [
- "8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738"
], - "hide_approval": false,
- "covered_access": false,
- "opencover_terms_accepted": false
}Updates the global hide list and/or the approval-banner flag in one
signed payload. SignedRequest, action prefs.update. The recovered
wallet must equal the path addr or the call fails 403 owner_mismatch.
At least one of hide_vault_key, unhide_vault_key, hide_approval is
required. Hides and unhides merge into the stored list; the full
resulting list is returned.
Hiding a held vault is allowed (it can force-exit positions). The
response then carries an advisory warnings array listing each active
strategy whose eligible pool drops below its max_positions + 1 floor.
| addr required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Wallet address ( |
required | object (SiweBlock) A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
| hide_vault_key | string^\d+:0x[0-9a-fA-F]{40}$ Vault key to add to the hide list. |
| unhide_vault_key | string^\d+:0x[0-9a-fA-F]{40}$ Vault key to remove from the hide list. |
| hide_approval | boolean Set the approval-banner dismissal flag. |
{- "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..."
}
}{- "ok": true,
- "hidden_vault_keys": [
- "8453:0x616a4e1db48e22028f6bbf20444cd3b8e3273738"
], - "hide_approval": true
}Records the wallet's ToS acceptance. SignedRequest, action
tos_agreement; the signature binds terms_url, which is echoed back.
The recovered wallet must equal the path addr or the call fails 403
owner_mismatch.
| addr required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Wallet address ( |
required | object (SiweBlock) A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
| terms_url | string <uri> The ToS URL being agreed to; echoed back in the response. |
{- "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..."
}
}{- "ok": true,
- "address": "0x1234567890abcdef1234567890abcdef12345678",
- "has_agreement": true,
}Stores the wallet's acknowledgment that it viewed OpenCover's terms
before selecting COVERED in the strategy wizard. SignedRequest; action
opencover_terms (statement Authorize opencover_terms). Unlike the
platform ToS, the FULL signed SIWE message and the raw signature are
PERSISTED as proof (a third party's terms), keyed one-row-per-wallet with
last-write-wins semantics. The addr is in the PATH; the recovered
wallet MUST equal it or the call is a 403 owner_mismatch. The signed
terms_url binds which terms version was acknowledged and is echoed
back. This ack is ENFORCED at covered strategy create (403
covered_terms_required), not merely client-gated.
| addr required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: 0x1234567890abcdef1234567890abcdef12345678 Wallet address ( |
required | object (SiweBlock) A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
| terms_url | string <uri> The OpenCover terms URL being acknowledged; bound into the signature and echoed back. |
{- "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..."
}
}{- "ok": true,
- "address": "0x1234567890abcdef1234567890abcdef12345678",
- "opencover_terms_accepted": true,
}Active and closed strategies for a wallet, each with a live valuation,
realized APY, fee/rebalance rollups, and a positions[] array. USDC
values are decimal dollars.
| wallet required | string (Address) ^0x[0-9a-fA-F]{40}$ Examples: wallet=0x1234567890abcdef1234567890abcdef12345678 Owner wallet address. |
{- "strategies": [
- {
- "id": "string",
- "wallet_address": "string",
- "name": "string",
- "capital_usdc": 0,
- "delta_pct": 0,
- "cross_chain_delta_pct": 0,
- "delta_confirmations": 0,
- "max_positions": 0,
- "min_tvl_usd": 0,
- "min_liquidity_usd": 0,
- "min_liquidity_entry_multiplier": 0,
- "chain_id": 0,
- "chain_ids": "string",
- "apy_smoothing_minutes": 0,
- "hidden_vault_keys": [
- "string"
], - "status": "pending_setup",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "first_deposit_at": "2019-08-24T14:15:22Z",
- "deactivated_at": "2019-08-24T14:15:22Z",
- "final_value_usdc": 0,
- "final_realized_apy": 0,
- "covered": true,
- "coverage_activated_at": "2019-08-24T14:15:22Z",
- "total_value_usdc": 0,
- "pending_bridge_value_usdc": 0,
- "pending_bridges_count": 0,
- "net_value_usdc": 0,
- "realized_apy": 0,
- "live_apy": 0,
- "strategy_apy": 0,
- "positions": [
- {
- "id": "string",
- "strategy_id": "string",
- "vault_name": "string",
- "vault_address": "string",
- "protocol": "string",
- "chain_id": 0,
- "shares_raw": "string",
- "usdc_value": 0,
- "entry_apy": 0,
- "current_apy": 0,
- "initial_usd_value": 0,
- "paused": true
}
], - "rebalance_count": 0,
- "total_fees_usdc": 0
}
], - "closedStrategies": [
- {
- "id": "string",
- "wallet_address": "string",
- "name": "string",
- "capital_usdc": 0,
- "delta_pct": 0,
- "cross_chain_delta_pct": 0,
- "delta_confirmations": 0,
- "max_positions": 0,
- "min_tvl_usd": 0,
- "min_liquidity_usd": 0,
- "min_liquidity_entry_multiplier": 0,
- "chain_id": 0,
- "chain_ids": "string",
- "apy_smoothing_minutes": 0,
- "hidden_vault_keys": [
- "string"
], - "status": "pending_setup",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "first_deposit_at": "2019-08-24T14:15:22Z",
- "deactivated_at": "2019-08-24T14:15:22Z",
- "final_value_usdc": 0,
- "final_realized_apy": 0,
- "covered": true,
- "coverage_activated_at": "2019-08-24T14:15:22Z",
- "total_value_usdc": 0,
- "pending_bridge_value_usdc": 0,
- "pending_bridges_count": 0,
- "net_value_usdc": 0,
- "realized_apy": 0,
- "live_apy": 0,
- "strategy_apy": 0,
- "positions": [
- {
- "id": "string",
- "strategy_id": "string",
- "vault_name": "string",
- "vault_address": "string",
- "protocol": "string",
- "chain_id": 0,
- "shares_raw": "string",
- "usdc_value": 0,
- "entry_apy": 0,
- "current_apy": 0,
- "initial_usd_value": 0,
- "paused": true
}
], - "rebalance_count": 0,
- "total_fees_usdc": 0
}
]
}Creates a strategy in pending_setup and returns it wrapped in a
strategy object (201). Body is a SignedRequest, action
strategy.create; the signed payload is the body minus siwe. The
recovered wallet owns the strategy; any wallet field in the body is
ignored.
Optional idempotencyKey (string, 1-128 chars, else 400) rides inside
the signed payload and is scoped per wallet. A repeated key returns the
original stored response verbatim, so do not reuse a key across different
requests.
required | object (SiweBlock) A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
| name required | string <= 50 characters |
| capital_usdc required | number Capital in decimal dollars. |
| delta_pct | number Same-network APY-gap threshold (percentage points). Optional: when omitted it defaults to the same-network floor. Minimum 3, or 5 when the strategy's networks include Ethereum (chain id 1) — a present value below the floor is rejected with a 400. |
| cross_chain_delta_pct | number 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. For a multi-network strategy the EFFECTIVE cross bar (this value when > 0, else the inherited delta_pct) must be >= 5, or >= 7 when the networks include Ethereum — requests below the floor are rejected with a 400. |
| delta_confirmations | integer |
| max_positions required | integer >= 1 |
| min_tvl_usd | number |
| min_liquidity_usd | number |
| min_liquidity_entry_multiplier | number Multiplier on min_liquidity_usd for the non-held entry threshold. Defaults to 2 when omitted. |
| chain_id | integer Optional primary chain id; defaults to the first supported chain. |
| chain_ids | Array of integers Chain ids to span. Omit for a single-chain strategy on |
| apy_smoothing_minutes | integer Defaults to 30 when omitted. |
| covered | boolean Opt the strategy into OpenCover coverage. Set once at create and
immutable thereafter. When true the strategy is forced Base-only
( |
| hidden_vault_keys | Array of strings[ items^\d+:0x[0-9a-fA-F]{40}$ ] |
| idempotencyKey | string [ 1 .. 128 ] characters Optional dedupe key (part of the signed payload). A repeat
returns the ORIGINAL stored response verbatim, so never reuse a
key across logically different requests. Malformed values are 400
|
{- "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..."
}
}{- "strategy": {
- "id": "string",
- "wallet_address": "string",
- "name": "string",
- "capital_usdc": 0,
- "delta_pct": 0,
- "cross_chain_delta_pct": 0,
- "delta_confirmations": 0,
- "max_positions": 0,
- "min_tvl_usd": 0,
- "min_liquidity_usd": 0,
- "min_liquidity_entry_multiplier": 0,
- "chain_id": 0,
- "chain_ids": "string",
- "apy_smoothing_minutes": 0,
- "hidden_vault_keys": [
- "string"
], - "status": "pending_setup",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "first_deposit_at": "2019-08-24T14:15:22Z",
- "deactivated_at": "2019-08-24T14:15:22Z",
- "final_value_usdc": 0,
- "final_realized_apy": 0,
- "last_cycle_at": "2019-08-24T14:15:22Z",
- "cycle_state": "idle",
- "last_error": "string",
- "swap_signals": [
- { }
], - "unmatched_exits": [
- { }
], - "last_skip": { }
}
}Single-strategy detail: config, live-valued positions, cycle/gauge fields
(cycle_state, swap_signals, last_skip, …), closed positions, and the
same computed rollups as the list. Poll this endpoint to watch a status
transition (e.g. pending_setup → active, closing → closed).
| id required | string <uuid> Strategy UUID. |
{- "strategy": {
- "id": "string",
- "wallet_address": "string",
- "name": "string",
- "capital_usdc": 0,
- "delta_pct": 0,
- "cross_chain_delta_pct": 0,
- "delta_confirmations": 0,
- "max_positions": 0,
- "min_tvl_usd": 0,
- "min_liquidity_usd": 0,
- "min_liquidity_entry_multiplier": 0,
- "chain_id": 0,
- "chain_ids": "string",
- "apy_smoothing_minutes": 0,
- "hidden_vault_keys": [
- "string"
], - "status": "pending_setup",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "first_deposit_at": "2019-08-24T14:15:22Z",
- "deactivated_at": "2019-08-24T14:15:22Z",
- "final_value_usdc": 0,
- "final_realized_apy": 0,
- "covered": true,
- "coverage_activated_at": "2019-08-24T14:15:22Z",
- "total_value_usdc": 0,
- "pending_bridge_value_usdc": 0,
- "pending_bridges_count": 0,
- "net_value_usdc": 0,
- "realized_apy": 0,
- "live_apy": 0,
- "strategy_apy": 0,
- "positions": [
- {
- "id": "string",
- "strategy_id": "string",
- "vault_name": "string",
- "vault_address": "string",
- "protocol": "string",
- "chain_id": 0,
- "shares_raw": "string",
- "usdc_value": 0,
- "entry_apy": 0,
- "current_apy": 0,
- "initial_usd_value": 0,
- "paused": true
}
], - "rebalance_count": 0,
- "total_fees_usdc": 0,
- "last_cycle_at": "2019-08-24T14:15:22Z",
- "cycle_state": "idle",
- "last_error": "string",
- "swap_signals": [
- { }
], - "unmatched_exits": [
- { }
], - "last_skip": { },
- "has_agreement": true,
- "closed_positions": [
- {
- "id": "string",
- "strategy_id": "string",
- "vault_name": "string",
- "vault_address": "string",
- "protocol": "string",
- "chain_id": 0,
- "shares_raw": "string",
- "usdc_value": 0,
- "entry_apy": 0,
- "current_apy": 0,
- "initial_usd_value": 0,
- "paused": true
}
], - "vault_names": {
- "property1": "string",
- "property2": "string"
}, - "pending_rebalance": {
- "chain_id": 0,
- "from_vault": "string",
- "to_vault": "string",
- "submitted_tx_hash": "string",
- "phase": "submitted",
- "created_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z"
}, - "cover": {
- "premium_rate_bps": 0,
- "premiums_paid_usdc": 0,
- "projected_1w_usdc": 0,
- "projected_1mo_usdc": 0,
- "projected_1y_usdc": 0,
- "coverage_activated_at": "2019-08-24T14:15:22Z"
}
}
}Updates config fields (delta, min TVL/liquidity, hide list, name, and
status for pause/resume — only active and paused are accepted).
SignedRequest, action strategy.update; the id rides in the path
and the proof's - path: entry binds it. Only fields present in the
body are written; explicit null writes null.
Hide-list toggles: hide_vault_key / unhide_vault_key merge a single
key server-side, so a toggle cannot clobber concurrent edits. Exactly
one op per request; combining an op with a hidden_vault_keys array
replace is a 400 invalid_request.
A hide op or array replace that would leave fewer than
max_positions + 1 eligible vaults is a 409 hide_floor_violated.
Hiding a never-eligible vault passes; unhide_vault_key is not checked.
| id required | string <uuid> Strategy UUID. |
required | object (SiweBlock) A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
| name | string <= 50 characters |
| capital_usdc | number Decimal dollars. Written verbatim when present; no balance check on update (unlike create). |
| delta_pct | number Same-network APY-gap threshold (percentage points). When either threshold field is present, the effective post-update pair is validated against the floors: delta_pct >= 3 (5 when the strategy's networks include Ethereum). Strategies created below the floors keep running unchanged until a PATCH touches a threshold field. |
| cross_chain_delta_pct | number 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. For a multi-network strategy the EFFECTIVE cross bar (this value when > 0, else delta_pct) must be >= 5, or >= 7 when the networks include Ethereum — validated whenever either threshold field is in the payload. |
| delta_confirmations | integer |
| max_positions | integer |
| min_tvl_usd | number |
| min_liquidity_usd | number |
| min_liquidity_entry_multiplier | number |
| apy_smoothing_minutes | integer |
| hidden_vault_keys | Array of strings[ items^\d+:0x[0-9a-fA-F]{40}$ ] Full-array REPLACE of the strategy hide list. Cannot be combined with the single-op toggles below. |
| hide_vault_key | string^\d+:0x[0-9a-fA-F]{40}$ Single-op: add this vault key to the strategy's hide list (lowercased, deduped server-side). |
| unhide_vault_key | string^\d+:0x[0-9a-fA-F]{40}$ Single-op: remove this vault key from the strategy's hide list. |
| status | string Enum: "active" "paused" |
{- "siwe": {
- "message": "string",
- "signature": "string"
}, - "name": "string",
- "capital_usdc": 0,
- "delta_pct": 0,
- "cross_chain_delta_pct": 0,
- "delta_confirmations": 0,
- "max_positions": 0,
- "min_tvl_usd": 0,
- "min_liquidity_usd": 0,
- "min_liquidity_entry_multiplier": 0,
- "apy_smoothing_minutes": 0,
- "hidden_vault_keys": [
- "string"
], - "hide_vault_key": "string",
- "unhide_vault_key": "string",
- "status": "active"
}{- "strategy": {
- "id": "string",
- "wallet_address": "string",
- "name": "string",
- "capital_usdc": 0,
- "delta_pct": 0,
- "cross_chain_delta_pct": 0,
- "delta_confirmations": 0,
- "max_positions": 0,
- "min_tvl_usd": 0,
- "min_liquidity_usd": 0,
- "min_liquidity_entry_multiplier": 0,
- "chain_id": 0,
- "chain_ids": "string",
- "apy_smoothing_minutes": 0,
- "hidden_vault_keys": [
- "string"
], - "status": "pending_setup",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "first_deposit_at": "2019-08-24T14:15:22Z",
- "deactivated_at": "2019-08-24T14:15:22Z",
- "final_value_usdc": 0,
- "final_realized_apy": 0,
- "last_cycle_at": "2019-08-24T14:15:22Z",
- "cycle_state": "idle",
- "last_error": "string",
- "swap_signals": [
- { }
], - "unmatched_exits": [
- { }
], - "last_skip": { }
}
}Deletes a pre-deposit strategy; closes a funded strategy that needs no
on-chain withdrawal (the fallback: "delete" target of
POST .../calldata/withdraw). SignedRequest, action strategy.delete,
empty signed payload ({}); the id rides in the path and the proof's
- path: entry binds it.
| id required | string <uuid> Strategy UUID. |
required | object (SiweBlock) A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
| txHash | string Close tx hash (legacy envelope). |
| finalValueUsdc | number 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. |
object Per-chain close data keyed by chain id (string). |
{- "siwe": {
- "message": "string",
- "signature": "string"
}, - "txHash": "string",
- "finalValueUsdc": 0,
- "perChain": {
- "property1": {
- "txHash": "string",
- "finalValueUsdc": 0,
- "feeUsdc": 0
}, - "property2": {
- "txHash": "string",
- "finalValueUsdc": 0,
- "feeUsdc": 0
}
}
}{- "success": true,
- "action": "deleted"
}Up to 100 events (deposits, rebalances, withdrawals, cross-chain
bridge-backs), newest-first. forced marks a liquidity- or hide-driven
exit from a higher-APY vault.
?format=entries merges the same events with the strategy's CCTP
transfers (limit 50) into one entry per action, each with per-chain
phases.
| id required | string <uuid> Strategy UUID. |
| format | string Default: "events" Enum: "events" "entries" Response shape. |
{- "events": [
- {
- "id": "string",
- "strategy_id": "string",
- "wallet_address": "string",
- "event_type": "string",
- "kind": "deposit",
- "from_vault": "string",
- "from_vault_name": "string",
- "from_apy": 0,
- "to_vault": "string",
- "to_vault_name": "string",
- "to_apy": 0,
- "usdc_amount": 0,
- "fee_usdc": 0,
- "gas_cost_usdc": 0,
- "tx_hash": "string",
- "burn_tx_hash": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "chain_id": 0,
- "log_index": 0,
- "forced": true
}
]
}The strategy's cross-chain (CCTP) transfers, newest-first, limit 50.
amount_usdc is a string; fee_usdc is a number or null.
| id required | string <uuid> Strategy UUID. |
{- "transfers": [
- {
- "id": "string",
- "strategy_id": "string",
- "source_chain_id": 0,
- "dest_chain_id": 0,
- "amount_usdc": "string",
- "burn_tx_hash": "string",
- "relay_tx_hash": "string",
- "status": "burn_submitted",
- "user": "string",
- "hooks_completed": true,
- "source_vault": "string",
- "dest_vault": "string",
- "dest_vaults": [
- { }
], - "transfer_type": "deposit",
- "fee_usdc": 0,
- "batch_id": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}Yield snapshots (~55-min cadence) for the trailing hours, oldest-first,
plus period_fee_usdc and net_yield (see YieldSnapshot). USDC values
are decimal dollars; weighted_apy is a percentage. Unknown strategy:
404. Capped at 2000 rows keeping the newest; truncated: true flags a
capped response.
| id required | string <uuid> Strategy UUID. |
| hours | integer [ 1 .. 2160 ] Default: 168 Trailing window in hours. Defaults to 168 (7d); clamped to [1, 2160] (90d). |
{- "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"
}
]
}Poll the lifecycle of an intent returned by the calldata endpoints.
status is derived:
failed: a failure reason was recorded (including pre-transaction
failures); fulfilled_tx_hash is always null.fulfilled: fulfilled_at is set and the intent did not fail.expired: unfulfilled and past expires_at.pending: everything else.404 intent_not_found covers both a missing intent and an intent
belonging to another strategy. Raw calldata is never returned.
| id required | string <uuid> Strategy UUID. |
| intentId required | string <uuid> Intent id, returned as |
{- "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
}Returns one ready-to-sign selfBatchDeposit transaction on the
strategy's chain, covering local vault deposits plus per-remote-chain
CCTP burns. The matching intent (kind deposit) is written server-side;
do not post an intent separately. No auth; no request body (the id is
in the path).
Strategy must be pending_setup (top-ups are not supported).
Timing: expiresAt is an advisory ~1h plan-freshness bound. The intent
itself has a 30-day TTL; regenerating an unchanged plan within it returns
the same intentId, and regenerating after expiry or fulfillment mints a
new one. A strategy that has never received a deposit is purged about one
hour after it was last touched, taking its intent with it; calling this
endpoint refreshes that hour. If signing takes longer than an hour (e.g.
a multisig), regenerate at least hourly until the transaction is
broadcast.
Approve vault share tokens and USDC before calling: live allowances
affect vault selection, and approvalsNeeded reflects what is still
missing.
Submit the returned data verbatim (re-encoding breaks intent
attribution) and use gasHint as the gas limit (wallet re-estimates can
run out of gas).
| id required | string <uuid> Strategy UUID. |
Deposit calldata request body. Public (no SIWE). The deposit endpoint
takes no parameters, so send no body or an empty JSON object ({}).
{ }{- "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"
}Returns one ready-to-sign selfBatchWithdraw transaction per chain,
source chain first then ascending chain id. Sign and submit in that
order, data verbatim. Paused and zero-share positions are excluded.
Each transaction has its own intentId (kind close), written
server-side. No auth; no status gate. Optional chainIds in the JSON
body restricts the plan to those chains.
If nothing is withdrawable, transactions is [] and fallback is
"delete": close via DELETE /v1/strategies/{id} instead.
expiresAt and intent TTL semantics match the deposit endpoint (the
pre-deposit purge does not apply).
| id required | string <uuid> Strategy UUID. |
| chainIds | Array of integers Optional subset of chain ids to withdraw from. Omit to close all chains. |
{ }{- "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"
}Escape hatch for a stalled deposit bridge leg: fetches the finalized
Circle attestation and returns one ready-to-sign emergencyClaimBridge
transaction on the destination chain. Minted USDC goes directly to the
burn-time beneficiary's wallet, bypassing the destination vault deposit.
The matching intent (kind emergency_claim) is written server-side. No
auth.
Eligibility (each failure is a 400 invalid_state): the transfer must be
a deposit leg, not already confirmed or user_claimed, at least 30
minutes old, and its recorded beneficiary must be the strategy owner. If
Circle has not finalized the attestation, the response is a 409
attestation_pending; poll on that status code.
approvalsNeeded is always []. expiresAt is nominal: the claim stays
valid until the message nonce is consumed on the destination chain. A
null gasHint with estimation_failed can mean the nonce was already
consumed (the transfer completed normally); the on-chain result is
authoritative.
| id required | string <uuid> Strategy UUID. |
| transferId required | string <uuid> The stalled transfer's |
{- "transferId": "9a8b7c6d-0000-4000-8000-000000000123"
}{- "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"
}