tRPC v2 Portfolio API
tRPC Endpoints
tRPC v2 Portfolio API
Canonical portfolio-first tRPC surface for CLI and agent integrations
POST
tRPC v2 Portfolio API
The
For
For
The quote response includes
For
v2 tRPC namespace is the canonical API surface for agent and CLI consumers.
- Base endpoint:
POST /v1/trpc - Namespace:
v2.* - Auth:
- Wallet session for
v2.portfolio.create.*andv2.portfolio.permission.refresh.* - Wallet session for
v2.portfolio.builder.getProfile,saveProfile,getDraft,saveDraft,recommend, andseedCopilot - Wallet session or API key for
v2.portfolio.builder.generateDraft - Wallet session for
v2.portfolio.scheduledFunctions.* - Wallet session or API key for other
v2.portfolio.*,v2.executions.*,v2.automationRuns.*, and deprecatedv2.operations.* - For API key calls, owner is resolved from canonical API key metadata/KMS linkage.
x-glider-owner-addressis optional and must match canonical owner when provided. - Public rate-limited access for
v2.public.*non-owner read surfaces
- Wallet session for
Procedure Groups
v2.portfolio.*list,listByOwnerDbV2,get,statusvaults.listperformance.get,performance.series,performance.assetMetricsactivity.list,activity.allocationHistory.list,activity.allocationHistory.chartrecipients.listdeposit.instructionsbuilder.getProfile,builder.saveProfile,builder.getDraft,builder.saveDraft,builder.recommend,builder.generateDraft,builder.seedCopilotcreate.prepare,create.confirmpermission.status,permission.refresh.prepare,permission.refresh.confirmarchive,unarchiveupdates.list,updates.get,updates.preview,updates.create,updates.submit,updates.supersedeapprovals.list,approvals.get,approvals.decideruns.list,runs.getevents.list,events.streamcontext.list,context.getagents.list,agents.getactions.previewOndoDirectQuote,actions.submitpolicy.evaluate,policy.scheduleAdvisorypolicy.config.get,policy.config.updateschedule.get,schedule.create,schedule.update,schedule.setFromText,schedule.pause,schedule.archive,schedule.resume,schedule.runNowscheduledFunctions.list,scheduledFunctions.create,scheduledFunctions.delete(beta/private rollout; exposed in the webapp only behind the recurring-swaps and recurring-transfers feature flags)
v2.executions.*get,detail,result,list,stream
v2.automationRuns.*get,events,stream
v2.workflows.*(feature-gated beta/private rollout)list,getdrafts.list,drafts.start,drafts.startFromWorkflow,drafts.get,drafts.answer,drafts.cancelauthoring.validate,authoring.preview
v2.operations.*(deprecated)- legacy compatibility reads
v2.agentAuth.*createApiKey
v2.public.*portfolio.list,portfolio.listByIds,portfolio.getportfolio.vaults.listportfolio.schedule.getportfolio.activity.list,portfolio.activity.allocationHistory,portfolio.activity.allocationHistoryChartaccount.profile.getaccount.assets.listaccount.performance.chartaccount.performance.seriesaccount.performance.overview
actions.submit Withdrawal Modes
For v2.portfolio.actions.submit with kind: "withdraw":
mode: "withdraw"transfers the selected assets out directly.mode: "transfer"requiresrecipientand is limited to the owner address or an owned vault.mode: "external"requiresrecipientfor custom-recipient withdrawals. It is available for Investing Account portfolios on EVM and Solana. Legacy normal-portfolio support remains for embedded-Privy EVM withdraws and the Privy Solana external fallback; other normal-portfolio custom-recipient requests are rejected.mode: "withdraw_as_usdc"requiresrecipientand lets the backend either:- transfer already-USDC assets directly, or
- smart-route one-or-many non-USDC assets through backend swap execution and
send the resulting USDC to that recipient.
For Investing Account portfolios,
recipientmay be a custom EVM wallet address. Normal portfolio recipients remain limited to the owner or one of the owner’s managed vault addresses.
actions.submit Swap Constraints
For v2.portfolio.actions.submit with kind: "swap":
params.chainIdremains the execution chain for the submitted LiFi route.params.executionConstraintsis optional and additive. When present, the backend enforces stricter settlement rules without changing existing generic swap callers.params.executionConstraints.sameChainOnly: truerejects multi-chain routes.params.executionConstraints.expectedVaultChainIdrequires the route to stay on that chain and settle into the authenticated portfolio’s exact vault on that chain.
execution_ondo_direct_market_orders. When enabled, callers can first query
v2.portfolio.actions.previewOndoDirectQuote:
provider: "ondo_direct", side, raw
spendAmount, raw estimatedReceiveAmount, asset/cash token metadata, and the
Ondo manager address used by the execution engine.
To execute the confirmed quote, submit kind: "swap" with
params.provider: "ondo_direct":
recipientAddress must be one of the authenticated owner’s vaults on the
requested chain. expectedReceiveAmount is required: send the previewed
estimatedReceiveAmount; the engine rejects firm attestations that fall outside
the server-side quote-deviation guard and fails closed if it is absent.
actions.submit Yield Actions
For v2.portfolio.actions.submit with kind: "yield":
mode: "deposit"requiresassetIdand submits a manual engineyield-depositoperation.mode: "withdraw"requiresassetIdand submits a manual engineyield-withdrawoperation for a USDC-denominated exit amount.mode: "redeem"requiresshareAssetIdand submits a manual engineyield-redeemoperation for an exact share-token amount.- The contract is generic enough to carry any validated protocol/source/chain,
but current product scope is limited to Arbitrum (
42161) USDC targets for Aave V3, Morpho Steakhouse High Yield, and Fluid. - Submitted yield actions return an execution handle with
kind: "execution"; clients should pollv2.executions.getor streamv2.executions.stream.
Performance Series Response Shape
v2.portfolio.performance.series and v2.public.account.performance.series return a unified response containing historical portfolio value and cash-flow points, response metadata identifying the selected return methodology, pre-computed stats per timeframe, and a live value snapshot. Portfolio and account clients should request returnMethod: "MWR"; strategy-only performance surfaces remain TWR.
meta.returnMethodis eitherMWRorTWR; portfolio performance defaults toMWRwhen no method is provided.MWRis a money-weighted return, which reflects the user’s actual money outcome after deposits and withdrawals. Portfolio and public account callers should passMWR; strategy performance surfaces remain TWR.series[]may include a trailingisLive: truepoint representing the current live valuation.statsis keyed byPerformanceChartResolution(DAY,WEEK,MONTH,YEAR,ALL).livecontains the real-time portfolio value and per-asset breakdown.live.assetsis present onv2.portfolio.performance.seriesbut omitted from the account-level endpoint.- Responses are cached in Redis with a 30s TTL.
Public Account Performance Chart
v2.public.account.performance.chart is the lightweight user-profile chart contract. It is fixed to a 90 day window and returns daily account TVL points for hoverable public user pages without sending the full account performance series payload:
- The endpoint is derived from the existing account performance service; it does not require a dedicated database table.
statspowers the user-profile hero headline and matches the 90D chart window.- The public user page should prefer this endpoint over
v2.public.account.performance.seriesunless it needs the complete event series.
Execution Handle
Long-running actions return a canonical execution envelope:v2.portfolio.actions.submit also includes additive execution guidance:
nextSteps.poll->v2.executions.getnextSteps.stream->v2.executions.streamnextRecommendedCommandswith ready-to-copy polling/stream commandsrefs.executionSourcedistinguishes manual and automated starts (manual,scheduled,event,scheduled_function)refs.requestId,refs.scheduleId,refs.scheduledFunctionId, andrefs.scheduledRunAtare additive correlation fields when the upstream flow can provide them
Execution Detail
Usev2.executions.get for a lightweight summary handle,
v2.executions.detail for the persisted execution narrative, and
v2.executions.result for raw terminal machine output.
v2.executions.detail returns:
operation: the canonical execution handlesummary: the current headline, phase, and terminal reason when availabletimeline: curated execution events suitable for user-facing progress UIs or agentsgraph: optional rebalance observer graph metadata for richer visualizations
Execution Stream
v2.executions.stream is the canonical typed realtime surface for first-party
web clients. It replays ordered canonical execution events and then switches to
live tail delivery.
Each event uses this envelope:
- Resume is supported with
cursor. SSE reconnects also use the same opaque cursor viaLast-Event-ID. v2.executions.getandv2.executions.detailremain the snapshot and hydration APIs.GET /v1/executions/:operationId/streamexposes the same canonical event log over SSE for CLIs, API integrators, and future chat surfaces.GET /v1/ai/executions/:operationId/ag-ui-streamexposes an AG-UI projection of that same execution log for TanStack AI-compatible clients.
Automation Run Detail
Usev2.automationRuns.get for the current canonical authored-run snapshot and
v2.automationRuns.events for ordered timeline pagination.
v2.automationRuns.get returns:
run: the current automation run handlelatestSequence: the latest persisted timeline sequence
v2.automationRuns.events returns:
runIditems: canonical automation timeline itemsnextSequence: the last delivered sequence for resume/polling
Automation Run Stream
v2.automationRuns.stream is the typed realtime surface for authored
automation-engine runs. It follows the same resume model as
v2.executions.stream.
Each event uses this envelope:
- Resume is supported with
cursor. - Direct HTTP SSE for automation runs has been retired. HTTP consumers can read
snapshots and event pages through
GET /v1/automation-runs/:runIdandGET /v1/automation-runs/:runId/events.
Retired/Legacy Mapping
sessionKeys.getPortfolioSignableMessage->v2.portfolio.create.preparesessionKeys.createPortfolioWithSignature->v2.portfolio.create.confirmsessionKeys.refreshSessionKeyMessage*->v2.portfolio.permission.refresh.preparesessionKeys.refreshSessionKeyWithSignature->v2.portfolio.permission.refresh.confirmstrategyInstances.getStrategyInstancesOwnedByAddress->v2.portfolio.list- authenticated dashboard owner-list hydration ->
v2.portfolio.listByOwnerDbV2 strategyInstances.getStrategyInstance->v2.portfolio.getstrategyInstances.archiveStrategyInstance->v2.portfolio.archivestrategyInstances.unarchiveStrategyInstance->v2.portfolio.unarchivestrategyInstances.getUnifiedPortfolioHistory->v2.portfolio.activity.list(kind="history")- derived allocation history feed ->
v2.portfolio.activity.allocationHistory.listitemsremains the cursor-paginated newest-first activity feed for the History table.- The response still includes
checkpointsfor backward compatibility whenincludeCheckpoints=true; chart consumers should passincludeCheckpoints=falseand use the fixed chart route instead.
- fixed allocation history chart ->
v2.portfolio.activity.allocationHistory.chartcheckpointsis the 90-day chart-ready sampled allocation series. It includes daily balance-history snapshots, including days without events, so charts should not treat event rows as the only drawable points.- Event feed pagination remains on
allocationHistory.list; chart checkpoints do not use the event cursor.
strategyInstances.getStrategyPerformanceMultipleTimeframes->v2.portfolio.performance.get(timeframes)strategyInstances.getNetDepositsAndWithdrawals->v2.portfolio.performance.get(netFlows)strategyInstances.getStrategyPerformanceSeries->v2.portfolio.performance.series- dashboard asset analytics ->
v2.portfolio.performance.assetMetrics strategyInstances.getStrategyInstanceVaultsOwnedByAddress->v2.portfolio.recipients.liststrategyInstances.getStrategyInstancesOwnedByAddress(public profile view) ->v2.public.portfolio.list- curated public portfolio-card hydration ->
v2.public.portfolio.listByIds strategyInstances.getStrategyInstance(unowned read) ->v2.public.portfolio.getvaults.getVaultsPortfolioDataForStrategyInstance(unowned read) ->v2.public.portfolio.vaults.listschedules.getStrategyInstanceSchedule(unowned read) ->v2.public.portfolio.schedule.get- public allocation history feed ->
v2.public.portfolio.activity.allocationHistory - public fixed allocation history chart ->
v2.public.portfolio.activity.allocationHistoryChart - Explore strategy discovery boards ->
v2.public.explore.strategyDiscoveryBoards - user profile hydration aggregate ->
v2.public.account.profile.get strategyInstances.getAllAssetsAcrossWalletStrategies(public profile aggregate) ->v2.public.account.assets.liststrategyInstances.getAccountPerformanceOverview(public profile aggregate) ->v2.public.account.performance.overview- compact 90D public account chart ->
v2.public.account.performance.chart strategyInstances.getAccountPerformanceSeries(public profile aggregate) ->v2.public.account.performance.seriesrebalance.execute->v2.portfolio.actions.submit(kind="rebalance")withdrawAndDeposits.process*->v2.portfolio.actions.submit(kind="withdraw")bridge.processBridgeRequest->v2.portfolio.actions.submit(kind="bridge")executeLifiQuote.execute->v2.portfolio.actions.submit(kind="swap")- backend-quoted LiFi swap execution ->
v2.portfolio.actions.submit(kind="swap_backend_quote") - manual Aave/Morpho/Fluid yield execution ->
v2.portfolio.actions.submit(kind="yield", returned askind="execution") schedules.*->v2.portfolio.schedule.*- user-approved scheduled functions ->
v2.portfolio.scheduledFunctions.* rebalance.getStatus/workflows.*/temporal.*/bridge.getBridgeStatus/executeLifiQuote.getStatus->v2.executions.get/v2.executions.detail/v2.executions.result/v2.executions.stream
Portfolio Create Confirm
v2.portfolio.create.confirm accepts the signed permission payload returned by
v2.portfolio.create.prepare plus one strategy attachment:
portfolioBlueprint: create a new blueprint from submitted strategy data.blueprintTemplateId: create from an existing template/forked blueprint. The id must reference an existing blueprint.blueprintTemplateMode: "mirror"withsourcePortfolioId: create a backend-gated mirrored portfolio from a public, mirrorable upstream source portfolio.
strategy_blueprints.can_mirror, derives the blueprint from that source,
and creates the downstream portfolio owned by the authenticated caller.
blueprintTemplateId is optional in mirror mode; when provided it must match
the source portfolio’s blueprint. portfolioBlueprint is invalid in mirror
mode.
Mirrored creates inherit the source portfolio’s configured rebalance schedule
state from db-v2. If the source portfolio has an active interval, windowed,
cron, or rrule schedule, the mirrored portfolio gets the same spec with a fresh
next-due time. If the source schedule is paused or disabled, the mirrored
schedule is created in the same paused or disabled state. Schedule execution
history, cooldown, failure counters, and last-error fields are not copied. If
the source portfolio has no configured rebalance schedule, the mirrored
portfolio is created without one. The createSchedule flag is ignored as a
mirror schedule override; for mirror creates the source portfolio’s schedule
state is authoritative.
Mirrored creates also inherit source portfolio-local configuration snapshots.
Stored smart-portfolio policy config is copied with the downstream
owner/portfolio id when the source has a stored config. Source effective swap
preferences (thresholdUsd, slippageBps, priceImpactBps) are resolved from
source portfolio override, source owner global override, and chain defaults,
then copied into the downstream user’s portfolio override. These are
creation-time snapshots, like schedule inheritance; later edits to the source
portfolio’s local config do not rewrite already-created mirrors. Strategy-level
allocation and drift-threshold changes remain live because mirrored portfolios
continue pointing at the source blueprint.
Mirrored child portfolios cannot edit inherited schedule, smart-portfolio policy
config, or portfolio swap preference overrides. The owning user can still use
global swap defaults for other portfolios, but the mirrored portfolio uses its
creation-time copied portfolio override.
Compatibility Notes
v2.portfolio.create.confirmvalidates construction-style blueprints server-side regardless of stored builder-draft state: weight blocks usingequal/specified-percentageallocation modes must have a valid asset per slot, a matching positive weight per slot, no duplicate holdings, and custom percentages summing to 100%. Violations returnBAD_REQUESTwith aPortfolio draft is not ready to create: …message. Blueprints using other allocation modes are unaffected.- Selected legacy namespaces remain mounted for backwards compatibility.
- Retired procedure paths are intentionally removed from legacy routers (for example
rebalance.executeand migratedschedules.*write mutations), and should be treated asv2.portfolio.*-only. - New agent/CLI integrations should target only
v2.*. - v2 keeps external language portfolio-first (
portfolioId), even when internal implementations still use strategy-instance identifiers. v2.executions.listreads from the persisted execution narrative store forrebalance,swap,withdraw, andbridge.v2.executions.getcan also hydrate manual engineexecutionhandles, including yield actions, from the runtime engine operation API while those operations are in flight.v2.executions.detailreads the persisted execution narrative store and adds timeline plus optional graph metadata.v2.executions.streamreplays and tails the canonical execution event log rather than synthesizing updates from repeated detail polling in the normal path.v2.operations.*remains mounted temporarily as a deprecated, best-effort surface only.v2.portfolio.statusis resilient: permission/session data still returns if rebalance status source is unavailable;rebalanceisnullanddegraded.rebalanceUnavailable=true.v2.portfolio.statusincludes additive semanticconvergencemetadata for owner UIs:state:awaiting_review | queued | moving | blocked | aligned | failedsummary: portfolio-level explanation of the highest-priority outstanding conditionapprovalId?,proposalId?,runId?,targetId?,targetType?,executionStatus?,updatedAt: optional references for detail surfaces
v2.portfolio.updates.*is the desired-state composer surface forwebapp-v2:previewderives semantic before/after state without mutating live strategy definitionscreatewrites adraftrevisionsubmit(mode="review")promotes that revision topending_reviewsubmit(mode="apply_now")accepts the revision and syncs it through blueprint versioning for runtime paritygetreturnsPORTFOLIO_UPDATE_NOT_FOUNDwhen the revision is missing; it does not return a nullable success payload- update rows expose additive
revisionmetadata so owner surfaces can reason about desired-state history - update status includes
dismissedfor review declines
v2.portfolio.approvals.*is the canonical owner-facing approval surface:- approval targets are semantic (
portfolio_updateorportfolio_update) decideis the canonical owner decision entrypoint
- approval targets are semantic (
v2.portfolio.runs.*exposes semantic convergence/execution rows projected from desired-state apply, copilot execution, and rebalance runtime sources.v2.portfolio.events.*exposes stored semantic control-plane history:listreturns canonical timeline events and paginates with an opaque cursor derived from(createdAt,id)streamprovides polling-backed internal event streaming for owner surfaces and can resume from the last opaque cursor
- semantic control-plane reads are now pure
db-v2reads by default; operational read repair remains available only behindPORTFOLIO_CONTROL_PLANE_ENABLE_READ_REPAIR v2.portfolio.context.*exposes immutable context snapshots referenced by approvals, runs, and events.v2.portfolio.agents.*exposes portfolio-scoped automated principals and their allowed scopes.- Permission UIs should prefer
v2.portfolio.permission.status(session keys + evm agent only) for lighter polling. - Portfolio identity payloads expose blueprint lineage:
v2.portfolio.listreturnscanonical_strategy_blueprint_idandforked_from_blueprint_idon each portfolio row.v2.portfolio.getandv2.public.portfolio.getreturncanonical_strategy_blueprint_idandforked_from_blueprint_idon the nestedblueprintobject.- Consumers should prefer
forked_from_blueprint_idfor copied/forked portfolio strategy links, then fall back tocanonical_strategy_blueprint_idfor unchanged forks. - Dashboard owner-list rows also expose
forked_from_blueprint_idso campaign surfaces can fall back to direct fork lineage when canonical lineage is unavailable.
- Dashboard migration contracts:
v2.portfolio.listByOwnerDbV2is the db-v2-backed authenticated owner-list route used by the webapp dashboard shell.- Each row includes
id,archived,created_at,owner_address,owner_account_index,blueprint_name,blueprint_description,canonical_strategy_blueprint_id,forked_from_blueprint_id,is_public,primary_chain_id,updated_at, andvault_addresses. v2.portfolio.performance.seriesis now a typed envelope, not a raw array:meta: selected return methodology and currencyseries: historical/accounting performance pointsstats: backend-computedDAY | WEEK | MONTH | YEAR | ALLmetrics withabsoluteChangeUsdandreturnPct; the return method lives inmeta.returnMethodlive: current value plus normalizedlive.assets
live.assetsuses frontend-facing asset IDs directly and includes:assetId, optionaldbAssetId,symbol,decimals,priceUsd,valueUsd,liveBalanceFormatted,liveBalanceRaw,vaultAddress
v2.portfolio.performance.assetMetricsaugments live assets with db-v2 analytics:assetId, optionaldbAssetId,marketValueUsd,netInvestedUsd,pnlUsd,priceUsd,priceMissing,asOf
- Schedule APIs are manual-first and optional:
- new/forked portfolios do not auto-create a rebalance schedule. Backend-gated mirrored creates inherit the source portfolio’s configured rebalance schedule state, including no schedule;
createScheduledoes not override mirror schedule inheritance. Mirrored creates also snapshot source portfolio-local policy config when present and source effective swap preferences. v2.portfolio.schedule.getaddsscheduleStatus(active | paused | disabled | archived | null).- archived schedules are returned as non-active compatibility payloads (
scheduleExists=false,scheduleId=null,scheduleData=null) while still reportingscheduleStatus="archived". - archived schedules are terminal:
v2.portfolio.schedule.create,v2.portfolio.schedule.update, andv2.portfolio.schedule.setFromTextreturnSCHEDULE_ARCHIVEDand do not reactivate automation. v2.portfolio.schedule.runNowincludes additive operation identifiers:accepted,runId, andoperationId.
- new/forked portfolios do not auto-create a rebalance schedule. Backend-gated mirrored creates inherit the source portfolio’s configured rebalance schedule state, including no schedule;
- Scheduled function APIs are UTC-anchored:
v2.portfolio.scheduledFunctions.*is currently in beta/private rollout and is hidden in the webapp unless the relevant UI feature flag is enabled for the user.v2.portfolio.scheduledFunctions.listaccepts additiveincludeTargeting=trueto include schedules owned by the user’s investing account or other owned portfolios when their config targets the requested portfolio.v2.portfolio.scheduledFunctions.createsupports additivescheduleinput forfunctionKey="recurring_swap"andfunctionKey="recurring_transfer":hourly, or{ frequency: "daily" | "weekly", hourUtc, day? }.- The server translates that input into persisted
intervalMs/startAt/endAt. v2.portfolio.scheduledFunctions.deleteis a soft delete. Rows are retained for audit/history, marked deleted in storage, and excluded from normal list/run queries.hourUtcand weeklydayare interpreted in UTC; local DST shifts are not preserved.- Current product-supported handlers:
recurring_swap: buy a token with USDC or sell a token to USDC.recurring_transfer: move Base USDC from the Investing Account into an owned Base portfolio.
- Portfolio builder APIs:
v2.portfolio.builder.*persists pre-portfolio onboarding state indb-v2.builder.generateDraftacceptsportfolioConstructionPlanV2from the agent control plane. Platform-api treats typed slots, bps allocation, and hard constraints as the runtime contract; source-span text is trace evidence only.- Typed portfolio-construction plans preserve canonical exposure separately from the selected executable product. For example,
BTCexposure can resolve to BasecbBTCor EthereumWBTCwhile the response still carries the canonical underlying metadata (canonicalExposureId,canonicalExposureSymbol) alongside the resolved product/asset id. - Platform-api does not parse English, call model providers, or fall back to prompt-string strategy generation for v2 portfolio construction.
- Portfolio-construction draft responses include resolver review metadata on
construction:reviewStatus, per-assetrequiresConfirmation,reviewMode,reviewReason, candidate alternatives, andquestions[]for asset/chain/contract clarification before a generated draft is applied.
- Policy APIs are authoritative backend preflight surfaces:
v2.portfolio.policy.evaluateis the canonical machine-facing evaluator forrebalance,schedule,swap, andwithdraw.v2.portfolio.policy.scheduleAdvisoryis a convenience wrapper over the same schedule evaluation path.v2.portfolio.policy.config.get|updateis the additive Smart Portfolio configuration surface for declarative allocation, rebalance, and yield settings.- Observed portfolio facts for public policy evaluation come from backend resolvers, not caller-supplied exposure snapshots.
- Public
swapandwithdrawpolicy evaluation useschainIdsto derive request exposure. RawrequestExposureremains accepted temporarily for wire compatibility, but is ignored. - Public
rebalance,schedule, andscheduleAdvisorystill accept legacycurrentExposure/plannedExposurefields for compatibility, but those fields are ignored immediately. - Public
withdrawpolicy evaluation also ignores legacy rawportfolioTotalUsdandisFullWithdrawinputs; the backend derives those facts authoritatively when available. - Decision payloads preserve
status,allowed,primaryBlockingReason,reasons,remainingConditions, andnextEligibleAt, and now also include:authoritativefactsruleResults
primaryBlockingReasonand each entry inreasons[]now also include:policyIdcategorypolicySetIdwhen the reason came from a chain-scoped policy set
ruleResults[]now includes:reasonCodespolicyIdsreasonCoderemains as the first/primary code for backward compatibility.
facts[].statusdistinguishespresent,missing,stale, andsynthetic.facts[].origindistinguishes backend-loaded facts (server), backend-derived request facts (derived), and caller-supplied fallback/hypothetical facts (client).authoritative=trueonly means the triggered policy rules had sufficient trusted backend facts; missing, stale, synthetic-only, or client-sourced required facts downgrade the decision to non-authoritative.- Backend primary-chain resolution is authoritative: service loaders win over caller fallback hints, and
fallbackPrimaryChainIdis treated as a compatibility hint rather than the source of truth. remainingConditions[]may includemarket_availablefor Ondo-backedrebalanceandswapdecisions, carrying the blocked asset IDs, symbols, availability type (closedorhalted), andnextOpenAtwhen Ondo provides a reopen time.- Omitting
scheduleinput on the schedule policy endpoints returns the backend default cadence instead of a denial. - Schedule defaults are resolved through a backend chain-policy-set catalog. The generic non-ETH baseline is the current Base-mainnet policy shape, while Ethereum remains stricter because of gas-cost-driven limits.
- Multi-chain policy-set selection now prefers the authoritative primary chain over catalog order. If multiple supported policy sets match and the primary chain cannot disambiguate them, evaluation defers instead of picking one arbitrarily.
- Configured-but-unsupported chain policy sets fail closed with a blocking denial rather than silently inheriting the generic non-ETH baseline.
ETH Mainnet Product Limits (Effective March 2, 2026)
For portfolios whereprimary_chain_id = "1":
- Rebalance cooldown: max once every 24 hours.
- error:
REBALANCE_ETH_MAINNET_COOLDOWN_ACTIVE
- error:
- Schedule constraints:
- interval cadence only,
- interval must be
>= 24h, - default interval on schedule creation is
24h. - errors:
SCHEDULE_ETH_MAINNET_INTERVAL_TOO_SHORT,SCHEDULE_ETH_MAINNET_INTERVAL_ONLY
- Withdraw constraints:
- minimum selected withdraw amount
$10, - if portfolio total is below
$10, only full-balance withdraw is allowed. - errors:
WITHDRAW_ETH_MAINNET_BELOW_MINIMUM,WITHDRAW_ETH_MAINNET_FULL_REQUIRED_UNDER_MINIMUM
- minimum selected withdraw amount
- Swap-like actions (
kind="swap"andkind="swap_backend_quote"):- minimum notional
$10, - error:
LIFI_ETH_MAINNET_BELOW_MINIMUM_TRADE
- minimum notional
v2.portfolio.scheduledFunctions.*currently allows:functionKey="recurring_swap"functionKey="recurring_transfer"functionKey="recurring_account_deposit"for account-origin recurring deposits into an existing portfolio
- As of March 30, 2026:
recurring_swapis intentionally narrow: buy a token with USDC or sell a token to USDC.recurring_transferis intentionally narrow: transfer Base USDC from the Investing Account into an owned portfolio with an active Base vault.
Public Read Surface Notes
v2.public.portfolio.list,v2.public.portfolio.listByIds,v2.public.portfolio.get,v2.public.portfolio.vaults.list,v2.public.portfolio.schedule.get,v2.public.portfolio.performance.*,v2.public.portfolio.activity.list,v2.public.portfolio.activity.allocationHistory, andv2.public.portfolio.activity.allocationHistoryChartenforceis_public=true.- Private portfolios are treated as unreadable: detail-style routes return
PORTFOLIO_NOT_FOUND, list-by-id entries returnnull, and owner lists omit private rows.
- Private portfolios are treated as unreadable: detail-style routes return
- The web portfolio detail route now intentionally relies on this public-read surface for shared/read-only sections even when the viewer later authenticates as the owner.
- Shared page regions do not swap data sources after auth resolves.
v2.public.portfolio.listByIdsis the lightweight multi-card hydration route for curated public portfolio pages.- Input:
portfolioIds[]with up to 50 ids. - Output:
resultskeyed by portfolio id. - Missing or unreadable ids return
nullfor that entry instead of failing the whole batch.
- Input:
v2.public.explore.strategyDiscoveryBoardsis the backend-only V1 proof route for public Explore strategy rails.- It returns
highestTvl,mostUsed,newWithTraction,recentInflows, andfeaturedboards keyed by canonical root strategy blueprint. - The public route reads the latest successful DB-backed discovery snapshot. The engine refresh job recomputes the snapshot from cursor-backed hourly portfolio snapshots and hourly ledger aggregates every three hours in staging/production.
- Request-time reads do not scan raw ledgers, current positions, or asset prices.
- Forked/copied strategy instances roll up to their original root blueprint so board metrics match strategy-family semantics.
- Rollup equivalence currently uses full stored-spec equality between the instance version and root head version as a temporary guard. A follow-up should add and backfill an indexed
target_allocation_hashonstrategy_blueprint_versions, generated from validated target allocation extraction on version writes, then switch discovery equivalence to that allocation hash. - Ranked and featured boards apply discoverability filters so placeholder/internal roots such as Investing Account, Glider Reserve, blank, untitled, and test/refactor/staging strategies do not appear. Featured ids are de-duped and must resolve to roots with active usage.
activeInstanceCountandactiveUserCountcount active non-archived strategy instances/users under the public root, including private instances, while preserving the root-public and spec-equivalence filters.usageCountis the display adoption metric used by themostUsedboard. It matches the strategy page count semantics: direct mirrors plus direct forks plus configured display seed users.- TVL uses fresh active instances, including private instances under public roots;
recentInflowsremains based on public active instances with positive 7-day net inflow. newWithTractionrequires the configured active-user floor plus TVL and either enough active users or positive 7-day net inflow.- Production input is fixed to the warmed snapshot profile for now:
limitPerBoard=3,freshnessWindowHours=24,newWindowDays=30,minTvlUsd=50,newWithTractionMinUserCount=2,featuredLegacyBlueprintIds=[]. - Staging/non-production callers can use the relaxed profile:
minTvlUsd=0,newWithTractionMinUserCount=1, and the configured staging featured ids. The staging profile fails closed unless the server config explicitly marks the environment as non-production. - Unsupported request params are rejected so public reads cannot trigger ad hoc discovery recomputation outside the scheduled snapshot job.
- Output includes body-level cache metadata (
source,stale,ttlSeconds) so clients can surface stale snapshots while the last good refresh remains available. - Output data includes additive
schemaVersion=1andexplorefields while preservingdata.boardsfor existing consumers. The v1 Explore payload contains versionedtagAggregatesandtrendingTokensgenerated with 7-day and 30-day flow windows from the cached discovery snapshot.
- It returns
v2.public.account.profile.getis the canonical user-profile hydration endpoint used by/user/:userId.- It returns the user-page aggregate header value, Investing Account snapshot, and profile portfolio cards/table payload in one response.
- Profile portfolio rows include only active public non-Investing Account portfolios for the account.
- Private profile portfolios are omitted from the public response so the API does not reveal private portfolio count or ordering.
- Public profile portfolio
currentValueUsdandtokenPreviewscome from the dashboard-normalized account profile snapshot so the table can render without per-portfolio vault/performance fan-out; token previews include best-effort icon popover metadata such as name, chain/address, current price, and token value when already available, but the endpoint does not hydrate per-portfolio performance series. - Profile portfolio return fields may be
nullwhen no cheap aggregate is available. Use the dedicated public performance endpoints for return charts or detailed performance metrics. - User-profile schedule rendering is db-v2-only and public-row-only: public portfolios without a v2 schedule row surface as manual/unscheduled.
v2.public.account.assets.list,v2.public.account.performance.chart,v2.public.account.performance.series, andv2.public.account.performance.overviewaggregate only the public portfolio subset for the requested owner. Mixed public/private accounts no longer fail closed solely because private portfolios exist.
Agent Auth Notes
v2.agentAuth.createApiKeykeepsownerAddressoptional for compatibility.- If
ownerAddressis provided, it must match the authenticated wallet address. - API key metadata persists the canonical wallet owner; mismatches are rejected.
Webapp Migration Status
As of February 28, 2026, the webapp is migrating in phases:- Migrated to
v2.*in owner-authenticated flows:- portfolio create/confirm and permission refresh
- owner portfolio list/get/archive
- desired-state composer preview/draft/review/apply via
v2.portfolio.updates.* - owner schedule get/create/update/setFromText/pause/resume/runNow
- bridge submit via
v2.portfolio.actions.submit - withdraw/transfer submit via
v2.portfolio.actions.submit - swap submit via
v2.portfolio.actions.submit(kind="swap") - backend-quoted swap submit via
v2.portfolio.actions.submit(kind="swap_backend_quote") - unified history via
v2.portfolio.activity.list - rebalance runtime status reads via
v2.portfolio.status - execution polling via
v2.executions.getin migrated withdraw/transfer/swap paths
- Soft deprecation logging is enabled for selected migrated v1 procedures.
- Migrated to
v2.public.*in non-owner/public flows:- user profile hydration via
v2.public.account.profile.get - competition row vault data + schedule reads
- user profile hydration via
Deferred v1 Allowlist
The following remain on v1 in this phase by design:- Operation/status exceptions with no clean parity signal yet:
- withdraw-as-target-asset (
ETH/USDC) is unsupported and rejected client-side (legacy backend path is also unsupported)
- withdraw-as-target-asset (
- Public/unowned reads that conflict with v2 owner-auth requirements:
- public profile and competition views
- OG routes and legacy routes reading arbitrary user portfolio data
- Analytics endpoints without v2 parity:
- chart/account/competitor/backtest-style
strategyInstances.*
- chart/account/competitor/backtest-style
schedules.getSchedulesForMultipleStrategies(no v2 equivalent yet)