Referenz
Endpoints
Alles liegt unter https://api.gravl.ai und gibt JSON zurück. Nutzerdaten sind über stabile GUIDs identifiziert, Katalog-Ressourcen über Integer-IDs. Enum-Werte sind camelCase-Strings; behandle unbekannte Werte als vorwärtskompatibel.
Alle Endpoints
| Methode | Pfad | Scope |
|---|---|---|
| GET | /api/v1/user | profile:read |
| GET | /api/v1/workouts | workouts:read |
| GET | /api/v1/workouts/{id} | workouts:read |
| GET | /api/v1/workout-templates | workouts:read |
| GET | /api/v1/workout-templates/{id} | workouts:read |
| GET | /api/v1/splits | splits:read |
| GET | /api/v1/splits/{id} | splits:read |
| GET | /api/v1/personal-records | records:read |
| GET | /api/v1/measurements | measurements:read |
| GET | /api/v1/stats | stats:read |
| GET | /api/v1/trophies | stats:read |
| GET | /api/v1/exercises | exercises:read |
| GET | /api/v1/exercises/{id} | exercises:read |
| GET | /api/v1/equipment | exercises:read |
| GET | /api/v1/equipment/{id} | exercises:read |
| GET | /oauth/authorize | — |
| POST | /oauth/token | — |
| POST | /oauth/revoke | — |
Der Pagination-Envelope
Endpoints, die unten als paginiert markiert sind, verpacken ihre Einträge in einem einheitlichen Envelope:
{
"items": [ … ], // the page of results
"pageNumber": 2,
"totalPages": 9,
"totalCount": 174,
"hasPreviousPage": true,
"hasNextPage": true
}Jeder Endpoint erfordert seinen Scope; ein fehlender Scope gibt 403 mit Problem Details zurück. Fehler und Rate Limits sind unter Konventionen dokumentiert.
Nutzer
/api/v1/userprofile:readDas Profil des autorisierenden Nutzers: Anzeigefelder, bevorzugte Einheiten und Trainingsziel.
Response — Nutzerprofil
| Feld | Typ | Beschreibung |
|---|---|---|
| id | string | Stabile Nutzer-ID. Verwende sie als Schlüssel für deine Datenhaltung. |
| displayName | string | null | |
| username | string | null | |
| weightUnit | "kilograms" | "pounds" | Bevorzugte Anzeigeeinheit. Gewichte in der API sind immer Pfund. |
| distanceUnit | "kilometers" | "miles" | |
| gender | "male" | "female" | "other" | null | |
| height | number | null | In der Einheit aus heightUnit. |
| heightUnit | "centimeters" | "feetInches" | |
| goal | "stronger" | "muscleMass" | "lean" | "generalFitness" | null |
{
"id": "8fKq2ZbXvNc…",
"displayName": "Alex",
"username": "alexlifts",
"weightUnit": "pounds",
"distanceUnit": "kilometers",
"gender": "male",
"height": 183,
"heightUnit": "centimeters",
"goal": "muscleMass"
}Workouts
/api/v1/workoutsworkouts:readpaginiertDer Workout-Verlauf des Nutzers, neueste zuerst.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
startDate | query | string (ISO 8601) | Nur Workouts, die an oder nach diesem Zeitpunkt beginnen (inklusive). |
endDate | query | string (ISO 8601) | Nur Workouts, die an oder vor diesem Zeitpunkt beginnen (inklusive). |
Response — Workout-Übersicht (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| notes | string | null | |
| startDate | string (ISO 8601, UTC) | |
| endDate | string (ISO 8601, UTC) | |
| durationMinutes | integer | |
| type | string | Herkunft des Workouts, z. B. "custom", "external". Neue Werte können hinzukommen. |
| volume | number | Insgesamt bewegtes Volumen, in Pfund. |
| calories | number | |
| personalRecordCount | integer | In diesem Workout aufgestellte PRs. |
| exerciseCount | integer |
{
"items": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Push Day",
"notes": null,
"startDate": "2026-07-12T17:03:00Z",
"endDate": "2026-07-12T18:05:00Z",
"durationMinutes": 62,
"type": "custom",
"volume": 24150,
"calories": 410,
"personalRecordCount": 1,
"exerciseCount": 6
}
],
"pageNumber": 1,
"totalPages": 87,
"totalCount": 174,
"hasPreviousPage": false,
"hasNextPage": true
}/api/v1/workouts/{id}workouts:readEin Workout vollständig: jede Übung mit ihren geloggten Sätzen, in ausgeführter Reihenfolge.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
iderforderlich | path | string (uuid) | Die Workout-ID aus dem Listen-Endpoint. |
Response — Workout-Detail
| Feld | Typ | Beschreibung |
|---|---|---|
| …alle Felder der Workout-Übersicht | Wie das Listen-Element, ohne exerciseCount. | |
| exercises[].exerciseId | integer | Übungs-ID aus dem Katalog. |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | Übungen mit derselben ID wurden als Supersatz ausgeführt. |
| exercises[].sets[].order | integer | 1-basierte Position innerhalb der Übung. |
| exercises[].sets[].reps | integer | Ausgeführte Wiederholungen. |
| exercises[].sets[].weight | number | Pfund. |
| exercises[].sets[].duration | integer | null | Sekunden, bei Sätzen auf Zeit. |
| exercises[].sets[].distance | integer | null | Meter, bei Sätzen auf Distanz. |
| exercises[].sets[].rpe | integer | null | Empfundene Anstrengung (RPE, 1–10). |
| exercises[].sets[].setType | "normal" | "warmup" | "dropSet" | "failure" |
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Push Day",
"startDate": "2026-07-12T17:03:00Z",
"endDate": "2026-07-12T18:05:00Z",
"durationMinutes": 62,
"type": "custom",
"volume": 24150,
"calories": 410,
"personalRecordCount": 1,
"exercises": [
{
"exerciseId": 42,
"exerciseName": "Bench Press",
"supersetId": null,
"sets": [
{ "order": 1, "reps": 10, "weight": 155, "duration": null,
"distance": null, "rpe": 7, "setType": "normal" },
{ "order": 2, "reps": 8, "weight": 175, "duration": null,
"distance": null, "rpe": 9, "setType": "normal" }
]
}
]
}/api/v1/workout-templatesworkouts:readpaginiertDie gespeicherten Workout-Vorlagen des Nutzers, neueste zuerst. Sätze sind geplante Werte, keine Logs.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
Response — Vorlagen-Übersicht (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| description | string | null | |
| exerciseCount | integer | |
| exercises | array | In Listen-Responses immer leer; nutze den Detail-Endpoint. |
/api/v1/workout-templates/{id}workouts:readEine Vorlage mit ihren geplanten Übungen und Sätzen.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
iderforderlich | path | string (uuid) | Die Vorlagen-ID aus dem Listen-Endpoint. |
Response — Vorlagen-Detail
| Feld | Typ | Beschreibung |
|---|---|---|
| …alle Felder der Vorlagen-Übersicht | ||
| exercises[].exerciseId | integer | |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | |
| exercises[].sets[].order | integer | 1-basierte Position innerhalb der Übung. |
| exercises[].sets[].reps | integer | null | Geplante Wiederholungen. |
| exercises[].sets[].weight | number | null | Pfund. |
| exercises[].sets[].duration | integer | null | Sekunden, bei Sätzen auf Zeit. |
| exercises[].sets[].distance | integer | null | Meter, bei Sätzen auf Distanz. |
| exercises[].sets[].rpe | integer | null | Empfundene Anstrengung (RPE, 1–10). |
| exercises[].sets[].setType | "normal" | "warmup" | "dropSet" | "failure" |
/api/v1/splitssplits:readpaginiertDie eigenen Trainingssplits des Nutzers, neueste zuerst.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
Response — Split-Übersicht (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| id | string (uuid) | |
| name | string | null | |
| description | string | null | |
| sessionCount | integer | |
| sessions | array | In Listen-Responses immer leer; nutze den Detail-Endpoint. |
/api/v1/splits/{id}splits:readEin Split mit seinen Sessions in Reihenfolge.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
iderforderlich | path | string (uuid) | Die Split-ID aus dem Listen-Endpoint. |
Response — Split-Detail
| Feld | Typ | Beschreibung |
|---|---|---|
| …alle Felder der Split-Übersicht | ||
| sessions[].order | integer | |
| sessions[].type | "muscles" | "workoutTemplate" | |
| sessions[].muscles | string[] | Zielmuskeln (camelCase, z. B. "chest"); gesetzt, wenn type muscles ist. |
| sessions[].workoutTemplateId | string (uuid) | null | Verknüpfte Vorlage; gesetzt, wenn type workoutTemplate ist. |
Rekorde & Messungen
/api/v1/personal-recordsrecords:readpaginiertDie persönlichen Rekorde des Nutzers, neueste zuerst.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
exerciseId | query | integer | Auf eine Katalog-Übung filtern. |
Response — Persönlicher Rekord (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| exerciseId | integer | |
| exerciseName | string | |
| type | "oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax" | |
| reps | integer | null | |
| weight | number | null | Pfund. |
| duration | integer | null | Sekunden. |
| distance | integer | null | Meter. |
| workoutId | string (uuid) | null | Workout, in dem der Rekord aufgestellt wurde, sofern bekannt. |
| achievedAt | string (ISO 8601, UTC) |
{
"exerciseId": 87,
"exerciseName": "Deadlift",
"type": "oneRepMax",
"reps": 1,
"weight": 405,
"duration": null,
"distance": null,
"workoutId": "a1b2c3d4-…",
"achievedAt": "2026-06-28T18:41:00Z"
}/api/v1/measurementsmeasurements:readpaginiertKörpergewichts- und Umfangseinträge, neueste zuerst.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
startDate | query | string (ISO 8601) | Nur Einträge mit Datum an oder nach diesem Wert (inklusive). |
endDate | query | string (ISO 8601) | Nur Einträge mit Datum an oder vor diesem Wert (inklusive). |
Response — Messung (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| id | string (uuid) | |
| date | string (ISO 8601 date) | |
| weightUnit | "kilograms" | "pounds" | Einheitensystem dieses Eintrags: Umfänge sind Inches bei Pfund, Zentimeter bei Kilogramm. |
| weight | number | null | |
| bodyFat | number | null | Prozent. |
| neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calf | number | null | Umfänge im Einheitensystem des Eintrags. |
Stats & Trophäen
/api/v1/statsstats:readDie zentralen Trainings-Stats des Nutzers in einem einzigen Request.
Response — Stats
| Feld | Typ | Beschreibung |
|---|---|---|
| streak.current | integer | Aktuelle Streak, in Wochen. |
| streak.longest | integer | Längste Streak aller Zeiten, in Wochen. |
| streak.workoutsThisWeek | integer | |
| totalXp | integer | |
| trophyCount | integer |
{
"streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
"totalXp": 12480,
"trophyCount": 22
}/api/v1/trophiesstats:readpaginiertErrungene Trophäen, neueste zuerst.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
Response — Trophäe (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| type | string | Trophäen-Art, camelCase, z. B. "workout100", "streak5", "calendar15". Neue Werte können hinzukommen. |
| exerciseId | integer | null | Gesetzt bei übungsspezifischen Trophäen. |
| exerciseName | string | null | |
| achievedAt | string (ISO 8601, UTC) |
Übungs- & Equipment-Katalog
Globale Referenzdaten, identifiziert über dieselben stabilen Integer-IDs wie in der Gravl-App. Übungslisten enthalten außerdem die eigenen Custom-Übungen des autorisierenden Nutzers, niemals die anderer Nutzer.
/api/v1/exercisesexercises:readpaginiertDer Übungskatalog plus die Custom-Übungen des Nutzers, alphabetisch.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
search | query | string | Namenssuche ohne Beachtung der Groß-/Kleinschreibung (enthält). Max. 200 Zeichen. |
Response — Übung (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| id | integer | Stabile Katalog-ID; referenziert von Workouts, Rekorden und Vorlagen. |
| name | string | |
| description | string | null | |
| category | string | camelCase-Enum, z. B. "barbell", "dumbbell". |
| type | string | camelCase-Enum, z. B. "weight", "duration". |
| muscle | string | Primärer Muskel, camelCase, z. B. "chest". |
| secondaryMuscles | string[] | |
| equipment | string[] | Equipment-Namen, die für diese Übung nutzbar sind. |
/api/v1/exercises/{id}exercises:readEine Übung per Katalog-ID.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
iderforderlich | path | integer | Übungs-ID aus dem Katalog. |
Response — Übung
| Feld | Typ | Beschreibung |
|---|---|---|
| …wie das Listen-Element |
/api/v1/equipmentexercises:readpaginiertDer globale Equipment-Katalog, alphabetisch.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
page | query | integer | 1-basierte Seitennummer. Standard: 1 |
pageSize | query | integer | Einträge pro Seite, 1–100. Standard: 30 |
Response — Equipment (als items[] im Pagination-Envelope)
| Feld | Typ | Beschreibung |
|---|---|---|
| id | integer | Stabile Katalog-ID. |
| name | string | |
| subtitle | string | null | |
| category | string | camelCase-Enum. |
/api/v1/equipment/{id}exercises:readEin Equipment-Eintrag per Katalog-ID.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
iderforderlich | path | integer | Equipment-ID aus dem Katalog. |
Response — Equipment
| Feld | Typ | Beschreibung |
|---|---|---|
| …wie das Listen-Element |
OAuth
Token-Endpoints sind form-encoded (application/x-www-form-urlencoded) und folgen RFC 6749/7009. Der vollständige Flow ist auf der Seite zu OAuth-Apps dokumentiert.
/oauth/tokenLöst einen Authorization Code ein oder rotiert ein Refresh Token, jeweils gegen ein neues Access/Refresh-Token-Paar.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
grant_typeerforderlich | form | string | "authorization_code" oder "refresh_token". |
client_iderforderlich | form | string | |
client_secret | form | string | Nur für Confidential Clients; Public Clients lassen es weg (stattdessen PKCE). |
code | form | string | authorization_code-Grant: der gac_…-Code. |
redirect_uri | form | string | authorization_code-Grant: muss der URI entsprechen, für die der Code ausgestellt wurde. |
code_verifier | form | string | authorization_code-Grant: der PKCE-Verifier zu deiner Challenge. |
refresh_token | form | string | refresh_token-Grant: das grt_…-Token (einmalig verwendbar, wird bei Erfolg rotiert). |
Response — Token-Response
| Feld | Typ | Beschreibung |
|---|---|---|
| access_token | string | Bearer Token (gat_…), 6 Stunden gültig. |
| token_type | "Bearer" | |
| expires_in | integer | Sekunden bis zum Ablauf des Access Tokens. |
| refresh_token | string | Neues rotierendes Refresh Token (grt_…), bis zu 180 Tage gültig. |
| scope | string | Gewährte Scopes, durch Leerzeichen getrennt. |
{
"access_token": "gat_…",
"token_type": "Bearer",
"expires_in": 21600,
"refresh_token": "grt_…",
"scope": "workouts:read stats:read"
}/oauth/revokeWiderruft ein Token (RFC 7009). Der Widerruf eines Refresh Tokens widerruft den gesamten Grant. Gibt auch bei unbekannten Tokens 200 zurück.
Parameter
| Name | In | Typ | Beschreibung |
|---|---|---|---|
tokenerforderlich | form | string | Das zu widerrufende Access oder Refresh Token. |
client_iderforderlich | form | string | |
client_secret | form | string | Nur für Confidential Clients. |
Response — Leer
| Feld | Typ | Beschreibung |
|---|---|---|
| (kein Body) | 200 bei Erfolg; 401 invalid_client bei falschen Client-Credentials. |