outcomes

Record a new outcome measurement for an intent.

Accepts a pm_live_ Bearer key with write scope OR a session cookie. The two paths differ on purpose: a key authenticates through the API-key layer, reads with the admin client, and scopes the intent lookup to the key's workspace — RLS is bypassed there, so that filter is what prevents cross-workspace writes. A session keeps the original anon+RLS client and its owner|admin|editor role gate, and needs no workspace_id query param, so the in-app caller is unaffected. Body is zod-validated (lib/outcomeMeasurementRecord.ts). met is the SERVER's conclusion, not the caller's claim: when numericValue and measurement.expectation are both present the comparator is evaluated here, met_basis is 'computed', and a contradicting caller-supplied met is discarded rather than blended. Otherwise a caller-supplied met is stored with met_basis 'self_report', and with neither, met and met_basis are null. Prefer outcomeId: the outcome's text, index, and id are derived server-side from the intent row, so client input can never mislabel a measurement; the legacy outcomeIndex/outcomeText path remains for compatibility and still derives text/id when the index resolves. recorded_by is the authenticated user (the key's owner on the key path); source defaults to 'api' for key writes and 'manual' for session writes; evaluatedAt defaults to now when a measurement block omits it, since a measurement with no evaluation time cannot be placed against the change it measures. workspace_id derives from the intent row. No rate limiting on this endpoint.

post/api/v1/intents/{id}/outcomes

Path parameters

idstring required

id path parameter.

Request body

outcomeIdstring

Preferred: stable StructuredOutcome UUID of the outcome being measured. Must match an outcome on the intent (400 otherwise); outcome text and index are derived server-side from the intent row.

outcomeIndexnumber

Legacy positional path, used only when outcomeId is absent (one of the two is required; 0 is valid, must be a non-negative integer). When the index resolves against the intent's outcomes, text and id are derived server-side.

outcomeTextstring

Legacy outcome text snapshot. Only consulted when outcomeIndex does not resolve against the intent's current outcomes; required non-empty in that case.

actualValuestring required

Measured value. Required non-empty string; trimmed before insert.

baselineValuestring

Baseline value before the change. Stored null if falsy.

unitstring

Unit of measure. Stored null if falsy.

numericValuenumber

The measured value as a finite number, alongside the displayable actualValue. Required for the server to evaluate measurement.expectation — without it a verdict can only be recorded as a self-report.

measurementobject

Provenance for the number: { provider, queryRef: { kind, id }, queryVersion?, window: { kind: rolling|fixed|since_ship, duration?, start?, end? }, evaluatedAt?, expectation?: { operator: gt|gte|lt|lte|eq, target, unit? }, rawResponse?, recordedByTool? }. queryRef.id must be a STABLE id (for PostHog, a saved insight id), never a metric name: names are mutable, so a renamed query would orphan the record. A rolling window requires duration; a fixed window requires start and end. rawResponse must be JSON-serialisable and under 20000 characters.

metboolean

Whether the outcome target was met. Stored null if not provided (uses ?? null).

sourcestring

Provenance label. Defaults to 'manual' when falsy.

Response

Measurement recorded; returns the created measurement

object required

Changes