{"openapi":"3.1.0","info":{"title":"HubFit API","version":"1.0.0","summary":"Read your clients and their check-in submissions, and review them, from your own tools.","description":"The HubFit API gives coaches programmatic access to the same clients and check-in submissions they can reach in the app.\n\n**Quickstart.** Create a key under Settings > API, then:\n\n```\ncurl https://api.hubfit.com/v1/check-in-submissions \\\n  -H \"Authorization: Bearer $HUBFIT_API_KEY\"\n\ncurl -X POST https://api.hubfit.com/v1/check-in-submissions/<submissionId>/review \\\n  -H \"Authorization: Bearer $HUBFIT_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\"comment\": \"Great week. Keep the step count up.\"}'\n```\n\nThe API is available on the Ultimate plan.\n\n**Authentication.** Create an API key in HubFit under Settings > API and send it in the `Authorization` header:\n`Authorization: Bearer hubfit_sk_...`. Only the workspace's owner can create keys, and a key acts as them in that\nworkspace. Each key carries scopes chosen at creation.\n\n**Deleted clients** are never returned. **Archived clients** appear in `GET /clients` when you ask for them with\n`status`, and nowhere else: other lists leave them and everything of theirs out, and asking for an archived client, or\nfor something of theirs, answers `409 client_archived`: unarchive them in HubFit to view them.\nAsking for a deleted one answers `404`, as for one that doesn't exist.\n\n**Errors** are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details (`application/problem+json`).\nBranch on `code`, which is stable; `detail` is for humans. Every response carries an `X-Request-Id` header, and\nerror bodies repeat it as `requestId`. Quote it when contacting support.\n\n**Pagination.** List endpoints return `nextCursor`. Pass it back as `cursor` to get the next page; `null` means\nthere are no more results. Cursors are opaque. They also return `totalCount`, how many results match across every\npage, counted up to 10,000.\n\n**Rate limits.** 120 requests per minute per coach, shared by all of that coach's API keys and connected apps,\nacross the REST API and the MCP server. Each team member has their own budget. If your workspace needs more, contact\nsupport. Every request that's counted gets `RateLimit-Policy` and `RateLimit` headers (IETF draft 8). Requests\nturned away before they're counted don't: a 401, a 413, a body that isn't valid JSON, and a 403 `plan_required`,\n`not_available`, `account_suspended` or `account_restricted`. A 429 adds `Retry-After` in seconds.\n\nSending messages has a limit of its own on top: 30 messages a minute and 500 a day per coach, across the API and\nthe MCP server and all of that coach's keys and connections. A send past it gets a `429 rate_limited` whose `detail`\nnames the limit and when it resets, with `Retry-After` and the `public-api-messages-minute` or\n`public-api-messages-day` policy in its `RateLimit` headers.\n\n**Idempotency.** Send an `Idempotency-Key` header (any unique string, up to 255 characters) on POST requests. Retrying with\nthe same key within 24 hours returns the original response with `Idempotent-Replayed: true` instead of repeating\nthe write. A `5xx` and the message limit's `429` aren't kept, so a retry with the same key runs again.\n\n**MCP.** AI assistants such as Claude and ChatGPT connect to the HubFit MCP server at\n`https://mcp.hubfit.com/mcp` (Streamable HTTP), with the same permissions and rate limit. Add HubFit as a custom\nconnector with that URL; the coach signs in to HubFit and chooses what it can do. It only offers the tools the\nconnection's permissions allow. Sign-in tokens only work on the MCP server, not this API. The MCP server always\nfollows the newest version of this API, and its tools' output only changes in the ways Versioning allows. It's\navailable on every paid plan.\n\n**Versioning.** Breaking changes ship as a new major version path (`/v2`) alongside this one. These are not\nbreaking and can ship in this version: new endpoints, new optional parameters, new response fields, new enum\nvalues in responses, new error `code`s, new `include` sections, and cursors of a new length or format. So ignore\nfields you don't recognise, treat an enum value or `code` you don't recognise as \"other\", and don't parse cursors.\nAn `ObjectId` stays a 24-character hex string. A response value listed with `x-extensible-enum` instead of `enum`\nnames the values known today, and new ones can appear: generate it as a plain string.\n\nWebhook events can gain fields, and new event types can appear, so ignore fields you don't recognise. An endpoint\nonly gets the types it subscribes to, but answer a 2xx to one you don't handle, or it's retried. An event type's\nname and its existing fields never change: a breaking change ships as a new event type.\n\nA deprecated field, parameter or version keeps working for at least 6 months after we announce it. From then on its\nresponses carry `Deprecation` and `Sunset` headers ([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745),\n[RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)), and the changelog lists it.\n\n**Changelog.** `info.version` gets a minor bump for each release that adds to this version.\n\n- 1.0.0: First release.\n"},"servers":[{"url":"https://api.hubfit.com/v1"}],"security":[{"apiKey":[]}],"tags":[{"name":"Clients"},{"name":"Tags","description":"Labels coaches put on clients to group them. Each client lists its tags; filter clients by one with `tagId`."},{"name":"Onboarding flows","description":"An onboarding flow is what a coach set up to give a new client in one go, such as their programs, nutrition plans, check-ins, habits, metrics, communities and team members. Pass one's `id` as `onboardingFlowId` when creating a client to run it for them, as adding a client with a flow does in the app."},{"name":"Check-in submissions","description":"A check-in submission is one completed check-in form, with the client's answers. To compare clients' check-ins over a period, use `GET /client-summaries` with `include=checkIns`."},{"name":"Questionnaire submissions","description":"A questionnaire submission is one questionnaire a client filled in, such as an intake form, with their answers. Check-ins, which repeat on a schedule, are separate: see Check-in submissions."},{"name":"Package purchases","description":"A package purchase is one checkout a client completed for one of your packages, paid or free, with the package's name as it was then and what the client paid. A purchase isn't revenue: renewals and refunds aren't purchases. Purchases made before this API launched aren't recorded."},{"name":"Completed workouts","description":"A workout a client finished and logged, with each exercise's sets and any personal records it set. To compare clients' workouts over a period, use `GET /client-summaries` with `include=workouts`."},{"name":"Exercises","description":"The exercises a client has logged, with their best results and each time they did one."},{"name":"Nutrition","description":"A nutrition log is one day of food a client logged in HubFit, with the meals, foods, photos and note they recorded and the day's target. A day appears only once the client logs something. A client's nutrition target is the daily goal set for each weekday, by the coach or by the client in the app. Clients who track in Cronometer instead have no logs here. To compare clients' nutrition over a period, use `GET /client-summaries` with `include=nutrition`."},{"name":"Habits","description":"A habit is something a coach asked a client to do each day or week, like 10,000 steps a day or meditating 3 times a week, with a goal the client logs against. A habit's progress is one row per day or week it ran, with what the client logged and whether it met the goal in force then. To compare clients' habits over a period, use `GET /client-summaries` with `include=habits`."},{"name":"Metrics","description":"A metric is something a client tracks over time, like weight, waist or body fat, in the unit the coach set. Clients log entries in the app or through check-in answers, one value for a day. To compare clients' metrics over a period, use `GET /client-summaries` with `include=metrics`."},{"name":"Notes","description":"Notes your team keeps on a client. A private note is seen only by your team; a shared one also shows in the client's app."},{"name":"Messages","description":"Chat messages to a client, in the same chats as Messages in the app. Each coach on your team has their own chat with each client they message."},{"name":"Tasks","description":"Your team's to-dos, the same tasks as on the Tasks page in the app. A task can be about a client, and only your team sees it; the client is never told. A workspace has at most 250 open tasks."},{"name":"On-demand","description":"What a coach puts up for clients to use any time, in three kinds that work alike, each up to 100 per workspace. Workout studios hold workouts, recipe books hold recipes, and resource collections hold resources. A client sees one in their app once you grant them access."},{"name":"Health","description":"The daily health data a client's phone sends from Apple Health or Health Connect. The client's phone sends its data only while they have the HubFit app open, so the data is as recent as `lastSyncedAt`, and today's numbers grow until it next syncs. To compare clients' health data over a period, use `GET /client-summaries` with `include=health`."},{"name":"Webhooks","description":"A webhook endpoint is a URL we send events to as they happen: a client added, a check-in or questionnaire\nsubmitted, a workout completed, a package purchased. An event carries ids, not the record: fetch it with your own key, which checks it's still yours to read\nand returns it as it is now. Every request is signed as [Standard Webhooks](https://www.standardwebhooks.com)\ndescribes, so its libraries verify it. A request that doesn't get a 2xx within 10 seconds is retried 3 times, after\n1 minute, 1 hour and 6 hours; redirects aren't followed. Events can arrive more than once and out of order: use `webhook-id`\nto skip one you already have. Requests don't come from fixed IP addresses, so check the signature to know a\nrequest is from HubFit. Only a key that belongs to the workspace's\nowner or an admin manages endpoints. It sees every endpoint in the workspace, but changes only the ones it made;\nfor the others it sees just the origin of the URL.\n\nThe response that creates an endpoint carries its signing secret, and the API never shows it again. In\nSettings > API, the workspace's owner can copy the secret or rotate it, and rotating stops the old one at once. An integration that needs a new secret deletes its endpoint and creates it again.\n\nReply with a 2xx before you fetch anything: the 10 seconds include your work. Fetches count towards your rate\nlimit, and one can answer `404` or `409` if the record or its client has since been deleted or archived, or your\nkey can't reach them: skip that event. An event doesn't name its workspace, so a receiver for several workspaces\nneeds a URL for each (a path or query parameter will do) to know which secret checks it. Requests are `POST`s with\n`Content-Type: application/json` and `User-Agent: HubFit-Webhooks/1`. They go over IPv4 only, so your host needs\nan A record, and TLS 1.2 or later, with a certificate from a publicly trusted authority.\n\nAn event that still fails after the last retry isn't sent again. Settings > API keeps 30 days of deliveries. A 410 reply, or 5 days without a success, turns the\nendpoint off. Nothing is recorded while it's off, so turning it back on doesn't send what you missed. To catch up,\nread the lists: `GET /clients` and `GET /package-purchases` from the newest until one you've seen,\n`GET /check-in-submissions` with `status=all` and `submittedAfter`, and `GET /completed-workouts` as its\ndescription says.\n"},{"name":"Client summaries","description":"One row per client you can reach, adding up what they did over a period (their workouts, nutrition, habits, metrics, health data and check-ins), to compare clients (who trained fewer than 3 times this week, who is under their protein target, who lost weight this month) without fetching every record."}],"paths":{"/clients":{"get":{"operationId":"listClients","tags":["Clients"],"summary":"List clients","description":"Clients you can reach, newest first. Team members with the Standard role only see their assigned clients. Deleted clients are never listed, and archived ones only when you ask for them with `status`. Requires `clients:read`.","parameters":[{"name":"search","in":"query","description":"Case-insensitive match on part of a name or email.","allowReserved":true,"schema":{"type":"string","minLength":1,"maxLength":100}},{"name":"email","in":"query","description":"Only the client with exactly this email, ignoring case. Use it to find one specific client; `search` also matches partial emails.","allowReserved":true,"schema":{"type":"string","minLength":1,"maxLength":320}},{"name":"tagId","in":"query","description":"Only clients with this tag. An id from `GET /tags`.","schema":{"$ref":"#/components/schemas/ObjectId"}},{"name":"status","in":"query","description":"`active` (the default) for current clients, `archived` for archived ones, `all` for both. An archived client is only ever listed: anything else about them answers `409 client_archived` until you unarchive them in HubFit.","schema":{"type":"string","enum":["active","archived","all"],"default":"active"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of clients.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Client"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]},"post":{"operationId":"createClient","tags":["Clients"],"summary":"Create a client","description":"Adds a client to your workspace, exactly as adding one in the app does: it counts toward your plan's client\nlimit, and a team member who adds a client is assigned to them. With `sendLoginInstructions`\n(the default), the client is emailed their login instructions straight away.\n\nA client's email is unique within your workspace, so adding an email that's already a client returns\n`409 client_exists` (with the existing `clientId` when you can reach that client) instead of a second client.\nThat makes retries safe even without an `Idempotency-Key`. If the existing client is archived and you can reach\nthem, it's `409 client_archived` instead: unarchive them in HubFit.\n\nWith `onboardingFlowId`, the flow runs for the new client once they're added, as adding a client with a flow does\nin the app: they get its programs, nutrition plans, check-ins, habits, metrics and the rest, and its team members\nare assigned them. A flow that isn't your workspace's is a 404, and no client is added. A client that already\nexists (`409`) gets nothing. Requires `clients:create`.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["name","email"],"properties":{"name":{"type":"string","minLength":1,"maxLength":50,"pattern":"^[^@]*$","description":"The client's full name. Can't contain `@`."},"email":{"type":"string","format":"email","maxLength":254},"sendLoginInstructions":{"type":"boolean","default":true,"description":"Email the client their login instructions now."},"onboardingFlowId":{"$ref":"#/components/schemas/ObjectId","description":"An onboarding flow to run for the new client, by an `id` from `GET /onboarding-flows`."}}}}}},"responses":{"201":{"description":"The new client.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"`client_limit_reached` (your plan has no room for another client), `insufficient_scope`, `plan_required`, `not_available`, `account_suspended` or `account_restricted`.","headers":{"WWW-Authenticate":{"$ref":"#/components/headers/WWWAuthenticate"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`client_exists` (`clientId` names the existing client when you can reach it), `client_archived` when that client is archived, or `idempotency_key_in_use` while the first request with that key is still running.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:create"]}]}},"/clients/{clientId}":{"get":{"operationId":"getClient","tags":["Clients"],"summary":"Get a client","description":"One client, the same object the list returns. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"}],"responses":{"200":{"description":"The client.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/clients/{clientId}/exercises":{"get":{"operationId":"listClientExercises","tags":["Exercises"],"summary":"List a client's exercises","description":"Every exercise the client has logged, most recently logged first, with their best results. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"},{"name":"search","in":"query","description":"Case-insensitive match on part of the exercise's name, e.g. `bench`.","schema":{"type":"string","minLength":1,"maxLength":100}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of exercises.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClientExercise"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/clients/{clientId}/exercises/{exerciseId}/history":{"get":{"operationId":"listClientExerciseHistory","tags":["Exercises"],"summary":"List a client's history for an exercise","description":"Each time the client logged the exercise, most recent first, with the sets they did. `totalCount` is how many times they've logged it (within the dates you pass). 404 if the client has never logged it. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/ExerciseId"},{"$ref":"#/components/parameters/CoachStartDate"},{"$ref":"#/components/parameters/CoachEndDate"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of the exercise's history.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExerciseHistoryEntry"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/clients/{clientId}/nutrition-target":{"get":{"operationId":"getClientNutritionTarget","tags":["Nutrition"],"summary":"Get a client's nutrition target","description":"The client's current daily target for calories and macros, for each day of the week, set by the coach or by the client in the app. A weekday marked as a rest day has the rest-day target. `data` is `null` if no target is set. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"}],"responses":{"200":{"description":"The client's target for each weekday.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WeeklyNutritionTarget"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/clients/{clientId}/habits":{"get":{"operationId":"listClientHabits","tags":["Habits"],"summary":"List a client's habits","description":"The habits the coach gave the client, newest first, each with its current goal and whether it's upcoming, active or ended. A client has no more than 25 (`MAX_HABITS_PER_CLIENT`), so they all come in one response, without paging; a client given more before that limit still gets every one. Empty if the coach turned habits off for the client. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"}],"responses":{"200":{"description":"Every habit the client has.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Habit"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/habits/{habitId}/progress":{"get":{"operationId":"listHabitProgress","tags":["Habits"],"summary":"List a habit's progress","description":"One row for each day (or Monday-to-Sunday week, or month, as the habit's goal is set) the habit was running, up to today, most recent first. Each has what the client logged in it, the goal in force then, and whether it was completed. Days or weeks the client logged nothing in are listed too. 404 if the coach turned habits off for the client. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/HabitId"},{"name":"startDate","in":"query","description":"Only periods from the one this day falls in, as `YYYY-MM-DD`. Days are the client's own calendar days.","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","description":"Only periods up to the one this day falls in, as `YYYY-MM-DD`.","schema":{"type":"string","format":"date"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of the habit's periods.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/HabitPeriodProgress"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/clients/{clientId}/metrics":{"get":{"operationId":"listClientMetrics","tags":["Metrics"],"summary":"List a client's metrics","description":"The metrics the client tracks, in the order the coach added them, each with how many entries it has and the last one. A client has no more than 25 (`MAX_METRICS_PER_CLIENT`), so they all come in one response, without paging; a client given more before that limit still gets every one. Empty if the coach turned metrics off for the client. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"}],"responses":{"200":{"description":"Every metric the client tracks.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Metric"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/metrics/{metricId}/entries":{"get":{"operationId":"listMetricEntries","tags":["Metrics"],"summary":"List a metric's entries","description":"What the client logged for one metric, latest day first, and of one day's entries the last added first. 404 if the coach turned metrics off for the client. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/MetricId"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of the metric's entries.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MetricEntry"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/clients/{clientId}/notes":{"get":{"operationId":"listClientNotes","tags":["Notes"],"summary":"List a client's notes","description":"The notes your team keeps on the client, newest first, private and shared alike. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"},{"name":"limit","in":"query","description":"How many notes a page holds. 10 unless you ask for more, and at most 50, fewer than other lists, as a note can be 5,000 characters.","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of the client's notes.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClientNote"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]},"post":{"operationId":"createClientNote","tags":["Notes"],"summary":"Add a note to a client","description":"Adds a note to the client, as adding one in the app does. A `private` note is seen only by your team. A `shared`\none shows in the client's app straight away and sends them a push notification with its text.\nEach call adds another note, so send an `Idempotency-Key` to retry safely. A client holds at most 100 notes;\npast that it's `409 note_limit_reached`. Requires `clients:write`.\n","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["text","visibility"],"properties":{"title":{"type":"string","maxLength":100,"description":"A heading for the note."},"text":{"type":"string","maxLength":5000,"pattern":"\\S","description":"The note. Can't be blank."},"visibility":{"$ref":"#/components/schemas/NoteVisibility"}}}}}},"responses":{"201":{"description":"The new note.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClientNote"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:write"]}]}},"/clients/{clientId}/messages":{"post":{"operationId":"sendClientMessage","tags":["Messages"],"summary":"Send a message to a client","description":"Sends a chat message to the client as you, the coach the key belongs to, from your own chat with them, as sending\none in the app does. The client sees it in their app straight away and is notified as for any chat message. If\nyou haven't messaged the client before, this starts your chat with them. Each call sends another message, so\nsend an `Idempotency-Key` to retry safely. A client whose messaging is turned off in HubFit answers\n`409 messaging_disabled`. Each coach can send 30 messages a minute and 500 a day, across the API and the MCP\nserver; past either it's a `429` naming that limit, which sends nothing and leaves the `Idempotency-Key` free\nfor the retry. Requires `messages:write`.\n","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","maxLength":5000,"pattern":"\\S","description":"The message. Can't be blank."}}}}}},"responses":{"201":{"description":"The message, as sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClientMessage"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["messages:write"]}]}},"/clients/{clientId}/health-data":{"get":{"operationId":"getClientHealthData","tags":["Health"],"summary":"Get a client's health data","description":"The client's daily health data from Apple Health or Health Connect, newest day first. Days with no data are left out. The client's phone sends its data only while they have the HubFit app open, so the data is as recent as `lastSyncedAt`, and today's numbers grow until it next syncs. A client who switched phones has data from both; this is the one that synced last. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/ClientId"},{"name":"startDate","in":"query","required":true,"description":"The first day, included, as `YYYY-MM-DD`.","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","required":true,"description":"The last day, included, as `YYYY-MM-DD`. The period is at most 90 days, counting both ends.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The client's health data.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/HealthData"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/client-summaries":{"get":{"operationId":"listClientSummaries","tags":["Client summaries"],"summary":"List client summaries","description":"For each client you can reach (team members with the Standard role only see their assigned clients), or one with `clientId`, what they did over a period, in the sections you name in `include`. Clients come newest first, as in `GET /clients`, including those who logged nothing. Requires `clients:read`.","parameters":[{"name":"include","in":"query","required":true,"description":"The sections each row has, comma-separated, such as `workouts,nutrition`. Ask only for the ones you need, since each one adds to every row.","style":"form","explode":false,"schema":{"type":"array","minItems":1,"maxItems":6,"items":{"type":"string","enum":["workouts","nutrition","habits","metrics","health","checkIns"]}}},{"name":"clientId","in":"query","description":"Only this client's summary. A client you can't reach is a 404, and an archived one a 409.","schema":{"$ref":"#/components/schemas/ObjectId"}},{"name":"clientIds","in":"query","description":"Only these clients, up to 25, comma-separated. A filter like the rest, so a client you can't reach or who is archived is left out. Compare `totalCount` with how many you asked for. Not with `clientId`.","style":"form","explode":false,"schema":{"type":"array","minItems":1,"maxItems":25,"items":{"$ref":"#/components/schemas/ObjectId"}}},{"name":"startDate","in":"query","required":true,"description":"The first day of the period, included, as `YYYY-MM-DD`. Workouts count on their day in your own calendar (the timezone in your HubFit settings), as the app shows them to you; the other sections count the client's own days.","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","required":true,"description":"The last day of the period, included, as `YYYY-MM-DD`. The period is at most 31 days, counting both ends, or 366 when `include` is only `metrics`.","schema":{"type":"string","format":"date"}},{"name":"metricName","in":"query","description":"Only the metric with this name, ignoring case, as the app matches names. `weight` finds `Weight` but not `Body weight`. Needs `metrics` in `include`.","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"limit","in":"query","description":"How many clients a page holds. At most 25, fewer than other lists, as a row with every section is about 600 tokens.","schema":{"type":"integer","minimum":1,"maximum":25,"default":25}},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of summaries, one per client.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClientSummary"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/tags":{"get":{"operationId":"listTags","tags":["Tags"],"summary":"List tags","description":"Every tag in the workspace, alphabetically. A workspace adds no more than 15 (`MAX_TAGS_PER_WORKSPACE`), so they all come in one response, without paging. Requires `clients:read`.","responses":{"200":{"description":"Every tag in the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/onboarding-flows":{"get":{"operationId":"listOnboardingFlows","tags":["Onboarding flows"],"summary":"List onboarding flows","description":"Every onboarding flow in the workspace, newest first, to pass one as `onboardingFlowId` when creating a client. A workspace adds no more than 50 (`MAX_ONBOARDING_FLOWS_PER_WORKSPACE`), so they all come in one response, without paging. Requires `clients:create`, which creating a client with one needs.","responses":{"200":{"description":"Every onboarding flow in the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OnboardingFlow"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:create"]}]}},"/completed-workouts":{"get":{"operationId":"listCompletedWorkouts","tags":["Completed workouts"],"summary":"List completed workouts","description":"Workouts your clients logged, most recently completed first, without their exercises. Across every client you can reach (team members with the Standard role only see their assigned clients), or one client's with `clientId`. To pick up new workouts, pass the day before the newest one you've seen as `startDate` and skip ones you've seen. Workouts are ordered by when the client's app says they finished, so one can arrive after a workout that finished later. Requires `clients:read`.","parameters":[{"name":"clientId","in":"query","description":"Only this client's workouts.","schema":{"$ref":"#/components/schemas/ObjectId"}},{"$ref":"#/components/parameters/CoachStartDate"},{"$ref":"#/components/parameters/CoachEndDate"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of completed workouts.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CompletedWorkoutSummary"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/completed-workouts/{workoutId}":{"get":{"operationId":"getCompletedWorkout","tags":["Completed workouts"],"summary":"Get a completed workout","description":"One completed workout with every exercise in order, its sets as the client logged them and any personal records, plus the sections the coach grouped exercises into. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/WorkoutId"}],"responses":{"200":{"description":"The completed workout.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CompletedWorkout"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/nutrition-logs":{"get":{"operationId":"listNutritionLogs","tags":["Nutrition"],"summary":"List nutrition logs","description":"The days your clients logged food, newest first, with each day's totals and target but not its meals. Across every client you can reach (team members with the Standard role only see their assigned clients), or one client's with `clientId`. Requires `clients:read`.","parameters":[{"name":"clientId","in":"query","description":"Only this client's logs.","schema":{"$ref":"#/components/schemas/ObjectId"}},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of nutrition logs.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NutritionLogSummary"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/nutrition-logs/{logId}":{"get":{"operationId":"getNutritionLog","tags":["Nutrition"],"summary":"Get a nutrition log","description":"One day a client logged, with every meal (in the order of the client's Daily Meals) and the foods in it. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/LogId"}],"responses":{"200":{"description":"The nutrition log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/NutritionLog"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/check-in-submissions":{"get":{"operationId":"listCheckInSubmissions","tags":["Check-in submissions"],"summary":"List check-in submissions","description":"Check-in submissions, most recently submitted first. By default only those awaiting your review. Requires `clients:read`.","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["awaiting_review","reviewed","all"],"default":"awaiting_review"}},{"name":"clientId","in":"query","description":"Only this client's submissions.","schema":{"$ref":"#/components/schemas/ObjectId"}},{"name":"submittedAfter","in":"query","allowReserved":true,"description":"Only submissions received after this time, e.g. `2026-10-05T00:00:00Z` (encode a `+` offset as `%2B`). To fetch what's new since your last run, pass the time of that run.","schema":{"type":"string","format":"date-time"}},{"name":"submittedBefore","in":"query","allowReserved":true,"description":"Only submissions received before this time.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of submissions, without their answers.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CheckInSubmissionSummary"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/check-in-submissions/{submissionId}":{"get":{"operationId":"getCheckInSubmission","tags":["Check-in submissions"],"summary":"Get a check-in submission","description":"A submission with its questions and answers. Answers are written by the client. Uploads are counted (`fileCount`) and a signature says whether the client signed (`signed`). None are linked. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/SubmissionId"}],"responses":{"200":{"description":"The submission.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CheckInSubmission"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/check-in-submissions/{submissionId}/review":{"post":{"operationId":"reviewCheckInSubmission","tags":["Check-in submissions"],"summary":"Review a check-in submission","description":"Marks a submission as reviewed. If you include a `comment`, the client receives it by email and push notification,\nexactly as when you review in the app. A submission can be reviewed once; reviewing it again returns\n`409 already_reviewed`. An archived client's submissions can't be reviewed (`409 client_archived`). Requires `check-ins:review`.\n","parameters":[{"$ref":"#/components/parameters/SubmissionId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"comment":{"type":"string","maxLength":5000,"pattern":"\\S","description":"Your review comment, sent to the client. Can't be blank."}}}}}},"responses":{"200":{"description":"The submission is now reviewed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ReviewResult"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["check-ins:review"]}]}},"/questionnaire-submissions/{submissionId}":{"get":{"operationId":"getQuestionnaireSubmission","tags":["Questionnaire submissions"],"summary":"Get a questionnaire submission","description":"One questionnaire a client filled in, with every answer, such as the one a `questionnaire_submission.created` webhook names. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/SubmissionId"}],"responses":{"200":{"description":"The questionnaire submission.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/QuestionnaireSubmission"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/package-purchases":{"get":{"operationId":"listPackagePurchases","tags":["Package purchases"],"summary":"List package purchases","description":"Packages your clients bought, most recent first. Across every client you can reach (team members with the Standard role only see their assigned clients), or one client's with `clientId`. To pick up new purchases, read from the newest and stop at one you've seen before. Requires `clients:read`.","parameters":[{"name":"clientId","in":"query","description":"Only this client's purchases.","schema":{"$ref":"#/components/schemas/ObjectId"}},{"name":"startDate","in":"query","description":"Only purchases from this day, included, as `YYYY-MM-DD`, in your own calendar (the timezone in your HubFit settings).","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","description":"Only purchases up to this day, included, as `YYYY-MM-DD`, in your own calendar.","schema":{"type":"string","format":"date"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of package purchases.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PackagePurchase"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/package-purchases/{purchaseId}":{"get":{"operationId":"getPackagePurchase","tags":["Package purchases"],"summary":"Get a package purchase","description":"One package purchase, such as the one a `package_purchase.created` webhook names. Requires `clients:read`.","parameters":[{"$ref":"#/components/parameters/PurchaseId"}],"responses":{"200":{"description":"The package purchase.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PackagePurchase"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["clients:read"]}]}},"/tasks":{"get":{"operationId":"listTasks","tags":["Tasks"],"summary":"List tasks","description":"Your team's tasks, as you see them in the app: an owner or admin sees every task in the workspace, a team member\nthe tasks assigned to them or that they added. Open tasks come newest first; completed ones newest completed\nfirst. A task about a client you can't reach (not assigned to you, archived or deleted) shows `client: null`.\nRequires `tasks:write`.\n","parameters":[{"name":"status","in":"query","description":"`open` (the default) or `completed`.","schema":{"$ref":"#/components/schemas/TaskStatus","default":"open"}},{"name":"clientId","in":"query","description":"Only tasks about this client.","schema":{"$ref":"#/components/schemas/ObjectId"}},{"name":"assigneeId","in":"query","description":"Only tasks assigned to this coach (an `assignee.id`).","schema":{"$ref":"#/components/schemas/ObjectId"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of tasks.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor","totalCount"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"nextCursor":{"$ref":"#/components/schemas/NextCursor"},"totalCount":{"$ref":"#/components/schemas/TotalCount"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["tasks:write"]}]},"post":{"operationId":"createTask","tags":["Tasks"],"summary":"Add a task","description":"Adds a task, as adding one in the app does. It's assigned to you unless you choose a team member. A workspace has\nat most 250 open tasks; past that it's `409 open_task_limit_reached`. Each call adds another task, so send an\n`Idempotency-Key` to retry safely. Requires `tasks:write`.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","maxLength":200,"pattern":"\\S","description":"What needs doing. Can't be blank."},"dueDate":{"type":"string","format":"date","description":"The day it's due, as `YYYY-MM-DD`, in your own calendar."},"assigneeId":{"$ref":"#/components/schemas/ObjectId","description":"The team member to give it to (a coach who has joined your workspace). Defaults to you."},"clientId":{"$ref":"#/components/schemas/ObjectId","description":"The client the task is about. Only your team sees the task; the client isn't told."}}}}}},"responses":{"201":{"description":"The new task.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Task"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["tasks:write"]}]}},"/tasks/{taskId}":{"get":{"operationId":"getTask","tags":["Tasks"],"summary":"Get a task","description":"One task you can see. Requires `tasks:write`.","parameters":[{"$ref":"#/components/parameters/TaskId"}],"responses":{"200":{"description":"The task.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Task"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["tasks:write"]}]},"patch":{"operationId":"updateTask","tags":["Tasks"],"summary":"Change a task","description":"Changes only the fields you send. `null` clears the due date or the client, or unassigns the task. `status`\ncompletes a task, as ticking it in the app does, or reopens it; a task that already has that status is left as\nit is. Reopening counts toward the workspace's 250 open tasks; past that it's `409 open_task_limit_reached` and\nnothing changes. Requires `tasks:write`.\n","parameters":[{"$ref":"#/components/parameters/TaskId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"title":{"type":"string","maxLength":200,"pattern":"\\S"},"dueDate":{"type":["string","null"],"format":"date","description":"`YYYY-MM-DD` in your own calendar, or `null` for no due date."},"assigneeId":{"type":["string","null"],"pattern":"^[0-9a-fA-F]{24}$","description":"A coach who has joined your workspace, or `null` to unassign it."},"clientId":{"type":["string","null"],"pattern":"^[0-9a-fA-F]{24}$","description":"The client it's about, or `null` for none."},"status":{"$ref":"#/components/schemas/TaskStatus"}}}}}},"responses":{"200":{"description":"The task as it is now.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Task"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["tasks:write"]}]}},"/workout-studios":{"get":{"operationId":"listWorkoutStudios","tags":["On-demand"],"summary":"List workout studios","description":"Every workout studio in the workspace, newest first, to pass one's `id` when granting a client access. A workspace has no more than 100, so they all come in one response, without paging. Requires `on-demand:write`.","responses":{"200":{"description":"Every workout studio in the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WorkoutStudio"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/clients/{clientId}/workout-studios":{"post":{"operationId":"grantClientWorkoutStudioAccess","tags":["On-demand"],"summary":"Grant a client access to a workout studio","description":"Grants the client access to the workout studio, as adding them under Manage Access in the app does: it shows in\ntheir app and they get a notification. Workout Studio appears in their app if it wasn't there. If they already\nhave access, nothing changes and nothing is sent: that's a `200` instead of a `201`, so a retry is safe.\nRequires `on-demand:write`.\n","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["workoutStudioId"],"properties":{"workoutStudioId":{"$ref":"#/components/schemas/ObjectId"}}}}}},"responses":{"200":{"description":"The client already had access, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WorkoutStudio"}}}}}},"201":{"description":"Access granted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WorkoutStudio"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/clients/{clientId}/workout-studios/{workoutStudioId}":{"delete":{"operationId":"revokeClientWorkoutStudioAccess","tags":["On-demand"],"summary":"Revoke a client's access to a workout studio","description":"Revokes the client's access to the workout studio, as removing them under Manage Access in the app does. It disappears from their app, and Workout Studio with it once they have no workout studios left. If they didn't have access, nothing changes. Requires `on-demand:write`.","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/WorkoutStudioId"}],"responses":{"200":{"description":"The client no longer has access.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["id","revoked"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"revoked":{"type":"boolean","const":true}}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/recipe-books":{"get":{"operationId":"listRecipeBooks","tags":["On-demand"],"summary":"List recipe books","description":"Every recipe book in the workspace, newest first, to pass one's `id` when granting a client access. A workspace has no more than 100, so they all come in one response, without paging. Requires `on-demand:write`.","responses":{"200":{"description":"Every recipe book in the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RecipeBook"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/clients/{clientId}/recipe-books":{"post":{"operationId":"grantClientRecipeBookAccess","tags":["On-demand"],"summary":"Grant a client access to a recipe book","description":"Grants the client access to the recipe book, as adding them under Manage Access in the app does: it shows in\ntheir app and they get a notification. Recipe Books appears in their app if it wasn't there. If they already\nhave access, nothing changes and nothing is sent: that's a `200` instead of a `201`, so a retry is safe.\nRequires `on-demand:write`.\n","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["recipeBookId"],"properties":{"recipeBookId":{"$ref":"#/components/schemas/ObjectId"}}}}}},"responses":{"200":{"description":"The client already had access, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/RecipeBook"}}}}}},"201":{"description":"Access granted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/RecipeBook"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/clients/{clientId}/recipe-books/{recipeBookId}":{"delete":{"operationId":"revokeClientRecipeBookAccess","tags":["On-demand"],"summary":"Revoke a client's access to a recipe book","description":"Revokes the client's access to the recipe book, as removing them under Manage Access in the app does. It disappears from their app, and Recipe Books with it once they have no recipe books left. If they didn't have access, nothing changes. Requires `on-demand:write`.","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/RecipeBookId"}],"responses":{"200":{"description":"The client no longer has access.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["id","revoked"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"revoked":{"type":"boolean","const":true}}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/resource-collections":{"get":{"operationId":"listResourceCollections","tags":["On-demand"],"summary":"List resource collections","description":"Every resource collection in the workspace, newest first, to pass one's `id` when granting a client access. A workspace has no more than 100, so they all come in one response, without paging. Requires `on-demand:write`.","responses":{"200":{"description":"Every resource collection in the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ResourceCollection"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/clients/{clientId}/resource-collections":{"post":{"operationId":"grantClientResourceCollectionAccess","tags":["On-demand"],"summary":"Grant a client access to a resource collection","description":"Grants the client access to the resource collection, as adding them under Manage Access in the app does: it\nshows in their app and they get a notification. If they already have access, nothing changes and nothing is\nsent: that's a `200` instead of a `201`, so a retry is safe. Requires `on-demand:write`.\n","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["resourceCollectionId"],"properties":{"resourceCollectionId":{"$ref":"#/components/schemas/ObjectId"}}}}}},"responses":{"200":{"description":"The client already had access, so nothing changed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ResourceCollection"}}}}}},"201":{"description":"Access granted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ResourceCollection"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/clients/{clientId}/resource-collections/{resourceCollectionId}":{"delete":{"operationId":"revokeClientResourceCollectionAccess","tags":["On-demand"],"summary":"Revoke a client's access to a resource collection","description":"Revokes the client's access to the resource collection, as removing them under Manage Access in the app does. It disappears from their app. If they didn't have access, nothing changes. Requires `on-demand:write`.","parameters":[{"$ref":"#/components/parameters/ClientId"},{"$ref":"#/components/parameters/ResourceCollectionId"}],"responses":{"200":{"description":"The client no longer has access.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["id","revoked"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"revoked":{"type":"boolean","const":true}}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["on-demand:write"]}]}},"/webhook-endpoints":{"get":{"operationId":"listWebhookEndpoints","tags":["Webhooks"],"summary":"List webhook endpoints","description":"Every endpoint in the workspace, newest first, whole (a workspace has at most 10), made with any key or in Settings. An endpoint this key didn't make shows only the origin of its URL. Requires `webhooks:write`.","responses":{"200":{"description":"The workspace's endpoints.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["webhooks:write"]}]},"post":{"operationId":"createWebhookEndpoint","tags":["Webhooks"],"summary":"Add a webhook endpoint","description":"Subscribes a URL to events. The endpoint is `enabled` at once and gets events from then on.\n\nThe URL must be HTTPS on port 443, with a public domain name (never an IP address) written in lowercase, and no\nuser name, password or `#` fragment. Every event type needs the scope that reads its records (`clients:read`).\nThe response carries the endpoint's signing secret, the only time the API shows it. A workspace has at most 10\nendpoints. One key can't add the same URL twice: that's `409 webhook_endpoint_exists`. Requires `webhooks:write`,\nand a key belonging to the workspace's owner or an admin: anyone else's gets `403 role_required`. The endpoint\ngets events about every client, and stops for good (`role_changed`) if the key's coach is made a Standard member.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["name","url","events"],"properties":{"name":{"type":"string","maxLength":100,"pattern":"\\S","description":"What your team calls the endpoint, e.g. `CRM`. Can't be blank."},"url":{"type":"string","maxLength":2048,"description":"Where to send events, e.g. `https://example.com/hubfit-webhooks`."},"events":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/WebhookEventType"}}}}}}},"responses":{"201":{"description":"The new endpoint, and its signing secret.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data","secret"],"properties":{"data":{"$ref":"#/components/schemas/WebhookEndpoint"},"secret":{"type":["string","null"],"format":"password","description":"The signing secret, `whsec_` and 32 random bytes in base64. `null` in a replay of this request with\nthe same `Idempotency-Key`. If you lost it, the workspace's owner can copy it in Settings > API,\nor delete the endpoint and create it again.\n"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["webhooks:write"]}]}},"/webhook-endpoints/{endpointId}":{"get":{"operationId":"getWebhookEndpoint","tags":["Webhooks"],"summary":"Get a webhook endpoint","description":"One endpoint in the workspace. If this key didn't make it, only the origin of its URL is shown. Requires `webhooks:write`.","parameters":[{"$ref":"#/components/parameters/EndpointId"}],"responses":{"200":{"description":"The endpoint.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["webhooks:write"]}]},"patch":{"operationId":"updateWebhookEndpoint","tags":["Webhooks"],"summary":"Change a webhook endpoint","description":"Changes only the fields you send. A new `url` takes effect at once. `status: disabled` stops it at once and\ncancels what's waiting to be sent. `status: enabled` turns a disabled endpoint back on, for events from then on,\nif its `disabledReason` is `manual`, `gone` or `failing` (otherwise `409 webhook_endpoint_disabled`). Only an\nendpoint this key made can be changed; any other is `403 webhook_endpoint_read_only`. Requires `webhooks:write`.\n","parameters":[{"$ref":"#/components/parameters/EndpointId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","maxLength":100,"pattern":"\\S"},"url":{"type":"string","maxLength":2048},"events":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/WebhookEventType"}},"status":{"type":"string","enum":["enabled","disabled"]}}}}}},"responses":{"200":{"description":"The endpoint as it is now.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["webhooks:write"]}]},"delete":{"operationId":"deleteWebhookEndpoint","tags":["Webhooks"],"summary":"Delete a webhook endpoint","description":"Deletes the endpoint and cancels what's waiting to be sent to it. Only an endpoint this key made can be deleted (`403 webhook_endpoint_read_only`). Requires `webhooks:write`.","parameters":[{"$ref":"#/components/parameters/EndpointId"}],"responses":{"200":{"description":"It's deleted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["id","deleted"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"deleted":{"type":"boolean","const":true}}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{"apiKey":["webhooks:write"]}]}}},"webhooks":{"client.created":{"post":{"summary":"A client was added","description":"Sent when a client is added, however they were added (in the app, through the API, by a package purchase or by Zapier). Fetch them with `GET /clients/{clientId}`.","tags":["Webhooks"],"parameters":[{"name":"webhook-id","in":"header","required":true,"description":"The event's `id`. The same on every retry, so use it to skip an event you already have.","schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"description":"When this attempt was sent, in seconds since 1970. Refuse a request more than 5 minutes old.","schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"description":"`v1,` and the base64 HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, with the body exactly as received, keyed by the base64-decoded part of your signing secret after `whsec_`. We send one signature, but Standard Webhooks allows a space-separated list: accept one, and trust the request if any signature in it matches.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event's id, as in `webhook-id`."},"type":{"type":"string","const":"client.created"},"createdAt":{"$ref":"#/components/schemas/DateTime"},"data":{"$ref":"#/components/schemas/WebhookRecordRef"}}}}}},"responses":{"200":{"description":"Any 2xx reply means you have it. A 410 turns the endpoint off; anything else is retried."}}}},"check_in_submission.created":{"post":{"summary":"A client submitted a check-in","description":"Fetch the submission with `GET /check-in-submissions/{id}`.","tags":["Webhooks"],"parameters":[{"name":"webhook-id","in":"header","required":true,"description":"The event's `id`. The same on every retry, so use it to skip an event you already have.","schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"description":"When this attempt was sent, in seconds since 1970. Refuse a request more than 5 minutes old.","schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"description":"`v1,` and the base64 HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, with the body exactly as received, keyed by the base64-decoded part of your signing secret after `whsec_`. We send one signature, but Standard Webhooks allows a space-separated list: accept one, and trust the request if any signature in it matches.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event's id, as in `webhook-id`."},"type":{"type":"string","const":"check_in_submission.created"},"createdAt":{"$ref":"#/components/schemas/DateTime"},"data":{"$ref":"#/components/schemas/WebhookRecordRef"}}}}}},"responses":{"200":{"description":"Any 2xx reply means you have it. A 410 turns the endpoint off; anything else is retried."}}}},"completed_workout.created":{"post":{"summary":"A workout was completed","description":"Sent when a completed workout is saved, whether the client logged it or a coach logged it for them. Edits to it aren't sent. Fetch it with `GET /completed-workouts/{id}`.","tags":["Webhooks"],"parameters":[{"name":"webhook-id","in":"header","required":true,"description":"The event's `id`. The same on every retry, so use it to skip an event you already have.","schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"description":"When this attempt was sent, in seconds since 1970. Refuse a request more than 5 minutes old.","schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"description":"`v1,` and the base64 HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, with the body exactly as received, keyed by the base64-decoded part of your signing secret after `whsec_`. We send one signature, but Standard Webhooks allows a space-separated list: accept one, and trust the request if any signature in it matches.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event's id, as in `webhook-id`."},"type":{"type":"string","const":"completed_workout.created"},"createdAt":{"$ref":"#/components/schemas/DateTime"},"data":{"$ref":"#/components/schemas/WebhookRecordRef"}}}}}},"responses":{"200":{"description":"Any 2xx reply means you have it. A 410 turns the endpoint off; anything else is retried."}}}},"package_purchase.created":{"post":{"summary":"A client bought a package","description":"Sent once the client exists, for a paid or a free package. Fetch the purchase with `GET /package-purchases/{id}`. It records a purchase, not revenue: renewals and refunds aren't sent.","tags":["Webhooks"],"parameters":[{"name":"webhook-id","in":"header","required":true,"description":"The event's `id`. The same on every retry, so use it to skip an event you already have.","schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"description":"When this attempt was sent, in seconds since 1970. Refuse a request more than 5 minutes old.","schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"description":"`v1,` and the base64 HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, with the body exactly as received, keyed by the base64-decoded part of your signing secret after `whsec_`. We send one signature, but Standard Webhooks allows a space-separated list: accept one, and trust the request if any signature in it matches.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event's id, as in `webhook-id`."},"type":{"type":"string","const":"package_purchase.created"},"createdAt":{"$ref":"#/components/schemas/DateTime"},"data":{"$ref":"#/components/schemas/WebhookRecordRef"}}}}}},"responses":{"200":{"description":"Any 2xx reply means you have it. A 410 turns the endpoint off; anything else is retried."}}}},"questionnaire_submission.created":{"post":{"summary":"A client submitted a questionnaire","description":"Fetch the submission with `GET /questionnaire-submissions/{id}`.","tags":["Webhooks"],"parameters":[{"name":"webhook-id","in":"header","required":true,"description":"The event's `id`. The same on every retry, so use it to skip an event you already have.","schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"description":"When this attempt was sent, in seconds since 1970. Refuse a request more than 5 minutes old.","schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"description":"`v1,` and the base64 HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, with the body exactly as received, keyed by the base64-decoded part of your signing secret after `whsec_`. We send one signature, but Standard Webhooks allows a space-separated list: accept one, and trust the request if any signature in it matches.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event's id, as in `webhook-id`."},"type":{"type":"string","const":"questionnaire_submission.created"},"createdAt":{"$ref":"#/components/schemas/DateTime"},"data":{"$ref":"#/components/schemas/WebhookRecordRef"}}}}}},"responses":{"200":{"description":"Any 2xx reply means you have it. A 410 turns the endpoint off; anything else is retried."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key from Settings > API, e.g. `hubfit_sk_...`. Each operation's `security` names the scope a key needs for it."}},"parameters":{"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},"Cursor":{"name":"cursor","in":"query","description":"The `nextCursor` from the previous page.","schema":{"type":"string","minLength":1,"maxLength":200}},"ClientId":{"name":"clientId","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ObjectId"}},"ExerciseId":{"name":"exerciseId","in":"path","required":true,"description":"An `exerciseId` from `GET /clients/{clientId}/exercises`.","schema":{"type":"string","minLength":1,"maxLength":64}},"PurchaseId":{"name":"purchaseId","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ObjectId"}},"WorkoutId":{"name":"workoutId","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ObjectId"}},"LogId":{"name":"logId","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ObjectId"}},"HabitId":{"name":"habitId","in":"path","required":true,"description":"A habit `id` from `GET /clients/{clientId}/habits`.","schema":{"$ref":"#/components/schemas/ObjectId"}},"MetricId":{"name":"metricId","in":"path","required":true,"description":"A metric `id` from `GET /clients/{clientId}/metrics`.","schema":{"$ref":"#/components/schemas/ObjectId"}},"StartDate":{"name":"startDate","in":"query","description":"Only days from this date, included, as `YYYY-MM-DD`. Days are the client's own calendar days.","schema":{"type":"string","format":"date"}},"EndDate":{"name":"endDate","in":"query","description":"Only days up to this date, included, as `YYYY-MM-DD`.","schema":{"type":"string","format":"date"}},"CoachStartDate":{"name":"startDate","in":"query","description":"Only workouts from this day, included, as `YYYY-MM-DD`, in your own calendar (the timezone in your HubFit settings), as the app shows workouts to you.","schema":{"type":"string","format":"date"}},"CoachEndDate":{"name":"endDate","in":"query","description":"Only workouts up to this day, included, as `YYYY-MM-DD`, in your own calendar.","schema":{"type":"string","format":"date"}},"WorkoutStudioId":{"name":"workoutStudioId","in":"path","required":true,"description":"A workout studio `id` from `GET /workout-studios`.","schema":{"$ref":"#/components/schemas/ObjectId"}},"RecipeBookId":{"name":"recipeBookId","in":"path","required":true,"description":"A recipe book `id` from `GET /recipe-books`.","schema":{"$ref":"#/components/schemas/ObjectId"}},"ResourceCollectionId":{"name":"resourceCollectionId","in":"path","required":true,"description":"A resource collection `id` from `GET /resource-collections`.","schema":{"$ref":"#/components/schemas/ObjectId"}},"EndpointId":{"name":"endpointId","in":"path","required":true,"description":"A webhook endpoint `id` from `GET /webhook-endpoints`.","schema":{"$ref":"#/components/schemas/ObjectId"}},"TaskId":{"name":"taskId","in":"path","required":true,"description":"A task `id` from `GET /tasks`.","schema":{"$ref":"#/components/schemas/ObjectId"}},"SubmissionId":{"name":"submissionId","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ObjectId"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255}}},"headers":{"RateLimit":{"description":"Remaining requests and seconds until the window resets, e.g. `\"public-api\"; r=118; t=42`.","schema":{"type":"string"}},"RateLimitPolicy":{"description":"The limit and window, e.g. `\"public-api\"; q=120; w=60`.","schema":{"type":"string"}},"IdempotentReplayed":{"description":"`true` when this response is a replay of an earlier request with the same Idempotency-Key.","schema":{"type":"string"}},"XRequestId":{"description":"This request's id. Error bodies repeat it as `requestId`. Quote it when contacting support.","required":true,"schema":{"type":"string"}},"WWWAuthenticate":{"description":"The Bearer challenge (RFC 6750). On a 401, `error=\"invalid_token\"` when a key was sent and refused (invalid, expired, revoked, or its coach lost access). On a 403 `insufficient_scope`, `error=\"insufficient_scope\"` and the `scope` needed.","schema":{"type":"string"}}},"responses":{"InvalidRequest":{"description":"`invalid_request`: a parameter or the body is invalid, and `detail` says why. When the request doesn't match this specification, `errors` lists each problem. `url_not_allowed`: a webhook endpoint's `url` can't receive webhooks, and `detail` says why.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unauthenticated":{"description":"The API key is missing, invalid, expired or revoked.","headers":{"WWW-Authenticate":{"$ref":"#/components/headers/WWWAuthenticate"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Forbidden":{"description":"`insufficient_scope` (the key lacks a scope; `requiredScope` names it), `plan_required`, `not_available` (the API isn't open to your workspace yet), `account_suspended` or `account_restricted` (your workspace has unpaid invoices), `role_required` (only a key belonging to the workspace's owner or an admin can use `/webhook-endpoints`), or `webhook_endpoint_read_only` (another key or Settings added this webhook endpoint, so this key can't change or delete it).","headers":{"WWW-Authenticate":{"$ref":"#/components/headers/WWWAuthenticate"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"It doesn't exist, or you can't reach it.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Conflict":{"description":"`already_reviewed`, `client_archived`, `note_limit_reached`, `messaging_disabled`, `open_task_limit_reached`, `webhook_endpoint_exists`, `webhook_endpoint_limit_reached`, `webhook_endpoint_disabled`, or `idempotency_key_in_use` while the first request with that key is still running.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"UnsupportedMediaType":{"description":"The body must be sent as `application/json`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"PayloadTooLarge":{"description":"`payload_too_large`: the request body is too large.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"IdempotencyKeyReused":{"description":"The Idempotency-Key was already used with a different request.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"InternalError":{"description":"`internal_error`: something failed on our side. Retry, and quote `requestId` if it keeps happening.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RateLimited":{"description":"Too many requests. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"schemas":{"ObjectId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"NextCursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page. `null` on the last page."},"TotalCount":{"type":"integer","minimum":0,"maximum":10000,"description":"How many results match across every page, ignoring `cursor` and `limit`. Counting stops at 10,000, so 10,000 means at least that many."},"DateTime":{"type":"string","format":"date-time"},"NullableDateTime":{"type":["string","null"],"format":"date-time"},"NullableDate":{"type":["string","null"],"format":"date"},"WorkoutStudio":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"RecipeBook":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"ResourceCollection":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"OnboardingFlow":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"Tag":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"Client":{"type":"object","required":["id","name","email","status","tags","dateOfBirth","sex","heightCm","timezone","preferredUnits","coachingPeriod","lastCheckInAt","lastActiveAt","createdAt"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"},"email":{"type":"string"},"status":{"type":"string","x-extensible-enum":["active","archived"],"description":"`archived` clients only appear in the client list: unarchive them in HubFit to read anything else of theirs."},"tags":{"type":"array","description":"Alphabetical.","items":{"$ref":"#/components/schemas/Tag"}},"dateOfBirth":{"$ref":"#/components/schemas/NullableDate"},"sex":{"type":["string","null"],"x-extensible-enum":["male","female"]},"heightCm":{"type":["number","null"],"description":"Always in centimetres. `preferredUnits.length` is how the client prefers to see it."},"timezone":{"type":"string","description":"IANA time zone, e.g. `Europe/London`. Dates on the client (`dateOfBirth`, `coachingPeriod`) are calendar dates in this time zone."},"preferredUnits":{"type":"object","description":"The units the client prefers to see measurements in. Values in the API are always metric (e.g. `heightCm`); convert with these when showing them to the client.","required":["weight","length"],"properties":{"weight":{"type":"string","x-extensible-enum":["kg","lb"]},"length":{"type":"string","x-extensible-enum":["cm","inch"]}}},"coachingPeriod":{"type":["object","null"],"description":"The dates the coach set for this client's coaching (Set Duration in the app), inclusive. `null` if the coach hasn't set them.","required":["startDate","endDate"],"properties":{"startDate":{"$ref":"#/components/schemas/NullableDate"},"endDate":{"$ref":"#/components/schemas/NullableDate"}}},"lastCheckInAt":{"$ref":"#/components/schemas/NullableDateTime"},"lastActiveAt":{"description":"When the client last used the app. `null` if they never have.","$ref":"#/components/schemas/NullableDateTime"},"createdAt":{"$ref":"#/components/schemas/DateTime"}}},"SubmissionStatus":{"type":"string","x-extensible-enum":["awaiting_review","reviewed"]},"CheckInSubmissionSummary":{"type":"object","required":["id","name","status","client","submittedAt","checkInDate","reviewedAt","reviewedBy"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string","description":"The check-in's title in the app, such as `Weekly check-in`."},"status":{"$ref":"#/components/schemas/SubmissionStatus"},"client":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"submittedAt":{"description":"When the submission arrived. The `submittedAfter` and `submittedBefore` filters use this.","$ref":"#/components/schemas/NullableDateTime"},"checkInDate":{"description":"The day this check-in is for, as a calendar date. A client can fill in a missed check-in up to 14 days late and mark it for the day it was due; otherwise it's the day it was submitted, in the client's time zone. It's the date coaches see in the app.","$ref":"#/components/schemas/NullableDate"},"reviewedAt":{"$ref":"#/components/schemas/NullableDateTime"},"reviewedBy":{"description":"The coach who reviewed it. `null` until it's reviewed.","$ref":"#/components/schemas/NullableCoach"}}},"CheckInSubmission":{"allOf":[{"$ref":"#/components/schemas/CheckInSubmissionSummary"},{"type":"object","required":["answers","reviewComment"],"properties":{"reviewComment":{"type":"string","description":"The review comment sent to the client. Empty if none."},"answers":{"type":"array","items":{"$ref":"#/components/schemas/SubmissionAnswer"}}}}]},"QuestionnaireSubmission":{"type":"object","required":["id","name","client","submittedAt","answers"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string","description":"The questionnaire's name, as it was when the client filled it in."},"client":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"submittedAt":{"$ref":"#/components/schemas/DateTime"},"answers":{"type":"array","items":{"$ref":"#/components/schemas/SubmissionAnswer"}}}},"SubmissionAnswer":{"description":"One shape per question type, told apart by `type`. `answer` is the answer as text, empty if there's none, as for uploads and signatures. New question types can appear, and one not listed here has `question`, `type` and `answer`.","oneOf":[{"$ref":"#/components/schemas/TextAnswer"},{"$ref":"#/components/schemas/NumberAnswer"},{"$ref":"#/components/schemas/MetricAnswer"},{"$ref":"#/components/schemas/YesNoAnswer"},{"$ref":"#/components/schemas/DateAnswer"},{"$ref":"#/components/schemas/MultipleChoiceAnswer"},{"$ref":"#/components/schemas/UploadAnswer"},{"$ref":"#/components/schemas/SignatureAnswer"},{"$ref":"#/components/schemas/OtherAnswer"}]},"TextAnswer":{"type":"object","required":["question","type","answer"],"properties":{"question":{"type":"string"},"type":{"type":"string","const":"text"},"answer":{"type":"string"}}},"NumberAnswer":{"type":"object","required":["question","type","answer","value"],"properties":{"question":{"type":"string"},"type":{"type":"string","enum":["number","one_to_ten","star_rating"]},"answer":{"type":"string"},"value":{"type":["number","null"],"description":"The answer as a number, or `null` if it isn't one."}}},"MetricAnswer":{"type":"object","required":["question","type","answer","value","unit","metricId"],"properties":{"question":{"type":"string"},"type":{"type":"string","const":"metric"},"answer":{"type":"string"},"value":{"type":["number","null"],"description":"The answer as a number, or `null` if it isn't one."},"unit":{"type":["string","null"],"description":"`null` if the metric has no unit."},"metricId":{"type":["string","null"],"pattern":"^[0-9a-fA-F]{24}$","description":"The metric the answer was saved to, for `GET /metrics/{metricId}/entries`. `null` if it wasn't saved, as with a blank answer."}}},"YesNoAnswer":{"type":"object","required":["question","type","answer","answeredYes"],"properties":{"question":{"type":"string"},"type":{"type":"string","const":"yes_no"},"answer":{"type":"string"},"answeredYes":{"type":["boolean","null"],"description":"`null` if unanswered."}}},"DateAnswer":{"type":"object","required":["question","type","answer"],"properties":{"question":{"type":"string"},"type":{"type":"string","const":"date"},"answer":{"type":["string","null"],"format":"date","description":"The day the client picked. `null` if unanswered."}}},"MultipleChoiceAnswer":{"type":"object","required":["question","type","answer","selectedOptions"],"properties":{"question":{"type":"string"},"type":{"type":"string","const":"multiple_choice"},"answer":{"type":"string","description":"The options the client picked, comma-separated."},"selectedOptions":{"type":"array","items":{"type":"string"}}}},"UploadAnswer":{"type":"object","required":["question","type","answer","fileCount"],"properties":{"question":{"type":"string"},"type":{"type":"string","enum":["progress_photos","media"]},"answer":{"type":"string"},"fileCount":{"type":"integer","description":"How many files the client uploaded."}}},"SignatureAnswer":{"type":"object","required":["question","type","answer","signed"],"properties":{"question":{"type":"string"},"type":{"type":"string","const":"signature"},"answer":{"type":"string"},"signed":{"type":"boolean"}}},"OtherAnswer":{"type":"object","required":["question","type","answer"],"properties":{"question":{"type":"string"},"type":{"type":"string","description":"A question type added after this version.","pattern":"^(?!(?:text|number|one_to_ten|star_rating|metric|yes_no|date|multiple_choice|progress_photos|media|signature)$)"},"answer":{"type":"string"}}},"ReviewResult":{"type":"object","required":["id","status","reviewedAt","clientNotified"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"status":{"type":"string","const":"reviewed"},"reviewedAt":{"$ref":"#/components/schemas/DateTime"},"clientNotified":{"type":"boolean","description":"Whether the client was emailed your comment. False without a comment, or when the email failed; the comment is saved and shows in the client's app either way."}}},"CompletedWorkoutSummary":{"type":"object","required":["id","client","name","completedAt","durationSeconds","rating","note","newPersonalRecordCount","totals","weightUnit"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"client":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"name":{"type":"string","description":"The workout's name."},"completedAt":{"$ref":"#/components/schemas/DateTime"},"durationSeconds":{"type":["integer","null"],"description":"How long the client's workout timer ran. `null` if it wasn't timed."},"rating":{"type":["integer","null"],"minimum":1,"maximum":5,"description":"How the client rated the workout, from 1 (okay) to 5 (amazing). `null` if they didn't rate it."},"note":{"type":"string","description":"The client's note on the workout. Empty if none."},"newPersonalRecordCount":{"type":"integer","description":"How many personal records the workout set."},"totals":{"type":"object","required":["setCount","reps","volume"],"properties":{"setCount":{"type":"integer"},"reps":{"type":"number"},"volume":{"type":"number","description":"Weight times reps over every set, in `weightUnit`."}}},"weightUnit":{"$ref":"#/components/schemas/WeightUnit"}}},"CompletedWorkout":{"allOf":[{"$ref":"#/components/schemas/CompletedWorkoutSummary"},{"type":"object","required":["sections","exercises"],"properties":{"sections":{"type":"array","description":"The sections the coach grouped exercises into, such as a warm-up or a circuit. Exercises outside a section aren't in one.","items":{"$ref":"#/components/schemas/WorkoutSection"}},"exercises":{"type":"array","description":"Every exercise in the order of the workout, each pointing at its section, if any.","items":{"$ref":"#/components/schemas/WorkoutExercise"}}}}]},"NutritionLogSummary":{"type":"object","required":["id","client","date","totals","target","foodCount","photoCount","note"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"client":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"date":{"type":"string","format":"date","description":"The client's calendar day the log is for."},"totals":{"$ref":"#/components/schemas/Nutrients"},"target":{"$ref":"#/components/schemas/NutritionTarget"},"foodCount":{"type":"integer","description":"How many foods the client logged that day."},"photoCount":{"type":"integer","description":"How many meal photos the client added to the day's food journal."},"note":{"type":"string","description":"The client's note on the day. Empty if none."}}},"NutritionLog":{"allOf":[{"$ref":"#/components/schemas/NutritionLogSummary"},{"type":"object","required":["meals"],"properties":{"meals":{"type":"array","description":"The day's meals, named as the client's Daily Meals, in order.","items":{"$ref":"#/components/schemas/LoggedMeal"}}}}]},"LoggedMeal":{"type":"object","required":["name","foods","photoCount"],"properties":{"name":{"type":"string"},"foods":{"type":"array","items":{"$ref":"#/components/schemas/LoggedFood"}},"photoCount":{"type":"integer","description":"How many photos the client added to this meal."}}},"LoggedFood":{"type":"object","required":["name","servingDescription","servings","nutrients"],"properties":{"name":{"type":"string"},"servingDescription":{"type":"string","description":"The serving the client picked, such as `1 cup` or `100 g`."},"servings":{"type":"number","description":"How many of that serving the client ate."},"nutrients":{"$ref":"#/components/schemas/Nutrients"}}},"Nutrients":{"type":"object","description":"What was eaten, in kilocalories and grams. A nutrient the food doesn't list counts as 0.","required":["calories","proteinGrams","carbsGrams","fatGrams","fiberGrams","sugarGrams","saturatedFatGrams","monounsaturatedFatGrams","polyunsaturatedFatGrams"],"properties":{"calories":{"type":"number","description":"Kilocalories."},"proteinGrams":{"type":"number"},"carbsGrams":{"type":"number"},"fatGrams":{"type":"number"},"fiberGrams":{"type":"number"},"sugarGrams":{"type":"number"},"saturatedFatGrams":{"type":"number"},"monounsaturatedFatGrams":{"type":"number"},"polyunsaturatedFatGrams":{"type":"number"}}},"NutritionTarget":{"type":["object","null"],"description":"The day's target as it stood when the client last saved the day, the rest-day target on a rest day. `null` if no target was set; each value is `null` if it was left empty.","required":["calories","proteinGrams","carbsGrams","fatGrams"],"properties":{"calories":{"type":["number","null"]},"proteinGrams":{"type":["number","null"]},"carbsGrams":{"type":["number","null"]},"fatGrams":{"type":["number","null"]}}},"WeeklyNutritionTarget":{"type":["object","null"],"description":"The target for each day of the week. `null` if the coach hasn't set one.","required":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"properties":{"monday":{"$ref":"#/components/schemas/DayNutritionTarget"},"tuesday":{"$ref":"#/components/schemas/DayNutritionTarget"},"wednesday":{"$ref":"#/components/schemas/DayNutritionTarget"},"thursday":{"$ref":"#/components/schemas/DayNutritionTarget"},"friday":{"$ref":"#/components/schemas/DayNutritionTarget"},"saturday":{"$ref":"#/components/schemas/DayNutritionTarget"},"sunday":{"$ref":"#/components/schemas/DayNutritionTarget"}}},"DayNutritionTarget":{"type":"object","description":"Each value is `null` if it was left empty.","required":["calories","proteinGrams","carbsGrams","fatGrams","isRestDay"],"properties":{"calories":{"type":["number","null"]},"proteinGrams":{"type":["number","null"]},"carbsGrams":{"type":["number","null"]},"fatGrams":{"type":["number","null"]},"isRestDay":{"type":"boolean","description":"Whether this weekday is marked as a rest day, which has its own target."}}},"ClientSummary":{"type":"object","required":["client"],"description":"Each section is present only when `include` names it.","properties":{"client":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"workouts":{"$ref":"#/components/schemas/WorkoutsSummary"},"nutrition":{"$ref":"#/components/schemas/NutritionSummary"},"habits":{"type":"array","description":"Each habit that was running in the period, up to today. Empty if the client had none, or the coach turned habits off for them.","items":{"$ref":"#/components/schemas/HabitSummary"}},"metrics":{"type":"array","description":"Each of the client's metrics, or the one named with `metricName`, in the order the coach added them. Empty if the client has none, or the coach turned metrics off for them.","items":{"$ref":"#/components/schemas/MetricSummary"}},"health":{"$ref":"#/components/schemas/HealthSummary"},"checkIns":{"type":"array","description":"One entry per check-in form the client submitted for a day in the period, latest first. Empty if they submitted none. A check-in counts on the day it's for (`checkInDate`), the client's own day or the day they backdated it to. For submissions waiting for your review, use `GET /check-in-submissions?status=awaiting_review`.","items":{"$ref":"#/components/schemas/CheckInFormSummary"}}}},"CheckInFormSummary":{"type":"object","required":["name","submissionCount","lastSubmittedAt","lastSubmissionId"],"properties":{"name":{"type":"string","description":"The form's name on its latest submission in the period. Forms are told apart by the form itself, so a renamed form is one entry."},"submissionCount":{"type":"integer","minimum":1},"lastSubmittedAt":{"$ref":"#/components/schemas/DateTime"},"lastSubmissionId":{"$ref":"#/components/schemas/ObjectId","description":"The latest submission, to read with `GET /check-in-submissions/{submissionId}`."}}},"NutritionSummary":{"type":"object","description":"A day counts toward `loggedDayCount` and the averages once it has food in it. Clients who track in Cronometer have no logs here.","required":["loggedDayCount","lastLoggedDate","averageNutrients","averageTarget"],"properties":{"loggedDayCount":{"type":"integer","description":"How many days in the period the client logged food."},"lastLoggedDate":{"type":["string","null"],"format":"date","description":"The last day the client logged anything (food, a photo or a note), which can be after the period. `null` if they never have."},"averageNutrients":{"type":["object","null"],"description":"The daily average over the days logged. `null` if the client logged no food in the period.","required":["calories","proteinGrams","carbsGrams","fatGrams","fiberGrams","sugarGrams","saturatedFatGrams","monounsaturatedFatGrams","polyunsaturatedFatGrams"],"properties":{"calories":{"type":"number"},"proteinGrams":{"type":"number"},"carbsGrams":{"type":"number"},"fatGrams":{"type":"number"},"fiberGrams":{"type":"number"},"sugarGrams":{"type":"number"},"saturatedFatGrams":{"type":"number"},"monounsaturatedFatGrams":{"type":"number"},"polyunsaturatedFatGrams":{"type":"number"}}},"averageTarget":{"type":["object","null"],"description":"The average of the days logged's targets, each value over the days it was set. `null` if none of those days had a target.","required":["calories","proteinGrams","carbsGrams","fatGrams"],"properties":{"calories":{"type":["number","null"]},"proteinGrams":{"type":["number","null"]},"carbsGrams":{"type":["number","null"]},"fatGrams":{"type":["number","null"]}}}}},"WorkoutsSummary":{"type":"object","description":"The workouts the client completed in the period.","required":["workoutCount","lastCompletedAt","totals","newPersonalRecordCount","weightUnit"],"properties":{"workoutCount":{"type":"integer","description":"How many workouts the client completed in the period."},"lastCompletedAt":{"description":"When the client last completed a workout at all, which can be after the period. `null` if they never have.","$ref":"#/components/schemas/NullableDateTime"},"totals":{"type":"object","required":["setCount","reps","volume"],"description":"Added up over the workouts in the period.","properties":{"setCount":{"type":"integer"},"reps":{"type":"number"},"volume":{"type":"number","description":"Weight times reps over every set, in `weightUnit`."}}},"newPersonalRecordCount":{"type":"integer","description":"How many personal records the workouts in the period set."},"weightUnit":{"$ref":"#/components/schemas/WeightUnit"}}},"WorkoutSection":{"type":"object","required":["id","name","type","targetRounds","completedRounds","timeCapSeconds","durationSeconds"],"properties":{"id":{"type":"string","description":"What each of the section's exercises has as `sectionId`."},"name":{"type":"string"},"type":{"type":"string","x-extensible-enum":["regular","circuit","amrap","interval"],"description":"New section types can appear."},"targetRounds":{"type":["integer","null"],"description":"The rounds the coach set for a circuit or interval section. `null` for other sections."},"completedRounds":{"type":["number","null"],"description":"The rounds the client finished in a circuit, interval or AMRAP section. `null` for regular sections."},"timeCapSeconds":{"type":["integer","null"],"description":"The time the coach set for an AMRAP section. `null` for other sections."},"durationSeconds":{"type":["integer","null"],"description":"How long the client took over a circuit, interval or AMRAP section. `null` for regular sections or when it wasn't timed."}}},"WorkoutExercise":{"type":"object","required":["exerciseId","name","sectionId","supersetId","isAlternative","note","sets","newPersonalRecords"],"properties":{"exerciseId":{"type":["string","null"],"description":"The same for every time the client logged this exercise, so it tells you which entries across workouts are the same exercise. `null` when HubFit has no shared id for it."},"name":{"type":"string"},"sectionId":{"type":["string","null"],"description":"The `id` of the section the exercise is in, or `null` if it isn't in one."},"supersetId":{"type":["string","null"],"description":"Shared by the exercises done together as a superset, or `null` when the exercise wasn't part of one."},"isAlternative":{"type":"boolean","description":"Whether the client swapped in this exercise as an alternative to the planned one."},"note":{"type":"string","description":"The client's note on the exercise. Empty if none."},"sets":{"type":"array","items":{"$ref":"#/components/schemas/LoggedSet"}},"newPersonalRecords":{"type":"array","description":"The personal records the client set on this exercise in this workout. The first time a client logs an exercise sets none; records start from the next time.","items":{"$ref":"#/components/schemas/NewPersonalRecord"}}}},"LoggedSet":{"type":"object","description":"One set, with a value for each thing the exercise tracks, or `null` when the client left it blank. `weight` is in `weightUnit` and `durationSeconds` in seconds; other values are as the client entered them.","propertyNames":{"$ref":"#/components/schemas/SetField"},"additionalProperties":{"type":["number","null"]}},"NewPersonalRecord":{"type":"object","required":["type","value","improvementPercent"],"properties":{"type":{"$ref":"#/components/schemas/PersonalRecordType"},"value":{"type":"number","description":"The new record, in `weightUnit`."},"improvementPercent":{"type":"number","description":"How much it beat the previous record by, as a percentage. 100 when the previous record was 0, such as when earlier sessions had no weight entered (HubFit doesn't keep the previous value, so this can't be told apart from doubling it)."}}},"PersonalRecordType":{"type":"string","x-extensible-enum":["max_weight","max_volume","estimated_one_rep_max"],"description":"`max_weight` for the heaviest set, `max_volume` for the most weight times reps added up over the exercise's sets in one workout, `estimated_one_rep_max` for the one-rep max HubFit estimates from a set's weight and reps (the Epley formula). New record types can appear."},"ClientExercise":{"type":"object","required":["exerciseId","name","lastLoggedAt","personalRecords","weightUnit"],"properties":{"exerciseId":{"type":"string","description":"The same as `exerciseId` on the exercises of a completed workout. Use it to get the exercise's history."},"name":{"type":"string"},"lastLoggedAt":{"$ref":"#/components/schemas/NullableDateTime","description":"When the client last logged sets for it. `null` if they never entered any."},"personalRecords":{"type":"object","description":"The client's best results on this exercise, in `weightUnit`. Each is `null` until the client logs a weight for it.","required":["maxWeight","maxVolume","estimatedOneRepMax"],"properties":{"maxWeight":{"type":["number","null"],"description":"The heaviest set."},"maxVolume":{"type":["number","null"],"description":"The most weight times reps added up over the exercise's sets in one workout."},"estimatedOneRepMax":{"type":["number","null"],"description":"The highest one-rep max HubFit estimates from a set's weight and reps (the Epley formula)."}}},"weightUnit":{"$ref":"#/components/schemas/WeightUnit"}}},"ExerciseHistoryEntry":{"type":"object","required":["workout","completedAt","isAlternative","note","sets","newPersonalRecords","weightUnit"],"properties":{"workout":{"type":"object","description":"The completed workout this was logged in. Pass its `id` to `GET /completed-workouts/{workoutId}` for the rest of it.","required":["id","name"],"properties":{"id":{"type":["string","null"],"pattern":"^[0-9a-fA-F]{24}$","description":"`null` if HubFit didn't record which workout it was."},"name":{"type":"string"}}},"completedAt":{"$ref":"#/components/schemas/DateTime"},"isAlternative":{"type":"boolean","description":"Whether the client did this exercise as an alternative to the planned one."},"note":{"type":"string","description":"The client's note on the exercise that time. Empty if none."},"sets":{"type":"array","items":{"$ref":"#/components/schemas/LoggedSet"}},"newPersonalRecords":{"type":"array","description":"The personal records the client set on the exercise that time.","items":{"$ref":"#/components/schemas/NewPersonalRecord"}},"weightUnit":{"$ref":"#/components/schemas/WeightUnit"}}},"WeightUnit":{"type":"string","x-extensible-enum":["kg","lb"],"description":"The unit of every weight here, which is the client's unit setting. Weights aren't converted, the way amounts come with their currency. HubFit doesn't record the unit each workout was logged in, so a client who changed their setting may have older workouts logged in the other unit."},"SetField":{"type":"string","x-extensible-enum":["reps","weight","durationSeconds","time","distance","rpe","rpm","rir","speed","cadence","height","calories","round","other"],"description":"New fields can appear."},"ClientNote":{"type":"object","required":["id","title","text","visibility","createdAt"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"title":{"type":"string","description":"The note's heading. Empty if it has none."},"text":{"type":"string"},"visibility":{"$ref":"#/components/schemas/NoteVisibility"},"createdAt":{"$ref":"#/components/schemas/DateTime"}}},"NoteVisibility":{"type":"string","enum":["private","shared"],"description":"`private` if only your team sees the note. `shared` if the client sees it in their app too; adding a shared note sends them a push notification with its text."},"ClientMessage":{"type":"object","required":["id","text","sentAt","client"],"properties":{"id":{"type":"string","description":"The message's id in HubFit's chat."},"text":{"type":"string"},"sentAt":{"$ref":"#/components/schemas/DateTime"},"client":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}}}},"HabitGoal":{"type":"object","required":["type","value","unit","period"],"properties":{"type":{"type":"string","x-extensible-enum":["numeric","completion"],"description":"`numeric` when the client logs an amount (steps, litres), `completion` when they tick it off."},"value":{"type":["number","null"],"description":"The amount to reach each period. For a `completion` habit, how many ticks (1 a day, or 1 to 7 a week)."},"unit":{"type":["string","null"],"description":"The unit the coach picked, like `steps`, `km` or `cups`. `null` for a `completion` habit."},"period":{"type":"string","x-extensible-enum":["day","week","month"],"description":"Whether the goal is for each day, each Monday-to-Sunday week, or each month."}}},"Habit":{"type":"object","required":["id","name","emoji","description","goal","startDate","endDate","status","createdAt"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"},"emoji":{"type":"string"},"description":{"type":"string"},"goal":{"$ref":"#/components/schemas/HabitGoal"},"startDate":{"type":"string","format":"date"},"endDate":{"$ref":"#/components/schemas/NullableDate"},"status":{"type":"string","x-extensible-enum":["upcoming","active","ended"],"description":"Against today in the client's timezone."},"createdAt":{"$ref":"#/components/schemas/DateTime"}}},"HabitPeriodProgress":{"type":"object","required":["startDate","endDate","loggedValue","goalValue","completed"],"properties":{"startDate":{"type":"string","format":"date","description":"The period's first day; a week starts on a Monday."},"endDate":{"type":"string","format":"date","description":"The period's last day. The same as `startDate` for a daily habit."},"loggedValue":{"type":"number","description":"What the client logged in the period. For a `completion` habit, how many days they ticked it."},"goalValue":{"type":["number","null"],"description":"The goal in force in the period, which may differ from the habit's current goal if the coach changed it since."},"completed":{"type":"boolean","description":"Whether the period met its goal, as the app records it."}}},"HealthSource":{"type":["string","null"],"x-extensible-enum":["apple_health","health_connect"],"description":"Where the data comes from, or `null` if the client hasn't connected Apple Health or Health Connect. New sources can appear."},"HealthSleep":{"type":["object","null"],"description":"The night that ended this morning, or `null`.","required":["durationSeconds","deepSeconds","lightSeconds","remSeconds","awakeSeconds","startAt","endAt"],"properties":{"durationSeconds":{"type":"integer","description":"Time asleep, awake time not included."},"deepSeconds":{"type":["integer","null"],"description":"`null` if the phone recorded no sleep stages."},"lightSeconds":{"type":["integer","null"],"description":"Light sleep, which Apple Health calls core sleep. `null` if the phone recorded no sleep stages."},"remSeconds":{"type":["integer","null"],"description":"`null` if the phone recorded no sleep stages."},"awakeSeconds":{"type":["integer","null"],"description":"Time awake during the night. `null` if the phone recorded no sleep stages and no time awake."},"startAt":{"$ref":"#/components/schemas/NullableDateTime"},"endAt":{"$ref":"#/components/schemas/NullableDateTime"}}},"HealthDay":{"type":"object","required":["date","steps","activeCalories","restingHeartRateBpm","sleep"],"properties":{"date":{"type":"string","format":"date","description":"The day on the client's phone."},"steps":{"type":["integer","null"]},"activeCalories":{"type":["integer","null"],"description":"Calories burned by activity, in kcal. Not total energy."},"restingHeartRateBpm":{"type":["number","null"]},"sleep":{"$ref":"#/components/schemas/HealthSleep"}}},"HealthData":{"type":"object","required":["source","lastSyncedAt","days"],"properties":{"source":{"$ref":"#/components/schemas/HealthSource"},"lastSyncedAt":{"$ref":"#/components/schemas/NullableDateTime"},"days":{"type":"array","items":{"$ref":"#/components/schemas/HealthDay"}}}},"HealthSummary":{"type":"object","description":"From Apple Health or Health Connect, each average over the days that have it. `source` is `null` for a client who hasn't connected either.","required":["source","lastSyncedAt","recordedDayCount","averageSteps","averageActiveCalories","averageSleepSeconds","averageRestingHeartRateBpm"],"properties":{"source":{"$ref":"#/components/schemas/HealthSource"},"lastSyncedAt":{"$ref":"#/components/schemas/NullableDateTime"},"recordedDayCount":{"type":"integer","description":"How many days in the period have any data."},"averageSteps":{"type":["integer","null"]},"averageActiveCalories":{"type":["integer","null"]},"averageSleepSeconds":{"type":["integer","null"]},"averageRestingHeartRateBpm":{"type":["integer","null"]}}},"HabitSummary":{"type":"object","required":["id","name","emoji","goal","completedPeriodCount","expectedPeriodCount"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"},"emoji":{"type":"string"},"goal":{"$ref":"#/components/schemas/HabitGoal"},"completedPeriodCount":{"type":"integer","minimum":0,"description":"How many of the `expectedPeriodCount` days, weeks or months the client met the goal, as the app records it."},"expectedPeriodCount":{"type":"integer","minimum":1,"description":"The habit's days, weeks or months (its goal's `period`) in the period while it was running, up to today. A week counts when any of it falls in the period."}}},"MetricEntry":{"type":"object","required":["id","date","value"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"date":{"type":"string","format":"date","description":"The client's own calendar day the entry is for."},"value":{"type":"number","description":"In the metric's `unit`."}}},"Metric":{"type":"object","required":["id","name","unit","entryCount","lastEntry"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"},"unit":{"type":["string","null"],"description":"The unit the coach set, as they wrote it (`kg`, `cm`, `%`). `null` if they left it blank. Values are never converted."},"entryCount":{"type":"integer","minimum":0},"lastEntry":{"anyOf":[{"$ref":"#/components/schemas/MetricEntry"},{"type":"null"}],"description":"The entry for the latest day, and of that day's entries the last added. `null` if it has none."}}},"MetricSummary":{"type":"object","required":["id","name","unit","entryCount","firstEntry","lastEntry","average"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"},"unit":{"type":["string","null"]},"entryCount":{"type":"integer","minimum":0,"description":"The entries in the period."},"firstEntry":{"anyOf":[{"$ref":"#/components/schemas/MetricEntry"},{"type":"null"}],"description":"The period's earliest entry. `null` if it has none."},"lastEntry":{"anyOf":[{"$ref":"#/components/schemas/MetricEntry"},{"type":"null"}],"description":"The period's latest entry, picked the same way as a metric's `lastEntry`. `null` if it has none."},"average":{"type":["number","null"],"description":"The mean of the period's entries, to 1 decimal place, as the app shows it. `null` with no entries."}}},"TaskStatus":{"type":"string","enum":["open","completed"]},"NullableCoach":{"type":["object","null"],"required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":["string","null"],"description":"The coach's name. `null` if their account no longer exists."}}},"Task":{"type":"object","required":["id","title","status","dueDate","client","assignee","createdBy","createdAt","updatedAt","completedAt","completedBy"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"title":{"type":"string"},"status":{"$ref":"#/components/schemas/TaskStatus"},"dueDate":{"description":"The day it's due, in your own calendar. `null` if it has none.","$ref":"#/components/schemas/NullableDate"},"client":{"description":"The client it's about. `null` if it's about no one, or about a client you can't reach (not assigned to you, archived or deleted).","type":["object","null"],"required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"assignee":{"description":"The coach it's assigned to. `null` if it's unassigned.","$ref":"#/components/schemas/NullableCoach"},"createdBy":{"description":"The coach who added it.","$ref":"#/components/schemas/NullableCoach"},"createdAt":{"$ref":"#/components/schemas/DateTime"},"updatedAt":{"description":"When the task last changed. A change to its client or coaches, such as a new name, doesn't count.","$ref":"#/components/schemas/DateTime"},"completedAt":{"$ref":"#/components/schemas/NullableDateTime"},"completedBy":{"description":"The coach who completed it. `null` while it's open.","$ref":"#/components/schemas/NullableCoach"}}},"WebhookEventType":{"type":"string","enum":["client.created","check_in_submission.created","completed_workout.created","package_purchase.created","questionnaire_submission.created"]},"WebhookEndpoint":{"type":"object","required":["id","name","url","events","status","disabledReason","lastDelivery","createdAt","updatedAt"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"},"url":{"type":"string","format":"uri","description":"The full URL for an endpoint this key made. For any other, only its origin, since a URL like a Zapier hook's runs whatever it's attached to."},"events":{"type":"array","items":{"type":"string","x-extensible-enum":["client.created","check_in_submission.created","completed_workout.created","package_purchase.created","questionnaire_submission.created"]}},"status":{"type":"string","x-extensible-enum":["enabled","disabled"],"description":"An `enabled` endpoint gets nothing while the workspace is off a plan with webhooks, unpaid or suspended, and nothing from that time once it's back."},"disabledReason":{"type":["string","null"],"x-extensible-enum":["manual","gone","failing","api_key_revoked","api_key_expired","coach_removed","role_changed"],"description":"Why the endpoint is `disabled`, and `null` while it's `enabled`. These can be turned back on: `manual`, turned\noff with `status: disabled` or in Settings; `gone`, after a 410 reply; and `failing`, after 5 days without a\nsuccess. These stop it for good, with every other endpoint its key made: `api_key_revoked`, its key was revoked;\n`api_key_expired`, its key expired; `coach_removed`, the key's coach was removed from the workspace or their\naccount was deleted; and `role_changed`, the key's coach was made a Standard member.\n"},"lastDelivery":{"type":["object","null"],"required":["outcome","statusCode","attemptedAt"],"properties":{"outcome":{"type":"string","x-extensible-enum":["delivered","retry","failed","gone","blocked"],"description":"`delivered` after a 2xx reply. `retry` when the attempt failed and another is scheduled, `failed` when it failed with no attempt left. `gone` after a 410 reply, which turns the endpoint off. `blocked` when the URL led to an address webhooks can't be sent to, such as a private one: it isn't retried."},"statusCode":{"type":["integer","null"],"description":"The reply's status. `null` if there was none, such as a timeout."},"attemptedAt":{"$ref":"#/components/schemas/DateTime"}}},"createdAt":{"$ref":"#/components/schemas/DateTime"},"updatedAt":{"$ref":"#/components/schemas/DateTime"}}},"WebhookRecordRef":{"type":"object","required":["id","clientId"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"clientId":{"$ref":"#/components/schemas/ObjectId"}}},"PackagePurchase":{"type":"object","required":["id","client","package","amount","currency","free","purchasedAt"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"client":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string"}}},"package":{"type":"object","required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/ObjectId"},"name":{"type":"string","description":"As it was when bought."}}},"amount":{"type":"integer","description":"What the client paid at checkout, in the currency's minor units (cents, or thousandths for KWD and BHD), as Stripe's amounts are. `0` for a free package, a free trial or a full discount."},"currency":{"type":"string","description":"ISO 4217, lowercase, e.g. `usd`."},"free":{"type":"boolean","description":"Whether the package was free. A paid package bought on a free trial or with a full discount is `false`, with an `amount` of `0`."},"purchasedAt":{"$ref":"#/components/schemas/DateTime"}}},"Problem":{"type":"object","required":["title","status","code","detail","requestId"],"properties":{"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string","x-extensible-enum":["invalid_request","url_not_allowed","unauthenticated","account_suspended","account_restricted","plan_required","role_required","not_available","insufficient_scope","client_limit_reached","not_found","method_not_allowed","already_reviewed","client_archived","client_exists","note_limit_reached","messaging_disabled","open_task_limit_reached","webhook_endpoint_exists","webhook_endpoint_limit_reached","webhook_endpoint_disabled","webhook_endpoint_read_only","idempotency_key_in_use","payload_too_large","unsupported_media_type","idempotency_key_reused","rate_limited","internal_error"],"description":"New codes can appear. Handle one you don't recognise by its `status`."},"detail":{"type":"string"},"requestId":{"type":"string"},"requiredScope":{"type":"string"},"clientId":{"description":"On `client_exists`, the existing client, when you can reach it.","$ref":"#/components/schemas/ObjectId"},"errors":{"type":"array","items":{"type":"object","required":["location","name","detail"],"properties":{"location":{"type":"string","enum":["query","path","body","header"]},"name":{"type":"string"},"detail":{"type":"string"}}}}}}}}}