Model Portfolios
Model Portfolio Invest / Divest / Rebalance API - Integration Guide
Audience: Client developers (Financial Advisor tooling) integrating with IBKR’s Model Portfolio Web API.
Scope: This guide explains how the endpoints work together to create a model, invest client accounts into it, execute the resulting trades, and verify the result. It does not repeat field-level schemas - those live in the OpenAPI reference. Where useful, it calls out quirks, sequencing rules, and known issues that are not obvious from the schema alone.
Base URL: https://api.ibkr.com/v1/api/...
Common headers (all FA microservice calls):
Two distinct API surfaces are involved in a full Invest/Divest/Rebalance flow:
Everything the FA microservice returns (transfers, allocations) is a plan. Nothing trades or moves cash until you explicitly (a) submit the cached transfers via /model/submit-transfers, and/or (b) submit orders via the IServer order endpoint.
1. Key Concepts
Before wiring the endpoints together, it helps to understand the domain model:
- Independent / “Core” - the portion of an account’s portfolio that is not allocated to any model. Every account effectively has
account.Coreplusaccount.<ModelName>for each model it participates in. - Model - a named target allocation (
positionTargets+cashTargets, expressed as fractions of NLV, summing to1.0). Models can be:- Dynamic (
isStatic:false) - targets are percentages, driftable. - Static (
isStatic:true) - must be bootstrapped to be recognized as static.
- Dynamic (
- Bootstrapped - a model only has a real Model %/NLV once at least one account has invested. Before that,
summary/positionsreturn zeros andbootstrapped:false.invest-divestwill silently bootstrap a dynamic model as part of handling the request if it isn’t already. - Target % vs. Actual % (Model %) - Target % is what you want; Actual % (a.k.a. Model %, MI %) is what the model currently holds.
instrumentImbalanceis the (weighted) delta between them. See §8 for the imbalance formulas. - NLV - Net Liquidation Value, always expressed in the base currency of the entity in question (master, account, or model).
- Full vs. Partial Master - some model actions (Invest/Divest/Rebalance) may be blocked for a partial master. Check with
/is-full-masterif unsure. - Single-currency constraint - a model’s
positionTargets+cashTargetsmust all trade/settle in one currency. Mixed-currency target sets are rejected. - transfersInstructionId - a server-side cache key. Any endpoint that computes a set of moves (
invest-divest,tws-invest-divest,rebalance/*) returns this ID. It is not itself an execution - you must follow up with/model/submit-transfers(for the transfer leg) and/or an IServer order (for the allocation/order leg) to actually make it happen. Re-submitting the sametransfersInstructionIdis an error.
2. FA Presets - They Change What “Invest” Actually Does
GET/POST /fa-preset/get and /fa-preset/save control how the planning endpoints (invest-divest, tws-invest-divest, rebalance/*) decide to satisfy a target: buy new shares, transfer existing Independent shares into the model, cross trades, etc. Always fetch presets before planning an investment, and set them explicitly if the default behavior isn’t what you want.
Workflow implication: In Case 1A, the first invest-divest call (with preferTransferFromIndependent:false) produced a BUY order allocation for CSCO. After flipping the preset to true and re-running the identical invest-divest request, the plan changed to a position transfer for CSCO from Independent into the model. Nothing about the invest-divest request itself changed - only the preset. Always treat presets as sticky, account-level configuration that silently reshapes the output of every subsequent planning call.
3. Endpoint Catalog (grouped by role)
4. End-to-End Workflow: Invest a Single Account (Long-Only)
This is the reference flow from Case 1A. It composes the endpoints above into six logical phases.
Phase 1 - Define the model
POST /fa/model/save with positionTargets (by conid) and cashTargets (by ccy), all fractions summing to 1.0. Example: 45% CSCO / 45% INTC / 10% USD cash reserve.
- If
success:false, inspecterror- common causes: mixed currencies among targets, targets not summing to 1.0, or model name reused the same day it was deleted.
Phase 2 - Verify creation
Use /fa/model/summary (single model) and/or /fa/model/list (all models) to confirm the model exists. Immediately after creation the model is not bootstrapped (bootstrapped:false, nlv:"0", numAccounts:0) - this is expected; bootstrapping happens automatically the first time money is invested.
Cross-check the targets landed correctly with /fa/model/positions (sortField:"", limit:-1 to get the full, unsorted list). Before any account has invested, actual will be 0 for every row and target will echo what you saved.
Phase 3 - Check/set FA Presets
Call /fa-preset/get and decide whether the defaults suit this investment. In the reference case, the default preferTransferFromIndependent:false would have generated a BUY order for CSCO even though the account’s Independent bucket already held CSCO shares. To transfer those shares into the model instead of buying more, call /fa-preset/save with preferTransferFromIndependent:true.
You may also want /fa/model/invest-divest-positions here - it shows, per account, how much NLV currently sits in Independent positions vs. Independent cash vs. this model vs. other models. This is informational context for deciding how much to invest and whether a transfer-from-Independent makes sense.
Phase 4 - Plan and execute the investment
-
POST /fa/model/invest-divestwithaccountList: [{account, amtToInvest}].- This is a polling endpoint - see §5. The final response (once
subscriptionStatus:1) contains:transfersInstructionId- the cache key for phase 4b/4c.cashTransfers[]- cash moves fromCoreinto the model.positionTransfers[]- share transfers from Independent into the model (only populated when the relevant preset, e.g.preferTransferFromIndependent, causes a transfer instead of a trade).allocations[]- instruments that instead require a new order to be placed (because no preset directed a transfer, or the account didn’t hold the shares).
- It is entirely normal for the same invest call to return one instrument as a
positionTransferand a different instrument as anallocation- the decision is made per instrument based on whether the account’s Independent bucket already holds it and what the presets say.
- This is a polling endpoint - see §5. The final response (once
-
4a - Submit the transfers.
POST /fa/model/submit-transferswith thetransfersInstructionIdfrom step 1. This commits the cash + position transfers that don’t require new market orders. It’s a fire-and-forget commit - success means the base cash transfer succeeded (other transfers may still be checked individually viaerror). -
4b - Submit the orders. For every entry in
allocations[], an order must be placed through the IServer trading API, not the FA microservice. See Section #5 for the detailed sub-sequence (auth, suppress precautions, fetch model allocation codes, submit order). Wait for the order to fill before treating the investment as complete - partial fills will show up as imbalance in later verification calls.
Phase 5 - Verify the investment
/fa/model/accounts-details(withcalcPnls:true) - per-account cost basis, NLV, unrealized PnL, and count of instruments outside target range./fa/model/summary- model is nowbootstrapped:true, has a real NLV andnumAccounts:1./fa/model/positions- per-instrument actual vs. target, now populated./fa/model/invest-divest-positions- confirmsaccountModelNlvreflects the new investment.
5. Order Submission via IServer (the “allocation” leg)
The FA microservice never talks to the exchange directly - for any allocations[] entries returned by invest-divest / tws-invest-divest / rebalance/*, you must submit real orders through the trading gateway. This is a separate authentication context (OAuth 2.0-based IServer session), and is the same order-submission surface used for all IBKR trading, with two additions specific to models:
-
Authenticate the trading session:
POST /iserver/auth/ssodh/init -
Suppress order precaution dialogs (required for unattended/API submission):
POST /iserver/questions/suppresswith the standard list of message IDs. -
Fetch the model→account allocation codes (prerequisite before any model order):
GET /iserver/account/allocation/modelsReturns a map of model name → an internal allocation code string. You need this step once per session before placing model orders - it’s how the trading gateway resolvesisModel:trueorders to the correct FA account groupings. -
Submit the order, targeting the model as the account context:
POST /iserver/account/{modelCode}/ordersKey fields that make this a “model order”:"acctId": "<model name>","isModel": trueconidex= the instrument’s conid to allocatejsonPayload.allocation_profile-alloc_type: "SHARE"with a list of{account, amount}pairs (the quantity to allocate to each underlying account). Thequantityyou allocate here should match thequantityreported in the correspondingallocations[]entry from the planning call.
The response gives you an order_id / local_order_id and initial order_status (e.g. PreSubmitted). Wait for the fill before treating the invest/divest/rebalance as complete - subsequent verification calls (/model/positions, /model/accounts-details) reflect actual positions, not pending orders.
6. Polling Pattern (subscriptionKey / subscriptionStatus)
Several planning endpoints (invest-divest, rebalance/*, tws-invest-divest) can take longer than a single request/response round trip because they may need to bootstrap a model, fetch CCP snapshots, or compute large allocation sets. These use a simple polling contract:
- First call: either omit
subscriptionKeyentirely, or send it as an empty string"", to start the async computation. - Immediate response may come back “not ready”:
subscriptionStatus: 0, with asubscriptionKey(e.g."1") that identifies the in-flight job. - Poll: re-send the identical request payload, but now with
subscriptionKeyset to the value you were given. Repeat untilsubscriptionStatus: 1, at which point the response body contains the full result (transfers, allocations, etc.).
Other endpoints not involved in heavy computation (/model/save, /model/summary, /fa-preset/get, /is-full-master, etc.) always return subscriptionStatus:1 immediately - there’s nothing to poll.
7. Rebalancing - Three Flavors, One Response Shape
All three rebalance endpoints return the same response shape (allocations, allocationTotals, positionTransfers, miPositionTransfers, contractsAllocEnabled, contractsCashQtyEnabled, contractsFracEligible, accountsCanTradeFractions, errors, warnings) and a transfersInstructionId you feed to /model/submit-transfers / IServer order submission exactly like an invest-divest plan. They differ only in what targets they rebalance against:
Use to-specific-targets when a user selects one or two rows in a UI and wants to correct only those, without disturbing the rest of the model’s allocation.
8. Multi-Model / Batch Invest (tws-invest-divest)
/model/tws-invest-divest exists for flows where a single account (or group/list of accounts) needs to invest into multiple models in one call - e.g., “put 20K into Model B for this account.” Each entry in modelList[] can independently specify targetAmt, targetPercent (use 0.00 for full divest), or amtToInvest (negative to divest).
Who to invest - account, list, or group
tws-invest-divest accepts the invest target in one of three mutually exclusive shapes at the top level of the request:
Whether you pass accountList or group, the invest amount for a given model is divided equally between the resolved set of accounts - you cannot specify per-account amounts on tws-invest-divest. If you need per-account amounts, use /invest-divest (see the comparison below).
tws-invest-divest vs. invest-divest
Choosing between them:
- Reach for
tws-invest-divestwhen you need multi-currency instruments in a model, when you want a single account/list/group to invest into several models at once, or when you’re already thinking in terms of FA allocation groups. - Reach for
invest-divestwhen each account needs a different investment amount, sincetws-invest-divestsplits one amount equally across all target accounts when a list or group is used.
Batch ordering on the response
The response mirrors invest-divest but tags every cashTransfers / positionTransfers / allocations entry with a batchNumber. Respect batch ordering when submitting - batches are sequenced so that closing/freeing orders run before conversions, which run before opening/spending orders, matching how Multi Currency Model conversions must be sequenced to avoid transient negative cash.
9. Supporting / Maintenance Endpoints
/model/delete- deletes both the model and its targets. Note: a model cannot be re-created with the same name the same day it was deleted - you must wait at least one day./is-full-master- call this defensively before attempting Invest/Divest/Rebalance if you’re unsure of the master account type; partial masters may have these actions blocked outright./model/cash-analyzer- for multi-currency models, detects undesired non-model-currency cash balances sitting inaccount.Modelbuckets and returns the cash transfers / FX-conversion orders needed to clean them up. Also surfacesmarginWarning:trueif a margined account has gone negative in a non-base currency. IfaccountListis omitted, it scans the master’s entire book.

