Skip to main content

Create strategy – Earn API

Updated on
Jul 31, 2026

Overview​

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.

Endpoint​

POST https://earn-api.quicknode.dev/functions/v1/api/v1/strategies

Authorization​

Include the published apikey header and a valid Sign-In with Ethereum (SIWE) proof in the request body. The SIWE message binds the wallet to this action, payload, and request path.

Request body​

All fields except siwe are part of the signed payload. Name regex ^[a-zA-Z0-9 _\-.]{1,50}$. chain_ids is an ARRAY of chain ids on input (stored and returned as a CSV string); a missing/invalid chain silently falls back to the first supported chain.

FieldTypeRequiredDescription
siweobjectYesA Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime).
siwe.messagestringYesSIWE message for action `strategy.create`, including the payload hash and `/strategies` path resource.
siwe.signaturestringYesWallet signature over `siwe.message`.
namestringYesStrategy name. Maximum 50 characters; letters, numbers, spaces, underscores, hyphens, and periods are supported.
capital_usdcnumberYesCapital in decimal dollars.
delta_pctnumberNoSame-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_pctnumberNoAPY-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_confirmationsintegerNoNumber of consecutive qualifying APY observations required before rebalancing.
max_positionsintegerYesMaximum number of vault positions. Must be at least 1.
min_tvl_usdnumberNoMinimum vault TVL in decimal US dollars.
min_liquidity_usdnumberNoMinimum vault liquidity in decimal US dollars.
min_liquidity_entry_multipliernumberNoMultiplier on min_liquidity_usd for the non-held entry threshold. Defaults to 2 when omitted.
chain_idintegerNoOptional primary chain id; defaults to the first supported chain.
chain_idsarray<integer>NoChain ids to span. Omit for a single-chain strategy on `chain_id`.
apy_smoothing_minutesintegerNoDefaults to 30 when omitted.
coveredbooleanNoOpt the strategy into OpenCover coverage. Set once at create and immutable thereafter. When true the strategy is forced Base-only (`chain_ids` → "8453") and the wallet MUST be on the covered-access allowlist AND have acknowledged OpenCover's terms, else the create is a 403 (`covered_not_allowed` / `covered_terms_required`). Defaults to false (uncovered).
hidden_vault_keysarray<string>NoVault keys to exclude, formatted as `chainId:0xaddress`.
idempotencyKeystringNoOptional 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 `invalid_request`.

Example request​

curl --request POST \
--url 'https://earn-api.quicknode.dev/functions/v1/api/v1/strategies' \
--header 'Accept: application/json' \
--header 'apikey: <EARN_PUBLIC_API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
"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..."
}
}'

Response​

FieldTypeRequiredDescription
strategyobjectYesThe strategy row returned (inside `strategy`) by create and update: config fields plus cycle/gauge fields (`cycle_state`, `swap_signals`, `last_skip`, ...). No computed rollups.
strategy.idstringYesCreated strategy UUID.
strategy.wallet_addressstringYesRecovered SIWE wallet address.
strategy.namestringYesStrategy name.
strategy.capital_usdcnumberYesConfigured capital in decimal US dollars.
strategy.delta_pctnumberYesSame-chain APY threshold in percentage points.
strategy.cross_chain_delta_pctnumberNoCross-chain APY threshold; `0` inherits `delta_pct`.
strategy.delta_confirmationsintegerYesRequired consecutive threshold confirmations.
strategy.max_positionsintegerYesMaximum vault positions.
strategy.min_tvl_usdnumberYesMinimum vault TVL in decimal US dollars.
strategy.min_liquidity_usdnumberYesMinimum vault liquidity in decimal US dollars.
strategy.min_liquidity_entry_multipliernumberYesLiquidity multiplier for new entries.
strategy.chain_idintegerYesLegacy primary chain ID.
strategy.chain_idsstring | nullYesStored comma-separated chain IDs.
strategy.apy_smoothing_minutesintegerYesAPY smoothing window in minutes.
strategy.hidden_vault_keysarray<string>YesStrategy-level hidden vault keys.
strategy.statusstringYesInitial status, `pending_setup`.
strategy.activebooleanYesWhether the strategy is active.
strategy.created_atstringYesCreation timestamp.
strategy.updated_atstringYesLast-update timestamp.
strategy.first_deposit_atstring | nullYesFirst-deposit timestamp.
strategy.deactivated_atstring | nullYesDeactivation timestamp.
strategy.final_value_usdcnumber | nullYesFinal value for a closed strategy.
strategy.final_realized_apynumber | nullYesFinal realized APY percentage.
strategy.last_cycle_atstring | nullYesMost recent strategy-cycle timestamp.
strategy.cycle_statestring | nullYesCurrent strategy cycle state.
strategy.last_errorstring | nullYesMost recent cycle error.
strategy.swap_signalsarray<object> | nullYesOpaque internal swap signals.
strategy.unmatched_exitsarray<object> | nullYesExit records not matched to positions.
strategy.last_skipobject | nullYesMost recent skipped-cycle details.

Example response​

The API returns the stored strategy row. The exact UUID and timestamps are generated at creation.

{
"strategy": {
"id": "b7c1e2d3-0000-4000-8000-000000000001",
"wallet_address": "0x1234567890abcdef1234567890abcdef12345678",
"name": "My USDC auto-pilot",
"capital_usdc": 1000,
"delta_pct": 3,
"cross_chain_delta_pct": 5,
"delta_confirmations": 2,
"max_positions": 2,
"chain_ids": "8453,42161",
"apy_smoothing_minutes": 30,
"hidden_vault_keys": [],
"status": "pending_setup",
"cycle_state": null
}
}

HTTP responses​

StatusDescription
201The created strategy, wrapped in a `strategy` object.
400Malformed request or SIWE block. `code` is one of: `invalid_request`, `siwe_missing` (no `siwe` block), `siwe_shape` (message/signature not strings), `siwe_parse` (message does not match the documented line format).
401SIWE verification failed. `code` is one of: `siwe_domain` (message domain not allowlisted), `siwe_action` (statement/action mismatch), `siwe_stale` (expired or validity window out of bounds), `siwe_payload` (payload hash or resource mismatch — canonicalization drift, a re-serialized body, or a `- path:` entry that does not match the request path), `siwe_chain` (bad chain id), `siwe_recover` (signature does not match the claimed address), `siwe_replay` (nonce already used — sign a fresh request).
403Covered-access gate (only when the signed payload has `covered: true`). `covered_not_allowed`: the recovered wallet is not on the covered-access allowlist (`approved_wallets`). `covered_terms_required`: the wallet has not acknowledged OpenCover's terms (`POST /v1/wallets/{addr}/opencover-terms`). Both are checked BEFORE any insert; uncovered creates never hit this gate. A failed lookup during the check is a 503 `covered_access_lookup_failed` (fails closed — the boundary never falls through to an insert).
409`hide_floor_violated`: the supplied `hidden_vault_keys` would leave fewer than `max_positions + 1` eligible vaults. Hiding vaults that were never eligible passes. A failed read of the wallet's global hide list is a 503 `hide_lookup_failed`.
429Rate limit exceeded. `Retry-After` (seconds) tells you when to retry. Reads, feedback, and push writes are keyed per IP; create/update/delete are keyed per wallet+IP; deposit/withdraw/claim calldata is keyed per strategy+IP.
500Internal server error.
503Signature verification could not reach the chain to validate a contract (ERC-1271) wallet. Transient — retry. Only occurs for smart-wallet signers.
OpenAPI source

This page is based on operation createStrategy in the Earn OpenAPI 3.1 specification.