Application metadata¶
Apps can attach per-app JSON metadata to activities, traces, tests, and users. Metadata is keyed by the application that wrote it, scoped to a single user, and only visible to the app that wrote it.
This is useful when your app needs to store a small amount of state alongside SweatStack data without running its own database. Examples:
- A coaching app annotating activities with workout-plan IDs.
- A recovery app marking traces with its own readiness scores.
- An analytics app recording per-user feature flags without standing up a profile store.
Metadata is invisible to other apps reading the same entities. SweatStack does not interpret it.
Requirements¶
App metadata endpoints require an application token (a token issued via OAuth2 to a registered application). Personal user tokens (e.g. an API key generated in settings) cannot read or write metadata.
If your token doesn't have an aud (audience) claim identifying an application, every metadata endpoint returns 403.
The required scope is data:write.
Endpoints¶
The same shape applies to four entity types.
| Entity | Upsert | Delete |
|---|---|---|
| Activity | PUT /api/v1/activities/{id}/app-metadata |
DELETE /api/v1/activities/{id}/app-metadata |
| Trace | PUT /api/v1/traces/{id}/app-metadata |
DELETE /api/v1/traces/{id}/app-metadata |
| Test | PUT /api/v1/tests/{id}/app-metadata |
DELETE /api/v1/tests/{id}/app-metadata |
| User (current authenticated user) | PUT /api/v1/profile/app-metadata |
DELETE /api/v1/profile/app-metadata |
Writing metadata¶
The PUT body is a freeform JSON object. SweatStack does not constrain the shape beyond size and nesting limits.
curl -X PUT "https://app.sweatstack.no/api/v1/activities/{activity_id}/app-metadata" \
-H "Authorization: Bearer {your_app_access_token}" \
-H "Content-Type: application/json" \
-d '{
"workout_plan_id": "plan_42",
"intended_intensity": "z2",
"notes": "easy recovery ride"
}'
PUT replaces the metadata blob in full. There is no merge or patch operation. Read the existing metadata first if you need to update a single field.
Deleting metadata¶
curl -X DELETE "https://app.sweatstack.no/api/v1/activities/{activity_id}/app-metadata" \
-H "Authorization: Bearer {your_app_access_token}"
Returns 204 on success. Does not error if no metadata exists.
Reading metadata back¶
Metadata is automatically merged onto the entity's response under an app_metadata field, but only when the request is made by the same application that wrote it.
For an activity:
curl -X GET "https://app.sweatstack.no/api/v1/activities/{activity_id}" \
-H "Authorization: Bearer {your_app_access_token}"
The response includes:
{
"id": "...",
"sport": "cycling.road",
"start": "2026-05-06T10:00:00+02:00",
"...": "...",
"app_metadata": {
"workout_plan_id": "plan_42",
"intended_intensity": "z2",
"notes": "easy recovery ride"
}
}
If a different app reads the same activity, app_metadata is omitted (or contains the other app's data, never both).
Limits¶
| Entity | Maximum size (raw JSON body) | Maximum nesting depth |
|---|---|---|
| Activity | 1024 bytes | 32 |
| Trace | 1024 bytes | 32 |
| Test | 1024 bytes | 32 |
| User | 4096 bytes | 32 |
Exceeding the size limit returns 413. Exceeding the depth limit returns 422.
Patterns and limits¶
- Use it for app state, not user data. Metadata is keyed to your app and invisible to others. If the user deletes your app's authorization, your metadata is no longer reachable.
- Don't put PII in here that the user wouldn't expect. Metadata follows the user's data and is included in their export.
- Don't use it as a primary store. Per-entity round trips are not a substitute for an indexed database. For app state that needs querying, run a small store on your side.
- Do use it for small, per-entity annotations. Workout plan IDs, intent labels, your own scoring outputs. Things that meaningfully belong attached to the SweatStack record.
Python client¶
App metadata endpoints aren't yet exposed in the Python client. Use Client.session or any HTTP library until the methods land.