{
  "openapi": "3.1.1",
  "info": {
    "title": "Hollowride API (Ravito platform)",
    "description": "Hollowride client API, version 1. Authentication: a browser-style cookie login (POST /api/v1/account/login; every POST/PUT/DELETE then needs the header X-CSRF-TOKEN from GET /api/v1/antiforgery) or a personal app token (Authorization: Bearer hrt_...; made by a logged-in user with POST /api/v1/app-tokens; no antiforgery header needed). A token has the rights of its owner, except managing tokens, changing the password, deleting the account and linking Strava (403). Apps for other users use OAuth 2.1 (code + PKCE, scheme oauth2): an access token opens only what its scopes open. The live socket is wss://<host>/api/v1/ws (WebSocket, not described here; see docs/api.md). SAFETY: the server never drives a trainer for an external client and does not enforce the trainer safety rules. A client that controls a real trainer MUST itself: never set a high target without a valid heart rate; drop to minimum power on heart-rate loss or max heart rate; drop to minimum power on pause or stop; never let a simulated heart rate drive a real trainer. World bundles carry an attribution list (OSM/ODbL, PNOA, Catastro, ...) that a client MUST show to its users.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://ravito.online"
    }
  ],
  "paths": {
    "/api/v1/antiforgery": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/app-tokens": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AnonymousTypeOfGuidAndstringAndstringAndDateTimeAndDateTime"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Ravito.Server"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/app-tokens/{id}": {
      "delete": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/strava/status": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnonymousTypeOfbooleanAndbooleanAndbooleanAndstring"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/strava/climbs": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/rides/live": {
      "post": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/segments/{routeId}": {
      "put": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "routeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SegmentUpsert"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/segments/{routeId}/ghosts": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "routeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/last-power": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "targetHr",
            "in": "query",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/shared/ghosts": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "routeId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/profile": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": { }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "confirm",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RiderSettings"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/profile/target-hr": {
      "put": {
        "tags": [
          "Ravito.Server"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TargetHrUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/profile/apply-test/{rideId}": {
      "post": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "rideId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/shared/settings": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Ravito.Server"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharedSettings"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/shared-routes": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/shared-routes/{id}/live": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sandbox",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/world-map": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/routes/{id}": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "profile",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/account/login": {
      "post": {
        "tags": [
          "Ravito.Server"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/account/logout": {
      "post": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/account/me": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/rides": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "routeId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          {
            "name": "take",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "RideUpload"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/routes": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/rides/{id}": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/rides/{id}/samples": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "maxPoints",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/rides/{id}/fit": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/rides/{id}/strava": {
      "post": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/segments": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/segments/{id}": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/segments/{id}/goal": {
      "put": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GoalRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/progress": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "weeks",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/rides/{id}/steady": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/steady": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "parameters": [
          {
            "name": "weeks",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/v1/history/settings": {
      "get": {
        "tags": [
          "Ravito.Server"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnonymousTypeOfboolean"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Ravito.Server"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HistorySettings"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/connect/register": {
      "post": {
        "summary": "Register a public PKCE app (RFC 7591), no login needed.",
        "description": "Body: client_name (1-60 chars), redirect_uris (1-5: https; http://127.0.0.1/path or http://[::1]/path with any port at sign-in; a reverse-DNS private-use scheme such as com.example.app:/callback; http://localhost:port/path for testing), scope (profile:read presence rides:write-own offline_access). A new app is unverified. Errors are {code, message, hint}: invalid_client_metadata, invalid_redirect_uri, scope_not_allowed, registration_rate_limited (429, Retry-After), too_many_clients.",
        "responses": {
          "201": {
            "description": "client_id, client_id_issued_at, client_name, redirect_uris, scope, grant_types, response_types, token_endpoint_auth_method, verified=false."
          },
          "400": {
            "description": "{code, message, hint}: invalid_client_metadata, invalid_redirect_uri or scope_not_allowed."
          },
          "429": {
            "description": "{code, message, hint}: registration_rate_limited or too_many_clients."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AnonymousTypeOfboolean": {
        "required": [
          "autoUploadStrava"
        ],
        "type": "object",
        "properties": {
          "autoUploadStrava": {
            "type": "boolean"
          }
        }
      },
      "AnonymousTypeOfbooleanAndbooleanAndbooleanAndstring": {
        "required": [
          "configured",
          "connected",
          "allowed",
          "athlete"
        ],
        "type": "object",
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "connected": {
            "type": "boolean"
          },
          "allowed": {
            "type": "boolean"
          },
          "athlete": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AnonymousTypeOfbooleanAndstringAndbooleanAndbooleanAndstringAndbooleanAndbooleanAndboolean": {
        "required": [
          "passwordReset",
          "mode",
          "requiresLogin",
          "registrationOpen",
          "termsVersion",
          "passwords",
          "unprotected",
          "publicWarning"
        ],
        "type": "object",
        "properties": {
          "passwordReset": {
            "type": "boolean"
          },
          "mode": {
            "type": [
              "null",
              "string"
            ]
          },
          "requiresLogin": {
            "type": "boolean"
          },
          "registrationOpen": {
            "type": "boolean"
          },
          "termsVersion": {
            "type": [
              "null",
              "string"
            ]
          },
          "passwords": {
            "type": "boolean"
          },
          "unprotected": {
            "type": "boolean"
          },
          "publicWarning": {
            "type": "boolean"
          }
        }
      },
      "AnonymousTypeOfDateTimeAndboolean": {
        "required": [
          "createdAt",
          "current"
        ],
        "type": "object",
        "properties": {
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "current": {
            "type": "boolean"
          }
        }
      },
      "AnonymousTypeOfGuidAndstringAndstringAndDateTimeAndDateTime": {
        "required": [
          "id",
          "name",
          "clientId",
          "createdAt",
          "lastUsedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "clientId": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "ChangePasswordRequest": {
        "required": [
          "current",
          "next"
        ],
        "type": "object",
        "properties": {
          "current": {
            "type": [
              "null",
              "string"
            ]
          },
          "next": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ClaimRequest": {
        "required": [
          "name",
          "code",
          "password"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "code": {
            "type": [
              "null",
              "string"
            ]
          },
          "password": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ConfirmRequest": {
        "required": [
          "userId",
          "token",
          "password"
        ],
        "type": "object",
        "properties": {
          "userId": {
            "type": [
              "null",
              "string"
            ]
          },
          "token": {
            "type": [
              "null",
              "string"
            ]
          },
          "password": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateRequest": {
        "required": [
          "name",
          "clientId"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "clientId": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DeleteRequest": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "password": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "EmailRequest": {
        "required": [
          "email",
          "password"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "password": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "GoalRequest": {
        "required": [
          "targetSeconds",
          "targetDate"
        ],
        "type": "object",
        "properties": {
          "targetSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "targetDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "HistorySettings": {
        "required": [
          "autoUploadStrava"
        ],
        "type": "object",
        "properties": {
          "autoUploadStrava": {
            "type": "boolean"
          }
        }
      },
      "HrZoneMethod": {
        "enum": [
          "Karvonen",
          "PercentMax"
        ]
      },
      "LangRequest": {
        "required": [
          "lang"
        ],
        "type": "object",
        "properties": {
          "lang": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "LoginRequest": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "create": {
            "type": "boolean",
            "default": false
          },
          "acceptTerms": {
            "type": "boolean",
            "default": false
          },
          "healthConsent": {
            "type": "boolean",
            "default": false
          },
          "password": {
            "type": [
              "null",
              "string"
            ]
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "PasswordRequest": {
        "required": [
          "password"
        ],
        "type": "object",
        "properties": {
          "password": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ResetRequest": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RiderSettings": {
        "type": "object",
        "properties": {
          "riderKg": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "bikeKg": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "ftp": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "restingHr": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "thresholdHr": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "maxHr": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "targetHr": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "minPower": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "maxPower": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "cdA": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "crr": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "trainerDifficulty": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          },
          "birthYear": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "maxHrSource": {
            "$ref": "#/components/schemas/ValueSource"
          },
          "restingHrKnown": {
            "type": "boolean"
          },
          "ftpSource": {
            "$ref": "#/components/schemas/ValueSource"
          },
          "hrZoneMethod": {
            "$ref": "#/components/schemas/HrZoneMethod"
          },
          "maxPowerManual": {
            "type": "boolean"
          },
          "profileConfirmedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "totalKg": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "number",
              "string"
            ],
            "format": "double"
          }
        }
      },
      "SegmentUpsert": {
        "required": [
          "name",
          "length",
          "stravaId",
          "prSeconds",
          "komSeconds"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "length": {
            "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$",
            "type": [
              "null",
              "number",
              "string"
            ],
            "format": "double"
          },
          "stravaId": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int64"
          },
          "prSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          },
          "komSeconds": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "null",
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "SharedSettings": {
        "required": [
          "visible",
          "nickname",
          "ghostConsent",
          "hideWkg"
        ],
        "type": "object",
        "properties": {
          "visible": {
            "type": "boolean"
          },
          "nickname": {
            "type": [
              "null",
              "string"
            ]
          },
          "ghostConsent": {
            "type": "boolean"
          },
          "hideWkg": {
            "type": "boolean"
          },
          "shareAlways": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "TargetHrUpdate": {
        "required": [
          "targetHr"
        ],
        "type": "object",
        "properties": {
          "targetHr": {
            "pattern": "^-?(?:0|[1-9]\\d*)$",
            "type": [
              "integer",
              "string"
            ],
            "format": "int32"
          }
        }
      },
      "TermsRequest": {
        "required": [
          "acceptTerms",
          "healthConsent"
        ],
        "type": "object",
        "properties": {
          "acceptTerms": {
            "type": "boolean"
          },
          "healthConsent": {
            "type": "boolean"
          }
        }
      },
      "ValueSource": {
        "enum": [
          "Unknown",
          "Entered",
          "Estimated",
          "Test"
        ]
      }
    },
    "securitySchemes": {
      "cookie": {
        "type": "apiKey",
        "description": "Cookie login; send X-CSRF-TOKEN on POST/PUT/DELETE.",
        "name": "holloway",
        "in": "cookie"
      },
      "appToken": {
        "type": "http",
        "description": "Personal app token (hrt_...): a developer key for your own scripts, all scopes.",
        "scheme": "bearer"
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization code with PKCE (S256) for public clients: configured by the admin, or registered by the developer through POST /connect/register (RFC 7591, no secret); see docs/api.md and docs/ravito-protocol.md. Scopes per route: Ravito/Identity/ApiScopes.cs.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "/connect/authorize",
            "tokenUrl": "/connect/token",
            "refreshUrl": "/connect/token",
            "scopes": {
              "profile:read": "Read the profile (name, language, weight, settings).",
              "rides:read": "Read rides, segments, progress and FIT files.",
              "routes:write": "Add routes (GPX upload, Strava segment).",
              "world:read": "Read routes and world bundles.",
              "presence": "Report your place on a shared route on the socket (distance in metres along the route, speed in m/s up to 33, or lat/lon in degrees WGS84 within 30 m of it; or the place of a Hollowride world ride) and see the riders on it (no ride commands).",
              "rides:write-own": "Upload a ride from another app (POST /api/v1/history/rides); it never counts for a segment, PR or KOM.",
              "ride": "Ride: the full socket (start, pause, stop, devices, target power) under the server's safety rules. Also, for trusted apps only: live ride upload (POST /api/v1/history/rides/live), segment upsert, shared ghosts, Strava climbs.",
              "offline_access": "A refresh token (stay logged in)."
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "cookie": [ ]
    },
    {
      "appToken": [ ]
    },
    {
      "oauth2": [ ]
    }
  ],
  "tags": [
    {
      "name": "Ravito.Server"
    },
    {
      "name": "RideUpload"
    }
  ]
}