---
title: "Read Goal Detail"
method: GET
path: "/goals/{goal_id}"
tags: ["goals"]
---

# Read Goal Detail

`GET /goals/{goal_id}`

One goal in full, for the browser that settled it.

The owner's read, and the only one that carries the why, the checkpoints and
the fallbacks — the share page withholds all three on purpose and still does.
Owning it means holding the goals cookie, exactly as it does for publishing:
this surface has no Firebase and an athlete settles goals before they have an
account, so the browser session IS the identity here (#1851).

Safe, so the cookie alone is enough — no CSRF token and no Origin, like the
conversation read. A goal owned by someone else 404s rather than 403s, so the
status code cannot be used to tell a real goal id from an invented one.

## Path parameters

- `goal_id` string, required

## Response `200`

Successful Response

- GoalDetailResponse — The owner's goal detail page, in one read (#1984). **The opposite contract to** :class:`GoalSharePage`. That one is the promise at a distance, stripped to what a stranger may see; this one is the athlete looking at their own goal, so the why, the checkpoints and the fallbacks — the three things the share page most deliberately withholds — are the point of it. Two models rather than one with a flag, because "what a stranger sees" and "what the owner sees" are different contracts that must not be one edit away from each other. **Rim passthrough is the rim's own types**, not a parallel spelling of them. ``target``, ``factors``, ``checkpoints`` and ``fallbacks`` are the models the document validates against (:mod:`cheshire.goals.models`), serialized as they stand: a mirrored DTO here would be a second contract to keep in step, and the field names looking-glass renders would drift from the ones the coach writes. ``page`` is the same deal for the projected prose (:class:`cheshire.goals.agents.goal_page.GoalPage`). ``page`` is ``null`` for a goal whose projection refused or has not run — every other section still draws (:mod:`cheshire.goals.agents.goal_page` says why). A goal settled before #1983 carries no ``factors`` either, for the same reason: the rim did not have them, and nothing invents them after the fact. ``share_id``/``slug``/``published_at`` are ``StoredGoal.live_share`` read-only — all three together or none, the same guarantee that property already holds, not a fourth spelling of it. This is how the owner's page learns a goal is already live without ever calling publish just to find out: publishing is the athlete's deliberate act (wonderland goals.md, "The share page"), never a side effect of reading a page.
  - `goal_id` string, required
  - `name` string, required
  - `kind` 'destination' | 'practice', required
  - `status` 'exploring' | 'proposing' | 'settled', required
  - `sport` string, nullable
  - `set_at` string, date-time, required
  - `target` GoalDate, required — A dated commitment at its honest resolution. ``precision`` is the band: the date is normalized to the first day of its period (month → the 1st, quarter → the quarter's first month, season → the quarter carrying the season's start), so a coarse band can never license a specific day the source hasn't published — the same normalization the plan rim applies to its events.
    - `date` string, date, required
    - `precision` 'day' | 'month' | 'quarter' | 'season'
  - `anchor` GoalAnchorPayload — The event a goal is pinned to, resolved into what a client draws. Additive and optional: a goal that anchors nowhere — most of them — carries ``null`` here and the page looks exactly as it did before this existed. This is the whole reason the coach never writes a URL. The reply is plain chat text that ships to any channel as-is, so a link in it would be a link the model chose and nothing validated; here the host resolves the id the conversation anchored to and hands over the official name, date, place and site. The surface renders the card, and the coach's words stay words. ``website_url`` points at the organizer's own site rather than an imperfect.co event page: the athlete is being handed the place where they actually register. It resolves server-side, so retargeting it later is one line here and no change to any client.
    - `kind` 'event' | 'route', required
    - `id` string, required
    - `name` string, required
    - `date` string, date, nullable
    - `city` string, nullable
    - `country` string, nullable
    - `website_url` string, nullable
    - `short_link` string, nullable
  - `factors` object[]
  - `page` GoalPage — The prose sections of one settled goal's page. Every field is optional in the sense that matters: a section the document does not carry is absent rather than filled, because a slot is minting pressure (#1850) and this projection has no athlete left in the loop to correct an invention. The page reads fine short. **The field descriptions are the spec the model works from.** They are what the projection prompt deliberately does not restate — one place says what a section is, and it is the type the answer has to satisfy.
    - `why` string, nullable — Why this goal matters, in the athlete's own words — the quote from the document that carries their reason, not the loudest one in it.
    - `journey` string, nullable — One plain line on where the athlete is coming from and what this goal is the next step of: the races behind them, the step up ahead, where they are today.
    - `sport` string, nullable — The athlete's own word for the sport this goal is in, whenever the document makes it plain; nothing only when it genuinely does not.
    - `week` GoalWeekCommitment[] — Everything the week keeps, one entry each — the parts that are not the goal's own sport included.
      - `name` string, required — The commitment, in two or three words.
      - `detail` string, required — What it is, as the document states it.
    - `life` string[] — One short line each for the life the work has to fit: the days and hours actually available, the travel, the sleep, the dates already fixed, whatever the athlete called non-negotiable. Their constraints, never advice.
    - `history` GoalComingFrom — What this goal is built on: the race behind them, or where they start. An athlete with a race behind them is coming from that race and what it taught. A first-timer is coming from a starting point instead — the longest they have run in one go, what their weeks already hold — and their document states it just as plainly (#2013). A card only a past race could fill left theirs blank, on the one section of the page that shows the athlete it meets them where their journey starts. **The quotes are optional, and that is what makes both shapes fit.** ``felt`` and ``lesson`` are the athlete's words, verbatim-checked when present — nobody quotes how their current mileage felt, so a starting point stands on ``event`` alone, held to the document by the grounding judge like every other sentence the model writes. Bench round 1 (#1984) had already shown that requiring both quotes drops a real card whose body quoted the athlete once; requiring one drops every first-timer's.
      - `event` string, required — The race or attempt, named as the document names it — or, when the document tells of none, the athlete's starting point in the same handful of words: the longest they have gone in one go, what their weeks already hold. The thing itself, never a sentence introducing it.
      - `felt` string, nullable — How it felt — the athlete's words, verbatim.
      - `lesson` string, nullable — What it taught — their words, verbatim.
    - `honest_subtitle` string, nullable — One line on what keeps this goal honest: where the coach's read differs from the athlete's target, or what the goal is being held to.
  - `checkpoints` GoalCheckpoint[]
    - `claim` string, required — The unverified claim this checkpoint protects.
    - `measure` string, required — What must be observably true.
    - `by` GoalDate, required — A dated commitment at its honest resolution. ``precision`` is the band: the date is normalized to the first day of its period (month → the 1st, quarter → the quarter's first month, season → the quarter carrying the season's start), so a coarse band can never license a specific day the source hasn't published — the same normalization the plan rim applies to its events.
      - `date` string, date, required
      - `precision` 'day' | 'month' | 'quarter' | 'season'
    - `on_miss` string, required — What the goal reverts to, stated openly.
  - `fallbacks` GoalFallback[]
    - `when` string, required — The athlete's named obstacle.
    - `then` string, required — The athlete's own replacement.
    - `athlete_quote` string, required — Their words, verbatim.
  - `share_id` string, nullable
  - `slug` string, nullable
  - `published_at` string, date-time, nullable

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/imperfect/apis/imperfect-api.md) · [All operations](https://skmtc.dev/imperfect/apis/imperfect-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/imperfect/imperfect-api/revisions/cc2293a44257/schema)
