Referencja

Endpointy

Wszystko żyje pod https://api.gravl.ai i zwraca JSON. Dane użytkownika są identyfikowane stałymi GUID-ami, a zasoby katalogowe liczbowymi id. Wartości enumów to łańcuchy w camelCase; nieznane wartości traktuj jako zgodne w przód.

Wszystkie endpointy

MetodaŚcieżkaScope
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

Koperta paginacji

Endpointy oznaczone niżej jako paginowane opakowują swoje elementy w jedną standardową kopertę:

{
  "items": [ … ],           // the page of results
  "pageNumber": 2,
  "totalPages": 9,
  "totalCount": 174,
  "hasPreviousPage": true,
  "hasNextPage": true
}

Każdy endpoint wymaga swojego scope’a; brak scope’a zwraca 403 z problem details. Błędy i limity żądań są opisane w sekcji konwencje.

Użytkownik

GET/api/v1/userprofile:read

Profil autoryzującego użytkownika: pola wyświetlane, preferowane jednostki i cel treningowy.

Odpowiedź: Profil użytkownika

PoleTypOpis
idstringStały identyfikator użytkownika. To po nim indeksuj swoje dane.
displayNamestring | null
usernamestring | null
weightUnit"kilograms" | "pounds"Preferowana jednostka wyświetlania. Ciężary w API są zawsze w funtach.
distanceUnit"kilometers" | "miles"
gender"male" | "female" | "other" | null
heightnumber | nullW jednostce wskazanej przez heightUnit.
heightUnit"centimeters" | "feetInches"
goal"stronger" | "muscleMass" | "lean" | "generalFitness" | null
Przykładowa odpowiedź
{
  "id": "8fKq2ZbXvNc…",
  "displayName": "Alex",
  "username": "alexlifts",
  "weightUnit": "pounds",
  "distanceUnit": "kilometers",
  "gender": "male",
  "height": 183,
  "heightUnit": "centimeters",
  "goal": "muscleMass"
}

Treningi

GET/api/v1/workoutsworkouts:readpaginowany

Historia treningów użytkownika, od najnowszych.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30
startDatequerystring (ISO 8601)Tylko treningi rozpoczęte w tym momencie lub później (włącznie).
endDatequerystring (ISO 8601)Tylko treningi rozpoczęte w tym momencie lub wcześniej (włącznie).

Odpowiedź: Podsumowanie treningu (jako items[] w kopercie paginacji)

PoleTypOpis
idstring (uuid)
namestring
notesstring | null
startDatestring (ISO 8601, UTC)
endDatestring (ISO 8601, UTC)
durationMinutesinteger
typestringPochodzenie treningu, np. "custom", "external". Mogą pojawiać się nowe wartości.
volumenumberŁączna podniesiona objętość, w funtach.
caloriesnumber
personalRecordCountintegerRekordy pobite w tym treningu.
exerciseCountinteger
Przykładowa odpowiedź
{
  "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

Pojedynczy trening w całości: każde ćwiczenie z zapisanymi seriami, w kolejności wykonania.

Parametry

NazwaMiejsceTypOpis
idwymaganypathstring (uuid)Id treningu z endpointu listy.

Odpowiedź: Szczegóły treningu

PoleTypOpis
…wszystkie pola podsumowania treninguTakie same jak element listy, bez exerciseCount.
exercises[].exerciseIdintegerId ćwiczenia z katalogu.
exercises[].exerciseNamestring
exercises[].supersetIdinteger | nullĆwiczenia o wspólnym id były wykonane jako superseria.
exercises[].sets[].orderintegerPozycja w ramach ćwiczenia, liczona od 1.
exercises[].sets[].repsintegerWykonane powtórzenia.
exercises[].sets[].weightnumberW funtach.
exercises[].sets[].durationinteger | nullW sekundach, dla serii na czas.
exercises[].sets[].distanceinteger | nullW metrach, dla serii na dystans.
exercises[].sets[].rpeinteger | nullSubiektywna ocena wysiłku (1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
Przykładowa odpowiedź
{
  "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:readpaginowany

Zapisane szablony treningów użytkownika, od najnowszych. Serie to wartości zaplanowane, nie zapisy.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30

Odpowiedź: Podsumowanie szablonu (jako items[] w kopercie paginacji)

PoleTypOpis
idstring (uuid)
namestring
descriptionstring | null
exerciseCountinteger
exercisesarrayW odpowiedziach listy zawsze puste. Skorzystaj z endpointu szczegółów.
GET/api/v1/workout-templates/{id}workouts:read

Pojedynczy szablon z zaplanowanymi ćwiczeniami i seriami.

Parametry

NazwaMiejsceTypOpis
idwymaganypathstring (uuid)Id szablonu z endpointu listy.

Odpowiedź: Szczegóły szablonu

PoleTypOpis
…wszystkie pola podsumowania szablonu
exercises[].exerciseIdinteger
exercises[].exerciseNamestring
exercises[].supersetIdinteger | null
exercises[].sets[].orderintegerPozycja w ramach ćwiczenia, liczona od 1.
exercises[].sets[].repsinteger | nullZaplanowane powtórzenia.
exercises[].sets[].weightnumber | nullW funtach.
exercises[].sets[].durationinteger | nullW sekundach, dla serii na czas.
exercises[].sets[].distanceinteger | nullW metrach, dla serii na dystans.
exercises[].sets[].rpeinteger | nullSubiektywna ocena wysiłku (1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
GET/api/v1/splitssplits:readpaginowany

Własne splity treningowe użytkownika, od najnowszych.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30

Odpowiedź: Podsumowanie splitu (jako items[] w kopercie paginacji)

PoleTypOpis
idstring (uuid)
namestring | null
descriptionstring | null
sessionCountinteger
sessionsarrayW odpowiedziach listy zawsze puste. Skorzystaj z endpointu szczegółów.
GET/api/v1/splits/{id}splits:read

Pojedynczy split z sesjami w kolejności.

Parametry

NazwaMiejsceTypOpis
idwymaganypathstring (uuid)Id splitu z endpointu listy.

Odpowiedź: Szczegóły splitu

PoleTypOpis
…wszystkie pola podsumowania splitu
sessions[].orderinteger
sessions[].type"muscles" | "workoutTemplate"
sessions[].musclesstring[]Docelowe mięśnie (camelCase, np. "chest"); ustawione, gdy type to muscles.
sessions[].workoutTemplateIdstring (uuid) | nullPowiązany szablon; ustawiony, gdy type to workoutTemplate.

Rekordy i pomiary

GET/api/v1/personal-recordsrecords:readpaginowany

Rekordy życiowe użytkownika, od najnowszych.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30
exerciseIdqueryintegerFiltruje do jednego ćwiczenia z katalogu.

Odpowiedź: Rekord życiowy (jako items[] w kopercie paginacji)

PoleTypOpis
exerciseIdinteger
exerciseNamestring
type"oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax"
repsinteger | null
weightnumber | nullW funtach.
durationinteger | nullW sekundach.
distanceinteger | nullW metrach.
workoutIdstring (uuid) | nullTrening, w którym pobito rekord, jeśli znany.
achievedAtstring (ISO 8601, UTC)
Przykładowa odpowiedź
{
  "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:readpaginowany

Wpisy masy ciała i obwodów, od najnowszych.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30
startDatequerystring (ISO 8601)Tylko wpisy z tą datą lub późniejsze (włącznie).
endDatequerystring (ISO 8601)Tylko wpisy z tą datą lub wcześniejsze (włącznie).

Odpowiedź: Pomiar (jako items[] w kopercie paginacji)

PoleTypOpis
idstring (uuid)
datestring (ISO 8601 date)
weightUnit"kilograms" | "pounds"System jednostek tego wpisu: obwody są w calach przy funtach, a w centymetrach przy kilogramach.
weightnumber | null
bodyFatnumber | nullW procentach.
neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calfnumber | nullObwody w systemie jednostek wpisu.

Statystyki i trofea

GET/api/v1/statsstats:read

Najważniejsze statystyki treningowe użytkownika w jednym wywołaniu.

Odpowiedź: Statystyki

PoleTypOpis
streak.currentintegerBieżąca seria treningowa, w tygodniach.
streak.longestintegerNajdłuższa seria w historii, w tygodniach.
streak.workoutsThisWeekinteger
totalXpinteger
trophyCountinteger
Przykładowa odpowiedź
{
  "streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
  "totalXp": 12480,
  "trophyCount": 22
}
GET/api/v1/trophiesstats:readpaginowany

Zdobyte trofea, od najnowszych.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30

Odpowiedź: Trofeum (jako items[] w kopercie paginacji)

PoleTypOpis
typestringRodzaj trofeum, camelCase, np. "workout100", "streak5", "calendar15". Mogą pojawiać się nowe wartości.
exerciseIdinteger | nullUstawione dla trofeów za konkretne ćwiczenie.
exerciseNamestring | null
achievedAtstring (ISO 8601, UTC)

Katalog ćwiczeń i sprzętu

Globalne dane referencyjne, identyfikowane tymi samymi stałymi liczbowymi id, których używa aplikacja Gravl. Listy ćwiczeń zawierają też własne ćwiczenia autoryzującego użytkownika, nigdy ćwiczenia innych użytkowników.

GET/api/v1/exercisesexercises:readpaginowany

Katalog ćwiczeń plus własne ćwiczenia użytkownika, alfabetycznie.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30
searchquerystringDopasowanie nazwy bez rozróżniania wielkości liter (zawiera). Maks. 200 znaków.

Odpowiedź: Ćwiczenie (jako items[] w kopercie paginacji)

PoleTypOpis
idintegerStałe id katalogowe. Odwołują się do niego treningi, rekordy i szablony.
namestring
descriptionstring | null
categorystringEnum w camelCase, np. "barbell", "dumbbell".
typestringEnum w camelCase, np. "weight", "duration".
musclestringGłówny mięsień, camelCase, np. "chest".
secondaryMusclesstring[]
equipmentstring[]Nazwy sprzętu, którego można użyć w tym ćwiczeniu.
GET/api/v1/exercises/{id}exercises:read

Pojedyncze ćwiczenie po id katalogowym.

Parametry

NazwaMiejsceTypOpis
idwymaganypathintegerId ćwiczenia z katalogu.

Odpowiedź: Ćwiczenie

PoleTypOpis
…tak samo jak element listy
GET/api/v1/equipmentexercises:readpaginowany

Globalny katalog sprzętu, alfabetycznie.

Parametry

NazwaMiejsceTypOpis
pagequeryintegerNumer strony, liczony od 1. Domyślnie: 1
pageSizequeryintegerLiczba elementów na stronę, 1–100. Domyślnie: 30

Odpowiedź: Sprzęt (jako items[] w kopercie paginacji)

PoleTypOpis
idintegerStałe id katalogowe.
namestring
subtitlestring | null
categorystringEnum w camelCase.
GET/api/v1/equipment/{id}exercises:read

Pojedynczy element sprzętu po id katalogowym.

Parametry

NazwaMiejsceTypOpis
idwymaganypathintegerId sprzętu z katalogu.

Odpowiedź: Sprzęt

PoleTypOpis
…tak samo jak element listy

OAuth

Endpointy tokenów przyjmują dane formularza (application/x-www-form-urlencoded) i są zgodne z RFC 6749/7009. Pełny przepływ opisuje strona aplikacji OAuth.

GET/oauth/authorize

Hostowany ekran zgody, który rozpoczyna przepływ authorization code. Użytkownik loguje się i zatwierdza, a jego przeglądarka wraca do Ciebie z ?code=gac_…&state=….

Parametry

NazwaMiejsceTypOpis
client_idwymaganyquerystringClient ID Twojej aplikacji.
redirect_uriwymaganyquerystringMusi dokładnie odpowiadać zarejestrowanemu redirect URI.
scopequerystringScope’y rozdzielone spacjami. Pomiń, aby otrzymać wszystkie scope’y dozwolone dla Twojej aplikacji.
statequerystringLosowa wartość odsyłana z powrotem. Zweryfikuj ją przy powrocie (ochrona przed CSRF).
code_challengequerystringWyzwanie PKCE S256. Wymagane dla klientów publicznych.
code_challenge_methodquerystringObsługiwane jest wyłącznie "S256".

Odpowiedź: Przekierowanie

PoleTypOpis
codestringJednorazowy kod autoryzacyjny (gac_…), ważny 10 minut. Zwracany w przekierowaniu, nie jako JSON.
statestringOdesłany z żądania.
POST/oauth/token

Wymienia kod autoryzacyjny lub rotuje token odświeżania na parę tokenów dostępu i odświeżania.

Parametry

NazwaMiejsceTypOpis
grant_typewymaganyformstring"authorization_code" lub "refresh_token".
client_idwymaganyformstring
client_secretformstringTylko klienci poufni; klienci publiczni go pomijają (zamiast tego PKCE).
codeformstringGrant authorization_code: kod gac_….
redirect_uriformstringGrant authorization_code: musi odpowiadać URI, dla którego wydano kod.
code_verifierformstringGrant authorization_code: weryfikator PKCE dla Twojego wyzwania.
refresh_tokenformstringGrant refresh_token: token grt_… (jednorazowy, rotowany po udanej wymianie).

Odpowiedź: Odpowiedź z tokenami

PoleTypOpis
access_tokenstringToken typu Bearer (gat_…), ważny 6 godzin.
token_type"Bearer"
expires_inintegerLiczba sekund do wygaśnięcia tokena dostępu.
refresh_tokenstringNowy, rotowany token odświeżania (grt_…), ważny do 180 dni.
scopestringPrzyznane scope’y, rozdzielone spacjami.
Przykładowa odpowiedź
{
  "access_token": "gat_…",
  "token_type": "Bearer",
  "expires_in": 21600,
  "refresh_token": "grt_…",
  "scope": "workouts:read stats:read"
}
POST/oauth/revoke

Unieważnia token (RFC 7009). Unieważnienie tokena odświeżania unieważnia cały grant. Zwraca 200 nawet dla nieznanych tokenów.

Parametry

NazwaMiejsceTypOpis
tokenwymaganyformstringToken dostępu lub odświeżania do unieważnienia.
client_idwymaganyformstring
client_secretformstringTylko klienci poufni.

Odpowiedź: Pusta

PoleTypOpis
(brak treści)200 przy powodzeniu; 401 invalid_client przy błędnych danych klienta.