The precision model
Calculation responses carry aprecision object on every tier:
profile_complete: trueputs exact numbers in the response (carb_g_per_hrand friends).profile_complete: falseputs bands there instead (carb_range_g_per_hrand friends).missing_fieldsarrives sorted most-impactful-first, so it is your collection roadmap: the top entry is the question that buys the most precision.- The trial clock starts at the athlete’s first calculation carrying any real data. Zero-data calls never start it. Collect the profile first and the 30-day window delivers exact numbers rather than wide bands. See Freemium Model.
display_label, the plain-English name of the question (“how much you sweat”), so you can build the prompt without exposing an internal key. precision.message is written for an athlete to read and is safe to surface directly. band_impact is in per-hour units, rounded to the same increments the bands use, and says roughly how much narrower the band gets once that field is answered. Take missing_fields in the order it arrives rather than re-sorting on these numbers, since rounding can leave two fields tied.
What “complete” means, twice
Two different completeness checks share a name, and mixing them up is the usual surprise.precision.profile_complete, on a calculation response, is per-call. It requires every profile field and the activity parameters for that call: intensity_level, thermal_stress_level, meal_before_min, and is_race. An athlete with a perfect stored profile still gets a band if the call omits those. On POST /v1/nutrition/calculate you send them in the request body; on POST .../activities/{id}/calculate they are read from the stored activity, so set them when you create it.
The athlete’s own profile_complete field, the one ?profile_complete=false filters on and athlete.profile_completed fires for, covers the stored profile only: sex, year_of_birth, weight_kg, sweat_level, saltiness, satiety_level, fitness_level, carb_experience, usual_carb_consumption, and an answered concerns question.
Of the profile fields,
sex, year_of_birth, weight_kg, sweat_level, saltiness, carb_experience, and usual_carb_consumption are the safety core, and missing_fields marks them required: true. satiety_level, fitness_level, and concerns come back as required: false, and exactness needs all of them.Four ways to collect the profile
1. Hosted onboarding page (recommended start)
Every incomplete-profile response carriesprecision.onboarding.url, a durable athlete-scoped link to Saturday’s hosted onboarding page, co-branded with your platform. Send the athlete there by button, email, or push, whichever fits your product.
- It asks only the questions that are missing, one per screen, mobile-first, in about two minutes.
- Each answer commits as it is given, so a half-finished session still narrows the bands.
- The finish screen asks the athlete’s consent to show fuel for their most recent activity. If you have registered an
activity_link_templateon your partner account (yourapp://activity/{external_id}, for instance), it deep-links into your activity screen. Otherwise it renders the numbers itself and returns the athlete via yourreturn_url. - The link stays valid, so athletes can come back and edit their answers.
2. Your UI, our questions (headless)
PATCH /v1/athletes/{id}/settings or athlete create and update.
The answer values are odd-point selectors (1, 3, 5, 7, 9), not continuous sliders, and some questions map two labels onto one value: “Not sure” for saltiness stores the same 5 as “Somewhat salty”. Key your option state on the label or the index, not on the value, or those options will collide.
Attribution is required when you render Saturday’s questions in your UI. The schema response carries the attribution object, the same contract as a calculation response.
New questions arrive as recommended rather than required, so a schema change does not turn a complete athlete incomplete overnight. schema_version ships in the response; record which version an athlete answered under so you can tell when re-asking is worthwhile.
3. Saturday app (once, forever)
If the athlete uses the Saturday app with the same email address they have on your platform, their app onboarding powers their numbers on your platform too, resolved at calculation time rather than synced. Those answers are used for computation only and are never exposed through the API. An athlete can separately choose to share their profile values with you from app settings.4. Pass fields inline
Every calculate call accepts the full profile inline (sweat_level, saltiness, and the rest; see Nutrition Calculation). Inline values win over stored ones for that call, and do not overwrite the stored profile. Good for stateless integrations, where you carry the data.
Finding athletes to nudge
athlete.profile_completed webhook, which fires once on the crossing into completeness, so you can mark the moment in your UI when an athlete’s numbers go exact.