---
title: "A starter project for your fitness API integration"
canonical: "https://aifitnessapi.com/tools/starter-scaffold"
markdown: "https://aifitnessapi.com/tools/starter-scaffold.md"
type: "tool"
last_reviewed: "2026-11-05"
description: "Pick Express, Next.js or FastAPI and Oura, WHOOP, Polar, Strava or Wahoo: download a tested skeleton with OAuth, token storage and verified webhooks."
publisher: "AIFitnessAPI — funded by KinesteX; first-party pages disclose it"
cite_as: "\"A starter project for your fitness API integration\", AIFitnessAPI, https://aifitnessapi.com/tools/starter-scaffold"
---

# A starter project for your fitness API integration

> Pick a stack (Node.js + Express, Next.js route handlers or Python + FastAPI) and the vendors you integrate (Oura, WHOOP, Polar AccessLink, Strava and Wahoo), and download a project skeleton as a .zip built in your browser: an OAuth start and callback route per vendor with a state check and PKCE where the vendor documents it, a token-store interface with an in-memory stand-in, a webhook endpoint that answers each vendor’s handshake and checks its documented signature or token, a .env.example and a README. Every endpoint and header comes from a page this site already sources, and every combination is compiled and run in the site’s tests.

- Canonical: https://aifitnessapi.com/tools/starter-scaffold
- Last reviewed: 2026-11-05
- Publisher: AIFitnessAPI (https://aifitnessapi.com)
- Cite as: "A starter project for your fitness API integration", AIFitnessAPI, https://aifitnessapi.com/tools/starter-scaffold

---

*[Interactive: the generator and its download, in the browser at https://aifitnessapi.com/tools/starter-scaffold; not reproduced in this mirror.]*

## What each vendor gets

As the site had them on 2026-11-05. The OAuth details are from each vendor’s integration guide; the webhook rules are the ones the [webhook verifier](https://aifitnessapi.com/tools/webhook-verifier) quotes from the vendor’s own page.

### Oura

HMAC signature

- HMAC-SHA256, keyed by your client secret, over the x-oura-timestamp value followed by JSON.stringify(body); hex, upper-case, in x-oura-signature.
- Authorize: `https://cloud.ouraring.com/oauth/authorize`
- Token: `https://api.ouraring.com/oauth/token` (credentials in the form body)
- Default scopes: `daily heartrate workout spo2`
- PKCE: S256, as the vendor documents
- Oura marks PKCE as recommended; the skeleton sends an S256 code_challenge and the code_verifier on the exchange.
- Oura's refresh token is single-use: store the new pair after every refresh.

Sources: [Oura integration guide](https://aifitnessapi.com/integrate/oura-api) and [Oura's webhook documentation](https://cloud.ouraring.com/v2/static/json/openapi-1.43.json).

### WHOOP

HMAC signature

- HMAC-SHA256, keyed by your app's client secret, over the X-WHOOP-Signature-Timestamp value followed by the raw body; base64, in X-WHOOP-Signature.
- Authorize: `https://api.prod.whoop.com/oauth/oauth2/auth`
- Token: `https://api.prod.whoop.com/oauth/oauth2/token` (credentials in the form body)
- Default scopes: `read:recovery read:sleep read:workout offline`
- PKCE: not sent: the guide does not document it
- WHOOP issues a refresh token only when you request the offline scope.
- WHOOP says a state you generate must be eight characters long.
- A refresh invalidates the previous tokens, and two simultaneous refreshes for one member fail: refresh on a schedule, one at a time.

Sources: [WHOOP integration guide](https://aifitnessapi.com/integrate/whoop-api) and [WHOOP's webhook documentation](https://developer.whoop.com/docs/developing/webhooks/).

### Polar AccessLink

HMAC signature

- HMAC-SHA256 of the payload, keyed by the signature_secret_key returned when you created the webhook; hex, in Polar-Webhook-Signature. Event type in Polar-Webhook-Event.
- Authorize: `https://flow.polar.com/oauth2/authorization`
- Token: `https://polarremote.com/v2/oauth2/token` (HTTP Basic)
- Default scopes: `accesslink.read_all`
- PKCE: not sent: the guide does not document it
- Polar's token endpoint takes HTTP Basic client credentials.
- The authorization code expires in 10 minutes and must be used once: a second use makes Polar delete every token issued.
- The response carries x_user_id, the Polar user id later paths use.

Sources: [Polar AccessLink integration guide](https://aifitnessapi.com/integrate/polar-api) and [Polar AccessLink's webhook documentation](https://www.polar.com/accesslink-api/).

### Strava

Validation handshake; events unsigned

- No signature is documented for event POSTs. Subscription validation is a GET with hub.mode, hub.challenge and hub.verify_token; echo hub.challenge as JSON within two seconds.
- Authorize: `https://www.strava.com/oauth/authorize`
- Token: `https://www.strava.com/oauth/token` (credentials in the form body)
- Default scopes: `read,activity:read_all`
- PKCE: not sent: the guide does not document it
- Strava scopes are comma-separated, and activity:read (or activity:read_all) is required for activity webhooks.
- Access tokens expire six hours after creation; once a new refresh token is returned the old one is invalidated immediately.

Sources: [Strava integration guide](https://aifitnessapi.com/integrate/strava-api) and [Strava's webhook documentation](https://developers.strava.com/docs/webhooks/).

### Wahoo

Shared token in the body

- No signature. Wahoo sends the webhook_token you configured inside the JSON body; ignore any request without it.
- Authorize: `https://api.wahooligan.com/oauth/authorize`
- Token: `https://api.wahooligan.com/oauth/token` (credentials in the form body)
- Default scopes: `user_read workouts_read offline_data`
- PKCE: not sent: the guide does not document it
- Without user_read Wahoo returns 403, and offline_data is required for webhooks.
- Access tokens expire after 2 hours; an app may hold 10 unrevoked access tokens per user, so refresh only right before a call.

Sources: [Wahoo integration guide](https://aifitnessapi.com/integrate/wahoo-api) and [Wahoo's webhook documentation](https://cloud-api.wahooligan.com/).

## Not included, and why

- **Withings.** Its token exchange is a requesttoken call signed with a nonce from its signature service, which a generic skeleton cannot do without guessing; see the Withings guide and the Withings notification router recipe.
- **Terra.** Terra connects users through its own widget rather than a per-vendor OAuth app you register, so there is no callback to scaffold; its webhook signature is in the webhook verifier.
- **Google Health API.** It uses Google's OAuth, not a vendor-specific endpoint the site documents here; its webhook rule is in the webhook verifier.
- **Garmin.** Garmin's public pages state no request format or signing rule for its webhooks.

## How the templates are tested

`scripts/starter-scaffold.test.mjs` generates every stack with each vendor alone and with all five. The Express skeleton passes `node --check`, is started on a free port, and is driven over HTTP: the authorize redirect, the code exchange against a fake token endpoint, a single-use state, and each vendor’s webhook as the webhook verifier builds it from the vendor’s own example, accepted unchanged and refused when tampered with. The Next.js skeleton passes `tsc --noEmit` in strict mode against Next.js and Node types, and its webhook module runs the same cases. The FastAPI skeleton passes `python -m py_compile`, and its standard-library webhook module runs the same cases. The ZIP is read back by the site’s own ZIP reader and by Python’s zipfile.

What it does not do: the stores keep tokens in memory, the start route takes the user id as a query parameter, and the event handler only logs. The README in each download says what to replace, and the [cookbook](https://aifitnessapi.com/cookbook) has tested code for each: token encryption at rest, refresh rotation and a replay-safe receiver.

A free tool from AIFitnessAPI. More at [free tools for health-app builders](https://aifitnessapi.com/tools).

## The other 15 tools

- [Which error is this?](https://aifitnessapi.com/tools/error-diagnoser): Paste an error string and get the matching HKError.Code case in Apple's wording, plus the guide that covers it.
- [Sum it or average it?](https://aifitnessapi.com/tools/aggregation-checker): Whether Apple describes a quantity type as cumulative or discrete, with the sentence that says so.
- [Apple type, Android record](https://aifitnessapi.com/tools/identifier-translator): Two-way lookup between HealthKit identifiers and Health Connect records — verified pairs only.
- [HealthKit permission builder](https://aifitnessapi.com/tools/permission-builder): Pick the types your app touches: the Info.plist keys, the toShare/toRead Swift, and the Health Connect record names.
- [HealthKit query generator](https://aifitnessapi.com/tools/query-generator): Pick a quantity type and a window; get the HKStatisticsQuery with the aggregation option Apple's own prose states.
- [Fitness app stack generator](https://aifitnessapi.com/tools/stack-generator): Answer four questions and get the HealthKit types and APIs that survive them, with the exclusions shown.
- [Synthetic health data generator](https://aifitnessapi.com/tools/sample-data): Seeded synthetic HealthKit, Health Connect and Google Health API fixtures as JSON or CSV, with per-record UTC offsets.
- [Is this webhook real?](https://aifitnessapi.com/tools/webhook-verifier): Paste an Oura, WHOOP, Terra, Polar, Strava, Withings, Wahoo or Google Health API webhook: the vendor's documented check, run in your browser.
- [Wearable OAuth playground](https://aifitnessapi.com/tools/oauth-playground): Pick Oura, WHOOP, Strava, Polar, Withings, Wahoo, Dexcom, Suunto or Google Health: documented endpoints and scopes, an authorize URL, a PKCE pair and token curl. Sends nothing.
- [Pose keypoint playground](https://aifitnessapi.com/tools/keypoint-playground): Click a joint and see its index in BlazePose, COCO, OpenPose, ML Kit and Apple Vision, then copy an index map built from matching names only.
- [Exercise form rules](https://aifitnessapi.com/tools/exercise-form-rules): The joint-angle thresholds open-source exercise trackers use for squats, push-ups, lunges, planks, curls and presses, each quoted from its code; null where none says.
- [Exercise taxonomy crosswalk](https://aifitnessapi.com/tools/exercise-taxonomy-crosswalk): free-exercise-db, wger and ExerciseDB v1 categories, equipment and muscles against HealthKit and Health Connect workout types, matched by name only.
- [Health data schema converter](https://aifitnessapi.com/tools/schema-converter): Paste a HealthKit export row, Health Connect record or Google Health API data point and see it in one unified schema, each field traced to its source.
- [Fitbit to Google Health API map](https://aifitnessapi.com/tools/fitbit-to-google-health): Search every Fitbit Web API endpoint and see its Google Health API data type, method and scopes from Google's migration tables, or that none is listed.
- [Deprecation scanner](https://aifitnessapi.com/tools/deprecation-scanner): Paste code or a manifest and find Fitbit Web API, Google Fit and other retired fitness API calls, each with its replacement. Also a GitHub Action.

The planners and demos — the API picker, the cost planner, the side-by-side comparison, the day-boundary demo, the pose-model benchmark, the Bluetooth heart-rate lab and the step lab — are listed with these on [free tools for health-app builders](https://aifitnessapi.com/tools).

## Next step

Turning this into a choice of API:

- [Integration Guides](https://aifitnessapi.com/integrate)
- [The Best Wearable Fitness & Health-Data APIs (2026)](https://aifitnessapi.com/fitness-apis/wearable-data-apis)
- [How Much Does a Fitness API Cost?](https://aifitnessapi.com/pricing/how-much-does-a-fitness-api-cost)
