---
title: "Approve a proposal (approver or owner)"
method: POST
path: "/api/v1/proposals/{id}/approve"
tags: ["lodestar"]
---

# Approve a proposal (approver or owner)

`POST /api/v1/proposals/{id}/approve`

## Request

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

Developer keys cannot approve. Refused when the proposal is not pending, has expired, or the household changed since it was computed (it is then marked stale). Executes nothing; writes one signed projected-value ledger entry. A re-pin proposal moves the mandate to the newer profile version instead.

## Request body

- object
  - `note` string

## Response `200`

Approved.

- object
  - `ok` unknown, required
  - `proposal` Proposal, required
    - `id` integer, required
    - `mandateId` integer, required
    - `householdId` integer, nullable, required: null for a book-level proposal.
    - `householdVersion` integer, nullable, required: The household version it was computed on; a change makes it stale.
    - `kind` 'strategy' | 're-pin' | 'book-impact' | 'data-change', required: re-pin: adopt a newer version of the pinned assumption profile (for a book mandate, it lists every mandate that would move). book-impact: a rules change's ranked impact across the book; approving acknowledges it. data-change: an answer to a question about the household's data; approving writes exactly that field (a household update).
    - `strategy` string, nullable
    - `alternativeId` string, nullable
    - `trigger` string, required: What prompted it, in plain words.
    - `deadline` string, date, nullable
    - `projectedValue` object, nullable
      - `metric` 'endingNetWorthDelta' | '-totalTaxDelta', required
      - `valueUsd` integer, required
      - `nominal` unknown, required
    - `content` object, required: Recommendation, intents (verbatim), basis, ruin change, alternatives considered, caveats and a template-written summary.
    - `evidence` object, nullable
      - `decisionId` integer, required
      - `recordSha256` string, required
      - `signature` RecordSignature, required: 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.
      - `verify` string, required
    - `status` 'pending' | 'approved' | 'rejected' | 'expired' | 'superseded' | 'stale' | 'withdrawn', required
    - `expiresAt` string, date-time, required
    - `supersededBy` integer, nullable
    - `decidedBy` string, nullable
    - `decidedAt` string, date-time, nullable
    - `rejectionReason` 'not-material' | 'client-preference' | 'timing' | 'data-is-wrong' | 'already-done' | 'other', nullable
    - `note` string, nullable
    - `executed` object, nullable
      - `at` string, date-time, required
      - `by` string, nullable, required
      - `reference` string, nullable, required
    - `createdAt` string, date-time, required
  - `ledgerEntry` LedgerEntry
    - `id` integer, required
    - `mandateId` integer, required
    - `proposalId` integer, required
    - `householdId` integer, required
    - `metric` 'endingNetWorthDelta' | '-totalTaxDelta', required
    - `valueUsd` integer, required: Projected, nominal horizon-end dollars; not discounted, not realized.
    - `nominal` unknown, required
    - `basis` string, required
    - `counterfactual` 'decision-baseline-v1', required
    - `decisionId` integer, required
    - `decisionRecordSha256` string, required
    - `record` object, required: The entry's evidence record: sha256 over {model, specVersion, inputs, outputs} and its Ed25519 signature (keys at /api/v1/records/keys).
    - `createdAt` string, date-time, required

## Other responses

- `400`: Malformed JSON, a body of the wrong shape, or a top-level field that fails its check.
- `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 such proposal in your organization.
- `409`: Not pending, expired, stale, or the mandate ended.
- `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)
