---
title: "Run the projection engine on a stored household"
method: POST
path: "/api/v1/households/{id}/project"
tags: ["households", "project"]
---

# Run the projection engine on a stored household

`POST /api/v1/households/{id}/project`

## Request

- Base URL: `https://worthune.com`
- URL: `https://worthune.com/api/v1/households/{id}/project`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-Api-Key`

Deterministic projection (spec v0.11.0) with optional policy and seeded Monte Carlo. Stamps lastComputedAt (the active-household meter) and emits household.computed. The evidence record carries the hashed inputs.

## Request body

- StoredProjectionRequest: Assumptions: explicit `assumptions`, a pinned `profile`, or (neither) the labeled illustrative default — the resolved source is echoed as assumptionsSource so a default can never masquerade as advice.
  - `horizon` ProjectionHorizon, required
    - `startYear` integer, required: First projected calendar year, an integer in [1900, 2150] on every surface.
    - `years` integer, required: Number of years to project.
  - `assumptions` ProjectionAssumptions: Unknown keys are rejected — the accepted set is exactly: annualReturn, inflationRate, defaultIncomeGrowth, socialSecurityCola, returnsByWrapper.
    - `annualReturn` number, required: Nominal annual return applied to every account balance (one blended rate).
    - `inflationRate` number, required: Annual inflation applied to inflation-adjusted expense streams.
    - `defaultIncomeGrowth` number, required: Growth for income streams that carry no growthRate of their own.
    - `socialSecurityCola` number: COLA applied to COMPUTED social_security benefits from each benefit's start year. Defaults to inflationRate; pass 0 for a flat benefit.
    - `returnsByWrapper` object: Per-wrapper returns; unlisted wrappers keep the blended annualReturn.
      - `taxable` number: Return for taxable accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `traditional` number: Return for traditional accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `roth` number: Return for roth accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `hsa` number: Return for hsa accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `plan529` number: Return for plan529 accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `cash` number: Return for cash accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `annuity` number: Return for annuity accounts, replacing annualReturn (and any sampled path) for that wrapper.
  - `profile` ProfileRef: An assumption profile from GET /api/v1/assumption-profiles, or the built-in illustrative-6-3-3@1.0.0.
    - `id` string, required
    - `version` string, required: Exact version; a pinned run never silently gets a newer one.
  - `policy` ProjectionPolicy: Execution choices, hashed into the evidence record — a policy run never hashes like a default run. Unknown keys are rejected.
    - `withdrawalOrder` string[]: A permutation of cash, taxable, traditional, roth — the order deficits are drawn from. Default: cash → taxable → traditional → roth.
    - `rothConversions` object[]: Scheduled Roth conversions: each year's amount moves from traditional accounts (document order) into the first roth account, taxed as ordinary income the same year. A schedule with no roth destination is a 422 from the engine.
      - `year` integer, required
      - `amountUsd` number, required
  - `monteCarlo` MonteCarloConfig
    - `seed` integer, required: RNG seed — same seed, same result, byte for byte.
    - `simulations` integer: Number of paths (engine default 1000).
    - `returnVolatility` number: Annual return standard deviation (default 0.15).
    - `longevity` boolean: Sample death years from the NCHS life table; every adult member then needs `sex` (422 otherwise).

## Response `200`

The run.

- object
  - `ok` unknown, required
  - `householdId` integer, required
  - `householdVersion` integer, required
  - `schemaVersion` string, required
  - `assumptionsSource` string, required: "caller-supplied", "profile <id>@<version>", or the labeled illustrative default.
  - `assumptions` ProjectionAssumptions, required: Unknown keys are rejected — the accepted set is exactly: annualReturn, inflationRate, defaultIncomeGrowth, socialSecurityCola, returnsByWrapper.
    - `annualReturn` number, required: Nominal annual return applied to every account balance (one blended rate).
    - `inflationRate` number, required: Annual inflation applied to inflation-adjusted expense streams.
    - `defaultIncomeGrowth` number, required: Growth for income streams that carry no growthRate of their own.
    - `socialSecurityCola` number: COLA applied to COMPUTED social_security benefits from each benefit's start year. Defaults to inflationRate; pass 0 for a flat benefit.
    - `returnsByWrapper` object: Per-wrapper returns; unlisted wrappers keep the blended annualReturn.
      - `taxable` number: Return for taxable accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `traditional` number: Return for traditional accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `roth` number: Return for roth accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `hsa` number: Return for hsa accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `plan529` number: Return for plan529 accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `cash` number: Return for cash accounts, replacing annualReturn (and any sampled path) for that wrapper.
      - `annuity` number: Return for annuity accounts, replacing annualReturn (and any sampled path) for that wrapper.
  - `horizon` ProjectionHorizon, required
    - `startYear` integer, required: First projected calendar year, an integer in [1900, 2150] on every surface.
    - `years` integer, required: Number of years to project.
  - `projection` ProjectionResult, required: Projection spec v0.11.0 (docs/model-specs/household-projection.md).
    - `rows` YearRow[], required
      - `year` integer, required
      - `primaryAge` integer, required
      - `grossIncome` number, required
      - `rmd` number, required: Traditional-account withdrawals forced by the Uniform Lifetime Table.
      - `rothConversion` number: Present only when a scheduled conversion executed (> 0).
      - `federalTax` number, required
      - `stateTax` number, required
      - `expenses` number, required
      - `debtService` number, required
      - `netCashFlow` number, required: Negative = deficit.
      - `withdrawals` number, required: Deficit withdrawals taken this year.
      - `accountBalances` object, required: End-of-year balance by account id.
      - `liabilityBalances` object, required: End-of-year balance by liability id.
      - `netWorth` number, required
    - `endingNetWorth` number, required
    - `ranOutOfMoney` boolean, required
    - `ranOutInYear` integer, nullable, required
    - `assumptionsApplied` string[], required: The simplifications that applied to THIS run, stated plainly.
  - `monteCarlo` HouseholdMonteCarloResult
    - `simulations` integer, required
    - `seed` integer, required
    - `successRatePct` number, required: Share of paths that never fail to cover a deficit, in percent.
    - `endingNetWorth` Percentiles, required
      - `p10` number, required
      - `p25` number, required
      - `p50` number, required
      - `p75` number, required
      - `p90` number, required
      - `mean` number, required
    - `yearlyNetWorthBands` object[], required
      - `year` integer, required
      - `p10` number, required
      - `p25` number, required
      - `p50` number, required
      - `p75` number, required
      - `p90` number, required
    - `medianRuinYear` integer, nullable, required
    - `longevity` object: Present only when the run was longevity-aware.
      - `table` 'nchs.us-life-table.2024', required
      - `survivalConditionedSuccessRatePct` number, required
      - `perMember` object[], required
        - `id` string, required
        - `probAliveAtHorizonEndPct` number, required
      - `notes` string[], required
  - `record` DecisionRecord, required: Evidence record v2. Verify with POST /api/v1/records/verify or the SDKs' verifyRecord.
    - `sha256` string, required: SHA-256 (hex) over the canonical JSON of {model, specVersion, inputs, outputs} — keys sorted recursively, no whitespace.
    - `fields` unknown, required
    - `howToVerify` string, required
    - `model` string, required: The hashed model identity — a catalog model name, "household-projection", or "decision-<strategy>".
    - `specVersion` string, required
    - `inputs` object: The exact hashed inputs, present on records whose envelope does not otherwise expose them (household projections, decisions). Point-model run responses echo `inputs` at top level instead; a verify response's record covers the inputs the caller sent in the request.
    - `signature` RecordSignature: Proves Worthune minted the record's hash. Absent when the deployment has no signing key; a stored decision is signed only when its hash is recomputed from the stored inputs.
      - `alg` 'Ed25519', required
      - `keyId` string, required: Matches a key at GET /api/v1/records/keys.
      - `value` string, required: base64url signature.
      - `message` string, required: What was signed: "worthune-record:v1:" + sha256. Rebuild it from the hash when verifying.

## Other responses

- `400`: Malformed JSON, missing/invalid horizon, unresolvable assumptions or profile, a bad policy, or a bad monteCarlo block.
- `401`: No key presented (Authorization: Bearer wk_… or X-Api-Key).
- `402`: Unrecognized or revoked API key.
- `403`: The key's role is below the operation's floor (viewer < developer < owner), the key predates organization support, or the plan lacks the feature. On a sandbox key, also: the cap of 5 active households on create and import, the cap of 1 active Lodestar mandate, and the white-label surfaces (tenant config, embed tokens) and narration or mapping drafts on Worthune's model (they run on your own: a model assigned at /api/v1/model-policy, or x-model-key), which the sandbox does not carry.
- `404`: No household with this id in the key's organization (PUT, PATCH, POST project and POST decisions also answer 404 for archived rows; GET, the decision list and the picture still serve them).
- `422`: The engine rejected the policy (path "policy"), or longevity was requested and an adult member lacks `sex`.
- `429`: Hourly per-org stateful quota, a per-route durable limit, or the 120 requests/minute per-IP burst backstop.
- `503`: Billing is not configured on this deployment; household resources are disabled rather than served without tenancy (production fail-closed).

---

[API](https://skmtc.dev/worthune/apis/worthune-api.md) · [All operations](https://skmtc.dev/worthune/apis/worthune-api/llms.txt) · [OpenAPI document](https://skmtc.dev/worthune/apis/worthune-api/revisions/9f2241072555?raw)
