{"components":{"schemas":{"ActionResponse":{"properties":{"actionType":{"type":"string"},"collateralAfter":{"type":["string","null"]},"collateralDelta":{"type":["string","null"]},"collateralDeposited":{"type":["string","null"]},"entryPrice":{"type":["string","null"]},"failureDetails":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserErrorDetails"}]},"failureReason":{"type":["string","null"]},"forcedCloseReason":{"description":"Set when the protocol closed this leg on the user's behalf rather than\nthe user asking for it — today only `loan_gap_backstop`, the ILP loan\nprotection flattening a position whose mark reached the loan floor.\n\nThe chain can't tell us this: the operator signs every user action, so\na backstop close is shape-identical to a user's own market close and the\nindexer (which reads liquidations off the VENUE instruction\ndiscriminators) records a plain `decrease`. Had the venue's own loan\nstop won that race, `action_type` would already be `liquidation` for the\nidentical event. So this rides alongside `action_type` rather than\noverwriting it — the PnL and fee paths keyed on `\"decrease\" |\n\"liquidation\"` keep their existing meaning, and only the label changes.","type":["string","null"]},"hadSwap":{"type":"boolean"},"hasProfit":{"type":["boolean","null"]},"id":{"type":"string"},"interest":{"type":["string","null"]},"jupiterFee":{"type":["string","null"]},"orderCreatedAt":{"format":"int64","type":["integer","null"]},"orderDcaEndPrice":{"type":["string","null"]},"orderDcaIntervalSeconds":{"format":"int64","type":["integer","null"]},"orderDcaLegNumber":{"format":"int32","type":["integer","null"]},"orderDcaLegTrigger":{"type":["string","null"]},"orderDcaNumLegs":{"format":"int64","type":["integer","null"]},"orderDcaPriceLimit":{"type":["string","null"]},"orderDcaStartPrice":{"type":["string","null"]},"orderExecutionTriggerPrice":{"type":["string","null"]},"orderIndicatorConfig":{"type":["string","null"]},"orderRatchetSize":{"type":["string","null"]},"orderSizeUsd":{"type":["string","null"]},"orderSlippageBps":{"format":"int32","type":["integer","null"]},"orderThresholdValue":{"type":["string","null"]},"orderTriggerDelaySecs":{"format":"int64","type":["integer","null"]},"orderType":{"type":["string","null"]},"orderWaitDurationSecs":{"format":"int64","type":["integer","null"]},"orderWaitPrice":{"type":["string","null"]},"orderWorstPrice":{"type":["string","null"]},"pairsImpactFee":{"description":"Skew-impact share of this action's fee, already included in\n`jupiterFee`. Present only for pairs actions charged after the fee\nmode shipped — `None` means no such fee was levied, NOT that it was\nzero, so callers should omit the line rather than render 0.00.","type":["string","null"]},"platformFee":{"type":["string","null"]},"pnlRealized":{"type":["string","null"]},"positionSizeAfter":{"type":["string","null"]},"postLiquidationPriceUsd":{"type":["string","null"]},"proOrderFee":{"type":["string","null"]},"sequenceNumber":{"format":"int32","type":"integer"},"sizeDelta":{"type":["string","null"]},"sizeDeltaTokens":{"type":["string","null"]},"status":{"type":"string"},"swapSlippage":{"type":["string","null"]},"triggerCondition":{"type":["string","null"]},"triggerPrice":{"type":["string","null"]},"tx1Signature":{"type":"string"},"tx1Timestamp":{"format":"int64","type":"integer"},"tx2Signature":{"type":["string","null"]},"tx2Timestamp":{"format":"int64","type":["integer","null"]},"tx3PlatformFee":{"type":["string","null"]},"tx3Signature":{"type":["string","null"]},"tx3SwapFee":{"type":["string","null"]},"tx3SweepSlippage":{"type":["string","null"]},"tx3Timestamp":{"format":"int64","type":["integer","null"]},"userReceived":{"type":["string","null"]}},"required":["id","actionType","status","sequenceNumber","tx1Signature","tx1Timestamp","hadSwap"],"type":"object"},"ApiTouchPosition":{"description":"One touch contract, open or finished.","properties":{"askBps":{"description":"Fill price as bps of payout (`premiumUsd / payoutUsd × 10000`).","format":"int32","type":"integer"},"barrier1e9":{"description":"Barrier price, 1e9 scale.","format":"int64","type":"integer"},"expiryTs":{"description":"Cohort expiry, unix seconds. Settlement is automatic at this time.","format":"int64","type":"integer"},"isTouch":{"description":"True = Touch (pays if spot ever reaches the barrier before expiry),\nfalse = No-Touch (pays if it never does).","type":"boolean"},"marketId":{"format":"int32","type":"integer"},"openTs":{"description":"Cohort open, unix seconds — shared by every contract in the cohort.","format":"int64","type":"integer"},"openTxSig":{"type":["string","null"]},"payoutPaidUsd":{"description":"What the wallet actually received, µUSD — the payout on a win, the\nbuyback refund on a sell-back, 0 on a loss. Null while `open`.","format":"int64","type":["integer","null"]},"payoutUsd":{"description":"Payout if the contract wins, µUSD.","format":"int64","type":"integer"},"positionId":{"description":"Per-market contract id, from 0.","format":"int64","type":"integer"},"premiumUsd":{"description":"Premium actually paid at the fill, µUSD — never the staged budget.","format":"int64","type":"integer"},"settleTxSig":{"description":"Signature of the settle or buyback transaction. Null while `open`.","type":["string","null"]},"settledAt":{"format":"int64","type":["integer","null"]},"status":{"description":"`open` — live; `settled` — expired and paid out by the keeper;\n`bought_back` — sold back to the pool before expiry.","type":"string"},"symbol":{"description":"On-chain market symbol, e.g. `SOLTOUCH`.","type":"string"},"won":{"description":"Whether the contract paid out. Null while `open`, and on a buyback\n(which resolves the contract before the outcome is known).","type":["boolean","null"]}},"required":["marketId","symbol","positionId","isTouch","barrier1e9","payoutUsd","premiumUsd","askBps","openTs","expiryTs","status"],"type":"object"},"ArmadaActivityBucket":{"properties":{"longOiUsd":{"type":"string"},"shortOiUsd":{"type":"string"},"timestamp":{"format":"date-time","type":"string"},"tradeCount":{"format":"int64","type":"integer"},"volumeUsd":{"type":"string"}},"required":["timestamp","volumeUsd","tradeCount","longOiUsd","shortOiUsd"],"type":"object"},"ArmadaActivityResponse":{"properties":{"grouping":{"type":"string"},"period":{"type":"string"},"rows":{"items":{"$ref":"#/components/schemas/ArmadaActivityBucket"},"type":"array"}},"required":["period","grouping","rows"],"type":"object"},"ArmadaBalances":{"description":"Snapshot-only balance monitor. Thresholds share the treasury report's model;\ntransfers and pledged customer funds stay separate from owned capital.","properties":{"capturedAt":{"type":"string"},"customerFunds":{"description":"Settled customer collateral plus pending request escrow, wherever held.","format":"double","type":"number"},"escrowed":{"format":"double","type":"number"},"estimatedOwedToUsers":{"description":"Nonnegative claims per seat, including marked PnL, before funding/exit fees.\nOlder snapshots do not contain this estimate.","format":"double","type":["number","null"]},"hedge":{"$ref":"#/components/schemas/HedgeCapital"},"hedgeImperialFloat":{"format":"double","type":"number"},"inFlightToHedge":{"description":"Model-observed transfers, not confirmed destination balances.","format":"double","type":"number"},"inFlightToSolana":{"format":"double","type":"number"},"marksComplete":{"type":"boolean"},"modelVersion":{"type":"string"},"pledge":{"$ref":"#/components/schemas/PledgeCapital"},"pledgedRemote":{"format":"double","type":"number"},"rails":{"$ref":"#/components/schemas/RailsCapital"},"solana":{"$ref":"#/components/schemas/SolanaCapital"},"solanaFreeFloat":{"format":"double","type":"number"}},"required":["capturedAt","modelVersion","marksComplete","customerFunds","solana","hedge","pledge","rails","solanaFreeFloat","hedgeImperialFloat","pledgedRemote","escrowed","inFlightToHedge","inFlightToSolana"],"type":"object"},"ArmadaBalancesResponse":{"properties":{"snapshot":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ArmadaBalances"}]}},"type":"object"},"ArmadaBookLevel":{"description":"One aggregated level, in the venue's own `PRICE / SIZE / TOTAL` shape.","properties":{"price":{"format":"double","type":"number"},"quoteSize":{"description":"Sum of the original price times size, before display grouping.","format":"double","type":"number"},"size":{"description":"Base resting at this price.","format":"double","type":"number"},"total":{"description":"Base at this level and every better one. Sent rather than left to the\nclient to accumulate, so a truncated response cannot disagree with the\nwalk a quote is priced off.","format":"double","type":"number"}},"required":["price","size","quoteSize","total"],"type":"object"},"ArmadaCapitalStatus":{"description":"The public status: ratios, an enum and a boolean. **No dollar figures.**\n\n⚠ Nothing here may leak the size of Imperial's book — that is the whole\nreason it is a separate type from [`ArmadaTreasuryResponse`] rather than a\nprojection of it, so a field added to the report cannot arrive here by\naccident.","properties":{"hedgeHealth":{"description":"[`the hedge venueHealth::code`].","type":["string","null"]},"opensOpen":{"description":"Whether opens are being taken. `false` with no snapshot: no capital view\nis `Limit::NoCapitalView`, which refuses opens.","type":"boolean"},"solanaHealth":{"description":"[`SolanaHealth::code`].","type":["string","null"]},"tillRatio":{"description":"The share of locked margin the till holds back.","format":"double","type":["number","null"]},"utilisation":{"description":"Initial margin over total account value on the hedge venue — how much of\nthe account the hedge consumes. A ratio, so it says nothing about size.","format":"double","type":["number","null"]}},"required":["opensOpen"],"type":"object"},"ArmadaDecline":{"description":"Why a served quote was withheld. Every variant is an expected outcome, not\na fault: the caller spills the order to another venue.","enum":["book_too_thin","book_stale","cheaper_elsewhere","unpriceable"],"type":"string"},"ArmadaFillsResponse":{"properties":{"markets":{"description":"Empty when nothing was requested in the window. A market with no rows is\na market with no traffic, not a market with zero slippage.","items":{"$ref":"#/components/schemas/ArmadaMarketFills"},"type":"array"},"windowHours":{"format":"int32","type":"integer"}},"required":["windowHours","markets"],"type":"object"},"ArmadaLiquidationPreviewConfig":{"properties":{"fee1e6":{"format":"int32","minimum":0,"type":"integer"},"maintenanceMarginBps":{"format":"int32","minimum":0,"type":"integer"},"reserveCapBps":{"description":"Conservative cap across configured depth-dependent closing reserves.","format":"int64","minimum":0,"type":"integer"}},"required":["maintenanceMarginBps","reserveCapBps","fee1e6"],"type":"object"},"ArmadaMarginResponse":{"description":"One Armada seat's balance, and what of it pays out when.\n\n⚠ Every money field is `Option`. A seat the api could not read reports\n`null`, never `0` — an unknown balance rendered as zero is a recorded\nproduct bug, and the client shows \"—\" for it.","properties":{"arrivingEtaSecs":{"description":"How long the rest takes: the outbound rail's MEASURED settlement, not\nthe (deliberately pessimistic) window the till is sized over.","format":"int64","type":["integer","null"]},"collateralUsd":{"description":"Settled collateral on this seat.","format":"double","type":["number","null"]},"drawableUsd":{"description":"CROSS seat only: what the bot draws from this Armada account toward an\nisolated open before charging the wallet — the whole balance when the\nseat retains it and is flat, not Prime-bound and not reserved for\nliquidation, else `0`. Null on an isolated seat or an unreadable one.","format":"double","type":["number","null"]},"effectiveCollateralUsd":{"format":"double","type":["number","null"]},"freeCollateralUsd":{"description":"Equity after current losses, funding, initial margin and the withdrawal\nbuffer, capped by settled free cash. Null when health is unavailable.","format":"double","type":["number","null"]},"heldProfitUsd":{"description":"Recent profit still inside its hold, across both held tranches. Null\nwhen the guard is not live or unreadable.","format":"double","type":["number","null"]},"initialMarginUsd":{"format":"double","type":["number","null"]},"liquidationPending":{"type":["boolean","null"]},"liquidationThresholdMaxUsd":{"format":"double","type":["number","null"]},"liquidationThresholdMinUsd":{"description":"Authorized threshold range. Execution depth selects a value within it.","format":"double","type":["number","null"]},"maintenanceMarginBps":{"format":"int32","minimum":0,"type":["integer","null"]},"maintenanceMarginUsd":{"format":"double","type":["number","null"]},"openPositions":{"format":"int64","minimum":0,"type":["integer","null"]},"primeBound":{"description":"Prime-bound seats keep partial-close proceeds locked until the position is flat; collateral edits are unavailable.","type":["boolean","null"]},"profitAvailableAt":{"description":"Unix seconds at which the older held tranche frees itself — the next\ntime any held profit becomes withdrawable.","format":"int64","type":["integer","null"]},"profitFullyAvailableAt":{"description":"Unix seconds at which every held tranche has freed.","format":"int64","type":["integer","null"]},"retainsCollateral":{"type":["boolean","null"]},"withdrawableNowUsd":{"description":"`min(free collateral, vault balance − escrow)` — what the vault can\nactually pay this instant.","format":"double","type":["number","null"]},"withdrawableTotalUsd":{"description":"The whole free balance, some of which may be arriving.","format":"double","type":["number","null"]}},"type":"object"},"ArmadaMarket":{"description":"One market Armada will quote, with everything a market list renders.\n\n⚠ Mirrors the venue's own parameters rather than restating ours. Imperial\nmust never advertise a cap looser than the venue carrying the risk, and the\nonly way that stays true across an upgradable contract is to read it back.","properties":{"crossLiquidation":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ArmadaLiquidationPreviewConfig","description":"The same inputs for the CROSS seat, which never takes a tier."}]},"crossMaxLeverage":{"description":"The opening leverage of the CROSS seat, which never takes a tier: the\nlower of the hedge market's leverage and the account ceiling.","format":"double","type":"number"},"crossMmr":{"description":"The CROSS seat's maintenance margin: the hedge venue's, never a tier.","format":"double","type":"number"},"dailyPriceChange":{"description":"Signed percent.","format":"double","type":["number","null"]},"dailyQuoteTokenVolume":{"format":"double","type":["number","null"]},"fundingRate1hPct":{"description":"The rate accruing over the CURRENT funding hour, as the venue publishes\nit: a PERCENT, so `0.0025` renders as `0.0025%`. the hedge venue funds hourly.\n\n⚠ Off the websocket, not off `orderBookDetails` — the catalog carries\nthe funding PARAMETERS (clamps, premium multiplier) but not the rate.\n`None` for a market whose stats have aged out, which a client must\nrender as unknown rather than as zero: a market paying nothing and a\nmarket we cannot read are different facts.","format":"double","type":["number","null"]},"imperialFundingRate1hPct":{"description":"Armada's own live on-chain rate, hourly percent (positive: longs pay).\nAbsent when unread or stale; never substitute the hedge venue's rate.","format":"double","type":["number","null"]},"indexPrice":{"format":"double","type":["number","null"]},"lastTradePrice":{"format":"double","type":["number","null"]},"liquidation":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ArmadaLiquidationPreviewConfig","description":"Imperial's own opening-ticket risk inputs for an ISOLATED seat, not the\nhedge venue's MMR: the market's tier when it has one."}]},"listedAt":{"description":"Unix seconds. `None` when the venue omitted it.\n\n⚠ Load-bearing, not decoration: a market younger than the VaR window has\nno measurable tail, and a mark series from before it listed is not a\nprice history — it is noise that reads as enormous volatility.","format":"int64","type":["integer","null"]},"markPrice":{"format":"double","type":["number","null"]},"marketId":{"description":"The venue's market id. Stable, and what the book channels are keyed on.","format":"int32","type":"integer"},"maxLeverage":{"description":"The opening leverage of an ISOLATED seat: the market's on-chain tier,\nnet of fees, when it has one; else the lower of the hedge market's\nleverage and Imperial's on-chain account ceiling.","format":"double","type":"number"},"minBaseAmount":{"format":"double","type":"number"},"minQuoteAmount":{"format":"double","type":"number"},"mmr":{"description":"Maintenance margin as a fraction of notional, for an ISOLATED seat:\nthe market's tier when it has one.","format":"double","type":"number"},"openInterest":{"description":"Base units from `orderBookDetails`. Multiply by mark price for USD OI.","format":"double","type":["number","null"]},"priceDecimals":{"format":"int32","minimum":0,"type":"integer"},"sizeDecimals":{"description":"Smallest base increment the venue accepts. A close below one lot cannot\nbe hedged, which is what stranded sub-lot dust on every full close until\nthe rounding was made leg-aware.","format":"int32","minimum":0,"type":"integer"},"symbol":{"type":"string"}},"required":["symbol","marketId","maxLeverage","crossMaxLeverage","mmr","crossMmr","sizeDecimals","priceDecimals","minBaseAmount","minQuoteAmount"],"type":"object"},"ArmadaMarketFills":{"description":"One `(hedge_venue, market)` window.","properties":{"acceptRate":{"description":"Accepted over settled. ⚠ The denominator EXCLUDES pending, so a window\nfull of in-flight requests reads as the rate of what has actually\nsettled rather than being dragged toward zero by rows with no outcome\nyet. `None` when nothing has settled — never 1.0.","format":"double","type":["number","null"]},"accepted":{"format":"int64","type":"integer"},"adverseBps":{"description":"The adverse edge: 9 in 10 fills landed better than this.","format":"double","type":["number","null"]},"expired":{"format":"int64","type":"integer"},"filledSizeUsd":{"description":"What the venue filled, in USD.","format":"double","type":"number"},"hedgeVenue":{"description":"The venue that BACKED the fills, or that the refusals were aimed at.\n`\"any\"` where the request named none — a different fact from naming the\nvenue that happens to be live, and kept as one.","type":"string"},"market":{"description":"The asset key. Not a mint: most of what this venue exists to reach has\nno Solana token.","type":"string"},"medianBps":{"description":"Median of the signed fill-vs-expected series. **Positive means the fill\nwas BETTER for the user than the price the order stated.** `None` for a\nwindow with no fills — never `0.0`, which is a real and different\nanswer.","format":"double","type":["number","null"]},"pending":{"description":"Still in flight, or settled by a transaction this service never saw.","format":"int64","type":"integer"},"rejected":{"format":"int64","type":"integer"},"rejects":{"description":"Why the rejects were rejected. Empty when there were none.","items":{"$ref":"#/components/schemas/ArmadaRejectCount"},"type":"array"},"requested":{"format":"int64","type":"integer"}},"required":["hedgeVenue","market","requested","accepted","rejected","expired","pending","filledSizeUsd","rejects"],"type":"object"},"ArmadaMarketStats":{"description":"The venue's published numbers for one market, alongside its book.\n\nCarried on the same response as the book because they are read from the same\nfeed under the same freshness rule — splitting them into a second endpoint\nwould let a client render a mark from one moment against a book from\nanother, which is the exact pairing [`FeedState::quote_inputs`] exists to\nprevent on the pricing side.\n\n[`FeedState::quote_inputs`]: armada_client::FeedState::quote_inputs","properties":{"fundingRate1hPct":{"description":"The rate accruing over the CURRENT funding hour, as the HEDGE VENUE\npublishes it: a PERCENT, so `0.0025` renders as `0.0025%`. the hedge venue funds\nhourly, so this is its `1HR FUNDING` with no rescaling.\n\n⚠ **This is not what a Armada position is charged.** It is what the\nvenue carrying Imperial's hedge charges Imperial, published because it\nis real, public and worth seeing beside the book it belongs to. What the\nuser pays is [`Self::imperial_funding_rate_1h_pct`], and a surface that\nrenders one under the other's label is telling the user something false.","format":"double","type":["number","null"]},"imperialFundingRate1hPct":{"description":"**Imperial's own** funding rate on this market, hourly percent, signed so\nthat positive ⇒ longs pay shorts. Read off the `imperial_sunrise`\n`FundingIndex` account, so it is what the program will actually settle.\n`None` when no rate is in force on chain — no accumulator yet, or the\nlast publication is past `max_funding_staleness_secs`, in which case\nnothing accrues.\n\n⚠ Not derivable from [`Self::funding_rate_1h_pct`]. The publisher folds\nin Imperial's own book skew and scales by the book's marked-to-entry\nratio (the program charges on entry notional; the venue bills Imperial on\nmarked), so this is not the venue's rate plus a spread.","format":"double","type":["number","null"]},"indexPrice":{"format":"double","type":["number","null"]},"lastTradePrice":{"description":"The venue's last traded price — what the chart's live candle follows,\nsince its history is the trade series. Never a pricing input.","format":"double","type":["number","null"]},"markPrice":{"description":"The venue's own mark — what it margins and liquidates against, and the\nprice a Armada quote is anchored to.","format":"double","type":["number","null"]},"nextFundingAtMs":{"description":"Unix ms at which the current funding hour settles. `None` unless the\nvenue's timestamp is genuinely ahead of now and inside\n[`MAX_FUNDING_LOOKAHEAD_MS`] — a past timestamp is the LAST funding, and\nlabelling it \"next\" is worse than showing nothing.\n\n⚠ Belongs to [`Self::funding_rate_1h_pct`] alone. Imperial's own accrual\nis CONTINUOUS — `rate × dt` for whatever `dt` a position existed, with no\nconcept of an hour — so there is no next-funding instant to count down\nto, which is exactly why the open-after/close-before game does not work\nhere.","format":"int64","type":["integer","null"]}},"type":"object"},"ArmadaMarketStatus":{"properties":{"hedgeVenue":{"type":"string"},"market":{"type":"string"},"phase":{"$ref":"#/components/schemas/GuardPhase"}},"required":["hedgeVenue","market","phase"],"type":"object"},"ArmadaMetrics":{"properties":{"acceptRate":{"description":"`None` before the first request, rather than a misleading 1.0.","format":"double","type":["number","null"]},"markout1s":{"$ref":"#/components/schemas/MarkoutSummary"},"markout2s":{"$ref":"#/components/schemas/MarkoutSummary"},"rejected":{"format":"int64","minimum":0,"type":"integer"},"rejectsByCode":{"additionalProperties":{"format":"int64","minimum":0,"type":"integer"},"propertyNames":{"type":"string"},"type":"object"},"served":{"format":"int64","minimum":0,"type":"integer"}},"required":["served","rejected","rejectsByCode","markout1s","markout2s"],"type":"object"},"ArmadaOpenAvailability":{"properties":{"available":{"type":"boolean"},"reason":{"type":["string","null"]}},"required":["available"],"type":"object"},"ArmadaOrderbook":{"description":"The hedge venue's live book for one market, as the trading panel renders it.","properties":{"asks":{"items":{"$ref":"#/components/schemas/ArmadaBookLevel"},"type":"array"},"bestAsk":{"format":"double","type":["number","null"]},"bestBid":{"format":"double","type":["number","null"]},"bids":{"description":"Best price first: bids descending, asks ascending.","items":{"$ref":"#/components/schemas/ArmadaBookLevel"},"type":"array"},"bookAgeMs":{"description":"⚠ Milliseconds since the book last **changed**, not since it was\nconfirmed. A quiet market's book ages without the venue going anywhere,\nso a client must not read this as liveness — [`Self::venue_reachable`]\nis that signal.","format":"int64","type":"integer"},"hedgeVenue":{"description":"Whose book this is, in the same literal `/armada/status` reports.","type":"string"},"market":{"type":"string"},"maxAgeMs":{"description":"The carry bound this book was served under, so a client can render its\nown staleness without hard-coding a number the server owns.","format":"int64","type":"integer"},"mid":{"format":"double","type":["number","null"]},"spread":{"format":"double","type":["number","null"]},"spreadBps":{"format":"double","type":["number","null"]},"stats":{"$ref":"#/components/schemas/ArmadaMarketStats"},"venueReachable":{"description":"Whether ANY event has arrived from the venue recently, across every\nchannel. This is what says the venue is still there.","type":"boolean"}},"required":["hedgeVenue","market","bids","asks","bookAgeMs","maxAgeMs","venueReachable","stats"],"type":"object"},"ArmadaQuote":{"properties":{"allInBps":{"description":"What the user pays over the mark, all in.","format":"double","type":"number"},"bookAgeMs":{"format":"int64","type":"integer"},"chargedHedgeCostBps":{"description":"Book slippage charged after the risk gate's widen multiplier.\nExcludes the venue fee, which is already covered by `fee_bps`.","format":"double","type":"number"},"feeBps":{"description":"Total per-leg fee, covering the venue fee and Imperial's margin. Never widened.","format":"double","type":"number"},"hedgeBaseSize":{"description":"Base units the hedge will be sent in. The unit the venue's lot step\nrounds, and therefore the unit the unhedged residual lives in.","format":"double","type":"number"},"hedgeCostBps":{"description":"Marginal cost of hedging THIS order at THIS size, as measured: the walk's\nsigned slippage against the mark plus the venue's flat taker fee.\n\nSIGNED, and can be negative when the mark happens to sit on the far side\nof the touch. Recorded as measured because it is the input to grading.\nThe quote floors the slippage component at zero (see\n`charged_hedge_cost_bps`) rather than paying a user for a book/mark\nrelationship the hedge may not capture a second later.","format":"double","type":"number"},"hedgeVenue":{"$ref":"#/components/schemas/Venue"},"hedgeWorstPrice":{"description":"The limit an IOC hedge would need to carry to fill this size.","format":"double","type":"number"},"levelsConsumed":{"minimum":0,"type":"integer"},"mark":{"description":"The venue mark the quote was struck against. Persisted so markout can be\ngraded against it after the fill.","format":"double","type":"number"},"market":{"type":"string"},"notionalUsd":{"format":"double","type":"number"},"quotePrice":{"description":"`mark ± all_in`, on the side the user trades.","format":"double","type":"number"},"widenMult1e3":{"format":"int32","minimum":0,"type":"integer"}},"required":["hedgeVenue","market","mark","hedgeCostBps","chargedHedgeCostBps","feeBps","allInBps","quotePrice","notionalUsd","hedgeBaseSize","hedgeWorstPrice","levelsConsumed","widenMult1e3","bookAgeMs"],"type":"object"},"ArmadaRejectCount":{"description":"One reject reason's share of a window.","properties":{"category":{"format":"int32","type":"integer"},"categoryName":{"type":["string","null"]},"count":{"format":"int64","type":"integer"},"reason":{"description":"`imperial_sunrise`'s own reason byte.","format":"int32","type":"integer"},"reasonName":{"description":"The reason's stable wire name, from the program. `null` for a byte this\ndeployment does not classify — which means the chain and this binary\ndisagree, and inventing a label would hide that.","type":["string","null"]}},"required":["reason","category","count"],"type":"object"},"ArmadaSettledMetrics":{"description":"The venue-wide settlement record, summed across every market in the window.\n\n⚠⚠ This is NOT the same number as [`crate::sunrise::metrics`]'s accept rate\nand the two must never be presented as one. The in-process counter measures\nthe GATE: how many orders the risk check admitted, where a refusal never\nreached the chain and left no row anywhere. This measures what happened to\nthe requests that DID reach the chain. A gate refusal is absent from this\ndenominator entirely, and a request rejected on chain is absent from that\none. Both are real; conflating them would produce a rate describing neither.","properties":{"acceptRate":{"description":"`None` when nothing has settled in the window — never 1.0.","format":"double","type":["number","null"]},"accepted":{"format":"int64","type":"integer"},"expired":{"format":"int64","type":"integer"},"filledSizeUsd":{"format":"double","type":"number"},"pending":{"format":"int64","type":"integer"},"rejected":{"format":"int64","type":"integer"},"rejectsByReason":{"description":"Pooled across markets, because the claim being checked — that rejects\nare uncorrelated with price direction — is a claim about the venue.","items":{"$ref":"#/components/schemas/ArmadaRejectCount"},"type":"array"},"requested":{"format":"int64","type":"integer"},"windowHours":{"format":"int32","type":"integer"}},"required":["windowHours","requested","accepted","rejected","expired","pending","filledSizeUsd","rejectsByReason"],"type":"object"},"ArmadaStatusResponse":{"properties":{"enabled":{"type":"boolean"},"markets":{"description":"Per-`(hedge_venue, market)`, because none of the state behind a phase\nnets across venues.","items":{"$ref":"#/components/schemas/ArmadaMarketStatus"},"type":"array"},"metrics":{"$ref":"#/components/schemas/ArmadaMetrics","description":"⚠ THE GATE's accept rate, and markout. In-process, so it resets on every\ndeploy — which is a real limit on it as a \"published commitment\", and\nthe reason [`Self::settled`] exists beside it. What it measures cannot\ncome from the chain: a gate refusal never becomes a request, so it\nleaves no row anywhere. Markout is the same — it samples the venue's mid\none and two seconds after a quote, and nothing on chain records a later\nmid — so it stays here and is honestly per-process."},"openQuotes":{"minimum":0,"type":"integer"},"settled":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ArmadaSettledMetrics","description":"⚠ THE CHAIN's accept rate, over a trailing window of `sunrise_requests`.\nSurvives a restart, and answers a DIFFERENT question from `metrics`: of\nthe requests that actually reached the chain, how many filled. A gate\nrefusal is absent from this denominator; an on-chain reject is absent\nfrom that one. `None` when the ledger is unreadable — this endpoint's\njob is to say whether the venue is fit to serve, and that must not go\ndark because Postgres hiccuped."}]}},"required":["enabled","markets","metrics","openQuotes"],"type":"object"},"ArmadaTreasuryResponse":{"description":"The whole read. Every section is `None` before the treasury loop has ever\nwritten a snapshot — a well-formed body, never a 500.","properties":{"capacity":{"description":"Empty when the api has no account view of the hedge venue. Capacity is\ncomputed from the api's OWN feed, not from the snapshot, because it is\nthe answer to \"may I open now\", and the snapshot is minutes old.","items":{"$ref":"#/components/schemas/MarketCapacity"},"type":"array"},"floorDrift":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FloorDriftReport","description":"The api's own the hedge venue floor against the bot's. `None` when the api could\nnot recompute one — no account view, or no snapshot to compare with."}]},"hedge":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/HedgeCapital"}]},"imperial":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ImperialLines"}]},"pledge":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PledgeCapital"}]},"rails":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/RailsCapital"}]},"snapshot":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SnapshotMeta"}]},"solana":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SolanaCapital"}]}},"required":["capacity"],"type":"object"},"BuildDepositTxRequest":{"properties":{"amount":{"description":"Deposit amount in USDC native units (6-decimal — `1_000_000` = $1).","format":"int64","minimum":0,"type":"integer"},"deferOperatorSignature":{"description":"Return the transaction with the operator's signature slot still empty,\nto be handed back to `/deposit/cosign-tx` once the wallet has signed\nit. Wallets can't simulate a transaction that already carries a\nsignature from another account, so they warn the user that it may be\nunsafe; signing second avoids that. Defaults to false, which returns\nthe partially-signed transaction described below.","type":"boolean"},"mode":{"description":"`\"deposit\"` to credit USDC directly from the wallet's USDC ATA into the\nprofile's USDC ATA, or `\"withdraw\"` to pull USDC out of the profile back\nto the wallet's USDC ATA. Funding from native SOL is not supported —\nwrap to WSOL on the client first.","type":"string"},"profileIndex":{"description":"Sub-account index (0..5) the deposit lands in.","format":"int32","minimum":0,"type":"integer"},"wallet":{"description":"Owner wallet base58 pubkey. The returned tx is partially signed by the\norder-bot operator (sponsoring rent + ATA creation) and must be signed\nby this wallet before submission.","type":"string"}},"required":["wallet","profileIndex","amount","mode"],"type":"object"},"BuildDepositTxResponse":{"properties":{"transaction":{"description":"Base64-encoded `VersionedTransaction`. Decode, sign with the wallet's\nkeypair, and submit via your usual RPC.","type":"string"}},"required":["transaction"],"type":"object"},"BuilderPayoutEntry":{"properties":{"amountUsdcBase":{"format":"int64","type":"integer"},"notes":{"type":["string","null"]},"paidAt":{"description":"Unix seconds.","format":"int64","type":"integer"},"transactionSignature":{"description":"NULL while the payout is pending; only signed payouts reduce claimable.","type":["string","null"]}},"required":["amountUsdcBase","paidAt"],"type":"object"},"BuilderSummaryResponse":{"properties":{"accruedUsdcBase":{"description":"Lifetime builder fees accrued across both order legs (6-dp USDC).","format":"int64","type":"integer"},"active":{"type":"boolean"},"builderName":{"type":"string"},"claimableUsdcBase":{"description":"max(0, accrued − paid).","format":"int64","type":"integer"},"code":{"type":"string"},"feeBps":{"format":"int32","type":"integer"},"paidUsdcBase":{"description":"Lifetime settled payouts (rows with a transaction signature).","format":"int64","type":"integer"},"payouts":{"description":"Latest 50 payouts, newest first (includes pending unsigned rows).","items":{"$ref":"#/components/schemas/BuilderPayoutEntry"},"type":"array"}},"required":["code","builderName","feeBps","active","accruedUsdcBase","paidUsdcBase","claimableUsdcBase","payouts"],"type":"object"},"CloseSlippageBand":{"description":"One (market, venue, leg) band. All figures are signed basis points of the\ntrigger price, normalized so **negative means the fill was worse for the\nuser** — identical convention on long and short.","properties":{"adverseBps":{"description":"The adverse edge — the 10th percentile of the signed series, i.e. 9 in\n10 fills landed better than this. Percentile rather than a moment\nbecause fill slippage has a long adverse tail (a single 4.5% gap fill\nin the sample would drag a mean into uselessness).","format":"double","type":"number"},"assetSymbol":{"type":"string"},"formulaVersion":{"description":"Which definition of \"quoted\" the band is measured against: 1 is the\nbrowser's TP/SL preview, 2 a server-struck Armada quote. Part of the\nband's identity, not a label — averaging two versions together produces\na number that describes neither. Bands arrive ascending by it, so a\nconsumer keyed on (market, venue, leg) alone lands on the newest.","format":"int32","type":"integer"},"leg":{"description":"`\"tp\"`, `\"sl\"`, or `\"entry\"`. Kept separate because they are not\ninterchangeable: stops fill adversely and take profits roughly don't, so\npooling them would understate the stop band and invent one for take\nprofits.","type":"string"},"medianBps":{"description":"Typical fill. The centre of the quoted range.","format":"double","type":"number"},"sampleCount":{"format":"int64","type":"integer"},"underwriter":{"type":"string"}},"required":["assetSymbol","underwriter","leg","formulaVersion","sampleCount","medianBps","adverseBps"],"type":"object"},"CloseSlippageResponse":{"properties":{"bands":{"description":"Only markets that cleared `min_samples`. A market absent from this list\nhas no usable history — the client must show its point estimate rather\nthan inventing a band.","items":{"$ref":"#/components/schemas/CloseSlippageBand"},"type":"array"},"lookbackDays":{"format":"int32","type":"integer"},"minSamples":{"description":"The floor actually applied, after clamping.","format":"int64","type":"integer"}},"required":["lookbackDays","minSamples","bands"],"type":"object"},"CombinedOrdersResponse":{"properties":{"jupiterOrders":{"items":{"$ref":"#/components/schemas/EnhancedJupiterOrder"},"type":"array"},"passthroughOrders":{"items":{"$ref":"#/components/schemas/PassthroughOrderInfo"},"type":"array"},"totalCount":{"format":"int32","type":"integer"}},"required":["jupiterOrders","passthroughOrders","totalCount"],"type":"object"},"CostBreakdown":{"description":"Expected ROUND-TRIP cost model over the requested `holdHours` window.\nOnly `openFee`/`openSlip` are paid at open time; every other term is a\nLIFECYCLE EXPECTATION — an estimate of what the position will cost over its\nlife, never reserved or deducted upfront. Do NOT size deposits from this\n(integrators that did over-funded ~15x); use `requiredDeposit`, which is\nthe explicit upfront number.","properties":{"borrow":{"description":"Lifecycle estimate: borrow/funding cost over the FULL `holdHours`\nwindow, accrued hourly while the position is open. Not upfront.","format":"double","type":"number"},"closeFee":{"description":"Lifecycle estimate: fee paid on close. Not an upfront cost.","format":"double","type":"number"},"closeSlip":{"description":"Lifecycle estimate: close-leg slippage.","format":"double","type":"number"},"loanCost":{"description":"Lifecycle estimate: Imperial Lending Pool borrow cost over the hold\nperiod. Present only when a loan split applies to this venue (see\n`fold_loan_into_cost`).","format":"double","type":["number","null"]},"openFee":{"description":"UPFRONT: the venue's open fee, deducted from collateral at open.","format":"double","type":"number"},"openSlip":{"description":"UPFRONT: expected open-leg slippage/price impact.","format":"double","type":"number"},"total":{"description":"Expected round-trip cost (sum of the above). A comparison metric\nbetween venues — never what must sit in the wallet/profile.","format":"double","type":"number"}},"required":["openFee","closeFee","openSlip","closeSlip","borrow","total"],"type":"object"},"CustomizeReferralCodeRequest":{"additionalProperties":false,"properties":{"code":{"type":"string"},"expectedCode":{"description":"Exact current code from GET/ensure; stale tabs cannot overwrite a newer choice.","type":"string"}},"required":["code","expectedCode"],"type":"object"},"DcaSummary":{"properties":{"avgFillPrice":{"type":["string","null"]},"fillCount":{"format":"int64","type":"integer"},"legsExecuted":{"format":"int64","type":"integer"},"numLegs":{"format":"int64","type":"integer"}},"required":["legsExecuted","numLegs","fillCount"],"type":"object"},"DepositAggregatesResponse":{"properties":{"net":{"description":"In minus out (signed micro-USD).","type":"string"},"totalIn":{"description":"Total into profiles (micro-USD magnitude), USD-valued rows only.","type":"string"},"totalOut":{"description":"Total out of profiles (micro-USD magnitude), USD-valued rows only.","type":"string"}},"required":["totalIn","totalOut","net"],"type":"object"},"DepositEventResponse":{"properties":{"amount":{"description":"Signed raw token units (native decimals); positive = into the profile.","type":"string"},"amountUsd":{"description":"Signed micro-USD. Omitted for mints with no USD valuation.","type":["string","null"]},"annotation":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/DepositHistoryAnnotation"}]},"counterparty":{"description":"Source owner for deposits, destination wallet for withdrawals, the\nother profile PDA for transfers.","type":["string","null"]},"counterpartyProfileIndex":{"description":"The other profile's index for transfer legs.","format":"int32","type":["integer","null"]},"eventAt":{"format":"int64","type":"integer"},"eventType":{"description":"`deposit`, `external_deposit`, `withdrawal`, `transfer_in`, or\n`transfer_out`.","type":"string"},"id":{"type":"string"},"marketMint":{"type":"string"},"profileIndex":{"format":"int32","type":"integer"},"signature":{"type":"string"}},"required":["id","eventType","marketMint","profileIndex","amount","signature","eventAt"],"type":"object"},"DepositHistoryAnnotation":{"description":"Operator-added context for a verified transfer. Never sourced from an\non-chain memo, and never changes the transfer's classification or amounts.","properties":{"note":{"type":"string"},"relatedSignature":{"type":["string","null"]}},"required":["note"],"type":"object"},"DepositHistoryResponse":{"properties":{"aggregates":{"$ref":"#/components/schemas/DepositAggregatesResponse"},"events":{"items":{"$ref":"#/components/schemas/DepositEventResponse"},"type":"array"},"totalCount":{"format":"int64","type":"integer"}},"required":["events","totalCount","aggregates"],"type":"object"},"DepthLevel":{"properties":{"price":{"format":"double","type":"number"},"sizeBase":{"format":"double","type":"number"}},"required":["price","sizeBase"],"type":"object"},"DepthSnapshot":{"properties":{"asks":{"items":{"$ref":"#/components/schemas/DepthLevel"},"type":"array"},"bids":{"items":{"$ref":"#/components/schemas/DepthLevel"},"type":"array"},"fetchedAt":{"description":"Wall-clock time the most recent message landed, in milliseconds since\nthe unix epoch. Use this to gate routing on freshness (drop Phoenix\nwhen `now − fetched_at > STALE_THRESHOLD_MS`).","format":"int64","type":"integer"},"mid":{"format":"double","type":"number"},"sigmaBpsPerSqrtSec":{"description":"Short-horizon realized volatility of this book's mid, in basis points\nper √second — multiply by √(seconds) for the expected move over a\nhorizon. `None` until enough mid ticks have accumulated to mean\nanything; callers must not substitute zero for \"unknown\".","format":"double","type":["number","null"]},"symbol":{"type":"string"}},"required":["symbol","mid","bids","asks","fetchedAt"],"type":"object"},"DepthSummaryResponse":{"properties":{"snapshots":{"additionalProperties":{"$ref":"#/components/schemas/DepthSnapshot"},"propertyNames":{"type":"string"},"type":"object"}},"required":["snapshots"],"type":"object"},"DirectVenueMetadata":{"properties":{"accountCollateralUsd":{"type":"string"},"baseLots":{"type":"string"},"effectiveCollateralUsd":{"type":"string"},"maintenanceMarginUsd":{"type":"string"},"marginMode":{"type":"string"},"pdaIndex":{"format":"int32","minimum":0,"type":"integer"},"positionInitialMarginUsd":{"type":"string"},"subaccountIndex":{"format":"int32","minimum":0,"type":"integer"},"traderAccount":{"type":"string"}},"required":["marginMode","traderAccount","pdaIndex","subaccountIndex","baseLots","accountCollateralUsd","effectiveCollateralUsd","positionInitialMarginUsd","maintenanceMarginUsd"],"type":"object"},"EnhancedJupiterOrder":{"allOf":[{"$ref":"#/components/schemas/JupiterLimitOrder"},{"properties":{"ownership":{"type":"string"},"source":{"type":"string"}},"required":["ownership","source"],"type":"object"}]},"FeeBreakdown":{"properties":{"interest":{"type":"string"},"jupiterFee":{"type":"string"},"loanInterest":{"description":"ILP loan interest paid to the lending pool (distinct from the venue\nborrow/funding `interest` line). None = no loan.","type":["string","null"]},"pairsImpactFee":{"description":"Skew-impact share of `jupiter_fee`, summed over this position's\nactions. A COMPONENT of that line, not an addition. Absent when no\naction carried one — which is not the same as zero, so the display\nomits the row rather than showing 0.00. Derived from the actions\nrather than a lifecycle counter: an accumulating column would be a\nnon-idempotent write racing three indexer workers.","type":["string","null"]},"platformFee":{"type":"string"},"proOrderFee":{"type":"string"},"slippage":{"type":"string"},"swapFee":{"type":"string"}},"required":["platformFee","swapFee","slippage","jupiterFee","interest","proOrderFee"],"type":"object"},"FloorDriftReport":{"description":"The api's the hedge venue floor against the order bot's, with the tolerance it was\njudged at.","properties":{"apiFloor":{"description":"Recomputed here, from the api's own venue feed.","format":"double","type":"number"},"botFloor":{"description":"As the treasury loop stored it.","format":"double","type":"number"},"delta":{"format":"double","type":"number"},"drifted":{"type":"boolean"},"tolerance":{"format":"double","type":"number"}},"required":["apiFloor","botFloor","delta","tolerance","drifted"],"type":"object"},"FundingAggregatesResponse":{"properties":{"net":{"description":"Paid minus received (signed micro-USD).","type":"string"},"totalPaid":{"description":"Total the trader paid (micro-USD magnitude).","type":"string"},"totalReceived":{"description":"Total the trader received (micro-USD magnitude).","type":"string"}},"required":["totalPaid","totalReceived","net"],"type":"object"},"FundingEventResponse":{"properties":{"actionId":{"type":["string","null"]},"amount":{"description":"Signed micro-USD; positive = trader paid.","type":"string"},"eventAt":{"format":"int64","type":"integer"},"eventType":{"description":"`funding_settled` or `borrow_settled`.","type":"string"},"id":{"type":"string"},"lifecycleId":{"type":"string"},"marketMint":{"type":"string"},"payload":{},"positionSizeAfterUsd":{"type":["string","null"]},"positionSizeUsd":{"type":["string","null"]},"rate":{"description":"Effective per-second rate ×10^`rateScale`.","type":["string","null"]},"rateScale":{"format":"int32","type":"integer"},"side":{"type":"string"},"signature":{"type":["string","null"]},"spanEnd":{"format":"int64","type":["integer","null"]},"spanStart":{"format":"int64","type":["integer","null"]},"symbol":{"type":"string"},"underwriter":{"type":"string"}},"required":["id","lifecycleId","underwriter","marketMint","symbol","side","eventType","amount","rateScale","eventAt"],"type":"object"},"FundingHistoryResponse":{"properties":{"aggregates":{"$ref":"#/components/schemas/FundingAggregatesResponse"},"events":{"items":{"$ref":"#/components/schemas/FundingEventResponse"},"type":"array"},"totalCount":{"format":"int64","type":"integer"}},"required":["events","totalCount","aggregates"],"type":"object"},"FundingModel":{"description":"How collateral reaches the venue when opening a position. Branch on this\nBEFORE opening:\n- `self_funding`: the open transaction pulls collateral straight from your\n  wallet's USDC ATA, atomically. Do NOT pre-deposit — the deposit would\n  land in escrow, go unused (the open never reads it), and only get swept\n  back to your wallet minutes later, looking like a double-charge. Jupiter,\n  Phoenix, and GMTrade are self-funding.\n- `prefund_v2`: the open draws from your profile's staged deposit ledger,\n  so collateral must be in place before opening. Armada uses this.","enum":["self_funding","prefund_v2"],"type":"string"},"GmTradeFundingRatesResponse":{"description":"Per-symbol funding/borrow/net rates for the bot use case. All fields are\npercentages (0.04 = 0.04%) at hourly cadence, sourced from the GMTrade WS\n`indexTokens` feed and refreshed every 5 minutes. Sign convention follows\nGMTrade's: a positive `longFundingRatePerHourPercent` means longs pay shorts.","properties":{"longBorrowRatePerHourPercent":{"format":"double","type":"number"},"longFundingRatePerHourPercent":{"format":"double","type":"number"},"longNetRatePerHourPercent":{"format":"double","type":"number"},"openFeeNegativePercent":{"format":"double","type":"number"},"openFeePositivePercent":{"format":"double","type":"number"},"shortBorrowRatePerHourPercent":{"format":"double","type":"number"},"shortFundingRatePerHourPercent":{"format":"double","type":"number"},"shortNetRatePerHourPercent":{"format":"double","type":"number"},"symbol":{"type":"string"}},"required":["symbol","longFundingRatePerHourPercent","shortFundingRatePerHourPercent","longBorrowRatePerHourPercent","shortBorrowRatePerHourPercent","longNetRatePerHourPercent","shortNetRatePerHourPercent","openFeePositivePercent","openFeeNegativePercent"],"type":"object"},"GmTradeLiquidityResponse":{"description":"Per-symbol pool headroom (USD) for the preferred (open, USDC-collateral\nfirst) market variant. `null` on either side means the on-chain liquidity\npoller hasn't seen that symbol yet, or the data couldn't be parsed.","properties":{"longAvailableUsd":{"format":"double","type":["number","null"]},"shortAvailableUsd":{"format":"double","type":["number","null"]},"symbol":{"type":"string"}},"required":["symbol"],"type":"object"},"GmTradeMarketResponse":{"properties":{"closed":{"type":"boolean"},"indexTokenDecimals":{"format":"int32","minimum":0,"type":"integer"},"indexTokenMint":{"type":"string"},"longTokenMint":{"type":"string"},"longTokenVault":{"type":"string"},"market":{"type":"string"},"marketTokenMint":{"type":"string"},"oracle":{"type":"string"},"shortTokenMint":{"type":"string"},"shortTokenVault":{"type":"string"},"symbol":{"type":"string"},"underwriter":{"type":"string"}},"required":["symbol","underwriter","market","marketTokenMint","indexTokenMint","longTokenMint","shortTokenMint","longTokenVault","shortTokenVault","oracle","indexTokenDecimals","closed"],"type":"object"},"GuardPhase":{"enum":["normal","widened","halted"],"type":"string"},"HedgeCapital":{"description":"The the hedge venue side of the capital state, in the lines the plan names.\n\nSix lines rather than one float, because the decision this feeds — add seed\nmoney, or don't — is made from measured numbers and a single \"we're fine\"\nscalar cannot be argued with.","properties":{"cmr":{"format":"double","type":"number"},"distance":{"description":"TAV above the opens-stop line. Negative means opens are stopped.","format":"double","type":"number"},"exitRequirement":{"description":"Capital for selective exits; absent on snapshots from the old model.","format":"double","type":["number","null"]},"floor":{"description":"Below this a top-up hop is due.","format":"double","type":"number"},"funding":{"description":"Signed as the snapshot stores it: negative is funding we pay.","format":"double","type":"number"},"health":{"description":"[`the hedge venueHealth::code`].","type":"string"},"held":{"description":"Imperial's float plus what has been pledged.","format":"double","type":"number"},"imr":{"format":"double","type":"number"},"mmr":{"format":"double","type":"number"},"opensStop":{"description":"Below this opens stop.","format":"double","type":"number"},"reserve":{"format":"double","type":"number"},"shockKill":{"format":"double","type":"number"},"shockTopup":{"format":"double","type":"number"},"target":{"description":"Where the rebalancer aims.","format":"double","type":"number"},"tav":{"description":"Total account value: what is held, marked.","format":"double","type":"number"}},"required":["floor","opensStop","target","held","tav","distance","imr","mmr","cmr","shockTopup","shockKill","reserve","funding","health"],"type":"object"},"ImperialLines":{"description":"The six telemetry lines the plan kept after chapter 4 was removed.\n\n⚠⚠ **Every one of these is an OBSERVATION, not a requirement.** Nothing here\nis a floor, a gate, or a number any code sizes against; they are the\ndecomposition an operator reads to decide whether the $30k seed\n([`sunrise_capital::config::DEFAULT_FLOAT_CAP_USD`]) needs growing, and the\nseed is grown from what the book shows rather than declared up front.\n\nA line the api genuinely cannot compute is `None`, never zero — see each\nfield for why.","properties":{"badDebtWorst":{"description":"The worst loss users could leave past the margin they posted.\n\n⚠ **Always `None` here**, and not for want of a book.\n[`sunrise_capital::stress`] makes this identically ZERO for every book\nand every move while `Shock::fill_move` is `None` — the liquidator\ncloses a losing seat AT its line, so the loss never exceeds the margin,\nand the arithmetic reduces to `-notional × maintenance_margin`, clamped\nat zero. A non-zero figure exists only once a SLOWER liquidator is\npriced, and how much slower is a policy input the api has no reading of.\nPublishing the structural zero as though it were a measurement would say\n\"no bad debt is possible\" when what is true is \"none is modelled\".","format":"double","type":["number","null"]},"closeoutCost":{"description":"What taking the whole hedge off costs in the venue's own fees:\n`Σ |net| × liquidation_fee_fraction`, per market.\n\n⚠ Fees only. It does not walk the book, so it excludes the fill\nslippage a real unwind pays — the model prices that separately as\n`unwind_slippage`, which is a scenario parameter rather than something\nthe api measures. `None` when the venue account view is unreadable.","format":"double","type":["number","null"]},"floorGapHedge":{"description":"Floor that Imperial's float plus the whole pledgeable pool cannot reach\n— `Floors::capital_short_usd`, recovered from the row. This is the line\nthat says \"add capital on the hedge venue\".","format":"double","type":["number","null"]},"hopBudget":{"description":"The dollars the current state's top-up hop would have to move:\n`max(0, floor − tav)`. Zero is the healthy answer and is a measurement,\nnot an absence.","format":"double","type":["number","null"]},"nakedLeg":{"description":"User exposure Imperial carries with no hedge behind it:\n`Σ |user net − hedge net|` across every market either side names.\n\nThe user book comes from the snapshot's `per_market.book`, the hedge\nfrom the api's own live venue feed. ⚠ The two are therefore read at\nDIFFERENT instants, so anything that traded since the snapshot shows up\nhere — this line has a noise floor, and a small non-zero reading on a\nbusy book is lag rather than an unhedged position. `None` when either\nside is unreadable.","format":"double","type":["number","null"]},"seedSolana":{"description":"Vault shortfall against the Solana floor: `max(0, solana_floor −\nbalance)`. The line that says \"add capital on Solana\".","format":"double","type":["number","null"]}},"type":"object"},"IndicatorSummary":{"properties":{"condition":{"description":"`below` | `above` | `cross_below` | `cross_above`.","type":"string"},"period":{"format":"int32","minimum":0,"type":"integer"},"threshold":{"description":"RSI threshold as a plain decimal string (e.g. \"30\").","type":"string"},"timeframe":{"description":"Numeric timeframe code as a string (FE maps to labels).","type":"string"}},"required":["period","threshold","condition","timeframe"],"type":"object"},"JupiterLimitOrder":{"properties":{"collateralMint":{"type":"string"},"collateralTokenAmount":{"type":"string"},"collateralUsd":{"type":"string"},"collateralUsdAtTriggerPrice":{"type":"string"},"executed":{"type":"boolean"},"inputMint":{"type":"string"},"liquidationPriceUsd":{"type":"string"},"marketMint":{"type":"string"},"maxSizeUsdDelta":{"type":"string"},"maxSizeUsdDeltaFormatted":{"type":"string"},"minSizeUsdDelta":{"type":"string"},"minSizeUsdDeltaFormatted":{"type":"string"},"openTime":{"type":"string"},"positionPubkey":{"type":"string"},"positionRequestPubkey":{"type":"string"},"side":{"type":"string"},"sizeUsd":{"type":"string"},"triggerPrice":{"type":"string"},"triggerToLiquidationPercent":{"type":["string","null"]}},"required":["collateralMint","collateralUsd","collateralUsdAtTriggerPrice","collateralTokenAmount","executed","inputMint","liquidationPriceUsd","marketMint","maxSizeUsdDelta","maxSizeUsdDeltaFormatted","minSizeUsdDelta","minSizeUsdDeltaFormatted","openTime","positionPubkey","positionRequestPubkey","side","sizeUsd","triggerPrice"],"type":"object"},"JupiterPoolInfo":{"properties":{"longAvailableLiquidity":{"type":"string"},"longBorrowRatePercent":{"type":"string"},"longUtilizationPercent":{"type":"string"},"maxPriceImpactFeePercent":{"type":"string"},"maxRequestExecutionSec":{"type":"string"},"openFeePercent":{"type":"string"},"shortAvailableLiquidity":{"type":"string"},"shortBorrowRatePercent":{"type":"string"},"shortUtilizationPercent":{"type":"string"}},"required":["longAvailableLiquidity","longBorrowRatePercent","longUtilizationPercent","shortAvailableLiquidity","shortBorrowRatePercent","shortUtilizationPercent","openFeePercent","maxRequestExecutionSec","maxPriceImpactFeePercent"],"type":"object"},"LifecycleResponse":{"properties":{"accruedFundingFrom":{"description":"Unix seconds when the `accrued_funding_usd` window opened: the last\naction that booked interest into the ledger, else position open. NOT\n`last_action_at` — Phoenix increases settle funding without booking\nanything, so an add-only position's window reaches back to open even\nthough it was touched since (jax's SOL short: opened Jun 26, last\nincrease Jul 3 — labeling the accrual \"since Jul 3\" made Jun 26–Jul 3\nlook unrecorded, 2026-07-17).","format":"int64","type":["integer","null"]},"accruedFundingUsd":{"description":"Funding accrued on this open position that Funding History has not\nbooked yet — i.e. exactly what the next reduce/close will write as a\n`funding_events` row. Positive = the trader pays, matching\n`tx2_interest_charged`. This is NOT `borrow_fee_owed`: on Phoenix it\nalso covers funding already cash-settled into collateral at an\nincrease, which is not owed at close but is likewise missing from the\nledger. Consumers showing \"accrued funding\" want this; consumers\nshowing due-on-close want `borrow_fee_owed`.","type":["string","null"]},"accruedInterestUsd":{"type":["string","null"]},"actions":{"items":{"$ref":"#/components/schemas/ActionResponse"},"type":"array"},"asset":{"type":"string"},"baseLeverageX":{"type":["string","null"]},"borrowFeeOwed":{"type":["string","null"]},"borrowedCollateralUsd":{"type":["string","null"]},"breakevenPriceUsd":{"description":"Phoenix only: price the open residue must reach to recover residual\nfees/funding (kernel breakeven). None for non-Phoenix and closed positions.","type":["string","null"]},"closeFeeOwed":{"type":["string","null"]},"closedAt":{"format":"int64","type":["integer","null"]},"collateralUsd":{"type":["string","null"]},"directVenue":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/DirectVenueMetadata"}]},"effectiveLeverageX":{"type":["string","null"]},"entryPrice":{"type":["string","null"]},"fees":{"$ref":"#/components/schemas/FeeBreakdown"},"feesOwed":{"type":["string","null"]},"fundingRateHourlyPercent":{"type":["string","null"]},"hedgeVenue":{"description":"Recorded external hedge venue; absent on legacy and non-hedged rows.","type":["string","null"]},"id":{"type":"string"},"lastActionAt":{"format":"int64","type":"integer"},"leverageX":{"type":["string","null"]},"liqBandPriceUsd":{"description":"Worst acceptable liquidation fill (trigger ± 3× premium) — the\nvenue-enforced execution-band limit of the backstop stop: a cap on a\nshort's buy-back, a FLOOR on a long's sell. Phoenix loan rows only.","type":["string","null"]},"liqTriggerPriceUsd":{"description":"The loan-protection threshold (breakeven ∓ premium) — where the\nliquidation fill should AVERAGE, and where the venue backstop stop\nrests. Phoenix loan rows only (Flash/Jupiter loan stops fill at\noracle price — no orderbook band semantics). On closed rows: the\narmed trigger persisted at settle.","type":["string","null"]},"liquidationPrice":{"type":["string","null"]},"loanOriginationNotionalUsd":{"description":"The obligation's ORIGINATION notional (frozen at open / top-up; NOT\nmark-adjusted), 6-dec USD string. This is the notional the on-chain\n`min_loan_bps` floor measures, so loan-sizing consumers (the FE add\nquote) must floor against it — the live mark-adjusted notional drifts\nbelow it on a dip and under-sizes the add-loan (Custom 77 forever,\nVVV order DaRZTRA…, 2026-07-07). Open loan rows only.","type":["string","null"]},"loanRateBps":{"description":"Our live bucket rate on the loan, bps APR.","format":"int32","minimum":0,"type":["integer","null"]},"markPrice":{"type":["string","null"]},"maxCollateralUsd":{"type":["string","null"]},"maxLeverageX":{"type":["string","null"]},"maxSizeUsd":{"type":["string","null"]},"netCashPnlUsd":{"description":"Realized net cash PnL for a closed/liquidated position: total USDC the\nuser got back across close legs minus total USDC they put in across\nopening legs. `pnl_usd` is gross price PnL with costs booked in the\nseparate fee columns; this cash figure already absorbs every cost the\nuser actually paid — open and close fees, interest and funding, the\nswap-to-USDC fee and slippage on long closes, and Flash's borrow fee\n(which no fee column records). None while the position is open, and when\na close leg has no recorded received amount or there is no deposit\nbasis, so the frontend falls back to `pnl_usd`. Also None for Flash V2,\nwhose `tx3_user_received` is derived synthetically (collateral + profit −\nloss) and so would collapse to gross price PnL rather than real net cash.","type":["string","null"]},"openedAt":{"format":"int64","type":"integer"},"openingSignature":{"type":["string","null"]},"openingSlot":{"description":"Indexed opening evidence: a cached account deletion from an older slot\ncannot disprove a later reopening of the same Armada seat.","format":"int64","type":["integer","null"]},"ourLiquidationPriceUsd":{"description":"Imperial's (tighter) liquidation price — REPLACES the venue liq price\nin every display when present (safety requirement, D7). For OPEN\nPhoenix loan rows this is the book-aware FIRE level: the mark at\nwhich the bot's liquidator closes the whole position so the fill\naverages ≈ `liq_trigger_price_usd`. For closed/liquidated rows it is\nthe armed trigger frozen at settle.","type":["string","null"]},"ownedCollateralUsd":{"type":["string","null"]},"ownership":{"type":"string"},"packPrize":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PackPrizeOverlay","description":"Present only on a position a perp pack handed over.\n\n★★★ EVERY OTHER FIELD ON THIS RESPONSE STAYS TRUE. The story lives here\nand nowhere else, so a surface that has not opted in shows the real open\n— see the note on `apply_pack_backdate` for the compute sites that made\noverwriting in place the wrong call."}]},"pairsEntryOracleIndex":{"format":"double","type":["number","null"]},"pairsLegAAnchor":{"format":"double","type":["number","null"]},"pairsLegBAnchor":{"format":"double","type":["number","null"]},"pairsWABps":{"format":"int32","minimum":0,"type":["integer","null"]},"partialLiquidationPriceUsd":{"description":"Phoenix only: maintenance-breach (partial/first-liquidation) price from\nthe effective-collateral margin kernel. None for non-Phoenix and closed\npositions. Distinct from `liquidation_price`, which is left unchanged.","type":["string","null"]},"pnlPercent":{"type":["string","null"]},"pnlUsd":{"type":["string","null"]},"positionPda":{"type":"string"},"profileIndex":{"format":"int32","type":["integer","null"]},"profilePictureUrl":{"type":["string","null"]},"seatCollateralUsd":{"description":"Phoenix only: the LIVE gross seat collateral on the trader account = the\nuser's own collateral PLUS the loan already deposited on the seat, read\nfrom the same on-chain trader account this endpoint already decodes. This\nis the basis Phoenix's margin check (and the api add-headroom gate) uses,\nversus `collateral_usd` which is the user's own collateral net of the\nloan. The frontend needs it to size a loaned add against the same basis\nthe chain enforces. Present only on loan-backed Phoenix positions.","type":["string","null"]},"side":{"type":"string"},"sizeTokenAmount":{"type":["string","null"]},"sizeUsd":{"type":["string","null"]},"source":{"type":"string"},"status":{"type":"string"},"sunriseMarginSeat":{"description":"Armada margin seat: null is cross; an asset key identifies isolation.","type":["string","null"]},"swapFeeOwed":{"type":["string","null"]},"totalFeesUsd":{"type":"string"},"tpslOrders":{"items":{"$ref":"#/components/schemas/TpSlOrderResponse"},"type":"array"},"underwriter":{"type":"string"},"username":{"type":["string","null"]},"wallet":{"type":"string"}},"required":["id","positionPda","wallet","source","side","asset","totalFeesUsd","fees","status","openedAt","lastActionAt","underwriter","actions"],"type":"object"},"LifecyclesResponse":{"properties":{"count":{"format":"int32","type":"integer"},"dataList":{"items":{"$ref":"#/components/schemas/LifecycleResponse"},"type":"array"},"lifetimeCollateralUsd":{"type":"string"},"lifetimeFeeBreakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"lifetimeFeesUsd":{"type":"string"},"lifetimeNetPnlUsd":{"type":"string"},"lifetimePnlUsd":{"type":"string"},"phoenixCrossSeats":{"description":"Account-level state of each Phoenix cross-margin seat backing a position in\n`data_list`, one entry per profile. Empty for every wallet with no unified\n(cross) margin profile.\n\nOn a cross seat these are the ONLY numbers that describe risk: collateral,\nmargin and liquidation are properties of the seat, not of any one leg, so the\nrows themselves carry no `liquidationPrice`. See \"Cross margin (unified\nmargin)\" in the API overview.\n\nIn the same response rather than a separate endpoint: it removes a round trip\nand, more importantly, the consistency window — a user cannot see positions\nfrom one instant next to the margin figures from another, which for a\nliquidation-relevant number is the difference between reassuring and wrong.\n`GET /api/v1/phoenix/margin-account` serves the same object on its own, for\nwhen the profile holds no positions yet.\n\nAbsent when Phoenix's trader-state read failed. Treat that as unknown, never\nas `0`.","items":{"$ref":"#/components/schemas/PhoenixCrossSeatState"},"type":"array"},"totalCount":{"format":"int64","type":"integer"}},"required":["dataList","count","totalCount","lifetimeCollateralUsd","lifetimePnlUsd","lifetimeNetPnlUsd","lifetimeFeesUsd","lifetimeFeeBreakdown"],"type":"object"},"LoanSplitMode":{"enum":["reach","safer-liq"],"type":"string"},"MarginModeResponse":{"properties":{"marginMode":{"description":"The mode now in force.","type":"string"},"profileIndex":{"format":"int32","type":"integer"},"venueLock":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueLock","description":"Present only when the new mode constrains routing — under `\"unified\"`\nthis is Phoenix. Apply it client-side; the api and order bot both reject\nopens that violate it, but only after the round trip."}]},"wallet":{"type":"string"}},"required":["wallet","profileIndex","marginMode"],"type":"object"},"MarketCapacity":{"description":"How much more net exposure one market can take.","properties":{"binding":{"description":"Which limit binds first — [`Limit::code`].","type":"string"},"longUsd":{"description":"Dollars of additional NET LONG exposure before the opens-stop line.","format":"double","type":"number"},"market":{"type":"string"},"shortUsd":{"description":"The same net short. Smaller than the long side by construction: a short\nhedge's notional grows as the move goes against it.","format":"double","type":"number"}},"required":["market","longUsd","shortUsd","binding"],"type":"object"},"MarketRow":{"properties":{"byVenue":{"$ref":"#/components/schemas/MarketVenueVolume"},"longOiUsd":{"type":"string"},"openInterestUsd":{"type":"string"},"positionCount":{"format":"int64","type":"integer"},"shortOiUsd":{"type":"string"},"symbol":{"type":"string"},"traderCount":{"format":"int64","type":"integer"},"volumeUsd":{"type":"string"}},"required":["symbol","volumeUsd","openInterestUsd","longOiUsd","shortOiUsd","traderCount","positionCount","byVenue"],"type":"object"},"MarketVenueVolume":{"properties":{"flashUsd":{"type":"string"},"gmtradeUsd":{"type":"string"},"jupiterUsd":{"type":"string"},"phoenixUsd":{"type":"string"}},"required":["jupiterUsd","flashUsd","phoenixUsd","gmtradeUsd"],"type":"object"},"MarketsResponse":{"properties":{"period":{"type":"string"},"rows":{"items":{"$ref":"#/components/schemas/MarketRow"},"type":"array"}},"required":["period","rows"],"type":"object"},"MarkoutSummary":{"properties":{"meanBps":{"description":"`None` until a sample exists. Never report zero for \"unmeasured\" — the\nmeasured value IS ~zero, so the two would be indistinguishable.","format":"double","type":["number","null"]},"notionalUsd":{"format":"double","type":"number"},"samples":{"format":"int64","minimum":0,"type":"integer"}},"required":["samples","notionalUsd"],"type":"object"},"MobileBalancesResponse":{"properties":{"profiles":{"description":"One entry per Imperial subaccount (profile_index 0..5), in order.","items":{"$ref":"#/components/schemas/ProfileUsdcBalance"},"type":"array"},"wallet":{"type":"string"}},"required":["wallet","profiles"],"type":"object"},"MobileCancelOrderRequest":{"properties":{"cascadeChildren":{"description":"Whether cancelling an entry order also cancels the pending TP/SL orders\nlinked to it (their `parentOrderPda` is this order). Defaults to `true`:\nclose orders are market/side-scoped on-chain, so ones that outlive their\nentry order execute against whatever position exists on that market/side\nnext. Pass `false` only when the children should survive — e.g. the\n\"execute limit at market now\" flow, where the cancel is immediately\nfollowed by an equivalent market open the children are meant to protect.","type":["boolean","null"]},"orderPda":{"description":"Base58 PDA of the order to cancel.","type":"string"},"profileIndex":{"description":"Sub-account index (0..5) the order belongs to.","format":"int32","minimum":0,"type":"integer"},"wallet":{"description":"Base58 wallet pubkey. Must match the JWT's wallet claim.","type":"string"}},"required":["wallet","orderPda","profileIndex"],"type":"object"},"MobileCollateralEditRequest":{"properties":{"action":{"description":"0 = add collateral, 1 = remove collateral.\n\nRemoving from a cross seat is capped at\n`effectiveCollateralForWithdrawalsUsd - initialMarginForWithdrawalsUsd`\nfrom `GET /api/v1/phoenix/margin-account` — a stricter basis than free\nmargin.","format":"int32","minimum":0,"type":"integer"},"collateralAmount":{"description":"Amount of collateral to add or remove, in the collateral mint's native units.","format":"int64","minimum":0,"type":"integer"},"marketMint":{"description":"Base58 SPL mint of the market whose position is being adjusted.\n\nOn a Phoenix cross-margin seat there is one balance behind every market,\nso this (with `side`) only has to name a market the seat currently holds —\nthe funds land in the account either way.","type":"string"},"positionPubkey":{"description":"Expected Phoenix trader account from the withdrawal quote. Required for\nPhoenix removals; it constrains resolution and never selects a new seat.","type":["string","null"]},"price":{"description":"Client-observed market price. Same per-underwriter scale as `marketPrice` on\n`/mobile/orders`: Jupiter/Phoenix micro-USD (1e6), Flash Trade target-exponent\n(`price × 10^(-priceExponent)` from `GET /api/v1/flash/markets`), GMTrade 1e9.","format":"int64","minimum":0,"type":"integer"},"profileIndex":{"description":"Sub-account index (0..5).","format":"int32","minimum":0,"type":"integer"},"requestedHedgeVenue":{"description":"Optional Armada execution constraint: 0 = automatic, 1 = current hedge route.\nA selected route binds; reserved values are rejected. Use 0 for other underwriters.","format":"int32","minimum":0,"type":"integer"},"side":{"description":"0 = long, 1 = short.","format":"int32","minimum":0,"type":"integer"},"slippageBps":{"description":"Slippage tolerance in basis points.","format":"int32","minimum":0,"type":"integer"},"sunriseMarginMode":{"description":"Armada position's actual margin seat: \"cross\" or \"isolated\". Omitted\nby older clients, which resolve the seat from the existing position.","type":["string","null"]},"underwriter":{"description":"0 = Jupiter, 1 = Flash Trade, 2 = Phoenix, 3 = GMTrade, 4 = Flash Trade V2,\n5 = Imperial Pairs, 6 = Imperial Touch, 7 = Imperial Armada.\nWire format mirrors `passthrough_client::Underwriter`.\n\nByte 7 does NOT identify a venue on its own — see\n`requestedHedgeVenue`.","format":"int32","minimum":0,"type":"integer"},"wallet":{"description":"Base58 wallet pubkey. Must match the JWT's wallet claim.","type":"string"}},"required":["wallet","marketMint","side","action","collateralAmount","slippageBps","profileIndex","underwriter","price"],"type":"object"},"MobileConnectRequest":{"properties":{"delegate":{"description":"When set, this is a delegate-mode connect: `signature` is by this delegate\npubkey, which must match the on-chain `UserAccount.signer_delegate`.","type":["string","null"]},"message":{"description":"Plain message that was signed. Wallet mode: `imperial:mobile-connect:{wallet}:{nonce}`.\nDelegate mode: `imperial:delegate-connect:{wallet}:{delegate}:{nonce}`.\n`{nonce}` must be a current Unix timestamp (seconds or millis, e.g. `Date.now()`), not a\nrandom value: it is rejected if >5 min old or >60s in the future, and is single-use.","type":"string"},"signature":{"description":"Base58-encoded signature over `message`. Wallet mode: by `wallet`. Delegate\nmode: by the registered `delegate` key.","type":"string"},"wallet":{"description":"Base58 Solana wallet pubkey","type":"string"}},"required":["wallet","message","signature"],"type":"object"},"MobileConnectResponse":{"properties":{"code":{"description":"One-time code to exchange for a JWT via `/mobile/exchange` within 5 minutes.","type":"string"}},"required":["code"],"type":"object"},"MobileCreateOrderBatchRequest":{"properties":{"closeOrders":{"description":"Close-side legs (TP, SL, ratchet, DCA-close, ...) attached to the entry.\nEach must have `action == 1` and match the entry's\n`(wallet, marketMint, side, underwriter, profileIndex)` tuple.","items":{"$ref":"#/components/schemas/MobileCreateOrderRequest"},"type":"array"},"entry":{"$ref":"#/components/schemas/MobileCreateOrderRequest","description":"The entry order. Must have `action == 0` (Increase)."}},"required":["entry"],"type":"object"},"MobileCreateOrderBatchResponse":{"properties":{"closeOrders":{"description":"One result per close leg, in input order. Empty if the entry failed.","items":{"$ref":"#/components/schemas/MobileOrderResponse"},"type":"array"},"entry":{"$ref":"#/components/schemas/MobileOrderResponse","description":"Result of the entry submission. `success = false` skips the close legs."}},"required":["entry","closeOrders"],"type":"object"},"MobileCreateOrderRequest":{"properties":{"action":{"description":"0 = Increase, 1 = Decrease.","format":"int32","minimum":0,"type":"integer"},"builderCode":{"description":"Optional builder code (case-insensitive; normalized to uppercase). The\ncode's fixed per-code bps rate (at most 10) is charged on each leg's\nnotional, on top of the standard fee, on both the open and close legs\nat every venue including Armada. Unknown or deactivated codes are\nrejected with `400`.","type":["string","null"]},"closeBps":{"description":"For decrease orders (`action = 1`) only: fraction of the LIVE position\nto close, in basis points (`10000` = full close, `2500` = a quarter).\nStrongly recommended over `sizeUsd` for closes — it is evaluated\nagainst the on-chain position size at execution, so it is immune to\nthe price drift that can turn a `sizeUsd`-denominated full close into\na dust-leaving partial (which Flash rejects with `MinCollateral`).\nWhen set, `sizeUsd` is ignored for close sizing. A full close may\nequivalently send `sizeUsd = 0` with this field omitted. Not yet\nsupported on DCA close order types (12/13/14), which still size each\nleg from `sizeUsd`. Rejected with `400` on increase orders.","format":"int32","minimum":0,"type":["integer","null"]},"collateralAmount":{"description":"Collateral amount in the collateral mint's native units.\n\nOn a profile set to unified (cross) margin this is a top-up **deposit into\nthe Phoenix seat**, not the position's margin — the seat as a whole margins\nevery leg, so `0` is legal once the seat has free collateral. See \"Cross\nmargin (unified margin)\" in the API overview for the admission rule.\n\nOn Imperial Armada (7) it is always the position's **full** margin in\nµUSDC, on either seat — never a pre-netted top-up. The operator does the\nnetting: on the cross seat it deposits only what the seat's free\ncollateral does not cover; on an isolated seat it draws a flat Armada\naccount first (`drawableUsd` on `GET /api/v1/armada/margin`) and charges\nthe wallet for the rest.","format":"int64","minimum":0,"type":"integer"},"extraData":{"description":"Per-order-type parameters (e.g. `{ \"worstPrice\": ..., \"ratchetSize\": ... }`\nfor ratchet orders, plus an optional `stopTriggerPrice` on a Ratchet close; `{ \"dcaNumLegs\": ..., \"dcaIntervalSeconds\": ... }` for DCA).\nArmada orders may specify `sunriseSeat`: 0 = cross, 1 = isolated, 2 = the\nsecond isolated seat. An isolated increase names the isolated mode and\nthe order bot allocates which of the two isolated seats it opens on.\nNew increases otherwise use the profile preference; closes should carry\nthe target position's seat, which survives profile-mode changes."},"fundingStatus":{"description":"Must be 0 (or omitted): every order is funded at creation. Deferred funding (1) was\nretired and is refused.","format":"int32","minimum":0,"type":"integer"},"marketMint":{"description":"Base58 SPL mint of the market (e.g. SOL mint for SOL-PERP). Either this\nor `symbol` must be set; if both are provided, `marketMint` wins.\nUse `symbol` instead unless you already have a venue-specific mint and\nwant to skip the server-side lookup.","type":["string","null"]},"marketPrice":{"description":"Client-observed market price at the moment the order was placed.\nForwarded to the on-chain market-order instruction so the program can enforce slippage\nagainst an agreed-upon reference price. Ignored for resting orders.\n\n**Scale is per-underwriter** (unlike `triggerPrice`, which is always 1e9):\n- Jupiter (0) and Phoenix (2): micro-USD, `price × 1e6`. SOL at $64.94 → `64940000`.\n- Flash Trade (1): target-exponent encoding, `price × 10^(-priceExponent)`, where\n  `priceExponent` is per-market (typically `-8`) from `GET /api/v1/flash/markets`.\n  SOL at $64.94 with exponent `-8` → `6494000000`.\n- GMTrade (3): oracle scale, `price × 1e9` → `64940000000`. (1e6 truncates\n  sub-$0.001 assets like BONK to a zero slippage band.)\n- Flash Trade V2 (4): ignored — V2 market orders execute on the MagicBlock ER\n  without a client reference price. Send `0`.\n- Imperial Pairs (5), Imperial Touch (6): retired — orders are refused.\n- Imperial Armada (7): micro-USD. ⚠ It becomes the request's `expected_price` on\n  `imperial_sunrise` (rescaled to the program's 1e9), which is the reference BOTH\n  on-chain price bands measure against — including the one protecting Imperial. A\n  value far from the market is refused rather than filled.","format":"int64","minimum":0,"type":"integer"},"orderType":{"description":"0=Market, 1=Limit, 2=StopLimit, 3=LandMine, 4=Ratchet, 6=RatchetEntry, 9=DCA,\n10=FibRatchet, 11=FibRatchetEntry, 12=DcaClose, 13=DcaTimeClose,\n14=DcaRatchetClose, 15=DcaTime, 16=DcaRatchet.","format":"int32","minimum":0,"type":"integer"},"parentOrderPda":{"description":"Optional Order PDA of the parent limit order this close order is\nattached to. Set this when adding a TP/SL/Trailing to a still-resting\nlimit so the UI groups it under that limit (instead of relying on the\n`(market, side, profile)` heuristic, which mis-groups close orders\nacross unrelated parents on the same key). NULL for standalone close\norders (i.e. attached to an open position) and for entry orders.","type":["string","null"]},"phoenixNative":{"description":"Opt into Phoenix's native CLOB for this order instead of the\nkeeper-managed IOC fallback. Only meaningful when `underwriter = 2`\n(Phoenix) AND `orderType` is `Limit` (1) with `action = Increase` (0),\nor `PrivateTpSl` (5). Ignored for any other shape.\n\nDefault behavior (unset / `false`): the API rewrites Phoenix Limit /\nPrivateTpSl to StopLimit so the order routes through the keeper path.\nThat dodges a class of indexer + orderbook-watcher bugs that have\nstranded user collateral in the past (cancel-cleanup races, slot\nclassification edges).\n\nSet `true` only if you understand those risks and need genuine resting\nCLOB orders — e.g. maker-fee arbitrage bots. Orders placed with\n`phoenixNative = true` will not be rewritten and will rest natively on\nthe Phoenix orderbook.","type":["boolean","null"]},"priority":{"description":"Stored on the order; lower value = higher priority (0 is highest). Has no effect on\nSolana priority fees.","format":"int32","minimum":0,"type":"integer"},"profileIndex":{"description":"Sub-account index (0..5). Profiles don't share balances or positions.\nA profile is isolated-margin by default; one switched to unified margin\nopens new positions on Phoenix's cross seat instead, and rejects opens on\nevery other venue. `GET /passthrough/users/{wallet}/profiles` reports the\nmode.","format":"int32","minimum":0,"type":"integer"},"quote":{"description":"What the client's close preview quoted for this order, captured so the\nrealized fill can later be measured against it. Purely diagnostic.\n\nDeliberately opaque: a TYPED `Option<OrderQuotePayload>` here does NOT\nisolate a bad quote — serde fails the whole request body, so the order\nis rejected in the extractor before the handler ever runs, with no log\nline and no `order_placement_events` row. That is exactly what happened\non 2026-08-09: `lifecycleId` carries `unifiedPositionRowId`\n(`<pda>:<asset>:<side>`) for a Phoenix cross-seat row, which is not a\nUUID, so every quoted TP/SL 400'd and users saw \"Failed to create\norder\". Parsed AFTER the order lands, so a malformed quote costs the\ndiagnostic and nothing else."},"quoteId":{"description":"Id of a quote the api served on `/armada/quote`.\n\nArmada only. This is what makes the quote server-authoritative: the\nclient sends the id, not the numbers, and the api prices the order from\nits own record of what it served. A quote binds to exactly one order and\nexpires — an unknown or stale id is ignored, not fatal, so an order is\nnever lost to a quote that aged out.","type":["string","null"]},"requestedHedgeVenue":{"description":"Optional Armada execution constraint: 0 = automatic, 1 = current hedge route.\nA selected route binds; reserved values are rejected. Use 0 for other underwriters.","format":"int32","minimum":0,"type":"integer"},"side":{"description":"0 = long, 1 = short.","format":"int32","minimum":0,"type":"integer"},"sizeUsd":{"description":"Position size in USD, 6-decimal fixed point (1_000_000 = $1).","format":"int64","minimum":0,"type":"integer"},"slippageBps":{"description":"Slippage tolerance in basis points (100 = 1%).","format":"int32","minimum":0,"type":"integer"},"symbol":{"description":"Canonical Imperial symbol (e.g. `\"SOL\"`, `\"XAU\"`). Resolved server-side\nto the right `marketMint` for the requested `underwriter` — this is the\nrecommended addressing path: the same symbol you read from\n`/funding-rates` works directly here, including Phoenix synthetics\n(XAU, SPY, EUR, GBP, …) that have no SPL mint of their own.","type":["string","null"]},"triggerCondition":{"description":"0 = Above, 1 = Below. Direction of trigger condition relative to price.","format":"int32","minimum":0,"type":"integer"},"triggerPrice":{"description":"Trigger price in oracle scale (1e9). Required for non-market orders.","format":"int64","minimum":0,"type":"integer"},"underwriter":{"description":"0 = Jupiter, 1 = Flash Trade, 2 = Phoenix, 3 = GMTrade, 4 = Flash Trade V2,\n5 = Imperial Pairs, 6 = Imperial Touch, 7 = Imperial Armada.\nWire format mirrors `passthrough_client::Underwriter`.\n\nByte 7 does NOT identify a venue on its own — see\n`requestedHedgeVenue`.","format":"int32","minimum":0,"type":"integer"},"wallet":{"description":"Base58 wallet pubkey. Must match the JWT's wallet claim.","type":"string"}},"required":["wallet","side","orderType","action","triggerCondition","sizeUsd","collateralAmount","slippageBps","triggerPrice","profileIndex","priority","underwriter"],"type":"object"},"MobileExchangeRequest":{"properties":{"code":{"description":"One-time code returned by `/mobile/connect`.","type":"string"}},"required":["code"],"type":"object"},"MobileExchangeResponse":{"properties":{"expiresAt":{"description":"Unix timestamp (seconds) when the JWT expires.","format":"int64","minimum":0,"type":"integer"},"jwt":{"description":"JWT. Pass as `Authorization: Bearer <jwt>` on trading endpoints.","type":"string"}},"required":["jwt","expiresAt"],"type":"object"},"MobileOrderResponse":{"properties":{"adjustedSizeUsd":{"description":"Set when the Phoenix add-headroom gate silently reduced the order's\n`size_usd` so it would actually open (auto-clamp / \"sluff down to\nleverage\"). Present only when a clamp happened; carries the final\n6-decimal-USD size the order was submitted with. The FE surfaces this as\nan informational \"opened at a reduced size\" note — it is not an error.","format":"int64","minimum":0,"type":["integer","null"]},"cancelledChildOrderPdas":{"description":"Cancel-only: PDAs of pending TP/SL orders linked to the cancelled order\n(via `parentOrderPda`) that were cancelled along with it. On-chain\norders carry no parent binding — a close order that outlives its entry\norder stays armed on the (profile, market, side) triple and executes\nagainst whatever position exists there next, so the cancel cascades.","items":{"type":"string"},"type":"array"},"error":{"description":"Humanized error message when `success` is false.","type":["string","null"]},"errorCode":{"description":"Machine-matchable error code for programmatic branching. Named codes\ncover the stable rejection classes: `PostOnlyWouldCross`,\n`LoanMarketNotWhitelisted`, `LoanVenueUnsupported`,\n`SwapRouteUnavailable`, `MarketClosed`, `TradingPaused`,\n`SlippageExceeded`, `PositionAlreadyClosed`, `ProfileHasOpenPositions`,\n`CancelViaAppOnly`, `LoanSizeAddBreaksFloor`, `InsufficientBalance`,\n`LeverageTooHigh`, `LeverageTooLow`, `MinCollateral`,\n`ReduceBelowMinCollateral`, `InsufficientVenueBalance`,\n`InsufficientMargin`. Any other on-chain rejection surfaces its raw\nprogram error as `Custom<N>` (e.g. `Custom6024`). `None` on success and\non failures with no recognizable program code (read `error` for the\nhuman-facing message).","type":["string","null"]},"errorDetails":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserErrorDetails","description":"Structured user-safe reason, outcome, and recovery guidance. Additive;\n`error` and `errorCode` remain for existing integrations."}]},"makerOnly":{"description":"Maker-only proof metadata (IMP-355). `makerOnly` is `true` when this\norder is a Phoenix-native limit / TP — those always rest as\n`OrderPacketKind::PostOnly` with slide disabled, so they can only ever\nadd liquidity. It is `false` for every other shape (keeper-rewritten\nPhoenix limits, market orders, and all other underwriters), which take\nor may take liquidity. There is no request field — maker-only is implied\nby the order shape (see `imperial_venues::is_native_dispatch`).","type":"boolean"},"orderPda":{"description":"Base58 Order PDA for **resting orders only** (limit, stop-limit, ratchet,\nDCA, Phoenix native, etc.) — `None` for market orders, which open a\nposition rather than rest. Use this for downstream `/mobile/orders/cancel`\nor `/mobile/orders/update` calls without polling the indexer to discover\nthe PDA. Deterministic — derived from the profile PDA + the\n`user_account.order_counter` snapshot at placement.","type":["string","null"]},"postOnlyRejected":{"description":"`true` when a maker-only order was refused specifically because it would\nhave crossed the spread (taker fill) — the maker-only contract was\nenforced by rejecting it. Always paired with `wouldCross = true` and\n`errorCode = \"PostOnlyWouldCross\"`. No Order PDA, position, or trade is\ncreated in this case.","type":"boolean"},"reason":{"description":"Original explanation accompanying a known cancel no-op.","type":["string","null"]},"resolutionSignature":{"description":"Cancel-only: the transaction behind an outcome that is not a full\ncancellation (partial, unverified, scheduled, retired). Never cancel\nauthority; `signature` stays reserved for a confirmed cancellation.","type":["string","null"]},"signature":{"description":"Solana transaction signature, if one was produced.","type":["string","null"]},"status":{"description":"Cancel-only: a known no-op outcome reported by the order bot.","type":["string","null"]},"submissionOutcome":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SubmissionOutcome","description":"Evidence for this request only. Absent on legacy responses; never infer\nthe outcome of an earlier submission from this field."}]},"success":{"description":"True if the order bot accepted the request.","type":"boolean"},"wouldCross":{"description":"`true` when the requested limit price would immediately cross resting\nliquidity and fill as taker. For a maker-only order this is the reason it\nwas rejected; it is the proof that the order was not silently downgraded\nto a taker fill.","type":"boolean"}},"required":["success","makerOnly","postOnlyRejected","wouldCross"],"type":"object"},"MobilePointsResponse":{"properties":{"seasonName":{"description":"Name of the active points season, or `null` when no season is live\n(in which case both points figures are `0`).","type":["string","null"]},"seasonPoints":{"description":"Whole-point figure as displayed on the Imperial homepage\n(`seasonPointsMicros / 1_000_000`, floored).","format":"int64","minimum":0,"type":"integer"},"seasonPointsMicros":{"description":"Imperial points earned this season, in micro-points (6 decimals).","format":"int64","minimum":0,"type":"integer"},"wallet":{"type":"string"}},"required":["wallet","seasonPointsMicros","seasonPoints"],"type":"object"},"MobilePreflightResponse":{"description":"Verdict on an order the caller has NOT placed. `ok: false` is a 200 with a\nreason, not a 4xx: the caller is asking a question, and a \"no\" is a\nsuccessful answer to it.","properties":{"adjustedSizeUsd":{"description":"Present when the gates would silently shrink this order (Phoenix\nadd-headroom clamp). 6-decimal micro-USD, same scale as `sizeUsd`.","format":"int64","minimum":0,"type":["integer","null"]},"error":{"type":["string","null"]},"errorCode":{"type":["string","null"]},"ok":{"type":"boolean"}},"required":["ok"],"type":"object"},"MobileProOrderUpdate":{"properties":{"ratchetSize":{"format":"int64","minimum":0,"type":["integer","null"]},"stopTrigger":{"description":"Ratchet only: early stop trigger (0 clears it back to a classic trailing stop).","format":"int64","minimum":0,"type":["integer","null"]},"type":{"description":"One of: `ratchet`, `landMine`.","type":"string"},"waitDurationSeconds":{"format":"int64","minimum":0,"type":["integer","null"]},"waitPrice":{"format":"int64","minimum":0,"type":["integer","null"]},"worstPrice":{"format":"int64","minimum":0,"type":["integer","null"]}},"required":["type"],"type":"object"},"MobileUpdateOrderRequest":{"properties":{"closeBps":{"description":"For decrease orders: new close percentage in bps (0..10000). Omit to leave unchanged.","format":"int32","minimum":0,"type":["integer","null"]},"orderPda":{"description":"Base58 PDA of the order to update.","type":"string"},"priority":{"description":"New Solana priority fee tier. Omit to leave unchanged.","format":"int32","minimum":0,"type":["integer","null"]},"proOrderUpdate":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MobileProOrderUpdate","description":"Per-type parameter update for advanced orders (ratchet / land-mine)."}]},"profileIndex":{"description":"Sub-account index (0..5).","format":"int32","minimum":0,"type":"integer"},"quote":{"description":"The re-quote this edit produced. Moving a stop changes what the preview\npromises, and the number that matters when it fires is the last one\nshown — so an update appends a snapshot rather than leaving the\noriginal standing. Requires `side` / `orderType` / `underwriter` /\n`marketMint` on the quote itself; this request addresses an existing\norder by PDA and carries none of them.\n\nOpaque for the same reason as the create path — see the note there."},"sizeUsd":{"description":"New size in USD. Omit to leave unchanged.","format":"int64","minimum":0,"type":["integer","null"]},"slippageBps":{"description":"New slippage tolerance (bps). Omit to leave unchanged.","format":"int32","minimum":0,"type":["integer","null"]},"triggerPrice":{"description":"New trigger price (oracle scale). Omit to leave unchanged.","format":"int64","minimum":0,"type":["integer","null"]},"wallet":{"description":"Base58 wallet pubkey. Must match the JWT's wallet claim.","type":"string"}},"required":["wallet","orderPda","profileIndex"],"type":"object"},"OiHistoryPeak":{"properties":{"oiUsd":{"type":"string"},"timestamp":{"format":"date-time","type":"string"}},"required":["timestamp","oiUsd"],"type":"object"},"OiHistoryPoint":{"properties":{"oiUsd":{"type":"string"},"timestamp":{"format":"date-time","type":"string"}},"required":["timestamp","oiUsd"],"type":"object"},"OiHistoryResponse":{"properties":{"currentOiUsd":{"description":"Live current open interest (sum of `status='open'` lifecycles). Compare\nagainst `rows`' last entry — divergence indicates indexer drift, not\narithmetic error in this endpoint.","type":"string"},"grouping":{"type":"string"},"peak":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OiHistoryPeak"}]},"period":{"type":"string"},"rows":{"items":{"$ref":"#/components/schemas/OiHistoryPoint"},"type":"array"}},"required":["period","grouping","currentOiUsd","rows"],"type":"object"},"OpenInterestResponse":{"properties":{"asOf":{"format":"date-time","type":"string"},"grouping":{"type":"string"},"rows":{"items":{"$ref":"#/components/schemas/OpenInterestRow"},"type":"array"}},"required":["grouping","asOf","rows"],"type":"object"},"OpenInterestRow":{"properties":{"label":{"type":"string"},"longUsd":{"type":"string"},"positionCount":{"format":"int64","type":"integer"},"shortUsd":{"type":"string"},"totalUsd":{"type":"string"},"traderCount":{"format":"int64","type":"integer"}},"required":["label","longUsd","shortUsd","totalUsd","traderCount","positionCount"],"type":"object"},"OrderHistoryDetailResponse":{"allOf":[{"$ref":"#/components/schemas/OrderHistoryRowResponse"},{"properties":{"children":{"items":{"$ref":"#/components/schemas/OrderHistoryRowResponse"},"type":"array"},"config":{"description":"Full decoded strategy/order config (raw `order_data`)."},"events":{"items":{"$ref":"#/components/schemas/OrderHistoryEventResponse"},"type":"array"},"feesUsd":{"type":["string","null"]},"fills":{"items":{"$ref":"#/components/schemas/OrderHistoryFillResponse"},"type":"array"}},"required":["children","fills","events"],"type":"object"}]},"OrderHistoryEventResponse":{"properties":{"eventType":{"type":"string"},"legIndex":{"format":"int32","type":["integer","null"]},"payload":{},"price":{"type":["string","null"]},"reason":{"type":["string","null"]},"reasonCode":{"type":["string","null"]},"reasonDetails":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserErrorDetails"}]},"signature":{"type":["string","null"]},"source":{"type":"string"},"timestamp":{"format":"int64","type":"integer"},"value":{"type":["string","null"]}},"required":["eventType","timestamp","source"],"type":"object"},"OrderHistoryFillResponse":{"properties":{"feesUsd":{"type":["string","null"]},"legIndex":{"format":"int64","type":["integer","null"]},"price":{"type":["string","null"]},"sizeUsd":{"type":["string","null"]},"status":{"type":"string"},"statusReason":{"type":["string","null"]},"statusReasonDetails":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserErrorDetails"}]},"time":{"format":"int64","type":"integer"},"txSignature":{"type":["string","null"]}},"required":["time","status"],"type":"object"},"OrderHistoryResponse":{"properties":{"orders":{"items":{"$ref":"#/components/schemas/OrderHistoryRowResponse"},"type":"array"},"totalCount":{"format":"int64","type":"integer"}},"required":["orders","totalCount"],"type":"object"},"OrderHistoryRowResponse":{"properties":{"action":{"type":"string"},"avgFillPrice":{"type":["string","null"]},"bestPriceSeen":{"type":["string","null"]},"botState":{"type":["string","null"]},"cancelledAt":{"format":"int64","type":["integer","null"]},"childCount":{"format":"int64","type":"integer"},"collateralAmount":{"type":"string"},"createdAt":{"format":"int64","type":"integer"},"creationSignature":{"type":"string"},"dca":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/DcaSummary"}]},"displayStatus":{"description":"Derived user-facing status (see module docs).","type":"string"},"executedAt":{"format":"int64","type":["integer","null"]},"executionSignature":{"type":["string","null"]},"executionTriggerPrice":{"type":["string","null"]},"fillCount":{"format":"int64","type":"integer"},"filledSizeUsd":{"type":["string","null"]},"indicator":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/IndicatorSummary"}]},"marketMint":{"type":"string"},"orderPda":{"type":"string"},"orderType":{"type":"string"},"parentOrderPda":{"type":["string","null"]},"profileIndex":{"format":"int32","type":"integer"},"ratchet":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/RatchetSummary"}]},"side":{"type":"string"},"sizeUsd":{"type":"string"},"slippageBps":{"format":"int32","type":"integer"},"status":{"description":"Raw DB status, for debugging/tooling.","type":"string"},"statusReason":{"description":"User-readable reason, when known.","type":["string","null"]},"statusReasonCode":{"description":"Machine reason code underlying `status_reason`.","type":["string","null"]},"statusReasonDetails":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserErrorDetails"}]},"triggerCondition":{"type":["string","null"]},"triggerPrice":{"type":["string","null"]},"underwriter":{"type":"string"},"venueOrderId":{"type":["string","null"]}},"required":["orderPda","marketMint","side","orderType","action","sizeUsd","collateralAmount","slippageBps","displayStatus","status","underwriter","profileIndex","creationSignature","createdAt","fillCount","childCount"],"type":"object"},"OrderQuotePayload":{"description":"The client's `estimateOrderPnl` result plus the inputs it consumed.\n\nMirrors `order_quote_snapshots` one-for-one. Money fields are 6-decimal\nmicro-USD and prices are 1e9 oracle scale, matching `triggerPrice` on the\nenclosing request — the client already works in those units at the call\nsite, so no rescaling happens on either side of the wire.","properties":{"afterFeesOwedMicro":{"format":"int64","type":"integer"},"afterFeesPaidMicro":{"format":"int64","type":"integer"},"assetSymbol":{"type":["string","null"]},"borrowFeeOwedMicro":{"format":"int64","type":"integer"},"clientVersion":{"type":["string","null"]},"closeBps":{"format":"int32","type":"integer"},"closeFeeOwedMicro":{"format":"int64","type":"integer"},"collateralUsdMicro":{"format":"int64","type":"integer"},"entryPriceNano":{"format":"int64","type":"integer"},"execPriceNano":{"format":"int64","type":"integer"},"feesOwedMicro":{"format":"int64","type":"integer"},"feesPaidMicro":{"format":"int64","type":"integer"},"formulaVersion":{"description":"Bumped by the client whenever the quote formula changes, so a later\ncomparison never silently mixes two definitions of \"quoted\".","format":"int32","type":"integer"},"inputMode":{"description":"`\"price\"`, `\"pct\"` or `\"pnl\"`.","type":"string"},"lastPriceNano":{"format":"int64","type":"integer"},"leg":{"description":"`\"tp\"` or `\"sl\"`. Taken from the leg the user was looking at rather\nthan re-inferred from `triggerCondition`, so the label matches the UI.","type":"string"},"legPnlMicro":{"description":"This order's standalone PnL (`estimateClosePnl().total`).","format":"int64","type":"integer"},"lifecycleId":{"format":"uuid","type":["string","null"]},"markPriceNano":{"format":"int64","type":"integer"},"marketMint":{"type":["string","null"]},"netCapitalMicro":{"description":"Absent when `getNetCapitalAtRisk` returned null and the quote fell back\nto current collateral as its basis.","format":"int64","type":["integer","null"]},"netPnlMicro":{"description":"Whole-trade net PnL headline (the `getDisplayPnl` basis).","format":"int64","type":"integer"},"orderType":{"format":"int32","minimum":0,"type":["integer","null"]},"pricePnlMicro":{"format":"int64","type":"integer"},"priceRef":{"description":"`\"last\"` or `\"mark\"`.","type":"string"},"side":{"description":"Identity of the order being quoted. The create path takes these from\nthe enclosing request; the UPDATE path has no such fields (it addresses\nan existing order by PDA), so re-quoting a moved stop supplies them\nhere. Ignored when the enclosing request already carries them.","format":"int32","minimum":0,"type":["integer","null"]},"sizeUsdMicro":{"format":"int64","type":"integer"},"swapFeeOwedMicro":{"format":"int64","type":"integer"},"underwriter":{"format":"int32","minimum":0,"type":["integer","null"]}},"required":["leg","netPnlMicro","legPnlMicro","pricePnlMicro","afterFeesPaidMicro","afterFeesOwedMicro","execPriceNano","entryPriceNano","sizeUsdMicro","collateralUsdMicro","closeBps","feesPaidMicro","feesOwedMicro","borrowFeeOwedMicro","closeFeeOwedMicro","swapFeeOwedMicro","markPriceNano","lastPriceNano","priceRef","inputMode","formulaVersion"],"type":"object"},"OwnedReferralAlias":{"properties":{"code":{"type":"string"},"createdAt":{"type":"string"},"kind":{"type":"string"}},"required":["code","kind","createdAt"],"type":"object"},"OwnedReferralAliasesResponse":{"properties":{"aliases":{"items":{"$ref":"#/components/schemas/OwnedReferralAlias"},"type":"array"}},"required":["aliases"],"type":"object"},"PackPrizeOverlay":{"description":"What a pack pull's story needs from the desk, and what it hands back.\n\nSerialized onto [`LifecycleResponse::pack_prize`] so the client's two PnL\ncalculators can stay on the real numbers while everything else renders the\nstory. See `rip_ev::backdate` for the arithmetic.","properties":{"backdatedEntryPrice":{"description":"Where this pull's outcome would have started, if it had been a trade —\na real price the market really traded at [`backdated_opened_at`].\n\n[`backdated_opened_at`]: PackPrizeOverlay::backdated_opened_at","type":"string"},"backdatedOpenedAt":{"description":"Unix seconds, of the bar that proved it.","format":"int64","type":"integer"},"pnlOffsetUsd":{"description":"`granted collateral − (stake − rip fee)`, fixed at the draw — the pull's own result.\n\n★★★ SENT RATHER THAN DERIVED, because the client cannot derive it: the\ngranted collateral is a constant and `collateralUsd` on the wire is the\nLIVE figure, which on a Phoenix seat drifts with the mark. One\ndefinition, computed where the pull's own row can be read.\n\n⚠⚠ ADD IT, NEVER RECOMPUTE FROM [`backdated_entry_price`].\n`calculate_price_pnl` is `size × (mark − entry)/entry`, which reads\n`size_usd` as the notional AT ENTRY; ours is the notional at open, so\nthat route is wrong by `M/p₀` — up to 15% at the bottom of the common\nband on a 10× market, and plausible-looking at every size.\n\n[`backdated_entry_price`]: PackPrizeOverlay::backdated_entry_price","type":"string"},"ripFeeUsd":{"description":"Part of the full purchase price, excluded from the position PnL basis.","type":["string","null"]},"stakeUsd":{"description":"Full purchase price. Subtract rip_fee_usd for the displayed PnL basis —\nunless [`story_collateral_usd`] is set, which then IS the basis.\n\n[`story_collateral_usd`]: PackPrizeOverlay::story_collateral_usd","type":"string"},"storyCollateralUsd":{"description":"The collateral the pull shows, drawn with it. The displayed PnL basis\nwhen present; `pnl_offset_usd` is already measured against it.","type":["string","null"]}},"required":["backdatedEntryPrice","backdatedOpenedAt","stakeUsd","pnlOffsetUsd"],"type":"object"},"PartnerRegisterRequest":{"properties":{"code":{"description":"Partner code, case-insensitive (normalized to uppercase).","type":"string"}},"required":["code"],"type":"object"},"PartnerRegisterResponse":{"properties":{"code":{"type":"string"},"feeTierBps":{"format":"int64","minimum":0,"type":"integer"},"partner":{"type":"string"},"txSignature":{"type":"string"}},"required":["partner","code","feeTierBps","txSignature"],"type":"object"},"PartnerRegistrationStatusResponse":{"properties":{"code":{"type":["string","null"]},"discountPending":{"description":"True when the wallet is bound to the partner but the on-chain\n`set_wallet_fee_tier` leg has not succeeded yet — the partner should\nretry POST /mobile/partner/register to activate the discount.","type":"boolean"},"feeTierBps":{"description":"The fee tier currently ACTIVE on-chain — standard bps while the\non-chain flag leg is still pending (`discount_pending: true`).","format":"int64","minimum":0,"type":["integer","null"]},"partnerName":{"type":["string","null"]},"registered":{"type":"boolean"},"txSignature":{"type":["string","null"]}},"required":["registered","discountPending"],"type":"object"},"PassthroughOrderInfo":{"properties":{"action":{"type":"string"},"botState":{"description":"Order-bot overlay on a live order: `circuit_broken` (parked) or\n`insufficient_funds` (triggered, but the profile could not fund it).","type":["string","null"]},"collateralAmount":{"type":"string"},"createdAt":{"format":"int64","type":"integer"},"creationSignature":{"type":"string"},"creationSlot":{"description":"Solana slot at which the placement tx landed. The FE compares this\nagainst the slot of its locally-cached Phoenix orderbook snapshot\nbefore classifying a missing slot as \"removed from book\" — a\nsnapshot from a slot before this one couldn't have observed the\nplacement yet.","format":"int64","type":"integer"},"currentBestPriceUsd":{"type":["string","null"]},"fundingStatus":{"format":"int32","type":"integer"},"marketMint":{"type":"string"},"orderData":{},"orderPda":{"type":"string"},"orderType":{"type":"string"},"parentOrderPda":{"description":"Order PDA of the parent limit, when this is a close order (TP/SL/\nTrailing) attached to a still-resting limit. The FE uses this\ninstead of the legacy `(market, side, profile)` heuristic to group\nclose orders under their parent. `None` for entry orders and for\nclose orders attached to an open position.","type":["string","null"]},"priority":{"format":"int32","type":"integer"},"profileIndex":{"format":"int32","type":"integer"},"side":{"type":"string"},"sizeUsd":{"type":"string"},"slippageBps":{"format":"int32","type":"integer"},"status":{"type":"string"},"triggerCondition":{"type":["string","null"]},"triggerPrice":{"type":["string","null"]},"underwriter":{"type":"string"},"venueOrderState":{"description":"Per-venue resting state — Phoenix native carries (price_in_ticks,\norder_sequence_number, initial_base_lots, remaining_base_lots,\ntrader_account_pda) so the FE can subscribe to the orderbook and\nrender live partial-fill progress. None for venues with no\nresting state."},"wallet":{"type":"string"}},"required":["orderPda","wallet","marketMint","side","orderType","action","sizeUsd","collateralAmount","slippageBps","status","creationSignature","creationSlot","createdAt","profileIndex","priority","fundingStatus","underwriter"],"type":"object"},"PassthroughOrderResponse":{"properties":{"action":{"type":"string"},"cancelledAt":{"format":"int64","type":["integer","null"]},"collateralAmount":{"type":"string"},"createdAt":{"format":"int64","type":"integer"},"creationSignature":{"type":"string"},"creationSlot":{"description":"Solana slot at which the placement tx landed. The FE compares this\nagainst the slot of its locally-cached Phoenix orderbook snapshot\nbefore deciding the order is \"gone from book\" — a snapshot from a\nslot before this one couldn't have observed the placement, so we\nmust NOT classify a missing slot in that snapshot as a removal.","format":"int64","type":"integer"},"executedAt":{"format":"int64","type":["integer","null"]},"executionSignature":{"type":["string","null"]},"marketMint":{"type":"string"},"orderData":{},"orderPda":{"type":"string"},"orderType":{"type":"string"},"parentOrderPda":{"description":"Order PDA of the parent limit, when this is a close order (TP/SL/\nTrailing) attached to a still-resting limit. The FE uses this\ninstead of the legacy `(market, side, profile)` heuristic to group\nclose orders under their parent — explicit links don't mis-attach\nacross markets/profiles. `None` for entry orders and for close\norders attached to an open position.","type":["string","null"]},"profileIndex":{"format":"int32","type":"integer"},"profilePda":{"type":"string"},"side":{"type":"string"},"sizeUsd":{"type":"string"},"slippageBps":{"format":"int32","type":"integer"},"status":{"type":"string"},"triggerCondition":{"type":["string","null"]},"triggerPrice":{"type":["string","null"]},"underwriter":{"type":"string"},"venueOrderState":{"description":"Per-venue runtime state of the resting order. For Phoenix-native\nlimits this carries `(price_in_ticks, order_sequence_number,\nremaining_base_lots, trader_account_pda, …)` — the FE uses these\nto scan the Phoenix orderbook account directly and render live\nfill progress (X of Y filled). `None` for venues with no resting\nstate (Jupiter / Flash IOC fills, Phoenix native that hasn't\nlanded on the book yet)."},"wallet":{"type":"string"}},"required":["orderPda","wallet","profilePda","marketMint","side","orderType","action","sizeUsd","collateralAmount","slippageBps","status","creationSignature","creationSlot","createdAt","profileIndex","underwriter"],"type":"object"},"PassthroughOrdersResponse":{"properties":{"count":{"format":"int32","type":"integer"},"orders":{"items":{"$ref":"#/components/schemas/PassthroughOrderResponse"},"type":"array"}},"required":["orders","count"],"type":"object"},"PhoenixCrossSeatState":{"description":"Account-level state of one Phoenix cross-margin seat (subaccount 0).\n\nIn cross margin, collateral, margin and liquidation are properties of the\nSEAT, not of any one position — so these are the only numbers that describe a\nunified profile's risk, and no per-position figure substitutes for them.\n\nEvery field is a verbatim pass-through of Phoenix's `TraderView`. Imperial does\nnot recompute portfolio margin anywhere (that decision is in the plan: Phoenix is\nauthoritative and liquidates), and `risk_state` / `risk_tier` are surfaced as\nPhoenix's own words rather than an Imperial health score.\n\nValues are UI strings, matching `DirectVenueMetadata` and the rest of the Phoenix\nsurface. `None` means Phoenix did not report it — render it as unknown, never as\n`0` (`feedback_never_render_unknown_balance_as_zero`).","properties":{"accountCollateralUsd":{"description":"Total deposited collateral on the seat.","type":"string"},"cancelMarginUsd":{"type":["string","null"]},"effectiveCollateralForWithdrawalsUsd":{"type":["string","null"]},"effectiveCollateralUsd":{"description":"Collateral plus discounted unrealised PnL — what actually backs positions.","type":"string"},"initialMarginForWithdrawalsUsd":{"type":["string","null"]},"initialMarginUsd":{"description":"Margin the open positions and resting orders already consume. Free margin is\n`effectiveCollateral - initialMargin`; the order bot gates new unified opens\non exactly that subtraction.","type":["string","null"]},"liquidationPricesBySymbol":{"additionalProperties":{"type":["string","null"]},"description":"Per-market liquidation price as PHOENIX computes it, keyed by its RAW symbol.\n\nImperial's own `compute_phoenix_liquidation_price` cannot be used on a cross\nseat: it divides a per-position collateral figure into a per-position margin\nrequirement, and in cross neither exists — liquidation is a property of the\nwhole portfolio. Phoenix is the engine that actually liquidates, so its number\nis the only correct one.\n\n⚠ A price here does NOT promise which leg dies. Phoenix liquidates a cross seat\nby closing whichever positions its engine picks; this is the level at which\nTHIS market becomes liable, not a guarantee it is the one that closes.\n\nA `None` value means Phoenix quotes no price for that market — common, and not\nan error (see [`PhoenixDecimalView::liquidation_ui`]). The KEY is still\npresent: these keys are how a consumer enumerates the markets on the seat,\nincluding legs the position rows have not caught up with, so a market with no\nprice must stay in the map rather than vanish from it.","propertyNames":{"type":"string"},"type":"object"},"maintenanceMarginUsd":{"type":["string","null"]},"maxPositions":{"description":"Hard, unraisable ceiling on CONCURRENT positions. `max_positions` is\nimmutable and Phoenix has no resize instruction, so a power user who hits it\nneeds a fresh parent — surface it rather than letting them discover it as a\nfailed order.","format":"int64","minimum":0,"type":["integer","null"]},"openPositions":{"description":"Slots currently occupied, i.e. distinct markets open on the seat.","format":"int64","minimum":0,"type":"integer"},"portfolioValueUsd":{"type":["string","null"]},"profileIndex":{"format":"int32","minimum":0,"type":"integer"},"riskState":{"description":"Phoenix's own risk classification (e.g. `\"healthy\"`).","type":["string","null"]},"riskTier":{"description":"Phoenix's own tier ladder (e.g. `\"safe\"`).\n\n⚠ Phoenix liquidates a cross seat by closing whichever positions ITS engine\npicks. We can surface the tier but cannot promise which leg dies — UI copy\nmust say so.","type":["string","null"]},"traderAccount":{"type":"string"},"unrealizedPnlUsd":{"type":["string","null"]}},"required":["traderAccount","profileIndex","accountCollateralUsd","effectiveCollateralUsd","openPositions","liquidationPricesBySymbol"],"type":"object"},"PhoenixMarkPrice":{"description":"Mid-price snapshot per Phoenix-raw symbol. The mid is `(best_bid + best_ask) / 2`\nfrom the live orderbook WS feed, computed in `handle_text` and stored on the\nsnapshot. Empty until the WS connection has received its first orderbook\nmessage; stale entries (no message in 60s) are filtered by `get_fresh`.","properties":{"fetchedAt":{"description":"Wall-clock time the most recent message landed, in milliseconds since epoch.","format":"int64","type":"integer"},"mid":{"format":"double","type":"number"},"symbol":{"type":"string"}},"required":["symbol","mid","fetchedAt"],"type":"object"},"PhoenixMarketResponse":{"properties":{"activeTraderBuffer":{"items":{"type":"string"},"type":"array"},"assetId":{"format":"int64","minimum":0,"type":"integer"},"baseLotsDecimals":{"description":"Signed because Phoenix publishes negative values for some markets\n(e.g. PUMP = -2). Mirrors `PhoenixMarketInfo.base_lots_decimals`.","format":"int32","type":"integer"},"globalTraderIndex":{"items":{"type":"string"},"type":"array"},"isolatedOnly":{"description":"Phoenix rejects this market on the cross-margin seat, so unified-margin\nprofiles must not offer it. Meaningless for isolated trading.","type":"boolean"},"logoUri":{"description":"Display metadata from Phoenix; absent on older upstream responses.","type":["string","null"]},"makerFeeMicro":{"format":"int32","type":"integer"},"maxLeverage":{"description":"Max leverage from the first (smallest position) tier","format":"double","type":"number"},"maxSizeBaseLots":{"description":"Max position size in base lots from the first tier","format":"int64","minimum":0,"type":"integer"},"orderbook":{"type":"string"},"perpAssetMap":{"type":"string"},"splineCollection":{"type":"string"},"subaccountIndex":{"format":"int32","minimum":0,"type":"integer"},"symbol":{"type":"string"},"takerFeeMicro":{"format":"int32","minimum":0,"type":"integer"},"tickSizeInQuoteLotsPerBaseLot":{"format":"int64","minimum":0,"type":"integer"},"underwriter":{"type":"string"},"withdrawQueue":{"type":"string"}},"required":["symbol","underwriter","orderbook","perpAssetMap","splineCollection","withdrawQueue","globalTraderIndex","activeTraderBuffer","assetId","subaccountIndex","baseLotsDecimals","tickSizeInQuoteLotsPerBaseLot","makerFeeMicro","takerFeeMicro","maxLeverage","maxSizeBaseLots","isolatedOnly"],"type":"object"},"PhoenixMarketVolume":{"description":"Phoenix's own trailing-24h traded notional for one market. This is the\nvenue's total flow, not Imperial's share of it — the same `dayNtlVlm` the\ntrading header shows for the market you're looking at, exposed for every\nmarket at once so the market selector can render a column of them. The\nexisting volume endpoint also exposes funding, USD OI and price statistics\nfrom that same snapshot; existing volume consumers can ignore these fields.","properties":{"change24hPct":{"format":"double","type":["number","null"]},"fetchedAt":{"description":"Wall-clock time the most recent message landed, in milliseconds since epoch.","format":"int64","type":"integer"},"fundingRate1hPct":{"description":"Percent per hour, directly from Phoenix's market channel.","format":"double","type":"number"},"markPrice":{"format":"double","type":"number"},"openInterestUsd":{"description":"Phoenix publishes base units; converted to USD at the same snapshot's mark.","format":"double","type":["number","null"]},"symbol":{"description":"Phoenix-raw symbol (e.g. `GOLD`, not the Imperial-canonical `XAU`).","type":"string"},"volume24hUsd":{"format":"double","type":"number"}},"required":["symbol","volume24hUsd","fundingRate1hPct","markPrice","fetchedAt"],"type":"object"},"PledgeCapital":{"description":"The pledge: how much of users' locked margin is lent to the hedge venue.","properties":{"capitalShort":{"description":"Floor the pledgeable pool plus Imperial's float cannot reach. This is\nthe number a seed decision is actually made on.","format":"double","type":["number","null"]},"pledge":{"description":"What the rebalancer should have on the far side.","format":"double","type":"number"},"pledgeCap":{"description":"The same, less what the winners may realize on Solana.","format":"double","type":["number","null"]},"pledgeable":{"description":"Locked margin the till leaves free to lend.","format":"double","type":["number","null"]}},"required":["pledge"],"type":"object"},"PnlHistoryPoint":{"properties":{"cumulativeFundingPayment":{"description":"Cumulative funding/interest paid (Phoenix funding + Jupiter borrow\nrate + GMTrade funding). Signed: positive = paid out by trader.","format":"double","type":"number"},"cumulativePnl":{"description":"Realized PnL accumulated up to and including this bucket. USD float.","format":"double","type":"number"},"cumulativeTakerFee":{"description":"Cumulative taker / order / venue execution fees across underwriters.\nExcludes Imperial's platform fee and DEX swap fees.","format":"double","type":"number"},"timestamp":{"description":"Bucket start, unix seconds.","format":"int64","type":"integer"},"unrealizedPnl":{"description":"Mark-to-market PnL of currently open positions at the time of the\nrequest. **Only present on the last bucket** — historical\nmark-to-market is not supported because we do not persist historical\nmark prices. All earlier buckets serialize this field as `null`.","format":"double","type":["number","null"]}},"required":["timestamp","cumulativePnl","cumulativeFundingPayment","cumulativeTakerFee"],"type":"object"},"PriceSource":{"enum":["phoenix_orderbook_ws","jupiter_doves_oracle","gmtrade_ws","pyth_lazer"],"type":"string"},"PriorityFeeResponse":{"properties":{"priority_fee":{"description":"Median Solana priority fee (micro-lamports per CU) observed across\nrecent blocks that touched the passthrough program. Polled every 30s\nand clamped to a 500_000 ceiling so a stuck whale doesn't make the\nsuggestion useless. Field is `priority_fee` (snake_case) — this\nendpoint predates the camelCase convention and stays as-is for\nexisting integrators.","format":"int64","minimum":0,"type":"integer"}},"required":["priority_fee"],"type":"object"},"ProfileResponse":{"properties":{"excludedVenues":{"items":{"type":"string"},"type":"array"},"marginMode":{"description":"`\"isolated\"` (the default) or `\"unified\"`. Unified routes this profile's\nnew positions onto a venue's shared collateral pool — Phoenix's\ncross-margin seat, or Armada's cross margin account — see the \"Cross\nmargin\" guide in the API overview.","type":"string"},"name":{"type":["string","null"]},"profileIndex":{"format":"int32","minimum":0,"type":"integer"},"profilePda":{"type":"string"},"venueLock":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueLock","description":"Present only when the mode constrains routing. The client must consume this\nrather than deriving the lock from `margin_mode` itself, and must apply it\n*before* the exclusion-override branches in `computeRoute` — sticky,\nonly-option and leverage-reach all deliberately override `excludedVenues`,\nso an exclusion-encoded lock leaks through all three."}]}},"required":["profileIndex","profilePda","excludedVenues","marginMode"],"type":"object"},"ProfileUsdcBalance":{"properties":{"profileIndex":{"format":"int32","minimum":0,"type":"integer"},"profilePda":{"type":"string"},"usdc":{"description":"USDC balance held in the profile's USDC ATA, in raw native units\n(6-decimal — divide by `1_000_000` for whole USDC). `0` when the ATA\nhasn't been created yet.","format":"int64","minimum":0,"type":"integer"}},"required":["profileIndex","profilePda","usdc"],"type":"object"},"ProfilesResponse":{"properties":{"armadaParticipant":{"description":"Promotion preference, not a trading entitlement. True for any existing\nallowlist row, including revoked rows; null if the lookup is unavailable.\nThe client must only invite a connected wallet on an explicit false.","type":["boolean","null"]},"betaVenues":{"description":"Venues in limited release that THIS WALLET has been granted, by wire\nname — `[\"armada\"]` for a wallet on the Armada private beta, `[]` for\neveryone else.\n\nPer wallet, not per profile: access is a statement about a person, and\nthe six profiles all belong to the same one.\n\nA client must treat a venue that is absent here as one that does not\nexist for this user — do not offer it, do not route to it, do not render\nit in a venue picker. It is not `venueLock`'s inverse: `venueLock` is a\nper-profile margin-mode consequence the user chose, this is an\nentitlement the user cannot change.\n\n⚠⚠ Applies to OPENS ONLY. A wallet with a position on a venue absent\nfrom this list must still be able to close it, reduce it, and withdraw\nfrom it, and every close path is keyed on the POSITION's venue rather\nthan on this field. Empty here is never a reason to hide an exit.\n\nEmpty on any error, so a client that fails closed and a server that\nfails closed agree.","items":{"type":"string"},"type":"array"},"profiles":{"description":"Always six entries, indexed `0..5`, whether or not the profile has ever\nbeen used.","items":{"$ref":"#/components/schemas/ProfileResponse"},"type":"array"}},"required":["profiles","betaVenues"],"type":"object"},"RailsCapital":{"description":"The rails the snapshot was computed under, recovered from its horizons.","properties":{"coverage":{"description":"[`Coverage::code`].","type":["string","null"]},"inbound":{"description":"[`InboundRail::code`], or `None` when the stored horizons match no rail\nthis build knows — which is what a rail added on one side of a deploy\nlooks like, and is worth seeing rather than rounding away.","type":["string","null"]},"outbound":{"description":"[`OutboundRail::code`].","type":["string","null"]},"tauKillSecs":{"format":"int32","type":"integer"},"tauOutSecs":{"format":"int32","type":"integer"},"tauTopupSecs":{"format":"int32","type":"integer"}},"required":["tauTopupSecs","tauKillSecs","tauOutSecs"],"type":"object"},"RatchetSummary":{"properties":{"referencePrice":{"description":"Starting reference (`worst_price`, nano-USD).","type":["string","null"]},"targetPrice":{"description":"Fib target (nano-USD) — fib family only.","type":["string","null"]},"trailDistance":{"description":"Fixed trail distance (`ratchet_size`, nano-USD) — absent for fib.","type":["string","null"]}},"type":"object"},"RateSource":{"description":"Provenance tag for a per-venue rate entry. Helps a bot author distinguish\nlive data from fallbacks without parsing free-form text.","enum":["jupiter_api_pool_info","gmtrade_ws","phoenix_clob_no_snapshot","phoenix_market_ws","sunrise_onchain_funding","fallback_constant"],"type":"string"},"ReferralCodeResponse":{"properties":{"code":{"type":"string"},"kind":{"type":"string"},"maxAliases":{"format":"int32","type":"integer"},"nextNewCodeAt":{"type":["string","null"]},"remainingAliases":{"format":"int64","type":"integer"},"updatedAt":{"type":"string"},"wallet":{"type":"string"}},"required":["wallet","code","kind","updatedAt","maxAliases","remainingAliases"],"type":"object"},"RegisterPhoenixRequest":{"properties":{"profileIndex":{"description":"Sub-account index (0..5) being activated. Each profile activates\nindependently — bots farming across multiple subaccounts must register\neach one before placing Phoenix orders from it.","format":"int32","minimum":0,"type":"integer"},"wallet":{"description":"Owner wallet base58 pubkey. The activation is keyed on the profile PDA\nderived from `wallet` + `profileIndex`.","type":"string"}},"required":["wallet"],"type":"object"},"RegisterPhoenixResponse":{"properties":{"activated":{"type":"boolean"},"message":{"type":"string"},"profilePda":{"type":"string"}},"required":["profilePda","activated","message"],"type":"object"},"RequiredDeposit":{"description":"Explicit upfront funding guidance for one venue at one (notional,\nleverage). This is what must actually be available BEFORE the open —\nunlike `costBreakdown`, whose borrow / expected-liq terms are lifecycle\nEXPECTATIONS that are never reserved upfront. Deposit-first integrators\n(prefund venues) should size deposits from `requiredDepositUsd`, not\n`expectedCostUsd`.","properties":{"degen":{"description":"Whether the venue's degen tier (leverage above its normal cap) applies\nat this leverage — selects the degen minimum collateral.","type":"boolean"},"depositSkimBps":{"description":"Imperial's flat deposit fee rate actually used (bps). Resolved from\nthe `wallet` query param's fee tier when present, else the Standard\ntier — so partners see their real rate by passing `wallet`.","format":"double","type":"number"},"minPositionCollateralUsd":{"description":"The venue min-collateral floor applied (tier-selected, safety-padded).\nPresent only when the floor term is known for this venue.","format":"double","type":["number","null"]},"requiredDepositUsd":{"description":"USD that must sit in the profile (prefund venues) or wallet\n(self-funding venues) for the open to succeed, ceiled to the cent.\n`max(collateral for the target leverage, venue min-collateral floor +\nopen-time fees)`, grossed up for Imperial's deposit skim.","format":"double","type":"number"},"upfrontFeeUsd":{"description":"Total upfront skim: Imperial's flat deposit fee + the venue's cash\nopen fee. Everything else in `costBreakdown` is NOT upfront.","format":"double","type":"number"},"venueOpenFeeUsd":{"description":"The venue's cash open fee at this notional (deducted from collateral\nat open; reduces effective size/PnL, not the deposit).","format":"double","type":"number"}},"required":["requiredDepositUsd","upfrontFeeUsd","depositSkimBps","venueOpenFeeUsd","degen"],"type":"object"},"RevokeResponse":{"properties":{"success":{"type":"boolean"}},"required":["success"],"type":"object"},"RouteCandidate":{"properties":{"costBreakdown":{"$ref":"#/components/schemas/CostBreakdown"},"expectedCostUsd":{"format":"double","type":"number"},"filteredReason":{"type":["string","null"]},"maxLeverage":{"format":"double","type":"number"},"requiredDeposit":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/RequiredDeposit","description":"Upfront funding required to open on this venue at\n`min(desiredLeverage, maxLeverage)`."}]},"venue":{"$ref":"#/components/schemas/Venue"}},"required":["venue","expectedCostUsd","costBreakdown","maxLeverage"],"type":"object"},"RouteLoanSplit":{"description":"The split-attachment shape carried on route candidates/results.","properties":{"loanAmountUsd":{"format":"double","type":"number"},"mode":{"$ref":"#/components/schemas/LoanSplitMode"},"ourRateBps":{"format":"int32","minimum":0,"type":"integer"},"venueLeverage":{"format":"double","type":"number"}},"required":["loanAmountUsd","venueLeverage","ourRateBps","mode"],"type":"object"},"RouteResult":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/RouteCandidate"},"type":"array"},"clamped":{"type":"boolean"},"clampedMaxLeverage":{"format":"double","type":["number","null"]},"costBreakdown":{"$ref":"#/components/schemas/CostBreakdown"},"exclusionOverridden":{"description":"The winner is a venue the user excluded, but the router overrode the\nexclusion because nothing else can serve the trade.","type":"boolean"},"expectedCostUsd":{"format":"double","type":"number"},"fundingModel":{"$ref":"#/components/schemas/FundingModel","description":"How to fund an open on the winning `venue`. Branch on this BEFORE\nopening: `self_funding` opens pull collateral from your wallet ATA inside\nthe open tx (do NOT pre-deposit); `prefund_v2` requires collateral staged\nbeforehand. See [`FundingModel`]."},"leverDownToLeverage":{"description":"Highest leverage a non-excluded venue could natively serve. Present\nonly on a leverage-driven exclusion override, where dropping to this\nleverage would route the trade back onto an allowed venue.","format":"double","type":["number","null"]},"marketsVersion":{"description":"Stable hash over the cross-venue market universe at request time. Two\nresponses with the same `marketsVersion` were computed against the same\nmarket list — partners poll this to detect drift in the supported set\nwithout re-snapshotting every venue. Set by the handler post-`compute_route`.","format":"int64","minimum":0,"type":"integer"},"maxLeverage":{"description":"The winning venue's openable leverage cap. A per-market, fee-adjusted\nconstant — SIZE-INDEPENDENT (identical at any `notional`), so don't\nscale it by trade size. On prefund venues it already folds every\nopen-time deduction; orders sized at or below it clear the venue's\non-chain init-leverage check.","format":"double","type":"number"},"reason":{"type":"string"},"requiredDeposit":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/RequiredDeposit","description":"Upfront funding required on the winning venue at the EFFECTIVE leverage\n(`clampedMaxLeverage` when the route clamped, else `desiredLeverage`).\nSet by the handler post-`compute_route`."}]},"venue":{"$ref":"#/components/schemas/Venue"}},"required":["venue","fundingModel","reason","maxLeverage","expectedCostUsd","costBreakdown","clamped","candidates"],"type":"object"},"ServedQuote":{"properties":{"expiresAtMs":{"format":"int64","type":"integer"},"hedgeVenue":{"type":"string"},"quote":{"$ref":"#/components/schemas/ArmadaQuote"},"quoteId":{"type":"string"},"servedAtMs":{"format":"int64","type":"integer"}},"required":["quoteId","hedgeVenue","quote","expiresAtMs","servedAtMs"],"type":"object"},"ServiceHealth":{"properties":{"db":{"type":["string","null"]},"grpcStream":{"type":["string","null"]},"lastProcessedSlot":{"format":"int64","minimum":0,"type":["integer","null"]},"rpc":{"type":["string","null"]},"status":{"type":"string"}},"required":["status"],"type":"object"},"SetMarginModeRequest":{"properties":{"marginMode":{"description":"`\"isolated\"` or `\"unified\"`. Anything else is a `400`.","example":"unified","type":"string"}},"required":["marginMode"],"type":"object"},"Side":{"enum":["long","short"],"type":"string"},"SideRates":{"properties":{"long":{"format":"double","type":"number"},"short":{"format":"double","type":"number"}},"required":["long","short"],"type":"object"},"SnapshotMeta":{"description":"Provenance of the numbers above.","properties":{"ageSecs":{"format":"int64","type":"integer"},"marksComplete":{"description":"Whether every market in the book had a usable mark when the floors were\ncomputed. A floor computed on a partial book understates it, and after\nthe fact there is no way to tell — which is why it is stored.","type":"boolean"},"modelVersion":{"type":"string"},"unpriced":{"description":"Markets the api's own feed could find no tail or no venue parameters\nfor. A non-empty list means the recomputed floor below understates\nitself, so the drift check is advisory rather than binding.","items":{"type":"string"},"type":"array"}},"required":["ageSecs","marksComplete","unpriced","modelVersion"],"type":"object"},"SolanaCapital":{"description":"The Solana side: whether the vault can pay a close the moment it happens.","properties":{"allocationReason":{"type":["string","null"]},"arriving":{"description":"What closes are waiting on a return hop for.","format":"double","type":"number"},"balance":{"format":"double","type":"number"},"distance":{"description":"Balance less floor. Negative is the dollar figure of \"arriving\".","format":"double","type":"number"},"floor":{"format":"double","type":"number"},"freeCollateral":{"description":"Protected local cash. New snapshots protect escrow and explicit reserve\noverrides; older models also protected all free customer collateral.","format":"double","type":"number"},"health":{"description":"[`SolanaHealth::code`].","type":"string"},"lockedIm":{"description":"Users' locked initial margin: the lendable pool, till included.\n`None` when `till_ratio` is zero, which is the one case the identity\n`till = ratio × locked_im` cannot be inverted through.","format":"double","type":["number","null"]},"lockedOnSolana":{"description":"Locked margin that has NOT been pledged to the hedge venue.","format":"double","type":["number","null"]},"payoutsDue":{"description":"Actual pending payout cash beyond already protected collateral.","format":"double","type":["number","null"]},"realizable":{"description":"What the winners could realize inside one outbound hop.","format":"double","type":"number"},"till":{"description":"The share of locked margin held back so closes pay instantly.","format":"double","type":"number"},"tillRatio":{"description":"The ratio that till was computed at, echoed from the model.","format":"double","type":"number"},"withdrawalOnDemand":{"description":"Whether unused settled collateral can be bridged until requested.","type":"boolean"}},"required":["withdrawalOnDemand","floor","balance","distance","till","tillRatio","realizable","freeCollateral","arriving","health"],"type":"object"},"StatsSummaryResponse":{"properties":{"activeTraders24h":{"format":"int64","type":"integer"},"asOf":{"format":"date-time","type":"string"},"feeRevenue24hUsd":{"type":"string"},"openInterestUsd":{"type":"string"},"venues":{"items":{"$ref":"#/components/schemas/VenueStats"},"type":"array"},"volume24hUsd":{"type":"string"},"volume7dUsd":{"type":"string"},"volumeAllUsd":{"type":"string"}},"required":["volume24hUsd","volume7dUsd","volumeAllUsd","openInterestUsd","activeTraders24h","feeRevenue24hUsd","venues","asOf"],"type":"object"},"StatusResponse":{"properties":{"db":{"type":"string"},"indexer":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ServiceHealth"}]},"orderBot":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ServiceHealth"}]}},"required":["db"],"type":"object"},"SubmissionOutcome":{"enum":["rejected","unknown","duplicate"],"type":"string"},"SweepBalances":{"properties":{"BTC":{"format":"double","type":"number"},"ETH":{"format":"double","type":"number"},"SOL":{"format":"double","type":"number"}},"required":["SOL","BTC","ETH"],"type":"object"},"SyncSweepResponse":{"properties":{"balances":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SweepBalances","description":"Pre-sweep snapshot of the profile's tokenized residue. Present whenever\nthe response had something to act on — useful for the bot to confirm\npost-close what was reclaimed. Token amounts are in their respective\nnative scales (SOL: lamports / 1e9, BTC: 1e8, ETH: 1e8)."}]},"message":{"type":"string"},"status":{"description":"`\"clean\"` — nothing to sweep (no token-form residue on the profile).\n`\"queued\"` — a sweep was already triggered recently; this call is a\nno-op until the lock expires (~10s).\n`\"swept\"` — the operator submitted sweep instructions to bring residual\nWSOL/WBTC/WETH back to the user wallet as USDC.","type":"string"}},"required":["status","message"],"type":"object"},"TpSlOrderResponse":{"description":"Unified TP/SL order response - represents orders from both Jupiter native and Imperial passthrough","properties":{"closeBps":{"description":"Decrease-only close percentage in basis points (10000 = 100%). The\non-chain Order carries this so the program can apply it to live\nposition size at execution; surfacing it here lets the FE render the\nuser's chosen percentage without falling back to the\n`orderSizeUsd / positionSizeUsd` ratio (which drifts above 100% when\nthe parent limit fills slightly under-size). `None` for legacy rows\ncreated before the indexer started splatting `close_bps` into\n`passthrough_orders.order_data`.","format":"int32","minimum":0,"type":["integer","null"]},"currentBestPriceUsd":{"type":["string","null"]},"currentIndicatorValue":{"format":"double","type":["number","null"]},"dcaEndLeverageScaled":{"format":"int64","type":["integer","null"]},"dcaEndPriceUsd":{"type":["string","null"]},"dcaIntervalSeconds":{"format":"int64","type":["integer","null"]},"dcaLastLegTimestamp":{"format":"int64","type":["integer","null"]},"dcaLegsExecuted":{"format":"int64","type":["integer","null"]},"dcaLeverageWeightingType":{"format":"int64","type":["integer","null"]},"dcaNumLegs":{"format":"int64","type":["integer","null"]},"dcaPriceLimitUsd":{"type":["string","null"]},"dcaStartAt":{"description":"Optional delayed-start unix ts (0/None = no delay). Immutable after\ncreation, so the indexer's `order_data` is an authoritative source —\nunlike `dca_last_leg_timestamp`, this never needs WS enrichment.","format":"int64","type":["integer","null"]},"dcaStartLeverageScaled":{"format":"int64","type":["integer","null"]},"dcaStartPriceUsd":{"type":["string","null"]},"dcaWeightingType":{"format":"int64","type":["integer","null"]},"indicatorCondition":{"type":["string","null"]},"indicatorRatchetSize":{"type":["string","null"]},"indicatorThreshold":{"type":["string","null"]},"indicatorTimeframe":{"type":["string","null"]},"isActivated":{"type":["boolean","null"]},"isReverseFib":{"type":["boolean","null"]},"isStopArmed":{"type":["boolean","null"]},"orderPda":{"type":["string","null"]},"orderType":{"type":"string"},"profileIndex":{"format":"int32","type":["integer","null"]},"ratchetSizeUsd":{"type":["string","null"]},"sizeUsd":{"type":"string"},"source":{"type":"string"},"stopTriggerPriceUsd":{"type":["string","null"]},"triggerCondition":{"type":["string","null"]},"triggerHitAt":{"format":"int64","type":["integer","null"]},"triggerPriceUsd":{"type":"string"},"worstPriceUsd":{"type":["string","null"]}},"required":["source","orderType","triggerPriceUsd","sizeUsd"],"type":"object"},"UnifiedFundingRatesResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/UnifiedFundingRow"},"type":"array"}},"required":["rows"],"type":"object"},"UnifiedFundingRow":{"properties":{"gmtrade":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueFundingRate"}]},"jupiter":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueFundingRate"}]},"phoenix":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueFundingRate"}]},"sunrise":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueFundingRate","description":"Imperial Armada. Keyed on the UNDERWRITER's own book, not on the hedge\nvenue behind it: one Armada byte fans out to several user-facing venues\nand this rate is Imperial's, charged identically whichever venue carries\nthe hedge. The hedge venue's own rate is not served here — it belongs to\nthat venue and lives on `/armada/orderbook`."}]},"symbol":{"type":"string"}},"required":["symbol"],"type":"object"},"UnifiedMarkPriceRow":{"properties":{"gmtrade":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueMarkPrice"}]},"jupiter":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueMarkPrice"}]},"phoenix":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VenueMarkPrice"}]},"symbol":{"type":"string"}},"required":["symbol"],"type":"object"},"UnifiedMarkPricesResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/UnifiedMarkPriceRow"},"type":"array"}},"required":["rows"],"type":"object"},"UserErrorDetails":{"description":"Additive, user-safe error metadata shared by HTTP, WebSocket, history, and\nnotification surfaces. Raw provider/program text must never be copied into\nthis structure.","properties":{"action":{"type":["string","null"]},"code":{"type":"string"},"message":{"type":"string"},"outcome":{"type":["string","null"]},"referenceId":{"type":["string","null"]},"retryAfterSecs":{"description":"How long a client should wait before trying the SAME request again, when\nthe refusal is a \"not yet\" rather than a \"no\".\n\n⚠ The machine-readable half of a wait. The seconds are also in\n`message`, and a client must never parse them back out of it: that text\nis English prose, it is the half we intend to translate, and a client\nkeying on it would break on the first locale. Absent means \"we do not\nknow how long\", which is not the same as zero.","format":"int32","minimum":0,"type":["integer","null"]}},"required":["code","message"],"type":"object"},"Venue":{"enum":["jupiter","phoenix","gmtrade","armada"],"type":"string"},"VenueCapability":{"properties":{"asset":{"type":"string"},"borrowHourlyPct":{"description":"Hourly borrow/funding cost as a percent of notional. JLP-style venues charge\nborrow on utilized liquidity; CLOBs run zero-sum funding and pass ~0.","format":"double","type":["number","null"]},"borrowHourlyPctBySide":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SideRates","description":"Side-specific override for `borrow_hourly_pct`. Required for GMTrade where long\nand short pay different combined funding+borrow rates. Negative entries mean the\nvenue pays the trader on that side; cost computation clamps to 0."}]},"closeFeeBps":{"format":"double","type":"number"},"degenLeverageThreshold":{"description":"Leverage above which the venue's degen tier (and its minimum\ncollateral) applies.","format":"double","type":["number","null"]},"maxLeverage":{"format":"double","type":"number"},"minDegenPositionCollateralUsd":{"description":"Venue minimum collateral (USD) for degen-tier opens (leverage above\n`degen_leverage_threshold`).","format":"double","type":["number","null"]},"minPositionCollateralUsd":{"description":"Venue minimum collateral (USD) for normal-tier opens, conservative\n`max(target custody, collateral custody)`. Prefund venues only.","format":"double","type":["number","null"]},"mmr":{"description":"Maintenance margin ratio (fraction of notional). Liquidation triggers when\nresidual collateral falls below `mmr × notional`.","format":"double","type":"number"},"openDeductionRate":{"description":"Total size-scaled rate the venue deducts from collateral at open time\n(the same rate folded into `max_leverage`, including any currently\nactive volatility fee). Prefund venues only — drives `requiredDeposit`.","format":"double","type":["number","null"]},"openFeeBps":{"format":"double","type":"number"},"supported":{"type":"boolean"},"venue":{"$ref":"#/components/schemas/Venue"}},"required":["venue","asset","supported","maxLeverage","openFeeBps","closeFeeBps","mmr"],"type":"object"},"VenueFundingRate":{"properties":{"longBorrowRatePerHourPercent":{"description":"Hourly borrow rate in percent. Always non-negative — the side holding\nthe position pays this regardless of funding direction. Set even when\nonly one side borrows (Jupiter longs pay borrow on the long custody;\nshorts on the short/USDC custody — both sides populated).","format":"double","type":["number","null"]},"longFundingRatePerHourPercent":{"description":"Hourly funding rate in percent (`0.04` = 0.04%). Sign convention is\nuniform across every venue this API serves: **positive long rate ⇒\nlongs pay shorts** (i.e. shorts receive); negative ⇒ shorts pay longs.\nGMTrade rates are passed through raw from the venue WS feed (which\nalready follows that convention); Phoenix exposes a single signed\nrate so the short-side value is the negation of the long-side. For\nJupiter this field is `None` — that venue charges borrow fees\ninstead, see `*_borrow_rate_*`.","format":"double","type":["number","null"]},"shortBorrowRatePerHourPercent":{"format":"double","type":["number","null"]},"shortFundingRatePerHourPercent":{"format":"double","type":["number","null"]},"source":{"$ref":"#/components/schemas/RateSource"}},"required":["source"],"type":"object"},"VenueLock":{"description":"A whitelist, not a blacklist: `excludedVenues` cannot express \"only Phoenix\"\nwithout also encoding every future venue we add.","properties":{"reason":{"description":"Why the lock exists, e.g. `\"unified_margin\"`.","type":"string"},"venues":{"description":"The only venues this profile may OPEN on while the lock is in force.\nCloses, reduces, and cancels always route to the position's own venue.","items":{"type":"string"},"type":"array"}},"required":["venues","reason"],"type":"object"},"VenueMarkPrice":{"properties":{"fetchedAtUnixMs":{"description":"Wall-clock time the underlying snapshot was last refreshed, in\nmilliseconds since epoch. For Phoenix this is the WS message\ntimestamp.","format":"int64","type":"integer"},"price":{"format":"double","type":"number"},"source":{"$ref":"#/components/schemas/PriceSource"}},"required":["source","price","fetchedAtUnixMs"],"type":"object"},"VenueStats":{"properties":{"openInterestUsd":{"type":"string"},"traderCount":{"format":"int64","type":"integer"},"venue":{"type":"string"},"volumeUsd":{"type":"string"}},"required":["venue","volumeUsd","openInterestUsd","traderCount"],"type":"object"},"VolumeBucket":{"properties":{"flashUsd":{"type":"string"},"gmtradeUsd":{"type":"string"},"jupiterUsd":{"type":"string"},"phoenixUsd":{"type":"string"},"sunriseUsd":{"type":"string"},"timestamp":{"format":"date-time","type":"string"},"totalUsd":{"type":"string"},"tradeCount":{"format":"int64","type":"integer"}},"required":["timestamp","totalUsd","jupiterUsd","flashUsd","phoenixUsd","gmtradeUsd","sunriseUsd","tradeCount"],"type":"object"},"VolumeResponse":{"properties":{"grouping":{"type":"string"},"period":{"type":"string"},"rows":{"items":{"$ref":"#/components/schemas/VolumeBucket"},"type":"array"}},"required":["period","grouping","rows"],"type":"object"}},"securitySchemes":{"bearer_jwt":{"bearerFormat":"JWT","description":"JWT issued via `/mobile/connect` + `/mobile/exchange`. The wallet claim in the JWT must match the `wallet` field in each trading request body.","scheme":"bearer","type":"http"}}},"info":{"contact":{"name":"Imperial","url":"https://imperial.trade"},"description":"\nImperial is a perpetual futures router on Solana — a single passthrough program that brokers\ntrades across multiple underlying perp venues (Jupiter, Phoenix, GMTrade, …).\nThis HTTP API is the surface third-party bots and integrators use to authenticate, submit\norders, and read public on-chain state.\n\n## Authentication\n\nTrading endpoints are JWT-gated. The flow:\n\n1. `POST /mobile/connect` — sign `imperial:mobile-connect:{wallet}:{nonce}` with your Solana\n   keypair and post `{wallet, message, signature}`. `{nonce}` must be a **current Unix\n   timestamp** — seconds or milliseconds (e.g. `Date.now()`), **not** a random value. It is\n   rejected if it is more than 5 minutes old or more than 60 seconds in the future, and each\n   nonce may be used only once. Response: a one-time `code`.\n\n   Note: on failure this endpoint returns an opaque `401 {\"error\":\"Failed to generate mobile\n   session\"}` for *any* underlying cause — bad signature, malformed/expired nonce, or replay —\n   so an unexpected 401 with a valid signature almost always means the nonce is not a fresh\n   timestamp. (A `400 {\"error\":\"Invalid message format\"}` specifically means the signed string\n   didn't match the `imperial:mobile-connect:{wallet}:{nonce}` prefix.)\n2. `POST /mobile/exchange` — exchange the `code` for `{jwt, expires_at}`. The code expires in\n   5 minutes and can only be redeemed once.\n3. Send trading requests with `Authorization: Bearer <jwt>`. The JWT's wallet claim must match\n   the `wallet` field in every request body.\n4. `POST /mobile/revoke` — revoke the JWT when you're done (e.g. bot shutdown, key rotation).\n\nThe JWT is a Solana-signature-anchored delegation: holding it is equivalent to letting the\noperator place orders from the associated wallet's profiles. Treat it like an API key — copy\nit once, store it securely, and revoke it if compromised.\n\nMulti-wallet bots (e.g. fan-out traders): each wallet owner independently completes the flow\non their own dashboard and shares their JWT with the bot operator. The bot then holds N tokens\nand calls the API in parallel, one request per wallet. Any wallet owner can revoke their token\nanytime without affecting the others.\n\n## Partner program\n\nIntegration partners with a partner code register each trading wallet once to receive their\ndiscounted fee tier and volume attribution:\n\n1. Complete the authentication flow above for the wallet.\n2. `POST /mobile/partner/register` with `{ \"code\": \"<YOUR-CODE>\" }` and the wallet's JWT.\n   The response confirms the discounted fee rate (bps) and the on-chain transaction that\n   activated it. Registration is one-time per wallet and idempotent — on a `503`, retry the\n   same call.\n3. `GET /mobile/partner/registration` — verify a wallet's registration status at any time.\n\nRegister a wallet **before** its first order; the discount applies to fees charged from that\npoint on. A wallet binds to one partner code permanently (first code wins).\n\n## Builder codes\n\nThird-party frontends and bots (\"builders\") can charge their users an additional fee on top\nof Imperial's standard fee by attaching their builder code to orders. The fee is a fixed bps\nrate (at most 10 bps) of each leg's **notional** — the position size traded, not the collateral —\nat every venue, Armada included:\n\n1. Pass `builderCode` on `POST /mobile/orders` (or on every leg of `POST /mobile/orders/batch`\n   — all legs must carry the same code, or none). The code's rate is charged on **both** the\n   open and close legs. Codes are case-insensitive; unknown or deactivated codes are rejected\n   with `400`.\n2. Preview the fee: `GET /trade-preview?...&builderCode=<CODE>` adds `builderFeeUsd` and\n   `builderFeeBps` to each side's fee estimate, folded into `totalFeesUsd`.\n3. `GET /mobile/builder/summary?code=<CODE>` — accrued, paid, and claimable builder fees plus\n   the latest payouts. The code itself is the credential; no JWT required.\n\nAccrued fees are paid out periodically to the builder's registered payout wallet. Disclose\nthe fee to your users in your app — the charge is per-order and only applies to orders that\ncarry your code.\n\n### Builder codes on Armada\n\nArmada orders (`underwriter: 7`) take a builder code the same way: pass `builderCode` on the\norder and preview it with `trade-preview`. Nothing else in the request changes. What differs:\n\n- **Charged at the fill, on filled notional.** The fee comes out of the account's Armada margin\n  as each fill settles, after Armada's own fee, on the size that actually filled. If the account\n  cannot cover it, the builder receives what is left rather than the trade failing.\n- **Max leverage is slightly lower.** The fee counts against the account's leverage ceiling, so\n  a builder-coded open accepts a little less leverage — at a 25× ceiling, about 24.3× instead of\n  24.9× at 10 bps. Orders above it are refused with `400`.\n- **Prime-bound accounts** count the builder fee in the borrowing check, so an open right at\n  the Prime limit can be refused with a code that would pass without one.\n- **Resting orders keep the code.** Limit, TP/SL and DCA orders placed with your code are charged\n  when they execute. Liquidations never carry a builder fee.\n- **Accrual appears once indexed.** `GET /mobile/builder/summary` shows an Armada fee after the\n  fill is indexed, normally within seconds.\n\n## Imperial points\n\n`GET /mobile/points` (JWT required) returns the wallet's Imperial points for the active\nseason — the same on-chain balance the Imperial homepage displays. Points are credited in\nweekly batches at each week's close, so the figure updates weekly rather than per-trade.\n\n## Rate limits\n\nUnified limit: **600 requests/minute sustained, burst of 120**. Keyed per-wallet when a valid\nJWT is present, else per-IP. Exceeding the budget returns `429 Too Many Requests` with a\n`Retry-After` header and `{ \"error\": \"rate_limited\", \"retry_after_seconds\": N }` body. The\n`/health`, `/ws`, and Telegram webhook endpoints are not rate limited.\n\n## WebSockets\n\nTwo endpoints. Both are unauthenticated and exempt from the rate limiter.\n\n### `GET /ws` — wallet-scoped position/order stream\n\nSubscribe by wallet — single `wallet`, or a `wallets` array to multiplex a fleet over ONE\nsocket. By default you get coalesced invalidation triggers only. Add `\"positionState\": true`\nto also have the full position rows pushed (so you never poll `/positions`), and\n`\"tradeEvents\": true` for typed settled close events (so you never poll `/trades` at close).\n\n```json\n{ \"type\": \"subscribe\", \"wallet\": \"<base58-pubkey>\", \"positionState\": true }\n{ \"type\": \"subscribe\", \"wallets\": [\"<pk1>\", \"<pk2>\"], \"tradeEvents\": true }\n```\n\nEvery `subscribe` REPLACES the connection's whole subscription set (the single-`wallet` form\nis a set of one — identical to the historical behavior), so a fleet client just resends its\nfull roster when wallets change; `\"wallets\": []` clears and there is no separate unsubscribe\nmessage. Caps: 200 wallets per connection; `positionState` is honored up to 25 subscribed\nwallets (each open wallet costs a periodic `/positions` computation; the per-second re-marks\nbetween are served from a local price cache) — beyond that the\nserver sends `{ \"type\": \"error\", \"code\": \"position_state_wallet_limit\", \"limit\": 25 }` once\nand disables it for the connection (triggers and `tradeEvents` still flow). Every\nwallet-scoped frame carries a top-level `wallet` field for demuxing.\n\n- `{ \"type\": \"position_state\", \"seq\": N, \"positions\": [ ... ] }` — **opt-in** (`positionState:\n  true`). The full `/positions` response rows (identical shape: `markPrice`, `pnlUsd`,\n  `pnlPercent`, `collateralUsd`, `ourLiquidationPriceUsd`, `sizeUsd`, `entryPrice`, `side`,\n  `status`, and any attached `stopLossPrice`/`takeProfitPrice`). `seq` is a per-connection\n  monotonic counter for ordering / gap detection across a reconnect. Cadence:\n  - one frame immediately on `subscribe` (snapshot) — this doubles as the subscribe ack;\n    there is no separate `subscribed` frame. The snapshot may be `positions: []` if the\n    wallet is currently flat — that's a valid frame, not an error.\n  - a frame on every position change (open / close / SL-TP fire / liquidation);\n  - while a position is open, a re-marked frame ~1×/s so live PnL / mark / liq track without\n    polling. **While the wallet is flat, no periodic frames are sent** (the socket goes quiet\n    until the next change), so an empty frame followed by silence means \"no open position\".\n- `{ \"type\": \"positions_updated\", \"wallet\": \"<pk>\" }` — the coalesced invalidation trigger,\n  always sent (also alongside `position_state` when opted in). Clients consuming\n  `position_state` can ignore it.\n- `{ \"type\": \"orders_updated\", \"wallet\": \"<pk>\" }` — fired on order creation, execution, and\n  cancellation. Refetch `/orders` for the unified view, or\n  `/passthrough/users/{wallet}/orders` for the passthrough subset only.\n- `{ \"type\": \"order_rejected\", \"wallet\": \"<pk>\", \"actionId\": \"<uuid>\", \"reasonCode\":\n  \"<code>\" }` — a specific order was rejected on-chain.\n- `{ \"type\": \"position_closed\" | \"position_liquidated\", \"wallet\": \"<pk>\", \"lifecycleId\":\n  \"<uuid>\", \"closedAt\": <unix-seconds>, \"trade\": { ...LifecycleResponse } }` — **opt-in**\n  (`tradeEvents: true`). Fired once per closed lifecycle, only after the close has fully\n  SETTLED (every closing leg's sweep finalized), carrying the same row `/trades` would\n  return — `netCashPnlUsd`, `totalFeesUsd`, `fees`, per-action `userReceived`, `closedAt` —\n  so no close-time `/trades` re-poll is needed. Note the ordering: the instant the close\n  lands you get `positions_updated` (and a `position_state` frame in which the position is\n  gone); the typed event follows seconds later when settlement completes. Dedupe by\n  `lifecycleId` — delivery is at-least-once across reconnects.\n\nPing/pong keepalive: send `{ \"type\": \"ping\" }`, receive `{ \"type\": \"pong\" }`. On reconnect,\nre-`subscribe`; the snapshot `position_state` re-syncs current state (there is no server-side\nreplay buffer for missed intermediate frames).\n\n### `GET /ws/market` — public market-data stream\n\nReplaces polling on `/funding-rates`, `/mark-prices`, and `/phoenix/depth`. Each push carries\nthe full payload, so clients never have to refetch. On every successful `subscribe_*` the\nserver immediately pushes the current cached snapshot before tracking diffs — late\nsubscribers don't have to wait a publisher tick.\n\nSubscribe / unsubscribe messages:\n\n```json\n{ \"type\": \"subscribe_funding_rates\" }\n{ \"type\": \"subscribe_mark_prices\" }\n{ \"type\": \"subscribe_phoenix_depth\" }                      // all symbols\n{ \"type\": \"subscribe_phoenix_depth\", \"symbols\": [\"SOL\"] }  // filtered\n{ \"type\": \"unsubscribe_funding_rates\" }\n{ \"type\": \"unsubscribe_mark_prices\" }\n{ \"type\": \"unsubscribe_phoenix_depth\" }\n{ \"type\": \"ping\" }\n```\n\nServer-pushed events:\n\n- `{ \"type\": \"funding_rate_update\", \"symbol\": \"SOL\", \"venue\": \"phoenix\" | \"jupiter\" |\n  \"gmtrade\", \"source\": <RateSource>, \"longFundingRatePerHourPercent\": <f64|null>,\n  \"shortFundingRatePerHourPercent\": <f64|null>, \"longBorrowRatePerHourPercent\": <f64|null>,\n  \"shortBorrowRatePerHourPercent\": <f64|null> }` — same shape as a per-venue entry on\n  `/funding-rates`. The `source` discriminator carries provenance (live vs. fallback);\n  filter on it the same way you would in the REST response.\n- `{ \"type\": \"mark_price_update\", \"symbol\": \"SOL\", \"venue\": \"phoenix\" | \"jupiter\"\n  | \"gmtrade\" | \"index\", \"source\": <PriceSource>, \"price\": <f64>, \"fetchedAtUnixMs\":\n  <i64> }`. The `venue: \"index\"` entries (`source: \"pyth_lazer\"`) are **the price Imperial\n  marks positions with** — the same feed that drives `/positions`/`position_state`\n  `markPrice`, PnL, and liquidation monitoring. Chart, PnL, and liq logic can all read this\n  one number (≤1 Hz per symbol, change-gated); the other venue entries are each venue's own\n  oracle and may differ slightly.\n- `{ \"type\": \"phoenix_depth_update\", \"symbol\": \"SOL\", \"snapshot\": <DepthSnapshot> }` —\n  the `symbol` here is the **Phoenix-raw** symbol (`GOLD` rather than `XAU`), matching\n  what `/phoenix/depth` returns.\n\nFunding sign convention: `longFundingRatePerHourPercent > 0` means longs pay shorts.\nPhoenix's short rate is the negation of its long rate (single-pool perp); GMTrade reports\nboth sides independently. Funding fields are `null` for venues that only expose borrow\nrates (Jupiter) — populated borrow fields are non-negative regardless of side.\n\nSymbols are canonical (`SOL`, `BTC`, `XAU`) on `funding_rate_update` and `mark_price_update`;\n`phoenix_depth_update` uses Phoenix-raw symbols (`SOL`, `GOLD`, …). Translate accordingly.\n\n## Order types\n\nThe `orderType` field on `/mobile/orders` takes a numeric code:\n\n| Code | Name             | Notes                                                   |\n|-----:|------------------|---------------------------------------------------------|\n|    0 | Market           | `triggerPrice` ignored                                  |\n|    1 | Limit            | Requires `triggerPrice`                                 |\n|    2 | StopLimit        | Requires `triggerPrice`                                 |\n|    3 | LandMine         | Requires `triggerPrice`, `extraData.waitPrice`, `waitDuration` |\n|    4 | Ratchet          | Requires `extraData.worstPrice`, `ratchetSize`          |\n|    6 | RatchetEntry     | Same extras as Ratchet                                  |\n|    9 | Dca              | Requires `extraData.dcaStartPrice`, `dcaEndPrice`, `dcaNumLegs` |\n|   10 | FibRatchet       | Same extras as Ratchet                                  |\n|   11 | FibRatchetEntry  | Same extras as Ratchet                                  |\n|   12 | DcaClose         | Requires `extraData.dcaCloseStartPrice`, `dcaCloseEndPrice`, `dcaCloseNumLegs` |\n|   13 | DcaTimeClose     | Requires `extraData.dcaCloseIntervalSeconds`, `dcaCloseNumLegs` |\n|   14 | DcaRatchetClose  | Requires `extraData.dcaCloseNumLegs`, `dcaCloseRatchetSize` |\n|   15 | DcaTime          | Requires `extraData.dcaIntervalSeconds`, `dcaNumLegs`   |\n|   16 | DcaRatchet       | Requires `extraData.dcaNumLegs`, `dcaRatchetSize`       |\n\n`underwriter`: 0 = Jupiter, 2 = Phoenix, 3 = GMTrade, 7 = Imperial Armada. Wire format\nmirrors `passthrough_client::Underwriter`. Bytes 1 and 4 were Flash Trade and Flash Trade V2,\n5 was Imperial Pairs and 6 was Imperial Touch; those venues have SHUT DOWN and all four bytes\nare rejected on create. They still appear on historical orders and positions.\n`side`: 0 = long, 1 = short.\n`action`: 0 = Increase, 1 = Decrease.\n\nArmada uses `underwriter: 7`. `requestedHedgeVenue` is an optional execution\nconstraint: `0` (the default) lets Armada choose its hedge route; `1` binds to\nArmada's current hedge route. Values `2` and `3` are reserved and rejected.\nA binding route is never silently substituted. On every other underwriter,\nthis field must be `0`.\n\nOn Armada, `collateralAmount` on an open is the position's **full** margin (µUSDC) on either\nseat (`extraData.sunriseSeat`). Do not subtract what the account already holds — the operator\nnets it: a cross open deposits only what the cross seat's free collateral does not cover; an\nisolated open draws the Armada account's `drawableUsd` (`GET /api/v1/armada/margin`, cross\nseat) first and only then the wallet. Unlike Phoenix's cross seat, this is not a top-up.\n\nA Armada (`underwriter: 7`) **open** must carry a non-zero `slippageBps`: nothing on chain\ncan verify the price Imperial reports for an off-Solana fill, so the user's own band is the\nentire bound and `0` means unbounded. Closes are exempt so a must-close is never blocked.\n\n## Price scales\n\n`triggerPrice` — and every price inside `extraData` (`worstPrice`, `waitPrice`, DCA\nstart/end/limit prices) — is **always oracle scale (1e9)**: `price × 1e9`, on every venue.\n\n`marketPrice` on `/mobile/orders` (and `price` on `/mobile/orders/collateral`) is\n**per-underwriter**: it is forwarded to each venue's on-chain slippage check in that\nvenue's native encoding. Sending the wrong scale makes market orders fail the slippage\nguard (or pass it vacuously). Example encodings for SOL at $64.94:\n\n| Underwriter       | Scale                                  | Encoded value |\n|-------------------|----------------------------------------|---------------|\n| 0 Jupiter         | micro-USD, `price × 1e6`               | `64940000`    |\n| 2 Phoenix         | micro-USD, `price × 1e6`               | `64940000`    |\n| 3 GMTrade         | oracle scale, `price × 1e9`            | `64940000000` |\n| 7 Imperial Armada| micro-USD, `price × 1e6`               | `64940000`    |\n\n⚠ On Armada (7) this field is not only a slippage reference. It becomes the request's\n`expected_price` on `imperial_sunrise` (rescaled to that program's 1e9 units), which is the\nvalue BOTH on-chain price bands measure against — the user's `slippageBps` band and the\nconfig's `maxFavourableBps`, the latter being Imperial's own protection. A `marketPrice` far\nfrom the venue's mark is refused after the hedge rather than filled, so send a live one.\n\nGMTrade uses 1e9 because micro-USD truncates\nsub-$0.001 assets (BONK ≈ $0.0000074 → `7` at 1e6) and collapses the slippage band to zero.\n\n## Maker orders (Phoenix-native)\n\nBy default a `Limit` (or `PrivateTpSl`) order on `underwriter = 2` (Phoenix) is **not** a\nmaker order: the API rewrites it to `StopLimit` and the keeper triggers an IOC market fill\nwhen the price is reached. To rest a genuine maker order on Phoenix's CLOB, set\n`phoenixNative: true` on the create request. This is the only supported maker path; no other\nvenue rests natively today.\n\nSemantics when `phoenixNative` is set:\n\n- **Eligibility.** Honored only for `underwriter = 2` AND either `orderType = 1` (Limit) with\n  `action = 0` (Increase), or `orderType = 5` (PrivateTpSl). Ignored for any other shape. A\n  Phoenix-native `Limit` with `action = 1` (Decrease) is rejected on-chain — express\n  close-side resting orders as `PrivateTpSl` instead.\n- **Always post-only.** The order is placed with Phoenix `PostOnly` and slide disabled, so it\n  never takes liquidity. There is no separate `postOnly`/`maker` field — it's implied. An\n  order that would cross the spread is **rejected** (not repriced): submit a non-crossing\n  `triggerPrice`, or use `orderType = 0` (Market) for taker fills.\n- **Price/size scale is unchanged.** `triggerPrice` stays oracle scale (1e9), `sizeUsd` stays\n  6-decimal; the order bot converts to Phoenix ticks/base-lots internally.\n- **No in-place update.** `/mobile/orders/update` against a native order returns\n  `success: false` (\"native limit-order updates not supported\"). To re-price or re-size,\n  cancel via `/mobile/orders/cancel` and place a new one. A pre-fill cancel refunds the\n  unfilled portion of the Imperial fee.\n- **Fills.** Partial fills are supported — the order stays open with reduced remaining size\n  until fully filled. A resting maker order fills when a taker crosses it, so it has no\n  transaction of its own; poll `/orders` / `/positions` (or watch the `/ws` user channel)\n  rather than treating placement as the fill. The order's status moves `resting → filled` in\n  place; `action` never changes.\n\nDefault routing (unset / `false`) deliberately avoids the native path — it dodges a class of\nindexer/orderbook-watcher edge cases that have stranded collateral. Opt in only if you need\ntrue resting CLOB orders (e.g. maker-fee strategies).\n\n## Imperial Touch history\n\nImperial Touch (one-touch binaries, `underwriter: 6`) has SHUT DOWN and new orders are\nrejected. `GET /api/v1/touch/positions` still lists a wallet's past contracts: `status` is\n`settled` (expired, `won` tells you the outcome) or `bought_back`, and `payoutPaidUsd` is what\nthe wallet received. Touch positions appear only there — they are absent from `/positions`,\n`/trades`, `/orders`, `/order-history`, `/stats/*`, and both WebSocket streams.\n\n## Subaccounts (profiles)\n\nEvery wallet has six implicit subaccounts indexed `0..5`. Balances and positions don't mix\nacross them. There is no \"create subaccount\" call — the on-chain profile PDA is created\nlazily by the order-bot on the first `/deposit/build-tx` or first `/mobile/orders`\nplacement that targets that `profileIndex`. `/mobile/balances` will return `usdc: 0` for\nevery uninitialized profile without erroring; that's a normal \"this index has never been\nused\" response, not a provisioning gate.\n\n`profileIndex` is required on every order, deposit, and balance call. Pick a convention\n(e.g. profile 0 for default, 1+ for strategy buckets) and stick with it — there's no\nserver-side notion of a \"primary\" profile.\n\nEach profile carries a **margin mode**: `isolated` (the default, and the only model on\nevery venue but Phoenix) or `unified` — see below. `GET\n/passthrough/users/{wallet}/profiles` returns all six with their current mode.\n\n## Cross margin (unified margin)\n\nA profile set to `unified` routes its new positions onto Phoenix's **cross-margin seat**:\none collateral balance backing every market open on it, so a winning leg's equity supports\na losing one. Isolated profiles keep the per-position model — collateral belongs to the\norder, and each position liquidates on its own.\n\nCross margin is Phoenix-only and per-profile. A common setup is profile 0 isolated for\nmulti-venue work and, say, profile 1 unified for a Phoenix portfolio.\n\n### Turning it on\n\n1. `PUT /passthrough/users/{wallet}/profiles/{index}/name` with `{\"name\": \"...\"}` — the\n   margin mode lives on the row this creates, so an unnamed profile returns `400 \"Name\n   this profile before setting a margin mode\"`.\n2. `PUT /passthrough/users/{wallet}/profiles/{index}/margin-mode` with\n   `{\"marginMode\": \"unified\"}` (or `\"isolated\"` to switch back).\n\nBoth accept either a JWT (`Authorization: Bearer`) or a Solana signature sent as `x-pubkey`\n+ `x-signature` — over `imperial:profile-name:{wallet}:{index}` for the name call, and over\n`imperial:margin-mode:{wallet}:{index}:{marginMode}` for the margin-mode call (the mode is\nin the signed message, so a captured signature can't be replayed to flip it back). The\nmargin-mode response echoes the mode plus a `venueLock`\n(`{\"venues\": [\"phoenix\"], \"reason\": \"unified_margin\"}` under unified). Apply that lock in\nyour own routing: it is a whitelist, and the api and order bot both reject a violating open\nonly after the round trip.\n\nSwitching is legal in either direction **with positions open** — there is no flat\nrequirement. That has a consequence worth building around: a live cross seat routinely\noutlives a switch back to isolated, so an existing position's margin mode is a property of\nthe position, never of the profile's current flag. Read `directVenue.marginMode`\n(`\"cross\"`) off the position row.\n\n### What a unified profile may not do\n\nThese apply to **opens only** (`action: 0`). Closing, reducing, editing collateral, and\ncancelling always work, on any venue the profile already holds a position on.\n\n| Rejected | Why |\n|----------|-----|\n| Any `underwriter` other than `2` | The seat is Phoenix's. Switch to isolated to trade elsewhere. |\n| Imperial Lending Pool loans | Loans are isolated-margin by construction; the program refuses every loan affordance on the cross seat. A profile holding an open loan also can't be switched to unified at all (`400`). |\n| Markets with `isolatedOnly: true` on `GET /phoenix/markets` | Phoenix itself rejects them on the cross seat. |\n\n### Sizing an open\n\nOn a cross seat `collateralAmount` is **not the position's margin** — it is a top-up\ndeposit into the seat, riding the same instruction as the increase. Per-leg leverage is not\na real quantity there; the seat as a whole margins every position.\n\nAdmission control is seat-wide. Let\n\n- `free = effectiveCollateralUsd − initialMarginUsd` (both from\n  `GET /phoenix/margin-account`), and\n- `required = sizeUsd / maxLeverage` for the market (`maxLeverage` from\n  `GET /phoenix/markets`, first tier).\n\nThe open is admitted when `free + collateralAmount ≥ required`. So `collateralAmount: 0` is\nlegal and normal once the seat has headroom — the leg opens against posted margin. The\ncheck uses the market's headline leverage, so a position large enough to fall into a\nlower-leverage tier needs more than `required`; Phoenix's own margin check is the final\nauthority and will still revert an oversized open.\n\n### Reading seat state\n\n`GET /phoenix/margin-account?walletAddress={wallet}&profileIndex={n}` returns the seat's\naccount-level figures, or `null` when the profile has never been onboarded onto one (a real\nstate, not an error — nothing has been deposited yet). This is the endpoint to poll before\nthe first trade, when there are no position rows to read figures off.\n\n`GET /positions` carries the same object per profile as `phoenixCrossSeats[]` — in the same\nresponse as the rows deliberately, so margin figures and positions are never from two\ndifferent instants. The `/ws` `position_state` frames carry rows **only**, so a cross-margin\nbot still polls `/phoenix/margin-account` for margin and liquidation.\n\nEvery figure is a verbatim pass-through of Phoenix's own trader view — Imperial recomputes\nno portfolio margin, and `riskState` / `riskTier` are Phoenix's words. An absent field means\nPhoenix didn't report it: render it as unknown, never as `0`.\n\nKey fields:\n\n- `accountCollateralUsd` — deposited collateral. `effectiveCollateralUsd` — collateral plus\n  discounted unrealised PnL, i.e. what actually backs positions.\n- `initialMarginUsd` — what open positions and resting orders already consume. Free margin\n  is the subtraction above.\n- `maintenanceMarginUsd`, `riskState`, `riskTier` — Phoenix's liquidation-side view.\n- `maxPositions` / `openPositions` — the seat's hard, unraisable ceiling on concurrent\n  markets, and how many it currently holds. Phoenix has no resize instruction, so treat the\n  ceiling as fixed.\n- `liquidationPricesBySymbol` — per-market liquidation prices **as Phoenix computes them**,\n  keyed by Phoenix's RAW symbol (`GOLD`, not `XAU`). A **`null` value** means Phoenix quotes\n  no price for that market — routine on a cross seat, where a leg the rest of the balance\n  absorbs long before maintenance is breached has no level of its own. The key stays present\n  so the map still enumerates every market. Phoenix signals this with a negative price\n  (`-1`); render `null` as unknown, never as `0` — a `$0` liquidation price on a long reads\n  as \"cannot be liquidated\", which is the inverse of the truth here.\n\n### Positions on a cross seat\n\n- **Per-position liquidation fields are `null`**: `liquidationPrice`,\n  `partialLiquidationPriceUsd`, and `breakevenPriceUsd` are suppressed on cross rows. They\n  come from an isolated kernel that divides one position's collateral into one position's\n  requirement, and on a shared seat neither term exists. Use\n  `liquidationPricesBySymbol`. Note that a price there is the level at which *that market*\n  becomes liable — Phoenix liquidates a cross seat by closing whichever positions its own\n  engine picks, so it is not a promise about which leg dies.\n- **`positionPda` is the trader account, shared by every leg on the seat.** Key your local\n  state on `(positionPda, asset, side)`, not `positionPda` alone. Treat\n  `/phoenix/margin-account` as authoritative for what the seat holds — `openPositions` and\n  the keys of `liquidationPricesBySymbol` enumerate every market on it, including any leg\n  the position rows haven't caught up with.\n- `collateralUsd` and `leverageX` on the row still report what Imperial tracked as\n  deposited against that leg. They are real numbers, but they are not the seat's margin —\n  don't compute risk from them.\n\n### Adding and removing margin\n\n`POST /mobile/orders/collateral` with `underwriter: 2`, `action: 0` to add or `1` to\nremove, and `marketMint` / `side` naming **any market currently open on the seat**. The\ninstruction is keyed on a market because every other venue holds collateral per position;\na cross seat has one balance, so the choice of market is transport, not scope.\n\nThe withdrawal cap is `effectiveCollateralForWithdrawalsUsd − initialMarginForWithdrawalsUsd`,\n**not** free margin — Phoenix values a withdrawal on a stricter basis where unrealised\nprofit doesn't count out, and publishes both terms pre-derived. If it reports neither, do\nnot withdraw. The flat collateral fee is charged in both directions (it's a cash-movement\nfee), so the seat receives `amount − fee` on an add and you receive `amount − fee` on a\nremove.\n\n## Trading bot quickstart\n\nEnd-to-end flow for a bot that opens a position on one venue and hedges it on another.\n\n1. **Auth** — `POST /mobile/connect` then `POST /mobile/exchange` to get a JWT (see\n   \"Authentication\" above). All subsequent trading calls send `Authorization: Bearer <jwt>`.\n2. **Fund the profile** — `POST /deposit/build-tx` with `mode: \"deposit\"` returns a sponsored,\n   partially-signed transaction that creates the profile (if new) and deposits USDC from the\n   wallet's USDC ATA. Sign with the wallet's keypair and submit. Same endpoint with\n   `mode: \"withdraw\"` pulls USDC back to the wallet. Skip if the profile is already funded\n   for the size you want to trade. **Size deposits from `/route`'s `requiredDeposit`** (pass\n   `wallet` for your exact fee tier), NOT from `expectedCostUsd`/`costBreakdown` — the\n   breakdown's `borrow`/`expectedLiqCost` terms are hold-period expectations that are never\n   reserved upfront, and sizing off them over-funds by an order of magnitude.\n3. **Pick a venue pair** — `GET /funding-rates` returns one row per canonical symbol with\n   per-venue funding/borrow rates. Decide which venue to long and which to short. For a\n   single-venue trade, `GET /route?asset=...&side=...&notional=...` returns a cost-optimized\n   recommendation.\n4. **Check available USDC** — `GET /mobile/balances` returns the per-subaccount USDC balance\n   in 6-decimal native units. Size each leg against this; an underfunded profile fails the\n   margin check on submission.\n5. **Open the legs** — `POST /mobile/orders` with `action=0` (Increase). Use `symbol` (e.g.\n   `\"SOL\"`, `\"XAU\"`) plus `underwriter` to address the market — the same canonical symbol\n   you read from `/funding-rates`. The server resolves it to the right `marketMint` per\n   venue, including Phoenix synthetics that have no SPL mint of their own. Existing\n   integrations can still send `marketMint` directly. Fire the legs sequentially: if the\n   second leg fails, immediately submit a Decrease for the first to unwind. The endpoint\n   returns HTTP 200 even on rejection — always inspect `success` and `error` (see\n   \"Trading-endpoint response convention\" below). Successful resting orders return\n   `orderPda`, which you can pass straight to `/mobile/orders/cancel` or `/update` without\n   waiting for indexer catch-up. Phoenix orders auto-activate the trader account on first\n   use; you don't need to call `/phoenix/register` first. Per-venue market lists are still\n   available at `/phoenix/markets`, `/gmtrade/markets` if you need\n   underlying details (orderbook PDAs, leverage tiers, fee tables).\n6. **Track state** — subscribe to both WS endpoints. `/ws/market` pushes funding-rate / mark-\n   price / depth updates so you don't have to poll (use the `venue: \"index\"` mark-price\n   entries for the number Imperial marks with). `/ws` (with `subscribe` keyed on your\n   wallet) pings you when your positions or orders change; opt into `positionState` for\n   pushed rows instead of refetching `/positions`, and `tradeEvents` for the settled\n   `/trades` row at close instead of re-polling `/trades`. There is no replay buffer on\n   either socket — refetch on reconnect.\n7. **Close the legs** — `POST /mobile/orders` with `action=1` (Decrease) on each leg. Use\n   `closeBps` to size the close as a fraction of the live position: `10000` for a full\n   close, `2500` for a quarter. A full close may equivalently send `sizeUsd = 0` with\n   `closeBps` omitted. Avoid sizing closes by echoing the open's `sizeUsd`: venues that\n   denominate positions in the target asset convert `sizeUsd` at the CURRENT\n   oracle price, so any adverse price drift turns an intended full close into a partial\n   that leaves a dust position behind — which the venue then rejects (`MinCollateral`).\n   `closeBps` is evaluated against on-chain size at execution and is immune to that drift.\n8. **Reclaim residue** — after closing a position whose collateral was non-USDC (long\n   SOL/BTC/ETH custody on Jupiter leaves WSOL/WBTC/WETH on the profile), call\n   `POST /passthrough/users/{wallet}/profiles/{index}/sync` to swap residue back to USDC\n   and route to the user wallet. Idempotent. Pure-USDC venues (Phoenix shorts, USDC-margin\n   profiles) don't need this step.\n\n## Trading-endpoint response convention\n\n`POST /mobile/orders[/batch|/cancel|/collateral|/update]` always returns HTTP 200 once the\nrequest was well-formed and the order bot was reachable, even if the order was rejected\non-chain. Inspect the response body:\n\n- `success: true` — order accepted, `signature` is the Solana tx signature.\n- `success: false` — order rejected; `error` is a human-readable message and the raw bot\n  error is logged server-side. Common cases: leverage too high, market closed, slippage\n  exceeded, no matching open position for a close-side order.\n- `errorCode` — machine-matchable code for programmatic branching, present on every valid\n  submission rejection. Named codes: `LeverageTooHigh`, `LeverageTooLow`, `SlippageExceeded`,\n  `MarketClosed`, `TradingPaused`, `PositionAlreadyClosed`, `MinCollateral`\n  (open/increase collateral below the venue minimum), `ReduceBelowMinCollateral`\n  (a partial close would leave dust below the venue minimum — close fully or reduce\n  less), `InsufficientBalance`, `InsufficientVenueBalance`, `InsufficientMargin`,\n  `SwapRouteUnavailable`, `PostOnlyWouldCross`, `ProfileHasOpenPositions`,\n  `CancelViaAppOnly`, `LoanMarketNotWhitelisted`, `LoanVenueUnsupported`,\n  `LoanSizeAddBreaksFloor`, `VenueCapacity` (the venue's pool/custody is at its cap),\n  `VenuePriceFeedStale` (the venue's own mark-price feed failed its staleness/validity\n  check and the venue refused to price the order — upstream of Imperial, nothing was\n  submitted and no fee was charged; retry once the venue recovers), `VenueRejected`. The venue programs' Anchor 6000-range error numbers do not agree with\n  each other, so they are\n  resolved against the order's `underwriter`; a code in that band on a venue whose program\n  gives it no user-actionable meaning reports `VenueRejected` rather than naming the wrong\n  control. Any other on-chain rejection surfaces its raw program\n  error as `Custom<N>` (e.g. `Custom6034`). Failures without a recognizable provider/program\n  code use `UnknownTradeFailure` or `InternalError` rather than returning a null code.\n\nHTTP 4xx is reserved for malformed input or auth failures (missing/invalid JWT, mismatched\nwallet claim). HTTP 5xx is reserved for the server itself failing (database or order-bot\ntransport failures that aren't fully recoverable).\n","license":{"name":""},"title":"Imperial Trading API","version":"0.1.0"},"openapi":"3.1.0","paths":{"/api/v1/armada/fills":{"get":{"description":"# Read off the chain, not off a quote snapshot\n\n`order_quote_snapshots` exists because on Jupiter and Phoenix the quoted\nprice is computed in the browser and is lost unless it is stashed. Armada\nhas no such problem — `imperial_sunrise` needs `expected_price` on chain to\nbound its own price checks, and ACCEPT lands the fill beside it — so this\nreads `sunrise_requests`, where both halves already sit on one row and both\ncame from the chain.\n\n# What each number answers\n\n- `medianBps` / `adverseBps`: **did the user get the price the order\n  stated.** Positive is better for the user. ⚠ The reference is a\n  CLIENT-supplied `market_price`, so this is not a statement about\n  Imperial's spread.\n- The counts and `rejects`: **of the requests that reached the chain, how\n  many filled and why the rest did not.** Every reject carries the\n  price-blind reason that refused it, which is what makes \"we never reject\n  on price direction\" checkable rather than asserted.","operationId":"get_armada_fills","parameters":[{"description":"Trailing window in hours. Clamped to `1..=8760`.","in":"query","name":"windowHours","required":false,"schema":{"format":"int32","type":["integer","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaFillsResponse"}}},"description":"Per-market fill quality and settlement"}},"summary":"What Armada actually filled, per `(hedge_venue, market)`.","tags":["armada"]}},"/api/v1/armada/margin":{"get":{"operationId":"get_armada_margin","parameters":[{"in":"query","name":"wallet","required":true,"schema":{"type":"string"}},{"in":"query","name":"profileIndex","required":false,"schema":{"format":"int32","minimum":0,"type":"integer"}},{"description":"The ISOLATED seat to read, as seat text: the asset for the first\nisolated seat, the asset plus `#2` (`ZEC#2`) for the second. Absent\nreads the cross seat, which is the account keyed on the profile alone.","in":"query","name":"asset","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaMarginResponse"}}},"description":"Seat balance; every money field null when unreadable"},"400":{"description":"Bad wallet or an asset the program has no key for"}},"summary":"What one Armada margin seat holds, and what of it is withdrawable now.","tags":["armada"]}},"/api/v1/armada/markets":{"get":{"description":"⚠⚠ Read off the hedge venue's live catalog, NOT a list of ours. Armada used\nto quote a hardcoded four; the list is now whatever the venue lists and has\ntradable, so a market it delists disappears here on the next refresh without\na deploy — and one it adds appears the same way.\n\nEmpty until the first catalog probe lands. A client renders nothing for that\nmoment rather than rendering four markets and then two hundred.","operationId":"get_armada_markets","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ArmadaMarket"},"type":"array"}}},"description":""},"503":{"description":"Armada leverage limits are unavailable"}},"summary":"Every market Armada quotes right now.","tags":["armada"]}},"/api/v1/armada/open-availability":{"get":{"operationId":"get_armada_open_availability","parameters":[{"in":"query","name":"asset","required":true,"schema":{"type":"string"}},{"in":"query","name":"side","required":true,"schema":{"$ref":"#/components/schemas/Side"}},{"in":"query","name":"notionalUsd","required":true,"schema":{"format":"double","type":"number"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaOpenAvailability"}}},"description":"Advisory availability for this side and size; execution checks account collateral and reserves before opening"},"400":{"description":"Invalid notional or asset"},"503":{"description":"Armada is not enabled"}},"summary":"Advisory admission for a new trade; placement rechecks before execution.","tags":["armada"]}},"/api/v1/armada/orderbook":{"get":{"description":"# This reads existing state — it opens nothing\n\nThe api already streams `order_book/{market}` and `market_stats/{market}`\nfor every Armada market, because a size-dependent quote cannot be built\nfrom the touch. This endpoint is a projection of that in-memory feed, so a\nbrowser polling it costs one map lookup and never a request to the venue.\n\n# The freshness rule is the quote's, not a new one\n\nA book is served under `SUNRISE_QUOTE_MAX_AGE_MS` — the CARRY bound, the\nsame one a quote is struck under. That is deliberately generous, because\n`bookAgeMs` measures time since the book last **changed** and a quiet market\nstops changing without the venue going anywhere. `venueReachable` is the\nliveness signal, and it is a separate field for exactly that reason.\n\n# The risk gate is not consulted\n\nA halted or reduce-only market is still a market the user is looking at.\nHiding the venue's book because Imperial will not take more risk on it would\ntell the user nothing true about the market.","operationId":"get_armada_orderbook","parameters":[{"description":"CORE4 symbol. Anything else is not listed and is refused, not emptied —\nan empty book and an unlisted market look identical to a client and mean\nopposite things.","in":"query","name":"asset","required":true,"schema":{"type":"string"}},{"description":"Optional Armada execution constraint: 0 = automatic, 1 = current hedge route.\nA selected route binds; reserved values are rejected. Use 0 for other underwriters.","in":"query","name":"requestedHedgeVenue","required":false,"schema":{"format":"int32","minimum":0,"type":"integer"}},{"description":"Levels per side. Clamped to `1..=50`; defaults to 15.","in":"query","name":"depth","required":false,"schema":{"minimum":0,"type":["integer","null"]}},{"description":"Optional price bin width in USDC. Grouped before limiting depth.","in":"query","name":"priceStep","required":false,"schema":{"format":"double","type":["number","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaOrderbook"}}},"description":"Aggregated levels with cumulative totals, the spread, and the venue's mark, index and funding"},"400":{"description":"Refused, with a reason code naming the hedge venue"},"503":{"description":"Armada is not enabled on this deployment"}},"summary":"The hedge venue's live L2 book for one market, plus the numbers above it.","tags":["armada"]}},"/api/v1/armada/quote":{"get":{"description":"`quote = mark ± book slippage at this size`, then Armada's total 1 bp\nper-leg fee (0.5 bp for the hedge venue and 0.5 bp for Imperial). The fee is flat;\nthe quote varies because the underlying cost does.\n\nThe returned `quoteId` may be carried on a `/mobile/orders` create so the\nserved price is stored against the order it produced. ⚠ It is NOT required\nin order to trade: Armada takes an order through the same door as every\nother venue, carrying its own size, side, slippage and market price, and\n`RequestPositionChangeData` has no quote field on chain either.","operationId":"get_armada_quote","parameters":[{"description":"CORE4 symbol. Anything else is not listed and is refused, not priced.","in":"query","name":"asset","required":true,"schema":{"type":"string"}},{"in":"query","name":"side","required":true,"schema":{"$ref":"#/components/schemas/Side"}},{"description":"`open` (default) or `close`. A close hits the opposite side of the hedge\nbook, so it is a different price, not the same one negated.","in":"query","name":"leg","required":false,"schema":{"type":["string","null"]}},{"in":"query","name":"notionalUsd","required":true,"schema":{"format":"double","type":"number"}},{"description":"Optional Armada execution constraint: 0 = automatic, 1 = current hedge route.\nA selected route binds; reserved values are rejected. Use 0 for other underwriters.","in":"query","name":"requestedHedgeVenue","required":false,"schema":{"format":"int32","minimum":0,"type":"integer"}},{"in":"query","name":"wallet","required":true,"schema":{"type":"string"}},{"in":"query","name":"profileIndex","required":false,"schema":{"format":"int32","type":"integer"}},{"in":"query","name":"orderType","required":false,"schema":{"format":"int32","type":"integer"}},{"in":"query","name":"marketMint","required":false,"schema":{"type":["string","null"]}},{"description":"The position being reduced. **Required on `leg=close`** — a close is\ngraded against the lifecycle it settles, so a close quote without it is\nserved but permanently ungradeable, which is the exact failure the\ngrading loop exists to prevent.","in":"query","name":"lifecycleId","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"All-in bps of the cheapest alternative the caller already has in hand.\nSupplying it lets the quote say \"don't serve this one\" — the expected\nanswer for a median SOL order, where the hedge book is worse than\nPhoenix's and the depth advantage has not appeared yet.","in":"query","name":"bestAlternativeBps","required":false,"schema":{"format":"double","type":["number","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServedQuote"}}},"description":"Served quote"},"400":{"description":"Refused, with a reason code naming the hedge venue"},"503":{"description":"Armada is not enabled on this deployment"}},"summary":"A server-authoritative Armada quote.","tags":["armada"]}},"/api/v1/armada/status":{"get":{"operationId":"get_armada_status","parameters":[{"description":"Trailing window in hours. Clamped to `1..=8760`.","in":"query","name":"windowHours","required":false,"schema":{"format":"int32","type":["integer","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaStatusResponse"}}},"description":"Gate phases, both accept rates, and markout"}},"tags":["armada"]}},"/api/v1/armada/status.capital":{"get":{"description":"⚠ Every field here is a ratio, an enum or a boolean. A dollar figure would\nsay how big Imperial's book is, which is exactly what this endpoint exists\nto publish AROUND.","operationId":"get_armada_capital_status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaCapitalStatus"}}},"description":"Capital health as ratios"}},"summary":"The public half of the capital state: ratios only.","tags":["armada"]}},"/api/v1/armada/treasury":{"get":{"description":"⚠ Ops only. It carries Imperial's own float, its floors and its per-market\ncapacity, all of which say how big the book is.","operationId":"get_armada_treasury","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaTreasuryResponse"}}},"description":"Capital state, or a well-formed empty body before the treasury loop has ever run"},"401":{"description":"Missing or wrong ops token"}},"summary":"The Armada capital state, in the six lines the plan names.","tags":["armada"]}},"/api/v1/close-slippage":{"get":{"operationId":"get_close_slippage","parameters":[{"description":"Raise the sample floor (clamped up to the server minimum)","in":"query","name":"minSamples","required":false,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseSlippageResponse"}}},"description":"Per-market realized close-fill dispersion"}},"tags":["orders"]}},"/api/v1/deposit-history":{"get":{"operationId":"get_deposit_history","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Page size, capped at 200 server-side. Default 50.","in":"query","name":"limit","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Pagination offset.","in":"query","name":"offset","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Token mint filter.","in":"query","name":"market","required":false,"schema":{"type":["string","null"]}},{"description":"`deposit`, `external_deposit`, `withdrawal`, or `transfer` (both\nlegs).","in":"query","name":"eventType","required":false,"schema":{"type":["string","null"]}},{"description":"Restrict to one profile (0-5).","in":"query","name":"profileIndex","required":false,"schema":{"format":"int32","type":["integer","null"]}},{"description":"Unix-seconds lower bound on event time.","in":"query","name":"from","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Unix-seconds upper bound on event time.","in":"query","name":"to","required":false,"schema":{"format":"int64","type":["integer","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositHistoryResponse"}}},"description":"Deposits, withdrawals, and own-profile transfers for the wallet, newest first, paginated, with in/out aggregates computed under the same filter."},"400":{"description":"Invalid wallet address or filter value"}},"tags":["reads"]}},"/api/v1/deposit/build-tx":{"post":{"operationId":"deposit_build_tx","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuildDepositTxRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuildDepositTxResponse"}}},"description":"A partially-signed sponsored transaction. For `mode: \"deposit\"`, it creates the user-account PDA (if needed) and the profile's USDC ATA, then transfers USDC from the wallet's USDC ATA into the profile. For `mode: \"withdraw\"`, it creates the wallet's USDC ATA (if needed) and withdraws USDC from the profile back to the wallet. The operator signs for rent and ATA creation; the caller must sign with the wallet keypair before submission."},"400":{"description":"Coded refusal: `WithdrawalBlockedByLoan` when an active loan on the profile blocks egress"},"500":{"description":"Order bot unreachable or build failed"}},"tags":["trading"]}},"/api/v1/funding-history":{"get":{"operationId":"get_funding_history","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Page size, capped at 200 server-side. Default 50.","in":"query","name":"limit","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Pagination offset.","in":"query","name":"offset","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Market mint filter.","in":"query","name":"market","required":false,"schema":{"type":["string","null"]}},{"description":"Venue filter (`jupiter`, `flash_trade`, `phoenix`, `gmtrade`, ...).","in":"query","name":"underwriter","required":false,"schema":{"type":["string","null"]}},{"description":"`long` or `short`.","in":"query","name":"side","required":false,"schema":{"type":["string","null"]}},{"description":"`paid` (trader paid) or `received` (trader received).","in":"query","name":"direction","required":false,"schema":{"type":["string","null"]}},{"description":"Unix-seconds lower bound on event time.","in":"query","name":"from","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Unix-seconds upper bound on event time.","in":"query","name":"to","required":false,"schema":{"format":"int64","type":["integer","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FundingHistoryResponse"}}},"description":"Funding/borrow settlement events for the wallet, newest first, paginated, with paid/received aggregates computed under the same filter."},"400":{"description":"Invalid wallet address or filter value"}},"tags":["reads"]}},"/api/v1/funding-rates":{"get":{"operationId":"get_funding_rates_summary","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnifiedFundingRatesResponse"}}},"description":"Unified funding & borrow rates keyed by canonical symbol. Each row has up to four per-venue entries (jupiter, phoenix, gmtrade, sunrise) — present only when that venue lists the symbol. The `source` field on each entry marks provenance (live vs. fallback). GMTrade exposes per-side funding directly; Phoenix funding comes from the venue's `market` WS channel (`phoenix_market_ws` source; rows still warming report `phoenix_clob_no_snapshot` with null rates); Jupiter uses borrow rates. The `sunrise` entry is IMPERIAL's own on-chain funding rate, not the external venue Armada hedges on — that venue's rate is served separately on `/armada/orderbook`."}},"tags":["reads"]}},"/api/v1/gmtrade/funding-rates":{"get":{"operationId":"get_gmtrade_funding_rates","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/GmTradeFundingRatesResponse"},"type":"array"}}},"description":"Per-symbol GMTrade funding/borrow/net rates (percent per hour). Empty until the WS feed has populated."}},"tags":["reads"]}},"/api/v1/gmtrade/liquidity":{"get":{"operationId":"get_gmtrade_liquidity","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/GmTradeLiquidityResponse"},"type":"array"}}},"description":"Per-symbol open-interest headroom (USD) per side. Refreshed every 30s from the on-chain Market account."}},"tags":["reads"]}},"/api/v1/gmtrade/markets":{"get":{"operationId":"get_gmtrade_markets","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/GmTradeMarketResponse"},"type":"array"}}},"description":"GMTrade markets. A symbol may have multiple variants (per collateral pair); the `closed` flag reflects the on-chain `Market.flags.Closed` bit and is authoritative — the program rejects orders against a closed market with `MarketClosed (6127)`."}},"tags":["reads"]}},"/api/v1/jupiter/pool-info":{"get":{"operationId":"get_jupiter_pool_info","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JupiterPoolInfo"}}},"description":"Jupiter pool-info (available liquidity, borrow rates, utilization, fees) for a market mint, served from the server-side last-good cache."},"502":{"description":"Cache cold and live Jupiter fetch failed"}},"summary":"Cache-fronted Jupiter pool-info. The homepage used to call Jupiter's\n`/pool-info` directly from the browser, so every client hit Jupiter's\nper-client rate limit and a degraded/zero body would block trading. Serving\nit from `JupiterPoolCache` collapses that to one server-side 30s poll and\nreturns the last-good snapshot during Jupiter outages. Falls back to a live\nfetch only when the cache hasn't warmed the mint yet (or it isn't a tracked\nJupiter market).","tags":["reads"]}},"/api/v1/mark-prices":{"get":{"operationId":"get_mark_prices_summary","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnifiedMarkPricesResponse"}}},"description":"Unified mark prices keyed by canonical symbol, with one entry per venue listing the symbol. Phoenix mid (live orderbook WS), Jupiter Doves AgPriceFeed (read directly via RPC every 1s), and GMTrade indexMin/indexMax mid (from its `indexTokens` WS, refreshed every 5min). The `source` field on each entry marks provenance; `fetchedAtUnixMs` lets you gate on freshness."}},"tags":["reads"]}},"/api/v1/mobile/balances":{"get":{"operationId":"mobile_balances","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileBalancesResponse"}}},"description":"Per-subaccount USDC balances. Each entry is one Imperial profile (`profile_index` 0..5). USDC is held in the profile's USDC ATA in 6-decimal native units. Use this to size new orders before calling `/mobile/orders`."},"401":{"description":"Missing, invalid, or revoked JWT"}},"security":[{"bearer_jwt":[]}],"tags":["trading"]}},"/api/v1/mobile/builder/summary":{"get":{"operationId":"builder_summary","parameters":[{"description":"Builder code, case-insensitive. The code itself is the credential.","in":"query","name":"code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuilderSummaryResponse"}}},"description":"Builder fee summary"},"404":{"description":"Unknown builder code"}},"summary":"Accrual/payout summary for a builder code. Code-as-credential — the code\nstring is the only secret, mirroring the partner-code model.","tags":["mobile"]}},"/api/v1/mobile/connect":{"post":{"operationId":"mobile_connect","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileConnectRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileConnectResponse"}}},"description":"One-time code to exchange for a JWT"},"400":{"description":"Invalid message format or replayed nonce"},"401":{"description":"Signature verification failed"}},"tags":["auth"]}},"/api/v1/mobile/exchange":{"post":{"operationId":"mobile_exchange","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileExchangeRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileExchangeResponse"}}},"description":"JWT and expiry"},"401":{"description":"Invalid or expired connect code"}},"tags":["auth"]}},"/api/v1/mobile/orders":{"post":{"operationId":"mobile_create_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileCreateOrderRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileOrderResponse"}}},"description":"Order outcome. `success=false` with an `error` message is a rejected-but-valid response."},"400":{"description":"Validation error (e.g. missing `extraData` for ratchet/DCA order types)"},"401":{"description":"Missing, invalid, or revoked JWT, or JWT wallet doesn't match `wallet`"}},"security":[{"bearer_jwt":[]}],"tags":["trading"]}},"/api/v1/mobile/orders/batch":{"post":{"description":"The entry and close legs travel together so close-target validation is\nimplicit: a close leg whose tuple matches its entry is, by construction,\ntargeting the position that entry will create. This avoids the racy\n\"is the entry order indexed yet?\" lookup the per-order endpoint would\notherwise need when the mobile client fires entry + closes in quick\nsuccession.\n\nSubmission is sequential: entry first, then each close in input order. If\nthe entry fails, no close legs are submitted (no position to attach to).\nClose failures don't roll back the entry — same partial-state semantics as\nthe homepage flow today.","operationId":"mobile_create_order_batch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileCreateOrderBatchRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileCreateOrderBatchResponse"}}},"description":"Batch submitted (inspect per-leg `success` flags)"},"400":{"description":"Validation error on the bundle"},"401":{"description":"JWT missing, expired, or wallet mismatch"}},"security":[{"bearer_jwt":[]}],"summary":"Submit an entry order and its attached close legs (TP/SL/ratchet/etc.)\nin one request.","tags":["trading"]}},"/api/v1/mobile/orders/cancel":{"post":{"operationId":"mobile_cancel_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileCancelOrderRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileOrderResponse"}}},"description":"Cancel outcome. `success=false` with an `error` message means the request was rejected or the order was already resolved. `OrderAlreadyResolved` does not prove cancellation or authorize a replacement order."},"401":{"description":"Missing, invalid, or revoked JWT, or JWT wallet doesn't match `wallet`"}},"security":[{"bearer_jwt":[]}],"tags":["trading"]}},"/api/v1/mobile/orders/collateral":{"post":{"operationId":"mobile_collateral_edit","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileCollateralEditRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileOrderResponse"}}},"description":"Collateral-edit outcome. `success=false` with an `error` message means the order bot rejected the edit (e.g. removing more collateral than available, leverage out of bounds)."},"401":{"description":"Missing, invalid, or revoked JWT, or JWT wallet doesn't match `wallet`"}},"security":[{"bearer_jwt":[]}],"tags":["trading"]}},"/api/v1/mobile/orders/preflight":{"post":{"description":"Exists for multi-leg opens, where the legs are placed sequentially and a\nrejection on leg 2 leaves leg 1 live and unhedged. A pair open preflights\nboth legs and only then sends either, so the venue's own \"no\" arrives while\nnothing is at stake. Prod incident 2026-08-09: a BTC/ETH pair at 30x opened\nthe BTC leg and bounced the ETH leg off Phoenix's 24.78x cap for ETH,\nleaving a naked $75k long. Three of three stuck pair links had this shape.\n\nDeliberately free of side effects: no dedupe entry, no `record_placement_rejected`\n(nothing was placed, and logging these as placement rejections would poison\nthe very table used to diagnose real ones), and no order-bot dispatch.","operationId":"mobile_preflight_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileCreateOrderRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobilePreflightResponse"}}},"description":"Verdict. `ok=false` with `error` is a successful answer, not a failure."},"401":{"description":"Missing, invalid, or revoked JWT, or JWT wallet doesn't match `wallet`"}},"security":[{"bearer_jwt":[]}],"summary":"Ask whether an order would be accepted, without placing it.","tags":["trading"]}},"/api/v1/mobile/orders/update":{"post":{"operationId":"mobile_update_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileUpdateOrderRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileOrderResponse"}}},"description":"Update outcome. `success=false` with an `error` message means the order bot rejected the update (e.g. order already filled or cancelled, native limit-order updates not supported)."},"401":{"description":"Missing, invalid, or revoked JWT, or JWT wallet doesn't match `wallet`"}},"security":[{"bearer_jwt":[]}],"tags":["trading"]}},"/api/v1/mobile/partner/register":{"post":{"operationId":"partner_register","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerRegisterRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerRegisterResponse"}}},"description":"Wallet registered; discounted fee tier active"},"400":{"description":"Unknown, inactive, or full partner code; or wallet already bound to a different code"},"401":{"description":"Missing or invalid JWT"},"503":{"description":"On-chain tier flag could not be set; retry registration"}},"security":[{"bearer_jwt":[]}],"summary":"Register the JWT wallet under a partner code: binds the wallet to the\npartner (first-code-wins) and sets the on-chain `Discounted` fee tier via\nthe order bot before any order exists. Idempotent — retrying after a bot\nfailure re-fires only the on-chain leg.","tags":["mobile"]}},"/api/v1/mobile/partner/registration":{"get":{"operationId":"partner_registration_status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerRegistrationStatusResponse"}}},"description":"Registration status"},"401":{"description":"Missing or invalid JWT"}},"security":[{"bearer_jwt":[]}],"summary":"Registration status for the JWT wallet — lets partners verify onboarding\nprogrammatically.","tags":["mobile"]}},"/api/v1/mobile/points":{"get":{"operationId":"mobile_points","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobilePointsResponse"}}},"description":"The wallet's Imperial points for the active season, read from the on-chain passthrough `UserAccount` PDA — the same balance the Imperial homepage displays. Points are credited in weekly batches, so the figure updates at each weekly close rather than per-trade. `0` when the wallet has never traded, no season is active, or the wallet has no credit yet this season."},"401":{"description":"Missing, invalid, or revoked JWT"}},"security":[{"bearer_jwt":[]}],"tags":["trading"]}},"/api/v1/mobile/revoke":{"post":{"operationId":"mobile_revoke","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeResponse"}}},"description":"JWT was added to the revocation list"},"401":{"description":"Missing or invalid Bearer token"}},"security":[{"bearer_jwt":[]}],"tags":["auth"]}},"/api/v1/order-history":{"get":{"operationId":"get_order_history","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Page size, capped at 200 server-side. Default 50.","in":"query","name":"limit","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Pagination offset.","in":"query","name":"offset","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Market mint filter.","in":"query","name":"market","required":false,"schema":{"type":["string","null"]}},{"description":"Venue filter (`jupiter`, `flash_trade`, `phoenix`, `gmtrade`, ...).","in":"query","name":"underwriter","required":false,"schema":{"type":["string","null"]}},{"description":"`long` or `short`.","in":"query","name":"side","required":false,"schema":{"type":["string","null"]}},{"description":"Display status filter (`open`, `active`, `filled`, `canceled`, ...).","in":"query","name":"status","required":false,"schema":{"type":["string","null"]}},{"description":"Order category: `standard`, `dca`, `trailing`, `rsi`.","in":"query","name":"category","required":false,"schema":{"type":["string","null"]}},{"description":"Unix-seconds lower bound on creation time.","in":"query","name":"from","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Unix-seconds upper bound on creation time.","in":"query","name":"to","required":false,"schema":{"format":"int64","type":["integer","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHistoryResponse"}}},"description":"Order history for the wallet, newest first, paginated. Top-level rows only — strategy children live in the detail endpoint."},"400":{"description":"Invalid wallet address or filter value"}},"tags":["reads"]}},"/api/v1/order-history/{order_pda}":{"get":{"operationId":"get_order_history_detail","parameters":[{"description":"Order PDA","in":"path","name":"order_pda","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHistoryDetailResponse"}}},"description":"Full order/strategy detail: config, child orders, fills, and the audit event log (empty for orders created before order-history tracking)."},"404":{"description":"Order not found"}},"tags":["reads"]}},"/api/v1/orders":{"get":{"operationId":"get_combined_orders","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Optional underwriter filter. Accepts `jupiter`, `flash_trade`,\n`phoenix`, or `gmtrade`. Other values yield 400.","in":"query","name":"underwriter","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CombinedOrdersResponse"}}},"description":"All open and recently-resolved orders for the wallet, across all 6 profile subaccounts. The `passthroughOrders` array carries every Imperial-routed resting order — Jupiter, Flash, Phoenix, and GMTrade alike — including TP/SL, limit, stop-limit, ratchet, DCA, and Phoenix native limits; the `jupiterOrders` array carries only Jupiter-native limit orders submitted through Jupiter's own (non-Imperial) limit-order program. Use this endpoint rather than `/passthrough/users/{wallet}/orders` when you want both classes in one call."},"400":{"description":"Invalid wallet address"}},"tags":["reads"]}},"/api/v1/passthrough/users/{wallet}/orders":{"get":{"operationId":"get_passthrough_orders","parameters":[{"description":"Base58 wallet pubkey","in":"path","name":"wallet","required":true,"schema":{"type":"string"}},{"description":"Filter by status: `pending`, `executed`, `cancelled`.","in":"query","name":"status","required":false,"schema":{"type":["string","null"]}},{"description":"Max orders to return.","in":"query","name":"limit","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Sub-account index (0..5).","in":"query","name":"profile_index","required":false,"schema":{"format":"int32","type":["integer","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassthroughOrdersResponse"}}},"description":"List of passthrough orders (open, executed, cancelled)"}},"tags":["reads"]}},"/api/v1/passthrough/users/{wallet}/profiles":{"get":{"description":"Read this before submitting an order: `marginMode` decides where a new\nposition lands, and `venueLock` (when present) is the set of venues the\nprofile may open on.","operationId":"get_profiles","parameters":[{"description":"Base58 wallet pubkey","in":"path","name":"wallet","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilesResponse"}}},"description":"All six profiles, indexed 0-5."},"400":{"description":"Invalid wallet address"}},"summary":"The wallet's six subaccounts, with each one's margin mode and routing lock.","tags":["reads"]}},"/api/v1/passthrough/users/{wallet}/profiles/{index}/margin-mode":{"put":{"description":"Unified routes the profile's new positions onto Phoenix's cross-margin seat,\nwhere one balance backs every market on the seat. See the \"Cross margin\n(unified margin)\" guide in the API overview for what changes.\n\nAuth is JWT (`Authorization: Bearer`) or a Solana signature over\n`imperial:margin-mode:{wallet}:{profileIndex}:{marginMode}` sent as\n`x-pubkey` + `x-signature`. The mode is part of the signed message, so a\ncaptured signature cannot be replayed to flip the profile the other way.\n\nThe profile must already have a name (`PUT .../profiles/{index}/name`) —\nthat call creates the row this setting lives on.\n\nSwitching is legal in both directions with positions open. Because it is,\na live cross seat can outlive a switch back to isolated: never infer an\nexisting position's margin mode from this flag, read `directVenue.marginMode`\noff the position row instead.","operationId":"set_profile_margin_mode","parameters":[{"description":"Base58 wallet pubkey","in":"path","name":"wallet","required":true,"schema":{"type":"string"}},{"description":"Profile index, 0-5","in":"path","name":"index","required":true,"schema":{"format":"int32","minimum":0,"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetMarginModeRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarginModeResponse"}}},"description":"The mode now in force, with the routing lock it implies."},"400":{"description":"Unknown mode, profile index out of range, or the profile has no name yet."},"401":{"description":"Missing/invalid JWT or signature, or it doesn't match `wallet`"}},"security":[{"bearer_jwt":[]}],"summary":"Switch a profile between isolated and unified (cross) margin.","tags":["trading"]}},"/api/v1/passthrough/users/{wallet}/profiles/{index}/sync":{"post":{"operationId":"sync_profile_sweep","parameters":[{"description":"Owner wallet base58 pubkey","in":"path","name":"wallet","required":true,"schema":{"type":"string"}},{"description":"Sub-account index (0..5)","in":"path","name":"index","required":true,"schema":{"format":"int32","minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncSweepResponse"}}},"description":"Idempotent sweep trigger. After closing positions, your profile may hold WSOL / WBTC / WETH residue (collateral returned in the market token rather than USDC). Automatic conversion to USDC is retired: this endpoint now always returns `\"clean\"`, and those balances are withdrawn from the profile directly."},"400":{"description":"Invalid wallet pubkey or out-of-range profile_index"}},"tags":["trading"]}},"/api/v1/passthrough/users/{wallet}/referral-code":{"get":{"operationId":"get_referral_code","parameters":[{"description":"Wallet public key","in":"path","name":"wallet","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralCodeResponse"}}},"description":""},"404":{"description":"No code exists"},"503":{"description":"Lookup unavailable"}},"tags":["reads"]},"put":{"operationId":"customize_referral_code","parameters":[{"description":"Must match the owner session wallet","in":"path","name":"wallet","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomizeReferralCodeRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralCodeResponse"}}},"description":""},"400":{"description":"Invalid code"},"401":{"description":"Owner session required"},"409":{"description":"Code occupied or current code changed"},"429":{"description":"Reservation quota or cooldown"},"503":{"description":"Verification or persistence unavailable"}},"security":[{"bearer_jwt":[]}],"tags":["profiles"]}},"/api/v1/passthrough/users/{wallet}/referral-code/aliases":{"get":{"operationId":"owned_referral_aliases","parameters":[{"description":"Must match the owner session wallet","in":"path","name":"wallet","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnedReferralAliasesResponse"}}},"description":""},"401":{"description":"Owner session required"},"503":{"description":"Verification or lookup unavailable"}},"security":[{"bearer_jwt":[]}],"tags":["profiles"]}},"/api/v1/passthrough/users/{wallet}/referral-code/ensure":{"post":{"operationId":"ensure_referral_code","parameters":[{"description":"Must match the verified session wallet","in":"path","name":"wallet","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralCodeResponse"}}},"description":""},"401":{"description":"Wallet verification required"},"503":{"description":"Verification or persistence unavailable"}},"security":[{"bearer_jwt":[]}],"tags":["profiles"]}},"/api/v1/phoenix/depth":{"get":{"operationId":"get_phoenix_depth_summary","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepthSummaryResponse"}}},"description":"Live Phoenix orderbook snapshots keyed by Phoenix-raw symbol (e.g. `GOLD`, not the Imperial-canonical `XAU`). The cache holds a persistent WS connection to `wss://perp-api.phoenix.trade/ws`; entries are at most 60s stale."}},"tags":["reads"]}},"/api/v1/phoenix/direct-positions":{"get":{"operationId":"get_phoenix_direct_positions","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/LifecycleResponse"},"type":"array"}}},"description":"Wallet-owned positions fetched from Phoenix trader state."},"400":{"description":"Invalid wallet address"}},"summary":"Fetch wallet-owned Phoenix positions independently from the core lifecycle\nendpoint. Phoenix's trader-state API can take up to ten seconds to fail, so\nkeeping this enrichment on its own request prevents it from delaying every\n`/positions` consumer (including websocket position-state pushes).","tags":["reads"]}},"/api/v1/phoenix/margin-account":{"get":{"description":"`/positions` already attaches this to each cross row, but only a profile with\nan open position has rows to attach it to. The order ticket needs the same\nfigures *before* the first trade — free collateral is what decides whether an\norder needs a deposit at all — so it cannot read them off a position.\n\n`null` means the profile has never been onboarded onto a cross seat, which is\na real state and not an error: nothing has been deposited yet.","operationId":"get_phoenix_margin_account","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Which of the wallet's profiles owns the account (0-5).","in":"query","name":"profileIndex","required":true,"schema":{"format":"int32","minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PhoenixCrossSeatState"}]}}},"description":"Account-level margin state, or null when the profile has no margin account."},"400":{"description":"Invalid wallet address"}},"summary":"The profile's margin account (Phoenix cross seat) on its own, independent of\nwhether it holds any positions.","tags":["reads"]}},"/api/v1/phoenix/mark-prices":{"get":{"operationId":"get_phoenix_mark_prices","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PhoenixMarkPrice"},"type":"array"}}},"description":"Per-symbol mid prices from the live Phoenix orderbook. Symbols are Phoenix-raw (e.g. `GOLD`, not `XAU`)."}},"tags":["reads"]}},"/api/v1/phoenix/markets":{"get":{"operationId":"get_phoenix_markets","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PhoenixMarketResponse"},"type":"array"}}},"description":"Phoenix CLOB markets, keyed by Phoenix's raw symbol (e.g. `GOLD` not the Imperial-canonical `XAU`). Includes max leverage from the smallest tier, taker/maker fees in millionths, and the orderbook PDA."}},"tags":["reads"]}},"/api/v1/phoenix/register":{"post":{"description":"**Intentionally unauthenticated.** Imperial's Phoenix invite quota is\neffectively unlimited, so we don't gate this on a wallet signature or JWT\n— adding friction (sign-message prompt, JWT exchange) for a no-cost\nactivation hurts UX without protecting anything that matters. The\nactivation is keyed on the profile PDA derived from `wallet`; the\nattacker-controlled wallet still has no on-chain authority over a\nstranger's profile, so the worst a third party can do is pre-register a\nwallet under our referral, which is exactly what we want anyway.\n\nDo NOT add auth here without first checking with the team — see the\nreview thread on phoenix-native.","operationId":"register_phoenix_trader","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterPhoenixRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterPhoenixResponse"}}},"description":"Activates the Phoenix trader account for `(wallet, profileIndex)` under Imperial's referral. **Optional for bots**: `/mobile/orders` already auto-activates Phoenix on first use and caches the result. Use this endpoint only when you want to pre-activate from a UI flow or warm the cache before sending a latency-sensitive first Phoenix order. Idempotent."},"400":{"description":"Invalid wallet pubkey, or (`errorCode: PhoenixProfileNotEstablished`) the profile has never been used on Imperial. A Phoenix seat costs unrecoverable operator rent, so pre-registration is only available for profiles that exist: deposit first, or just place the order — `/mobile/orders` activates automatically."},"429":{"description":"Phoenix's activation API is throttling us (or this profile recently failed activation). Honor `Retry-After` — retrying sooner cannot succeed and extends the throttle for everyone."},"500":{"description":"Phoenix activation upstream failed"}},"summary":"Activates a wallet's Phoenix profile under Imperial's referral code.","tags":["trading"]}},"/api/v1/phoenix/volumes":{"get":{"operationId":"get_phoenix_volumes","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PhoenixMarketVolume"},"type":"array"}}},"description":"Phoenix's trailing-24h traded notional per market, from the live `market` WS channel. Symbols are Phoenix-raw (e.g. `GOLD`, not `XAU`). Markets with no message in the last 60s are omitted rather than served stale."}},"tags":["reads"]}},"/api/v1/pnl-history":{"get":{"operationId":"get_pnl_history","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Bucket size. Accepts `1h` or `1d`.","in":"query","name":"resolution","required":true,"schema":{"type":"string"}},{"description":"Inclusive start (unix seconds). Defaults to `now - 90d` for `1h` and\nthe wallet's earliest action for `1d` (within the underwriter filter\nwhen set).","in":"query","name":"since","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Inclusive end (unix seconds). Defaults to `now`.","in":"query","name":"until","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Optional underwriter filter. Accepts `jupiter`, `flash_trade`,\n`phoenix`, or `gmtrade`. Other values yield 400. When set, every\ncomponent (realized PnL, funding, taker fees, unrealized PnL) is\nscoped to that venue.","in":"query","name":"underwriter","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PnlHistoryPoint"},"type":"array"}}},"description":"Time-bucketed PnL history for the wallet, aggregated across every profile PDA. One row per `[since, until]` bucket (continuous — empty buckets carry forward the prior cumulative values). `unrealizedPnl` is populated only on the last bucket; earlier buckets serialize as null because historical mark-to-market is not supported."},"400":{"description":"Invalid wallet, resolution, range, or lookback exceeds the hourly cap (90 days)."}},"tags":["reads"]}},"/api/v1/positions":{"get":{"operationId":"get_jupiter_positions_with_ownership","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Optional underwriter filter. Accepts `jupiter`, `flash_trade`,\n`phoenix`, or `gmtrade`. Other values yield 400.","in":"query","name":"underwriter","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecyclesResponse"}}},"description":"Open positions for the wallet across all profiles and underwriters, with live PnL, leverage, entry price, liquidation price, accrued fees, and any attached TP/SL/ratchet/DCA close orders. Numeric fields are stringified to preserve precision."},"400":{"description":"Invalid wallet address"}},"summary":"Get open position lifecycles across every underwriter (Jupiter, Flash,\nPhoenix, GMTrade), enriched with live PnL where available. Despite this\nfunction's name, the response is multi-venue — the historical name predates\nthe broader underwriter set.","tags":["reads"]}},"/api/v1/priority-fee":{"get":{"operationId":"get_priority_fee","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriorityFeeResponse"}}},"description":"Suggested Solana priority fee for trades against the passthrough program. Median over recent blocks, polled every 30s, capped at 500_000 micro-lamports."}},"tags":["reads"]}},"/api/v1/revenue/armada-balances":{"get":{"operationId":"get_armada_balances","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaBalancesResponse"}}},"description":"Latest Armada balance snapshot; null before the first evaluation"},"401":{"description":"Missing, invalid or expired login JWT or ops token"},"403":{"description":"Session wallet is not the Armada admin"},"500":{"description":"Balance snapshot could not be read"}},"tags":["armada"]}},"/api/v1/route":{"get":{"operationId":"get_route","parameters":[{"description":"Canonical asset symbol (e.g. `SOL`, `BTC`, `XAU`).","in":"query","name":"asset","required":true,"schema":{"type":"string"}},{"description":"`long` or `short`.","in":"query","name":"side","required":true,"schema":{"$ref":"#/components/schemas/Side"}},{"description":"Trade notional in USD (collateral × leverage at entry).","in":"query","name":"notional","required":true,"schema":{"format":"double","type":"number"}},{"description":"Desired leverage multiplier (e.g. 5 = 5x). Optional: when omitted the\nroute is computed un-clamped (defaults to 1×), so `candidates[].maxLeverage`\nstill reports every venue's real per-market cap — callers that only need\nthe caps can omit it. Supply it to get accurate `clamped` / `recommendedVenue`\nfor a specific target leverage.","in":"query","name":"desiredLeverage","required":false,"schema":{"format":"double","type":["number","null"]}},{"description":"Expected hold period in hours. Defaults to 24h. Drives the borrow term.","in":"query","name":"holdHours","required":false,"schema":{"format":"double","type":["number","null"]}},{"description":"Optional sticky-venue override. If provided, the router treats this venue\nas already locked for the asset and returns it (clamped if leverage exceeds\nthe venue's cap). When `wallet` + `profileIndex` are present and `stickyVenue`\nis omitted, the sticky venue is auto-resolved from open lifecycles.","in":"query","name":"stickyVenue","required":false,"schema":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/Venue"}]}},{"description":"Optional per-subaccount default-venue override. Used when no sticky venue\napplies and the asset is supported by the default venue at the requested\nleverage.","in":"query","name":"subaccountDefaultVenue","required":false,"schema":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/Venue"}]}},{"description":"Optional wallet (base58). When present along with `profileIndex`, the\nhandler queries `position_lifecycles` for an open position on this asset\nand uses that venue as the sticky.","in":"query","name":"wallet","required":false,"schema":{"type":["string","null"]}},{"description":"Optional sub-account index (0..5).","in":"query","name":"profileIndex","required":false,"schema":{"format":"int32","type":["integer","null"]}},{"description":"Optional comma-separated list of venues the caller wants excluded\n(e.g. `excludedVenues=phoenix,gmtrade`). When omitted, the handler\nfalls back to `profile_names.excluded_venues` via `wallet` +\n`profile_index` if those are present. Sticky venues with open\npositions still route there even if excluded — open positions take\nprecedence over exclusions.","in":"query","name":"excludedVenues","required":false,"schema":{"items":{"$ref":"#/components/schemas/Venue"},"type":["array","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteResult"}}},"description":"Best venue for the trade plus a per-venue cost breakdown: open and close fees, slippage, borrow over the requested `holdHours` window (default 24h), and the Imperial loan's carry when a loan split attaches. Liquidation is not priced. Pass `holdHours` if your strategy holds positions for materially longer or shorter than a day. `costBreakdown` is an expected round-trip cost model, NOT a funding requirement — its `borrow`/`loanCost` terms are lifecycle estimates that are never reserved upfront. Size deposits from `requiredDeposit` (top-level for the winner at the effective leverage, per-candidate at each venue's openable leverage); pass `wallet` so the deposit skim reflects your fee tier. `maxLeverage` is a size-independent per-market cap — do not scale it by notional. `desiredLeverage` is OPTIONAL (defaults to 1x, un-clamped): omit it to just read caps, supply it for accurate `clamped`/`recommendedVenue`/`requiredDeposit` at your target."},"400":{"description":"Invalid query parameters"},"404":{"description":"No venue supports this asset"}},"tags":["reads"]}},"/api/v1/stats/armada/activity":{"get":{"operationId":"get_stats_armada_activity","parameters":[{"description":"Lookback window: `24h`, `7d`, `30d`, `90d`, `180d`, or `1y`. Default: `30d`.","in":"query","name":"period","required":false,"schema":{"type":"string"}},{"description":"Bucket size: `hour`, `day`, or `week`. Default: `hour` for 24h, `day` otherwise.","in":"query","name":"grouping","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmadaActivityResponse"}}},"description":"Armada round-trip volume per ET bucket, and long/short open interest (entry notional) at each bucket's close."}},"tags":["reads"]}},"/api/v1/stats/markets":{"get":{"operationId":"get_stats_markets","parameters":[{"description":"Volume lookback window: `24h`, `7d`, or `30d`. Default: `24h`. OI is always a live snapshot.","in":"query","name":"period","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsResponse"}}},"description":"Per-asset volume + open-interest breakdown. Volume is round-trip notional (both legs). Symbols active in either source appear; missing fields zero-fill."}},"tags":["reads"]}},"/api/v1/stats/open-interest":{"get":{"operationId":"get_stats_open_interest","parameters":[{"description":"Aggregation axis: `venue`, `asset`, `side`, or `venue_asset`. Default: `venue`.","in":"query","name":"grouping","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenInterestResponse"}}},"description":"Current open-interest snapshot. Each row carries long/short/total notional plus distinct-trader and position counts."}},"tags":["reads"]}},"/api/v1/stats/open-interest/history":{"get":{"operationId":"get_stats_open_interest_history","parameters":[{"description":"Lookback window: `24h`, `7d`, `30d`, `90d`, `180d`, `1y`, or `all`. Default: `30d`.","in":"query","name":"period","required":false,"schema":{"type":"string"}},{"description":"Bucket size: `hour`, `day`, or `week`. Default: `hour` for 24h, `day` otherwise.","in":"query","name":"grouping","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OiHistoryResponse"}}},"description":"Historical open-interest time series, derived from the signed `tx2_size_usd_delta` event log (no snapshot table). `currentOiUsd` is the live truth — compare against the last row to spot indexer drift."}},"tags":["reads"]}},"/api/v1/stats/summary":{"get":{"operationId":"get_stats_summary","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsSummaryResponse"}}},"description":"Protocol-level snapshot: 24h/7d/all-time volume, current open interest, 24h active traders and fee revenue, plus a per-venue breakdown. All volume figures are round-trip notional — both legs of every trade — so they run roughly 2x an entries-only measure. Excludes Jupiter-direct trades."}},"tags":["reads"]}},"/api/v1/stats/volume":{"get":{"operationId":"get_stats_volume","parameters":[{"description":"Lookback window: `24h`, `7d`, `30d`, `90d`, `180d`, or `1y`. Default: `7d`.","in":"query","name":"period","required":false,"schema":{"type":"string"}},{"description":"Bucket size: `hour`, `day`, or `week`. Default: `hour` for 24h, `week` for >30d, `day` otherwise.","in":"query","name":"grouping","required":false,"schema":{"type":"string"}},{"description":"Restrict to one venue: `jupiter`, `flash`, `phoenix`, `gmtrade`, or `sunrise`. Per-venue columns still split out every venue (others zero-fill).","in":"query","name":"venue","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VolumeResponse"}}},"description":"Time-series round-trip notional volume bucketed over the requested window. Every position action contributes the absolute value of its size delta — increases, decreases and liquidations alike — so a round-trip counts twice its notional. Excludes Jupiter-direct trades."}},"tags":["reads"]}},"/api/v1/status":{"get":{"operationId":"get_status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}},"description":"Service health snapshot — always returns 200, inspect fields for the actual state."}},"tags":["system"]}},"/api/v1/touch/positions":{"get":{"description":"Open contracts first, then finished ones newest-first, capped at 200. This\nis the only view of touch positions — they do not appear on `/positions`,\n`/trades`, or the `/ws` `position_state` stream, all of which are perp-only.","operationId":"get_touch_positions","parameters":[{"description":"Base58 wallet whose contracts to list.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ApiTouchPosition"},"type":"array"}}},"description":""},"400":{"description":""}},"summary":"List a wallet's touch contracts","tags":["touch"]}},"/api/v1/trades":{"get":{"operationId":"get_jupiter_trades_with_ownership","parameters":[{"description":"Base58 Solana wallet pubkey.","in":"query","name":"walletAddress","required":true,"schema":{"type":"string"}},{"description":"Page size, capped at 200 server-side.","in":"query","name":"limit","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Pagination offset.","in":"query","name":"offset","required":false,"schema":{"format":"int64","type":["integer","null"]}},{"description":"Optional underwriter filter. Accepts `jupiter`, `flash_trade`,\n`phoenix`, or `gmtrade`. Other values yield 400.","in":"query","name":"underwriter","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecyclesResponse"}}},"description":"Position lifecycles for the wallet, newest first, paginated. Each lifecycle carries its full action history (entry, increases, decreases, close)."},"400":{"description":"Invalid wallet address"}},"summary":"Get position lifecycles (open + closed) with their actions, paginated.\nMulti-venue: returns lifecycles across every underwriter — the function\nname is historical. Includes lazy background sync for Jupiter direct\npositions on stale wallets.","tags":["reads"]}}},"servers":[{"description":"Production","url":"https://api.imperial.trade"},{"description":"Production (previous domain)","url":"https://api.imperial.space"},{"description":"Local","url":"http://localhost:8080"}],"tags":[{"description":"Connect a wallet, issue a JWT, revoke a JWT","name":"auth"},{"description":"Submit, cancel, and update orders. JWT required.","name":"trading"},{"description":"Public read endpoints. No auth required.","name":"reads"},{"description":"Imperial Touch — retired one-touch binaries. A wallet's past contracts only; see the Imperial Touch history guide above.","name":"touch"},{"description":"Health and status.","name":"system"}]}