{
 "openapi": "3.1.0",
 "info": {
  "title": "Rikskampen X Theme 5 API - activity",
  "version": "0.1.0",
  "description": "Theme 5 API contract proposal (API01, decision D11): raw activity records and the derived activity summary. The distance and calorie estimate is provisional policy (D5). Real-client gate: a qualified nutrition professional must review this provisional policy before any real client relies on it, and no production backend may apply it to real clients before that review."
 },
 "tags": [
  {
   "name": "activity"
  }
 ],
 "paths": {
  "/api/theme5/clients/{clientId}/activity/records": {
   "get": {
    "operationId": "listActivityRecords",
    "summary": "List raw activity records",
    "description": "Raw recorded activity between the from and to dates (inclusive): one record per real source event, identified by source and sourceEventId; value is the step count, the number of stars or the workout minutes. Excluded records stay listed with excluded true and their reason. Each record appears once.",
    "tags": [
     "activity"
    ],
    "security": [
     {
      "coachAuth": []
     }
    ],
    "parameters": [
     {
      "$ref": "common.openapi.json#/components/parameters/ClientId"
     },
     {
      "name": "from",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "to",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "format": "date"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Success.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": false,
         "required": [
          "records"
         ],
         "properties": {
          "records": {
           "type": "array",
           "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
             "recordId",
             "date",
             "kind",
             "value",
             "source",
             "sourceEventId",
             "recordedAt",
             "excluded",
             "excludedReason",
             "revision"
            ],
            "properties": {
             "recordId": {
              "type": "string"
             },
             "date": {
              "type": "string",
              "format": "date"
             },
             "kind": {
              "type": "string",
              "enum": [
               "steps",
               "star",
               "workout"
              ]
             },
             "value": {
              "type": "integer",
              "minimum": 0
             },
             "source": {
              "type": "string",
              "enum": [
               "manual",
               "device",
               "import"
              ]
             },
             "sourceEventId": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
             },
             "recordedAt": {
              "type": "string",
              "format": "date-time"
             },
             "excluded": {
              "type": "boolean"
             },
             "excludedReason": {
              "type": [
               "string",
               "null"
              ],
              "minLength": 10
             },
             "revision": {
              "$ref": "common.openapi.json#/components/schemas/Revision"
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "400": {
      "$ref": "common.openapi.json#/components/responses/BadRequest"
     },
     "401": {
      "$ref": "common.openapi.json#/components/responses/Unauthorized"
     },
     "403": {
      "$ref": "common.openapi.json#/components/responses/Forbidden"
     },
     "404": {
      "$ref": "common.openapi.json#/components/responses/NotFound"
     }
    },
    "x-theme5-rules": [
     "R-RAW-APPEND-ONLY",
     "R-COUNT-ONCE",
     "R-COACH-ASSIGNED"
    ]
   },
   "post": {
    "operationId": "createActivityRecord",
    "summary": "Append one raw activity record",
    "description": "Append one raw record. The same source event is stored once: a record whose source and sourceEventId already exist returns 409 DUPLICATE_SOURCE_EVENT and stores nothing; the Idempotency-Key prevents a duplicate record from a retried request. Derived summaries are never written.",
    "tags": [
     "activity"
    ],
    "security": [
     {
      "coachAuth": []
     }
    ],
    "parameters": [
     {
      "$ref": "common.openapi.json#/components/parameters/ClientId"
     },
     {
      "$ref": "common.openapi.json#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
         "date",
         "kind",
         "value",
         "source",
         "sourceEventId"
        ],
        "properties": {
         "date": {
          "type": "string",
          "format": "date"
         },
         "kind": {
          "type": "string",
          "enum": [
           "steps",
           "star",
           "workout"
          ]
         },
         "value": {
          "type": "integer",
          "minimum": 0
         },
         "source": {
          "type": "string",
          "enum": [
           "manual",
           "device",
           "import"
          ]
         },
         "sourceEventId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Success.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": false,
         "required": [
          "recordId",
          "date",
          "kind",
          "value",
          "source",
          "sourceEventId",
          "recordedAt",
          "excluded",
          "excludedReason",
          "revision",
          "audit"
         ],
         "properties": {
          "recordId": {
           "type": "string"
          },
          "date": {
           "type": "string",
           "format": "date"
          },
          "kind": {
           "type": "string",
           "enum": [
            "steps",
            "star",
            "workout"
           ]
          },
          "value": {
           "type": "integer",
           "minimum": 0
          },
          "source": {
           "type": "string",
           "enum": [
            "manual",
            "device",
            "import"
           ]
          },
          "sourceEventId": {
           "type": "string",
           "minLength": 1,
           "maxLength": 128
          },
          "recordedAt": {
           "type": "string",
           "format": "date-time"
          },
          "excluded": {
           "type": "boolean"
          },
          "excludedReason": {
           "type": [
            "string",
            "null"
           ],
           "minLength": 10
          },
          "revision": {
           "$ref": "common.openapi.json#/components/schemas/Revision"
          },
          "audit": {
           "$ref": "common.openapi.json#/components/schemas/AuditStamp"
          }
         }
        }
       }
      }
     },
     "400": {
      "$ref": "common.openapi.json#/components/responses/BadRequest"
     },
     "401": {
      "$ref": "common.openapi.json#/components/responses/Unauthorized"
     },
     "403": {
      "$ref": "common.openapi.json#/components/responses/Forbidden"
     },
     "404": {
      "$ref": "common.openapi.json#/components/responses/NotFound"
     },
     "409": {
      "$ref": "common.openapi.json#/components/responses/Conflict"
     },
     "422": {
      "$ref": "common.openapi.json#/components/responses/Unprocessable"
     }
    },
    "x-theme5-rules": [
     "R-RAW-APPEND-ONLY",
     "R-DERIVED-READ-ONLY",
     "R-IDEMPOTENT-WRITE",
     "R-AUDITED-WRITE",
     "R-COACH-ASSIGNED",
     "R-SCHEMA-400",
     "R-SOURCE-EVENT-ONCE"
    ]
   }
  },
  "/api/theme5/clients/{clientId}/activity/records/{recordId}/exclusion": {
   "post": {
    "operationId": "excludeActivityRecord",
    "summary": "Exclude a duplicate activity record",
    "description": "A coach marks one raw record excluded, with a reason of at least 10 characters; the action is audited, the raw values are never changed or deleted, and the record no longer counts in any summary, which resolves conflicts between overlapping sources (D3 revision 2). Excluding an already excluded record returns it unchanged; a stale expectedRevision returns 409 STALE_REVISION.",
    "tags": [
     "activity"
    ],
    "security": [
     {
      "coachAuth": []
     }
    ],
    "parameters": [
     {
      "$ref": "common.openapi.json#/components/parameters/ClientId"
     },
     {
      "$ref": "common.openapi.json#/components/parameters/RecordId"
     },
     {
      "$ref": "common.openapi.json#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
         "expectedRevision",
         "reason"
        ],
        "properties": {
         "expectedRevision": {
          "$ref": "common.openapi.json#/components/schemas/Revision"
         },
         "reason": {
          "type": "string",
          "minLength": 10
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Success.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": false,
         "required": [
          "recordId",
          "date",
          "kind",
          "value",
          "source",
          "sourceEventId",
          "recordedAt",
          "excluded",
          "excludedReason",
          "revision",
          "audit"
         ],
         "properties": {
          "recordId": {
           "type": "string"
          },
          "date": {
           "type": "string",
           "format": "date"
          },
          "kind": {
           "type": "string",
           "enum": [
            "steps",
            "star",
            "workout"
           ]
          },
          "value": {
           "type": "integer",
           "minimum": 0
          },
          "source": {
           "type": "string",
           "enum": [
            "manual",
            "device",
            "import"
           ]
          },
          "sourceEventId": {
           "type": "string",
           "minLength": 1,
           "maxLength": 128
          },
          "recordedAt": {
           "type": "string",
           "format": "date-time"
          },
          "excluded": {
           "type": "boolean"
          },
          "excludedReason": {
           "type": [
            "string",
            "null"
           ],
           "minLength": 10
          },
          "revision": {
           "$ref": "common.openapi.json#/components/schemas/Revision"
          },
          "audit": {
           "$ref": "common.openapi.json#/components/schemas/AuditStamp"
          }
         }
        }
       }
      }
     },
     "400": {
      "$ref": "common.openapi.json#/components/responses/BadRequest"
     },
     "401": {
      "$ref": "common.openapi.json#/components/responses/Unauthorized"
     },
     "403": {
      "$ref": "common.openapi.json#/components/responses/Forbidden"
     },
     "404": {
      "$ref": "common.openapi.json#/components/responses/NotFound"
     },
     "409": {
      "$ref": "common.openapi.json#/components/responses/Conflict"
     },
     "422": {
      "$ref": "common.openapi.json#/components/responses/Unprocessable"
     }
    },
    "x-theme5-rules": [
     "R-EXCLUSION-AUDITED",
     "R-RAW-APPEND-ONLY",
     "R-OVERLAP-UNAVAILABLE",
     "R-STALE-REVISION",
     "R-IDEMPOTENT-WRITE",
     "R-AUDITED-WRITE",
     "R-COACH-ASSIGNED",
     "R-SCHEMA-400"
    ]
   }
  },
  "/api/theme5/clients/{clientId}/activity/summary": {
   "get": {
    "operationId": "getActivitySummary",
    "summary": "Get the derived activity summary",
    "description": "Derived on request from the raw records with the provisional D5 estimate: steps and stars are the sums of that date's non-excluded records of one source, each record counted once, 1 star = 1 km, and workouts are never added to distance. When more than one source reports steps or stars for the date, that value is null and the kind is listed in conflicts (never summed twice) until a coach excludes the duplicates (excludeActivityRecord). Unavailable values are null, never 0.",
    "tags": [
     "activity"
    ],
    "security": [
     {
      "coachAuth": []
     }
    ],
    "parameters": [
     {
      "$ref": "common.openapi.json#/components/parameters/ClientId"
     },
     {
      "name": "date",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "format": "date"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Success.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": false,
         "required": [
          "date",
          "steps",
          "stars",
          "stepDistanceKm",
          "starDistanceKm",
          "distanceKm",
          "calories",
          "stepsRecorded",
          "starsRecorded",
          "distanceAvailable",
          "caloriesAvailable",
          "method",
          "estimated",
          "conflicts"
         ],
         "properties": {
          "date": {
           "type": "string",
           "format": "date"
          },
          "steps": {
           "type": [
            "integer",
            "null"
           ],
           "minimum": 0
          },
          "stars": {
           "type": [
            "integer",
            "null"
           ],
           "minimum": 0
          },
          "stepDistanceKm": {
           "type": [
            "number",
            "null"
           ],
           "minimum": 0
          },
          "starDistanceKm": {
           "type": [
            "number",
            "null"
           ],
           "minimum": 0
          },
          "distanceKm": {
           "type": [
            "number",
            "null"
           ],
           "minimum": 0
          },
          "calories": {
           "type": [
            "number",
            "null"
           ],
           "minimum": 0
          },
          "stepsRecorded": {
           "type": "boolean"
          },
          "starsRecorded": {
           "type": "boolean"
          },
          "distanceAvailable": {
           "type": "boolean"
          },
          "caloriesAvailable": {
           "type": "boolean"
          },
          "method": {
           "const": "estimate-v1"
          },
          "estimated": {
           "type": "boolean"
          },
          "conflicts": {
           "type": "array",
           "uniqueItems": true,
           "items": {
            "type": "string",
            "enum": [
             "steps",
             "star"
            ]
           }
          }
         }
        }
       }
      }
     },
     "400": {
      "$ref": "common.openapi.json#/components/responses/BadRequest"
     },
     "401": {
      "$ref": "common.openapi.json#/components/responses/Unauthorized"
     },
     "403": {
      "$ref": "common.openapi.json#/components/responses/Forbidden"
     },
     "404": {
      "$ref": "common.openapi.json#/components/responses/NotFound"
     }
    },
    "x-theme5-rules": [
     "R-DERIVED-READ-ONLY",
     "R-COUNT-ONCE",
     "R-ESTIMATE-PROVISIONAL",
     "R-UNKNOWN-IS-NULL",
     "R-COACH-ASSIGNED",
     "R-OVERLAP-UNAVAILABLE"
    ]
   }
  }
 },
 "components": {
  "securitySchemes": {
   "coachAuth": {
    "$ref": "common.openapi.json#/components/securitySchemes/coachAuth"
   }
  }
 }
}
