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

MethodePfadScope
GET/api/v1/userprofile:read
GET/api/v1/workoutsworkouts:read
GET/api/v1/workouts/{id}workouts:read
GET/api/v1/workout-templatesworkouts:read
GET/api/v1/workout-templates/{id}workouts:read
GET/api/v1/splitssplits:read
GET/api/v1/splits/{id}splits:read
GET/api/v1/personal-recordsrecords:read
GET/api/v1/measurementsmeasurements:read
GET/api/v1/statsstats:read
GET/api/v1/trophiesstats:read
GET/api/v1/exercisesexercises:read
GET/api/v1/exercises/{id}exercises:read
GET/api/v1/equipmentexercises: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

GET/api/v1/userprofile:read

Das Profil des autorisierenden Nutzers: Anzeigefelder, bevorzugte Einheiten und Trainingsziel.

Response — Nutzerprofil

FeldTypBeschreibung
idstringStabile Nutzer-ID. Verwende sie als Schlüssel für deine Datenhaltung.
displayNamestring | null
usernamestring | null
weightUnit"kilograms" | "pounds"Bevorzugte Anzeigeeinheit. Gewichte in der API sind immer Pfund.
distanceUnit"kilometers" | "miles"
gender"male" | "female" | "other" | null
heightnumber | nullIn der Einheit aus heightUnit.
heightUnit"centimeters" | "feetInches"
goal"stronger" | "muscleMass" | "lean" | "generalFitness" | null
Beispiel-Response
{
  "id": "8fKq2ZbXvNc…",
  "displayName": "Alex",
  "username": "alexlifts",
  "weightUnit": "pounds",
  "distanceUnit": "kilometers",
  "gender": "male",
  "height": 183,
  "heightUnit": "centimeters",
  "goal": "muscleMass"
}

Workouts

GET/api/v1/workoutsworkouts:readpaginiert

Der Workout-Verlauf des Nutzers, neueste zuerst.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30
startDatequerystring (ISO 8601)Nur Workouts, die an oder nach diesem Zeitpunkt beginnen (inklusive).
endDatequerystring (ISO 8601)Nur Workouts, die an oder vor diesem Zeitpunkt beginnen (inklusive).

Response — Workout-Übersicht (als items[] im Pagination-Envelope)

FeldTypBeschreibung
idstring (uuid)
namestring
notesstring | null
startDatestring (ISO 8601, UTC)
endDatestring (ISO 8601, UTC)
durationMinutesinteger
typestringHerkunft des Workouts, z. B. "custom", "external". Neue Werte können hinzukommen.
volumenumberInsgesamt bewegtes Volumen, in Pfund.
caloriesnumber
personalRecordCountintegerIn diesem Workout aufgestellte PRs.
exerciseCountinteger
Beispiel-Response
{
  "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
}
GET/api/v1/workouts/{id}workouts:read

Ein Workout vollständig: jede Übung mit ihren geloggten Sätzen, in ausgeführter Reihenfolge.

Parameter

NameInTypBeschreibung
iderforderlichpathstring (uuid)Die Workout-ID aus dem Listen-Endpoint.

Response — Workout-Detail

FeldTypBeschreibung
…alle Felder der Workout-ÜbersichtWie das Listen-Element, ohne exerciseCount.
exercises[].exerciseIdintegerÜbungs-ID aus dem Katalog.
exercises[].exerciseNamestring
exercises[].supersetIdinteger | nullÜbungen mit derselben ID wurden als Supersatz ausgeführt.
exercises[].sets[].orderinteger1-basierte Position innerhalb der Übung.
exercises[].sets[].repsintegerAusgeführte Wiederholungen.
exercises[].sets[].weightnumberPfund.
exercises[].sets[].durationinteger | nullSekunden, bei Sätzen auf Zeit.
exercises[].sets[].distanceinteger | nullMeter, bei Sätzen auf Distanz.
exercises[].sets[].rpeinteger | nullEmpfundene Anstrengung (RPE, 1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
Beispiel-Response
{
  "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" }
      ]
    }
  ]
}
GET/api/v1/workout-templatesworkouts:readpaginiert

Die gespeicherten Workout-Vorlagen des Nutzers, neueste zuerst. Sätze sind geplante Werte, keine Logs.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30

Response — Vorlagen-Übersicht (als items[] im Pagination-Envelope)

FeldTypBeschreibung
idstring (uuid)
namestring
descriptionstring | null
exerciseCountinteger
exercisesarrayIn Listen-Responses immer leer; nutze den Detail-Endpoint.
GET/api/v1/workout-templates/{id}workouts:read

Eine Vorlage mit ihren geplanten Übungen und Sätzen.

Parameter

NameInTypBeschreibung
iderforderlichpathstring (uuid)Die Vorlagen-ID aus dem Listen-Endpoint.

Response — Vorlagen-Detail

FeldTypBeschreibung
…alle Felder der Vorlagen-Übersicht
exercises[].exerciseIdinteger
exercises[].exerciseNamestring
exercises[].supersetIdinteger | null
exercises[].sets[].orderinteger1-basierte Position innerhalb der Übung.
exercises[].sets[].repsinteger | nullGeplante Wiederholungen.
exercises[].sets[].weightnumber | nullPfund.
exercises[].sets[].durationinteger | nullSekunden, bei Sätzen auf Zeit.
exercises[].sets[].distanceinteger | nullMeter, bei Sätzen auf Distanz.
exercises[].sets[].rpeinteger | nullEmpfundene Anstrengung (RPE, 1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
GET/api/v1/splitssplits:readpaginiert

Die eigenen Trainingssplits des Nutzers, neueste zuerst.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30

Response — Split-Übersicht (als items[] im Pagination-Envelope)

FeldTypBeschreibung
idstring (uuid)
namestring | null
descriptionstring | null
sessionCountinteger
sessionsarrayIn Listen-Responses immer leer; nutze den Detail-Endpoint.
GET/api/v1/splits/{id}splits:read

Ein Split mit seinen Sessions in Reihenfolge.

Parameter

NameInTypBeschreibung
iderforderlichpathstring (uuid)Die Split-ID aus dem Listen-Endpoint.

Response — Split-Detail

FeldTypBeschreibung
…alle Felder der Split-Übersicht
sessions[].orderinteger
sessions[].type"muscles" | "workoutTemplate"
sessions[].musclesstring[]Zielmuskeln (camelCase, z. B. "chest"); gesetzt, wenn type muscles ist.
sessions[].workoutTemplateIdstring (uuid) | nullVerknüpfte Vorlage; gesetzt, wenn type workoutTemplate ist.

Rekorde & Messungen

GET/api/v1/personal-recordsrecords:readpaginiert

Die persönlichen Rekorde des Nutzers, neueste zuerst.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30
exerciseIdqueryintegerAuf eine Katalog-Übung filtern.

Response — Persönlicher Rekord (als items[] im Pagination-Envelope)

FeldTypBeschreibung
exerciseIdinteger
exerciseNamestring
type"oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax"
repsinteger | null
weightnumber | nullPfund.
durationinteger | nullSekunden.
distanceinteger | nullMeter.
workoutIdstring (uuid) | nullWorkout, in dem der Rekord aufgestellt wurde, sofern bekannt.
achievedAtstring (ISO 8601, UTC)
Beispiel-Response
{
  "exerciseId": 87,
  "exerciseName": "Deadlift",
  "type": "oneRepMax",
  "reps": 1,
  "weight": 405,
  "duration": null,
  "distance": null,
  "workoutId": "a1b2c3d4-…",
  "achievedAt": "2026-06-28T18:41:00Z"
}
GET/api/v1/measurementsmeasurements:readpaginiert

Körpergewichts- und Umfangseinträge, neueste zuerst.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30
startDatequerystring (ISO 8601)Nur Einträge mit Datum an oder nach diesem Wert (inklusive).
endDatequerystring (ISO 8601)Nur Einträge mit Datum an oder vor diesem Wert (inklusive).

Response — Messung (als items[] im Pagination-Envelope)

FeldTypBeschreibung
idstring (uuid)
datestring (ISO 8601 date)
weightUnit"kilograms" | "pounds"Einheitensystem dieses Eintrags: Umfänge sind Inches bei Pfund, Zentimeter bei Kilogramm.
weightnumber | null
bodyFatnumber | nullProzent.
neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calfnumber | nullUmfänge im Einheitensystem des Eintrags.

Stats & Trophäen

GET/api/v1/statsstats:read

Die zentralen Trainings-Stats des Nutzers in einem einzigen Request.

Response — Stats

FeldTypBeschreibung
streak.currentintegerAktuelle Streak, in Wochen.
streak.longestintegerLängste Streak aller Zeiten, in Wochen.
streak.workoutsThisWeekinteger
totalXpinteger
trophyCountinteger
Beispiel-Response
{
  "streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
  "totalXp": 12480,
  "trophyCount": 22
}
GET/api/v1/trophiesstats:readpaginiert

Errungene Trophäen, neueste zuerst.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30

Response — Trophäe (als items[] im Pagination-Envelope)

FeldTypBeschreibung
typestringTrophäen-Art, camelCase, z. B. "workout100", "streak5", "calendar15". Neue Werte können hinzukommen.
exerciseIdinteger | nullGesetzt bei übungsspezifischen Trophäen.
exerciseNamestring | null
achievedAtstring (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.

GET/api/v1/exercisesexercises:readpaginiert

Der Übungskatalog plus die Custom-Übungen des Nutzers, alphabetisch.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30
searchquerystringNamenssuche ohne Beachtung der Groß-/Kleinschreibung (enthält). Max. 200 Zeichen.

Response — Übung (als items[] im Pagination-Envelope)

FeldTypBeschreibung
idintegerStabile Katalog-ID; referenziert von Workouts, Rekorden und Vorlagen.
namestring
descriptionstring | null
categorystringcamelCase-Enum, z. B. "barbell", "dumbbell".
typestringcamelCase-Enum, z. B. "weight", "duration".
musclestringPrimärer Muskel, camelCase, z. B. "chest".
secondaryMusclesstring[]
equipmentstring[]Equipment-Namen, die für diese Übung nutzbar sind.
GET/api/v1/exercises/{id}exercises:read

Eine Übung per Katalog-ID.

Parameter

NameInTypBeschreibung
iderforderlichpathintegerÜbungs-ID aus dem Katalog.

Response — Übung

FeldTypBeschreibung
…wie das Listen-Element
GET/api/v1/equipmentexercises:readpaginiert

Der globale Equipment-Katalog, alphabetisch.

Parameter

NameInTypBeschreibung
pagequeryinteger1-basierte Seitennummer. Standard: 1
pageSizequeryintegerEinträge pro Seite, 1–100. Standard: 30

Response — Equipment (als items[] im Pagination-Envelope)

FeldTypBeschreibung
idintegerStabile Katalog-ID.
namestring
subtitlestring | null
categorystringcamelCase-Enum.
GET/api/v1/equipment/{id}exercises:read

Ein Equipment-Eintrag per Katalog-ID.

Parameter

NameInTypBeschreibung
iderforderlichpathintegerEquipment-ID aus dem Katalog.

Response — Equipment

FeldTypBeschreibung
…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.

GET/oauth/authorize

Gehosteter Consent-Screen: startet den Authorization-Code-Flow. Der Nutzer meldet sich an und stimmt zu; sein Browser wird mit ?code=gac_…&state=… zurückgeleitet.

Parameter

NameInTypBeschreibung
client_iderforderlichquerystringDie Client ID deiner App.
redirect_urierforderlichquerystringMuss exakt einer registrierten Redirect URI entsprechen.
scopequerystringScopes, durch Leerzeichen getrennt. Weglassen für alle Scopes, die deine App darf.
statequerystringZufallswert, der unverändert zurückkommt; prüfe ihn beim Rücksprung (CSRF-Schutz).
code_challengequerystringPKCE-S256-Challenge. Für Public Clients erforderlich.
code_challenge_methodquerystringNur "S256" wird unterstützt.

Response — Redirect

FeldTypBeschreibung
codestringEinmaliger Authorization Code (gac_…), 10 Minuten gültig. Kommt über den Redirect zurück, nicht als JSON.
statestringUnverändert aus dem Request übernommen.
POST/oauth/token

Löst einen Authorization Code ein oder rotiert ein Refresh Token, jeweils gegen ein neues Access/Refresh-Token-Paar.

Parameter

NameInTypBeschreibung
grant_typeerforderlichformstring"authorization_code" oder "refresh_token".
client_iderforderlichformstring
client_secretformstringNur für Confidential Clients; Public Clients lassen es weg (stattdessen PKCE).
codeformstringauthorization_code-Grant: der gac_…-Code.
redirect_uriformstringauthorization_code-Grant: muss der URI entsprechen, für die der Code ausgestellt wurde.
code_verifierformstringauthorization_code-Grant: der PKCE-Verifier zu deiner Challenge.
refresh_tokenformstringrefresh_token-Grant: das grt_…-Token (einmalig verwendbar, wird bei Erfolg rotiert).

Response — Token-Response

FeldTypBeschreibung
access_tokenstringBearer Token (gat_…), 6 Stunden gültig.
token_type"Bearer"
expires_inintegerSekunden bis zum Ablauf des Access Tokens.
refresh_tokenstringNeues rotierendes Refresh Token (grt_…), bis zu 180 Tage gültig.
scopestringGewährte Scopes, durch Leerzeichen getrennt.
Beispiel-Response
{
  "access_token": "gat_…",
  "token_type": "Bearer",
  "expires_in": 21600,
  "refresh_token": "grt_…",
  "scope": "workouts:read stats:read"
}
POST/oauth/revoke

Widerruft ein Token (RFC 7009). Der Widerruf eines Refresh Tokens widerruft den gesamten Grant. Gibt auch bei unbekannten Tokens 200 zurück.

Parameter

NameInTypBeschreibung
tokenerforderlichformstringDas zu widerrufende Access oder Refresh Token.
client_iderforderlichformstring
client_secretformstringNur für Confidential Clients.

Response — Leer

FeldTypBeschreibung
(kein Body)200 bei Erfolg; 401 invalid_client bei falschen Client-Credentials.