---
title: "Verified narrative for a decision"
method: POST
path: "/api/v1/narrate"
tags: ["narrate"]
---

# Verified narrative for a decision

`POST /api/v1/narrate`

## Request

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

A model drafts prose from the decision object; a deterministic gate verifies every number in the draft against the decision's own values (one regeneration, then refusal — never patched). Any key role. 60/hour per org. The drafting model: x-model-key for the request, else your narrator model (model policy), else ours; the response names it.

## Headers

- `x-model-key` string

## Request body

- object: Exactly one of decisionId (a stored decision) or decision (an inline object, validated first).
  - `decisionId` integer: A stored decision (org-scoped).
  - `decision` DecisionObject: Decision object v0.1.0 (docs/decision-object-spec.md).
    - `schemaVersion` '0.1.0', required
    - `strategy` string, required
    - `strategySpecVersion` string, required
    - `objective` string, required: What the scores maximize, in one sentence.
    - `baseline` object, required
      - `description` string, required
      - `recordSha256` string
    - `alternatives` DecisionAlternative[], required
      - `id` string, required
      - `label` string, required
      - `score` number, required: Objective score; alternatives are sorted by it, best first (validated).
      - `delta` OutcomeDelta, required
        - `endingNetWorthDelta` number, required: Variant − baseline, final-year net worth.
        - `totalTaxDelta` number, required: Variant − baseline, cumulative federal + state tax across the horizon.
        - `ranOutInYear` object, required
          - `baseline` integer, nullable, required
          - `variant` integer, nullable, required
      - `intents` ExecutionIntent[], required: Empty = status quo.
        - `id` string, required
        - `kind` 'transfer' | 'convert' | 'withdraw' | 'contribute' | 'claim' | 'set-policy', required
        - `summary` string, required: One human sentence; policy-level, never a trade.
        - `accountId` string: Household-schema account id, when the intent targets one.
        - `year` integer
        - `amountUsd` number
      - `notes` string[]
    - `assumptions` object, required
      - `source` string, required
      - `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.
    - `evidence` EvidenceLink[], required
      - `kind` 'projection' | 'model-run' | 'decision', required
      - `sha256` string, required
      - `specVersion` string, required
  - `audience` 'client' | 'advisor': Default "client".

## Response `200`

The verified narrative.

- object
  - `ok` unknown, required
  - `audience` 'client' | 'advisor', required
  - `narrative` string, required
  - `verification` NarrativeVerification, required
    - `numericClaims` integer, required
    - `verifiedClaims` integer, required
    - `structuralExempt` integer, required
    - `attempts` integer, required: 1, or 2 when the first draft was rejected and regenerated.
    - `gate` 'every numeric claim verified against the source computation', required
    - `recordSha256` string: Present when a stored decision was narrated.
    - `note` string, required
  - `drafter` Drafter, required: Which model drafted. The deterministic gates after it are the same whichever it was.
    - `source` 'caller-key' | 'organization-policy' | 'deployment', required: x-model-key for this request, else your organization's role model (PUT /api/v1/model-policy), else the deployment's.
    - `provider` 'anthropic' | 'gemini' | 'openai-compatible' | 'azure-openai', required
    - `model` string, required

## Other responses

- `400`: Malformed JSON, or neither decisionId nor decision.
- `401`: No key presented (Authorization: Bearer wk_… or X-Api-Key).
- `402`: Unrecognized or revoked API key.
- `403`: The key predates organization support (contact support for a free rotation).
- `404`: No decision with this id in the key's organization.
- `422`: Invalid audience, or the inline decision failed validation (paths).
- `429`: Hourly per-org stateful quota, a per-route durable limit, or the 120 requests/minute per-IP burst backstop.
- `502`: The narration model did not answer, or two drafts failed the numeric gate (violations listed).
- `503`: No narration model is configured on this deployment (or billing is not configured).

---

[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)
