---
title: "Portfolio rebalancing (drift, trades, tax-aware contributions-first)"
method: POST
path: "/v1/portfolio/rebalance"
tags: ["Portfolio"]
---

# Portfolio rebalancing (drift, trades, tax-aware contributions-first)

`POST /v1/portfolio/rebalance`

## Request

- Base URL: `https://api.numeratica.com`
- URL: `https://api.numeratica.com/v1/portfolio/rebalance`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Given holdings and target weights, computes the trades that restore the targets, the per-holding drift, and whether each holding is outside a tolerance band. Contributions and withdrawals scale the investable base first. When tax_aware is set, it estimates the realized gain of a full rebalance and — if there's new cash and no withdrawal — a contributions-first plan that directs the cash to underweight holdings without selling (the tax-efficient way to rebalance a taxable account). Target weights must sum to 1. Deterministic; not investment advice.

## Request body

- RebalanceRequest
  - `holdings` object[], required: The portfolio; target_weight values must sum to 1.
    - `label` string
    - `value` number, required: Current market value.
    - `target_weight` number, required: Desired share of the portfolio.
    - `cost_basis` number: For the realized-gain estimate (tax-aware). Omit when unknown — the holding is then excluded from the estimate and counted in holdings_missing_basis, not booked as all-gain; set 0 for a genuinely zero-basis lot.
  - `new_contribution` number: Cash added before rebalancing.
  - `withdrawal` number: Cash raised before rebalancing.
  - `band` number: Per-holding drift tolerance; 0 flags any nonzero trade.
  - `tax_aware` boolean: Adds the realized-gain estimate and the contributions-first plan.

## Response `200`

The rebalancing plan

- RebalanceResponse
  - `result_id` string
  - `engine_version` string
  - `inputs` RebalanceRequest
    - `holdings` object[], required: The portfolio; target_weight values must sum to 1.
      - `label` string
      - `value` number, required: Current market value.
      - `target_weight` number, required: Desired share of the portfolio.
      - `cost_basis` number: For the realized-gain estimate (tax-aware). Omit when unknown — the holding is then excluded from the estimate and counted in holdings_missing_basis, not booked as all-gain; set 0 for a genuinely zero-basis lot.
    - `new_contribution` number: Cash added before rebalancing.
    - `withdrawal` number: Cash raised before rebalancing.
    - `band` number: Per-holding drift tolerance; 0 flags any nonzero trade.
    - `tax_aware` boolean: Adds the realized-gain estimate and the contributions-first plan.
  - `result` object
    - `total_current` number: Portfolio value before cash flows.
    - `investable_total` number: After contribution − withdrawal (the rebalance base).
    - `trades` RebalanceTrade[]
      - `label` string
      - `value` number
      - `current_weight` number
      - `target_weight` number
      - `target_value` number
      - `drift` number: current_weight − target_weight.
      - `trade` number: + buy, − sell, to reach target.
      - `action` 'buy' | 'sell' | 'hold'
      - `out_of_band` boolean
      - `contribution` number: Cash directed here in the contributions-first plan.
    - `total_buys` number
    - `total_sells` number
    - `max_drift` number: Largest absolute drift.
    - `needs_rebalance` boolean: Any holding out of band (or any trade if band=0).
    - `estimated_realized_gain` number: From the full rebalance's sells (tax-aware, needs cost_basis).
    - `holdings_missing_basis` integer: Sold holdings excluded from the realized-gain estimate for lack of a cost_basis.
    - `contributions_first` object: Buy-only plan directing new cash to underweight holdings (tax-aware).
      - `contribution_allocated` number: Cash actually directed (≤ new_contribution).
      - `max_drift_after` number: Largest absolute drift once the cash is deployed.
      - `fully_rebalanced` boolean: True if the contribution alone reached every target.
  - `disclaimer` string

## Other responses

- `400`: Invalid request
- `401`: Missing or invalid API key.
- `405`: Method not allowed
- `429`: The key's per-minute request budget is exceeded. Retry after the delay in the Retry-After header.
- `503`: A dependency (e.g. the key store) is temporarily unavailable, or the request exceeded its compute deadline. Retryable.

---

[API](https://skmtc.dev/numeratica/apis/numeratica-financial-planning-calculations-api.md) · [All operations](https://skmtc.dev/numeratica/apis/numeratica-financial-planning-calculations-api/llms.txt) · [OpenAPI document](https://skmtc.dev/numeratica/apis/numeratica-financial-planning-calculations-api/revisions/075bcfbd1ebf?raw)
