Strategies
Strategy list/detail/performance reads and SIWE-signed lifecycle mutations.
/v1/strategiesList a wallet's strategies with positions summary
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.
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
walletqueryrequired | Address | Owner wallet address. |
Responses
| Status | Type | Description |
|---|---|---|
200 | StrategyListResponse | The wallet's active and closed strategies. |
400 | ApiError | Malformed request (bad/missing params). |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
/v1/strategiesCreate a strategy (SIWE strategy.create)
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.
Auth: apikey header
Request bodyCreateStrategyRequest, required
| Field | Type | Description |
|---|---|---|
siwerequired | SiweBlock | A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
namerequired | string | |
capital_usdcrequired | number | Capital in decimal dollars. Must be a finite number above 0, and at least the per-chain minimum (GET /v1/config minStrategyUsdc) of EVERY chain in chain_ids; a Monad strategy funded from Base still needs Monad's minimum. The funding wallet on chain_id must also hold at least this much USDC. Otherwise 400 (since v1.13.0). |
delta_pct | number | 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 | 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. 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 | integer | 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_positionsrequired | integer | |
min_tvl_usd | number | |
min_liquidity_usd | number | |
min_tvl_exit_usd | number | 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 | number | 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 | number | 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 | number | 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 | integer | Optional primary chain id; defaults to the first supported chain. |
chain_ids | array of integer | Chain ids to span. Omit for a single-chain strategy on chain_id. |
apy_smoothing_minutes | integer | 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 | boolean | Opt the strategy into OpenCover coverage. Set once at create and
immutable thereafter. When true the strategy's invest set is
pinned to Base (chain_ids → "8453"), where the covered fee
wrappers live; the funding chain (chain_id) is unrestricted,
and a non-Base funding chain bridges the deposit to Base via
CCTP. The wallet MUST have acknowledged OpenCover's terms, else
the create is a 403 (covered_terms_required). Defaults to
false (uncovered). |
hidden_vault_keys | array of string | |
cloned_from | string (uuid) | Id of the strategy this one was cloned from (part of the
signed payload). Optional. A malformed id is rejected with a
400; an id that no longer references an existing strategy is
ignored (the create succeeds with no lineage: the source
can be garbage-collected between fetch and signing). Lineage
is stored only when the
created strategy's settings canonically match the source's: capital, name, and funding chain are the cloner's own and
never count as edits, and a network the new capital cannot
clear is dropped from the comparison (a smaller clone
legitimately spans fewer networks); any other difference
stores null and the create still succeeds. Lineage is also
withheld when either side carries a per-strategy
hidden_vault_keys list (a curated vault set is part of the
configuration but is never carried by the clone flow). A
clone of a clone is attributed to the ORIGINAL (the stored
id is always a root). Feeds the leaderboard's clone_count
and cloned_from. A later PATCH that changes any compared
setting or the hide list clears the stored lineage (see
updateStrategy). |
idempotencyKey | string | 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
invalid_request. |
Responses
| Status | Type | Description |
|---|---|---|
201 | StrategyMutationResponse | The created strategy, wrapped in a strategy object. |
400 | ApiError | Malformed 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). |
401 | ApiError | SIWE 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). |
403 | ApiError | Covered gate (only when the signed payload has covered: true).
covered_disabled: the operator kill switch (app_config
ENABLE_COVERED_STRATEGIES) is off. 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).
There is no wallet allowlist: any wallet may create a covered
strategy while the switch is on. |
409 | ApiError | 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. |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
503 | ApiError | Signature verification could not reach the chain to validate a contract (ERC-1271) wallet. Transient: retry. Only occurs for smart-wallet signers. |
/v1/strategies/leaderboardPublic strategy leaderboard (eligible strategies by realized APY)
Every eligible strategy ranked by realized APY, computed over TOTAL contributed capital (initial deposit plus any later top-ups) with mid-life withdrawals credited back: a mid-life deposit can never masquerade as yield. Live rows are valued from their latest yield snapshot (roughly hourly) and annualized to that snapshot's own timestamp; closed rows use their settled final value annualized to their close time.
One dataset, no parameters: clients filter (creation span, networks,
duration) and number their own filtered views. Rows are sorted by
realized_apy_pct descending with null figures last, capped at 500.
The board shows every strategy that ever took a deposit: running
(live, including paused and closing) or fully closed (closed),
however short-lived. A row measured under 30 minutes carries a null
realized_apy_pct (annualizing minutes is noise) but still appears.
One timing caveat: a live row appears once its first yield snapshot
lands (within roughly an hour of funding): before that there is no
value to list it by. Closed rows appear immediately.
Clone tracking (v1.8.0): each row carries clone_count: how many
strategies currently record it as their clone source. Lineage is
stamped at create only when the clone's settings match the source,
so the figure means "cloned and left unchanged". Rows are never
folded or hidden; every figure describes the row's own strategy.
Privacy: wallet is truncated server-side (0x1234…abcd) as a
display convention, not anonymity: rows carry the public strategy
id, which the public strategy-detail read resolves like any other id.
The board is recomputed at most every ~5 minutes (updated_at marks
the compute time).
Auth: apikey header
Responses
| Status | Type | Description |
|---|---|---|
200 | LeaderboardResponse | The ranked board. |
400 | ApiError | Malformed request (bad/missing params). |
429 | ApiError | Rate 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. |
500 | ApiError | Leaderboard data is temporarily unavailable. |
/v1/strategies/{id}Strategy detail
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).
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
idpathrequired | string (uuid) | Strategy UUID. |
freshquery | "1" | 1 skips the gateway's 12-second read cache for this call. Use it
right after a Realtime signal that the strategy's positions changed. |
Responses
| Status | Type | Description |
|---|---|---|
200 | StrategyDetailResponse | Strategy detail. |
404 | ApiError | Resource not found. |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
/v1/strategies/{id}Delete or close a strategy (SIWE strategy.delete)
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.
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
idpathrequired | string (uuid) | Strategy UUID. |
Request bodyDeleteStrategyRequest
| Field | Type | Description |
|---|---|---|
siwerequired | 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. |
perChain | object | Per-chain close data keyed by chain id (string). |
Responses
| Status | Type | Description |
|---|---|---|
200 | DeleteStrategyResponse | Deletion or close result. |
400 | ApiError | Malformed request/SIWE, OR active positions remain on chains not
covered by a per-chain close (invalid_request). |
401 | ApiError | SIWE 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). |
404 | ApiError | Resource not found. |
409 | ApiError | A first-deposit or top-up bridge leg for this strategy is still
settling (transfer_in_flight): the arriving funds would orphan
into a closed strategy. Retry after it confirms. |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
503 | ApiError | Signature verification could not reach the chain to validate a contract (ERC-1271) wallet. Transient: retry. Only occurs for smart-wallet signers. |
/v1/strategies/{id}Update strategy config (SIWE strategy.update)
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.
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
idpathrequired | string (uuid) | Strategy UUID. |
Request bodyUpdateStrategyRequest, required
| Field | Type | Description |
|---|---|---|
siwerequired | SiweBlock | A Sign-In-With-Ethereum proof binding the wallet to (action, payload, issuedAt, expirationTime). |
name | string | |
capital_usdc | number | Decimal dollars. When present it must be a finite number above 0 (400 otherwise, since v1.13.0). No chain-minimum or balance check on update (unlike create). |
delta_pct | number | 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 | 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. Validated only when present in the payload: must be 0 or a number >= 0.25. |
delta_confirmations | integer | |
max_positions | integer | |
min_tvl_usd | number | 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 | number | 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 | number | 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 | number | 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 | number | Deprecated alias of min_liquidity_entry_usd. When sent, the stored dollar level becomes multiplier × the effective min_liquidity_usd. |
min_tvl_exit_ratio | number | 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 | integer | |
hidden_vault_keys | array of string | Full-array REPLACE of the strategy hide list. Cannot be combined with the single-op toggles below. |
hide_vault_key | string | Single-op: add this vault key to the strategy's hide list (lowercased, deduped server-side). |
unhide_vault_key | string | Single-op: remove this vault key from the strategy's hide list. |
status | "active" | "paused" |
Responses
| Status | Type | Description |
|---|---|---|
200 | StrategyMutationResponse | The updated strategy, wrapped in a strategy object. A missing or
unowned strategy returns 500 internal_error, not 404. |
400 | ApiError | Malformed 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). |
401 | ApiError | SIWE 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). |
409 | ApiError | hide_floor_violated: the hide change would shrink the eligible
vault pool below max_positions + 1. See the description above. |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
503 | ApiError | Transient failure, retry: strategy_read_failed (hide-list merge
read), hide_lookup_failed (hide-floor check read), or siwe_rpc
(smart-wallet signature verification RPC). |
/v1/strategies/{id}/historyStrategy event log
Up to 100 events (deposits, rebalances, withdrawals, cross-chain
bridge-backs), newest-first. forced marks a floor-driven
(liquidity/TVL) or hide-driven exit from a higher-APY vault;
force_reason names which floor when recorded.
?format=entries merges the same events with the strategy's CCTP
transfers (limit 50) into one entry per action, each with per-chain
phases.
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
idpathrequired | string (uuid) | Strategy UUID. |
formatquery | "events" | "entries" | Response shape. events (default) is the flat event log
(HistoryResponse); entries is the aggregated accordion view
(HistoryEntriesResponse). |
Responses
| Status | Type | Description |
|---|---|---|
200 | HistoryResponse | HistoryEntriesResponse | The flat event log (default) or the aggregated entries view
(format=entries). Branch on the format you requested. |
400 | ApiError | Invalid strategy id (a UUID is required) or unknown format. |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
/v1/strategies/{id}/bridgesCCTP bridge transfers for a strategy
The strategy's cross-chain (CCTP) transfers, newest-first, limit 50.
amount_usdc is a string; fee_usdc is a number or null.
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
idpathrequired | string (uuid) | Strategy UUID. |
Responses
| Status | Type | Description |
|---|---|---|
200 | BridgesResponse | Bridge transfer rows. |
400 | ApiError | Invalid strategy id (a UUID is required). |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
/v1/strategies/{id}/performanceStrategy value/yield time series
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.
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
idpathrequired | string (uuid) | Strategy UUID. |
hoursquery | integer | Trailing window in hours. Defaults to 168 (7d); clamped to [1, 2160] (90d). |
Responses
| Status | Type | Description |
|---|---|---|
200 | PerformanceResponse | The clamped window and the yield snapshot series. |
400 | ApiError | Invalid strategy id (a UUID is required). |
404 | ApiError | Resource not found. |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |
/v1/strategies/{id}/intents/{intentId}Single intent lifecycle status
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_hashis always null.fulfilled:fulfilled_atis set and the intent did not fail.expired: unfulfilled and pastexpires_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.
Auth: apikey header
Parameters
| Parameter | Type | Description |
|---|---|---|
idpathrequired | string (uuid) | Strategy UUID. |
intentIdpathrequired | string (uuid) | Intent id, returned as intentId by the calldata endpoints. |
Responses
| Status | Type | Description |
|---|---|---|
200 | IntentStatusResponse | The intent row with its derived status. |
400 | ApiError | The strategy id or intent id is not a UUID (invalid_request). |
404 | ApiError | No such intent for THIS strategy (intent_not_found; also returned when the intent belongs to another strategy). |
429 | ApiError | Rate 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. |
500 | ApiError | Internal server error. |