UTC --:--:--
Docs·API reference·POST /v1/score/:id/outcome

Report the observed outcome of a scored session

Report what actually happened after a score. Each outcome carries a structured reason_category so weather and safety results can feed suitability calibration, while other causes are recorded as business facts and excluded.

POSThttps://api.goable.io/v1/score/{sessionId}/outcome

Maturity. Outcome ingestion is live: these endpoints accept and record outcomes today. The collection and calibration workflow that consumes them, refitting local curves and measuring forecast skill, is design-partner-stage and is being exercised through the Sealect reference integration, not yet a generally available, self-serve calibration product.

Why submit outcomes

Goable is physics-first, but physics alone can't tell you whether a Tarifa kitesurf school actually runs sessions at 18kt or cancels at 20kt. Weather and safety outcomes are the evidence that can bend the curves toward local reality. Only outcomes tagged reason_category: weather or safety count as evidence for or against a forecast score; other causes are recorded as business facts and excluded from calibration. The design pipeline that consumes them (currently design-partner-stage) has three parts:

  • Self-calibration: an evidence-gated Bayesian refit of the per-(activity × cell) curves, once a cell has accumulated enough paired weather/safety outcomes and a candidate update beats a holdout baseline.
  • Forecast verification: Brier Skill Score + reliability diagrams, stratified by horizon, sub-spot, cluster. These measure forecast quality, not that a given suitability verdict is correct.
  • Drift monitor: statistical-process-control CUSUM over daily skill, to catch regime shifts between recalibrations.

As qualifying outcomes accumulate, a cell's curve can diverge from the generic base profile and track your operators more closely. Until a cell has enough evidence, scores fall back through the spatial hierarchy (sub-spot, then cluster, region, base).

Request

The sessionId in the URL is the session_id returned at the top level of every /v1/score response (UUID). There's no enforced submission-window cutoff in the API itself; submit as soon as you know the outcome so it lands in the next calibration batch. You can pass an optional Idempotency-Key header so a retried submission is de-duplicated rather than double-counted.

{
 "outcome_type": "ran",
 "reason_category": "weather",
 "detail": {
 "participants": 8,
 "wind_actual_kt": 18.5,
 "notes": "perfect lunch session"
 }
}

reason_category is optional and one of weather, operational, customer_demand, safety, mechanical or unknown; it records the structured cause when a session did not run as planned. Only weather and safety feed weather-suitability calibration; the others are recorded and excluded. It is echoed on the outcome.created webhook. The detail object is free-form JSON, schema is up to you: common fields are actual wind/wave readings, participant count and equipment. 202 Accepted on success (the record is queued for the next calibration batch).

Outcome types

ranSession went ahead as planned. With reason_category weather or safety it is evidence the conditions were workable; other reason categories are recorded but excluded from calibration.
cancelledOperator did not run the session. It counts as evidence against the score only when reason_category is weather or safety; an operational, demand or mechanical cancellation is recorded and excluded.
no_showParticipants didn't show up. Excluded from skill scoring (it's a demand signal, not a weather signal).
rescheduledMoved to a different slot. Excluded from skill scoring (the original window's verdict wasn't tested).
noteFree-form annotation, no signal. Useful for operator field journals; never feeds the calibrator.

Standalone outcomes (no session)

If you didn't call /v1/score first, or want to log an outcome against a spot/activity without a specific scored session, use POST /v1/outcomes instead. Same outcome_type, reason_category and detail shape, plus required occurred_at and activity_slug, and optional spot_id, audit_log_id (to still link it to a session after the fact — this must be a session_id from a POST /v1/score response; the correlation ids returned by /v1/score/multi and /v1/score/series are not linkable), equipment_type (electric, combustion or manual, which feeds the Stream G equipment-transition research dataset), and batch_ref, a client-supplied lot handle you can tag a batch of outcomes with so a later recall can find exactly that lot. Also 202 Accepted, the same outcomes:write scope and the same optional Idempotency-Key header.

Recalling a lot

POSThttps://api.goable.io/v1/outcomes/void

If a batch of outcomes was later found to be mislabelled, recall it with POST /v1/outcomes/void so it stops influencing calibration, without a destructive delete. A reason string is required and recorded on every voided row for audit. Select which rows to recall with one or more of batch_ref (the lot tag), audit_log_id (a single scored session), submitted_by_key_id (everything a given key submitted, combined with occurred_from / occurred_to), and optional outcome_type or reason_category narrowing (for example, void only the rows a script mislabelled weather).

Scope requirement

Your API key needs the outcomes:write scope. Both live and test keys carry it by default, so a staging or CI integration can exercise the full outcome pipe. Test-key outcomes are persisted and listable exactly like live ones, but they're stamped is_test and quarantined — excluded from the calibration and forecast-verification signal — so CI traffic never pollutes the model. Mint a key from the tenant portal at console.goable.io/portal/keys if the existing one lacks the scope.

Privacy + research

Outcomes are tenant-private by default and never cross-pollinate calibration. If you've opted into the research dataset, anonymised aggregates (k≥10, 1km² grid, 90-day lag) feed the public forecast-verification export. Opt out by flipping research_consent = false on your tenant.

Errors

403 FORBIDDEN: missing outcomes:write scope. 404 SESSION_NOT_FOUND: on /v1/score/{sessionId}/outcome, the sessionId isn't in the audit log (wrong tenant, or the id doesn't exist; check it's the exact session_id from the /v1/score response). 404 AUDIT_LOG_NOT_FOUND: on /v1/outcomes, the supplied audit_log_id matches no scored session — it must be a session_id from a POST /v1/score response (multi/series ids are not linkable). 422 VALIDATION_ERROR: invalid outcome_type or malformed detail JSON.