Skip to content

Custom calculations

When your app needs activity data, fetch it directly from SweatStack. Don't mirror activities into your own database. SweatStack already stores them; any mirror you build adds a sync problem you don't need.

The one thing you do need to put somewhere is the output of your own calculations on activities: a training-stress score, custom zones, a workout-style classifier. This guide covers how to iterate through a user's activity history reliably, and where to put what you compute.

Track what you've processed by ID, not by timestamp

To know which activities you've already processed, keep a set of activity IDs. Don't track them by start timestamp (something like "I've handled everything before T, only process newer ones").

The reason: backfill ingests a user's history newest-to-oldest on a best-effort basis, so an older activity can finish ingesting after a newer one. A timestamp filter would miss that late arrival. ID tracking sidesteps the ordering entirely.

Where to store the derived value

Two options: app metadata, or your own database.

App metadata keeps the value attached to the SweatStack entity it belongs to, so you don't need your own database. Two endpoints, depending on whether the value is per-activity or per-user.

For per-activity values (training stress, custom zones, classifier output), PUT it to the activity:

PUT /api/v1/activities/{id}/app-metadata
{"training_stress": 87, "version": 2}

The version field also serves as your "processed" flag: if app_metadata.version matches your current code, you're done. You don't need a separate set of activity IDs; each activity carries its own status. Bump the version when the calculation changes and existing activities re-process on next read. The cap is 1024 bytes of JSON.

For per-user values (28-day CTL, FTP estimate, readiness score, anything longitudinal), PUT it to the user:

PUT /api/v1/profile/app-metadata
{"ctl_28d": 62.4, "computed_through_activity": "act_01HV..."}

The cap is 4096 bytes of JSON.

Your own database is the fallback when you outgrow those caps (large model outputs, long text, complex nested structures). Keep the same shape: keyed by activity_id or user_id, holding your derived values only, never a mirror of activity data.

Next steps

  • App metadata: full request and response shapes, scopes, and size limits per entity type.
  • Activities: the activity payload and the summary.* aggregates SweatStack already computes. Check these before computing your own.
  • Onboarding and backfill: what to render during the first-session ingest while history is still arriving.
  • activity_created webhook: process new activities as they arrive instead of re-checking on every load.