---
title: "Patch Profile"
method: PATCH
path: "/harness/profile"
tags: ["harness"]
---

# Patch Profile

`PATCH /harness/profile`

## Request

- Base URL: `https://api.getathly.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.getathly.com/harness/profile`
- Auth: HTTP bearer

## Request body

- HarnessProfilePatchRequest
  - `athlete_slug` string, required
  - `plan_profile` PlanProfilePatch, required: A partial ``plan_profile`` as ``PATCH /harness/profile`` carries it. PATCH semantics on the field set of :class:`PlanProfile`, with the write limits of :class:`PlanProfileWrite`: a key that is absent leaves the field alone, a key set to ``null`` clears it, and the merge is the ``merge_plan_profile`` RPC's server-side ``jsonb ||``. The key set is closed (``extra="forbid"``) — the profile has one owner now, and an unknown key is a typo or an old client, not a new question.
    - `sport_position` string, nullable
    - `competitive_level` string, nullable
    - `primary_focus` string, nullable
    - `training_days` string, nullable
    - `training_setup` string, nullable
    - `age` string, nullable
    - `notes` string, nullable

## Response `200`

Successful Response

- HarnessPlanProfileResponse: ``PATCH /harness/profile``'s answer: the profile as merged, and only that. The raw answers were not in the request and did not change; the editor keeps the copy it already has.
  - `plan_profile` PlanProfile, required: Structured planning context for the pair — the successor to the planner's ``PROFILE_KEYS``, and since ATH-460 Phase 1 the pair's *only* profile (the public ``user_athlete_profiles`` table is archived). Every field is optional: it fills in over time (the settings editor, ATH-460's intake interview, or opportunistically from chat), and an absent field is simply not yet known. The column is written only through the ``merge_plan_profile`` RPC (server-side ``jsonb ||``), never through ``save_user``, so two writers touching different fields both land. The value shapes are the editor's: every field is a string. ``training_days`` is the joined label string the editor stores (``"Mon, Wed"``), deliberately — the athlete's own words, not a parsed set. Nothing has ever written another shape, so a row that carries one is a bug to surface, not a legacy to absorb. The sport is not a field: it is fixed by the athlete the pair is enrolled with (the harness sport seed), so there is nothing to ask. A ``sports`` key on an older row is ignored on read. The profile is not a field of :class:`UserProfile`: ``Store.save_user`` writes the user record from an in-memory copy, and a profile riding on that copy could clobber a merge. It is read through ``Store.get_plan_profile`` instead.
    - `sport_position` string, nullable
    - `competitive_level` string, nullable
    - `primary_focus` string, nullable
    - `training_days` string, nullable
    - `training_setup` string, nullable
    - `age` string, nullable
    - `notes` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/getathly/apis/athly-api.md) - [All operations](https://skmtc.dev/getathly/apis/athly-api/llms.txt) - [OpenAPI document](https://skmtc.dev/getathly/apis/athly-api/revisions/6adfdc258d75?raw)
