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żka | 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 | — |
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
/api/v1/userprofile:readProfil autoryzującego użytkownika: pola wyświetlane, preferowane jednostki i cel treningowy.
Odpowiedź: Profil użytkownika
| Pole | Typ | Opis |
|---|---|---|
| id | string | Stały identyfikator użytkownika. To po nim indeksuj swoje dane. |
| displayName | string | null | |
| username | string | null | |
| weightUnit | "kilograms" | "pounds" | Preferowana jednostka wyświetlania. Ciężary w API są zawsze w funtach. |
| distanceUnit | "kilometers" | "miles" | |
| gender | "male" | "female" | "other" | null | |
| height | number | null | W jednostce wskazanej przez 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"
}Treningi
/api/v1/workoutsworkouts:readpaginowanyHistoria treningów użytkownika, od najnowszych.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
startDate | query | string (ISO 8601) | Tylko treningi rozpoczęte w tym momencie lub później (włącznie). |
endDate | query | string (ISO 8601) | Tylko treningi rozpoczęte w tym momencie lub wcześniej (włącznie). |
Odpowiedź: Podsumowanie treningu (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| notes | string | null | |
| startDate | string (ISO 8601, UTC) | |
| endDate | string (ISO 8601, UTC) | |
| durationMinutes | integer | |
| type | string | Pochodzenie treningu, np. "custom", "external". Mogą pojawiać się nowe wartości. |
| volume | number | Łączna podniesiona objętość, w funtach. |
| calories | number | |
| personalRecordCount | integer | Rekordy pobite w tym treningu. |
| 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:readPojedynczy trening w całości: każde ćwiczenie z zapisanymi seriami, w kolejności wykonania.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
idwymagany | path | string (uuid) | Id treningu z endpointu listy. |
Odpowiedź: Szczegóły treningu
| Pole | Typ | Opis |
|---|---|---|
| …wszystkie pola podsumowania treningu | Takie same jak element listy, bez exerciseCount. | |
| exercises[].exerciseId | integer | Id ćwiczenia z katalogu. |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | Ćwiczenia o wspólnym id były wykonane jako superseria. |
| exercises[].sets[].order | integer | Pozycja w ramach ćwiczenia, liczona od 1. |
| exercises[].sets[].reps | integer | Wykonane powtórzenia. |
| exercises[].sets[].weight | number | W funtach. |
| exercises[].sets[].duration | integer | null | W sekundach, dla serii na czas. |
| exercises[].sets[].distance | integer | null | W metrach, dla serii na dystans. |
| exercises[].sets[].rpe | integer | null | Subiektywna ocena wysiłku (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:readpaginowanyZapisane szablony treningów użytkownika, od najnowszych. Serie to wartości zaplanowane, nie zapisy.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
Odpowiedź: Podsumowanie szablonu (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| description | string | null | |
| exerciseCount | integer | |
| exercises | array | W odpowiedziach listy zawsze puste. Skorzystaj z endpointu szczegółów. |
/api/v1/workout-templates/{id}workouts:readPojedynczy szablon z zaplanowanymi ćwiczeniami i seriami.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
idwymagany | path | string (uuid) | Id szablonu z endpointu listy. |
Odpowiedź: Szczegóły szablonu
| Pole | Typ | Opis |
|---|---|---|
| …wszystkie pola podsumowania szablonu | ||
| exercises[].exerciseId | integer | |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | |
| exercises[].sets[].order | integer | Pozycja w ramach ćwiczenia, liczona od 1. |
| exercises[].sets[].reps | integer | null | Zaplanowane powtórzenia. |
| exercises[].sets[].weight | number | null | W funtach. |
| exercises[].sets[].duration | integer | null | W sekundach, dla serii na czas. |
| exercises[].sets[].distance | integer | null | W metrach, dla serii na dystans. |
| exercises[].sets[].rpe | integer | null | Subiektywna ocena wysiłku (1–10). |
| exercises[].sets[].setType | "normal" | "warmup" | "dropSet" | "failure" |
/api/v1/splitssplits:readpaginowanyWłasne splity treningowe użytkownika, od najnowszych.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
Odpowiedź: Podsumowanie splitu (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| id | string (uuid) | |
| name | string | null | |
| description | string | null | |
| sessionCount | integer | |
| sessions | array | W odpowiedziach listy zawsze puste. Skorzystaj z endpointu szczegółów. |
/api/v1/splits/{id}splits:readPojedynczy split z sesjami w kolejności.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
idwymagany | path | string (uuid) | Id splitu z endpointu listy. |
Odpowiedź: Szczegóły splitu
| Pole | Typ | Opis |
|---|---|---|
| …wszystkie pola podsumowania splitu | ||
| sessions[].order | integer | |
| sessions[].type | "muscles" | "workoutTemplate" | |
| sessions[].muscles | string[] | Docelowe mięśnie (camelCase, np. "chest"); ustawione, gdy type to muscles. |
| sessions[].workoutTemplateId | string (uuid) | null | Powiązany szablon; ustawiony, gdy type to workoutTemplate. |
Rekordy i pomiary
/api/v1/personal-recordsrecords:readpaginowanyRekordy życiowe użytkownika, od najnowszych.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
exerciseId | query | integer | Filtruje do jednego ćwiczenia z katalogu. |
Odpowiedź: Rekord życiowy (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| exerciseId | integer | |
| exerciseName | string | |
| type | "oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax" | |
| reps | integer | null | |
| weight | number | null | W funtach. |
| duration | integer | null | W sekundach. |
| distance | integer | null | W metrach. |
| workoutId | string (uuid) | null | Trening, w którym pobito rekord, jeśli znany. |
| 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:readpaginowanyWpisy masy ciała i obwodów, od najnowszych.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
startDate | query | string (ISO 8601) | Tylko wpisy z tą datą lub późniejsze (włącznie). |
endDate | query | string (ISO 8601) | Tylko wpisy z tą datą lub wcześniejsze (włącznie). |
Odpowiedź: Pomiar (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| id | string (uuid) | |
| date | string (ISO 8601 date) | |
| weightUnit | "kilograms" | "pounds" | System jednostek tego wpisu: obwody są w calach przy funtach, a w centymetrach przy kilogramach. |
| weight | number | null | |
| bodyFat | number | null | W procentach. |
| neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calf | number | null | Obwody w systemie jednostek wpisu. |
Statystyki i trofea
/api/v1/statsstats:readNajważniejsze statystyki treningowe użytkownika w jednym wywołaniu.
Odpowiedź: Statystyki
| Pole | Typ | Opis |
|---|---|---|
| streak.current | integer | Bieżąca seria treningowa, w tygodniach. |
| streak.longest | integer | Najdłuższa seria w historii, w tygodniach. |
| streak.workoutsThisWeek | integer | |
| totalXp | integer | |
| trophyCount | integer |
{
"streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
"totalXp": 12480,
"trophyCount": 22
}/api/v1/trophiesstats:readpaginowanyZdobyte trofea, od najnowszych.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
Odpowiedź: Trofeum (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| type | string | Rodzaj trofeum, camelCase, np. "workout100", "streak5", "calendar15". Mogą pojawiać się nowe wartości. |
| exerciseId | integer | null | Ustawione dla trofeów za konkretne ćwiczenie. |
| exerciseName | string | null | |
| achievedAt | string (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.
/api/v1/exercisesexercises:readpaginowanyKatalog ćwiczeń plus własne ćwiczenia użytkownika, alfabetycznie.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
search | query | string | Dopasowanie nazwy bez rozróżniania wielkości liter (zawiera). Maks. 200 znaków. |
Odpowiedź: Ćwiczenie (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| id | integer | Stałe id katalogowe. Odwołują się do niego treningi, rekordy i szablony. |
| name | string | |
| description | string | null | |
| category | string | Enum w camelCase, np. "barbell", "dumbbell". |
| type | string | Enum w camelCase, np. "weight", "duration". |
| muscle | string | Główny mięsień, camelCase, np. "chest". |
| secondaryMuscles | string[] | |
| equipment | string[] | Nazwy sprzętu, którego można użyć w tym ćwiczeniu. |
/api/v1/exercises/{id}exercises:readPojedyncze ćwiczenie po id katalogowym.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
idwymagany | path | integer | Id ćwiczenia z katalogu. |
Odpowiedź: Ćwiczenie
| Pole | Typ | Opis |
|---|---|---|
| …tak samo jak element listy |
/api/v1/equipmentexercises:readpaginowanyGlobalny katalog sprzętu, alfabetycznie.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
page | query | integer | Numer strony, liczony od 1. Domyślnie: 1 |
pageSize | query | integer | Liczba elementów na stronę, 1–100. Domyślnie: 30 |
Odpowiedź: Sprzęt (jako items[] w kopercie paginacji)
| Pole | Typ | Opis |
|---|---|---|
| id | integer | Stałe id katalogowe. |
| name | string | |
| subtitle | string | null | |
| category | string | Enum w camelCase. |
/api/v1/equipment/{id}exercises:readPojedynczy element sprzętu po id katalogowym.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
idwymagany | path | integer | Id sprzętu z katalogu. |
Odpowiedź: Sprzęt
| Pole | Typ | Opis |
|---|---|---|
| …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.
/oauth/tokenWymienia kod autoryzacyjny lub rotuje token odświeżania na parę tokenów dostępu i odświeżania.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
grant_typewymagany | form | string | "authorization_code" lub "refresh_token". |
client_idwymagany | form | string | |
client_secret | form | string | Tylko klienci poufni; klienci publiczni go pomijają (zamiast tego PKCE). |
code | form | string | Grant authorization_code: kod gac_…. |
redirect_uri | form | string | Grant authorization_code: musi odpowiadać URI, dla którego wydano kod. |
code_verifier | form | string | Grant authorization_code: weryfikator PKCE dla Twojego wyzwania. |
refresh_token | form | string | Grant refresh_token: token grt_… (jednorazowy, rotowany po udanej wymianie). |
Odpowiedź: Odpowiedź z tokenami
| Pole | Typ | Opis |
|---|---|---|
| access_token | string | Token typu Bearer (gat_…), ważny 6 godzin. |
| token_type | "Bearer" | |
| expires_in | integer | Liczba sekund do wygaśnięcia tokena dostępu. |
| refresh_token | string | Nowy, rotowany token odświeżania (grt_…), ważny do 180 dni. |
| scope | string | Przyznane scope’y, rozdzielone spacjami. |
{
"access_token": "gat_…",
"token_type": "Bearer",
"expires_in": 21600,
"refresh_token": "grt_…",
"scope": "workouts:read stats:read"
}/oauth/revokeUnieważnia token (RFC 7009). Unieważnienie tokena odświeżania unieważnia cały grant. Zwraca 200 nawet dla nieznanych tokenów.
Parametry
| Nazwa | Miejsce | Typ | Opis |
|---|---|---|---|
tokenwymagany | form | string | Token dostępu lub odświeżania do unieważnienia. |
client_idwymagany | form | string | |
client_secret | form | string | Tylko klienci poufni. |
Odpowiedź: Pusta
| Pole | Typ | Opis |
|---|---|---|
| (brak treści) | 200 przy powodzeniu; 401 invalid_client przy błędnych danych klienta. |