Référence
Endpoints
Tout vit sous https://api.gravl.ai et renvoie du JSON. Les données utilisateur sont identifiées par des GUID stables ; les ressources du catalogue par des ids entiers. Les valeurs d’enum sont des chaînes en camelCase : prévois que de nouvelles valeurs apparaissent.
Tous les endpoints
| Méthode | Chemin | 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 | — |
L’enveloppe de pagination
Les endpoints marqués paginé ci-dessous enveloppent leurs éléments dans une enveloppe standard unique :
{
"items": [ … ], // the page of results
"pageNumber": 2,
"totalPages": 9,
"totalCount": 174,
"hasPreviousPage": true,
"hasNextPage": true
}Chaque endpoint exige son scope : un scope manquant renvoie 403 avec des problem details. Les erreurs et les rate limits sont documentées dans les conventions.
Utilisateur
/api/v1/userprofile:readLe profil de l’utilisateur qui autorise : champs d’affichage, unités préférées et objectif d’entraînement.
Réponse : Profil utilisateur
| Champ | Type | Description |
|---|---|---|
| id | string | Identifiant utilisateur stable : indexe ton stockage dessus. |
| displayName | string | null | |
| username | string | null | |
| weightUnit | "kilograms" | "pounds" | Unité d’affichage préférée : les poids de l’API sont toujours en livres. |
| distanceUnit | "kilometers" | "miles" | |
| gender | "male" | "female" | "other" | null | |
| height | number | null | Dans l’unité indiquée par 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"
}Séances
/api/v1/workoutsworkouts:readpaginéL’historique d’entraînement de l’utilisateur, du plus récent au plus ancien.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
startDate | query | string (ISO 8601) | Uniquement les séances commençant à cet instant ou après (inclus). |
endDate | query | string (ISO 8601) | Uniquement les séances commençant à cet instant ou avant (inclus). |
Réponse : Résumé de séance (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| notes | string | null | |
| startDate | string (ISO 8601, UTC) | |
| endDate | string (ISO 8601, UTC) | |
| durationMinutes | integer | |
| type | string | Origine de la séance, p. ex. "custom", "external". De nouvelles valeurs peuvent apparaître. |
| volume | number | Volume total soulevé, en livres. |
| calories | number | |
| personalRecordCount | integer | Records battus pendant cette séance. |
| 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:readUne séance complète : chaque exercice avec ses séries enregistrées, dans l’ordre d’exécution.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
idrequis | path | string (uuid) | L’id de séance renvoyé par l’endpoint de liste. |
Réponse : Détail de séance
| Champ | Type | Description |
|---|---|---|
| …tous les champs du résumé de séance | Identiques à l’élément de liste, sans exerciseCount. | |
| exercises[].exerciseId | integer | Id de l’exercice au catalogue. |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | Les exercices partageant un même id ont été exécutés en superset. |
| exercises[].sets[].order | integer | Position dans l’exercice (commence à 1). |
| exercises[].sets[].reps | integer | Répétitions effectuées. |
| exercises[].sets[].weight | number | En livres. |
| exercises[].sets[].duration | integer | null | En secondes, pour les séries chronométrées. |
| exercises[].sets[].distance | integer | null | En mètres, pour les séries de distance. |
| exercises[].sets[].rpe | integer | null | Effort perçu (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:readpaginéLes templates d’entraînement enregistrés de l’utilisateur, du plus récent au plus ancien. Les séries sont des valeurs prévues, pas des logs.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
Réponse : Résumé de template (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| description | string | null | |
| exerciseCount | integer | |
| exercises | array | Toujours vide dans les réponses de liste : utilise l’endpoint de détail. |
/api/v1/workout-templates/{id}workouts:readUn template avec ses exercices et séries prévus.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
idrequis | path | string (uuid) | L’id de template renvoyé par l’endpoint de liste. |
Réponse : Détail de template
| Champ | Type | Description |
|---|---|---|
| …tous les champs du résumé de template | ||
| exercises[].exerciseId | integer | |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | |
| exercises[].sets[].order | integer | Position dans l’exercice (commence à 1). |
| exercises[].sets[].reps | integer | null | Répétitions prévues. |
| exercises[].sets[].weight | number | null | En livres. |
| exercises[].sets[].duration | integer | null | En secondes, pour les séries chronométrées. |
| exercises[].sets[].distance | integer | null | En mètres, pour les séries de distance. |
| exercises[].sets[].rpe | integer | null | Effort perçu (RPE, 1–10). |
| exercises[].sets[].setType | "normal" | "warmup" | "dropSet" | "failure" |
/api/v1/splitssplits:readpaginéLes splits d’entraînement personnalisés de l’utilisateur, du plus récent au plus ancien.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
Réponse : Résumé de split (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| id | string (uuid) | |
| name | string | null | |
| description | string | null | |
| sessionCount | integer | |
| sessions | array | Toujours vide dans les réponses de liste : utilise l’endpoint de détail. |
/api/v1/splits/{id}splits:readUn split avec ses sessions dans l’ordre.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
idrequis | path | string (uuid) | L’id de split renvoyé par l’endpoint de liste. |
Réponse : Détail de split
| Champ | Type | Description |
|---|---|---|
| …tous les champs du résumé de split | ||
| sessions[].order | integer | |
| sessions[].type | "muscles" | "workoutTemplate" | |
| sessions[].muscles | string[] | Muscles ciblés (camelCase, p. ex. "chest") ; renseigné quand type vaut muscles. |
| sessions[].workoutTemplateId | string (uuid) | null | Template lié ; renseigné quand type vaut workoutTemplate. |
Records et mensurations
/api/v1/personal-recordsrecords:readpaginéLes records personnels de l’utilisateur, du plus récent au plus ancien.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
exerciseId | query | integer | Filtre sur un exercice du catalogue. |
Réponse : Record personnel (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| exerciseId | integer | |
| exerciseName | string | |
| type | "oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax" | |
| reps | integer | null | |
| weight | number | null | En livres. |
| duration | integer | null | En secondes. |
| distance | integer | null | En mètres. |
| workoutId | string (uuid) | null | Séance où le record a été battu, quand elle est connue. |
| 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:readpaginéLes entrées de poids de corps et de mensurations, de la plus récente à la plus ancienne.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
startDate | query | string (ISO 8601) | Uniquement les entrées datées de ce moment ou après (inclus). |
endDate | query | string (ISO 8601) | Uniquement les entrées datées de ce moment ou avant (inclus). |
Réponse : Mensuration (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| id | string (uuid) | |
| date | string (ISO 8601 date) | |
| weightUnit | "kilograms" | "pounds" | Système d’unités de cette entrée : les circonférences sont en pouces avec les livres, en centimètres avec les kilogrammes. |
| weight | number | null | |
| bodyFat | number | null | En pourcentage. |
| neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calf | number | null | Circonférences dans le système d’unités de l’entrée. |
Stats et trophées
/api/v1/statsstats:readLes stats d’entraînement clés de l’utilisateur en un seul appel.
Réponse : Stats
| Champ | Type | Description |
|---|---|---|
| streak.current | integer | Streak actuelle, en semaines. |
| streak.longest | integer | Plus longue streak jamais atteinte, en semaines. |
| streak.workoutsThisWeek | integer | |
| totalXp | integer | |
| trophyCount | integer |
{
"streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
"totalXp": 12480,
"trophyCount": 22
}/api/v1/trophiesstats:readpaginéLes trophées obtenus, du plus récent au plus ancien.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
Réponse : Trophée (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| type | string | Type de trophée, en camelCase, p. ex. "workout100", "streak5", "calendar15". De nouvelles valeurs peuvent apparaître. |
| exerciseId | integer | null | Renseigné pour les trophées liés à un exercice. |
| exerciseName | string | null | |
| achievedAt | string (ISO 8601, UTC) |
Catalogue d’exercices et de matériel
Des données de référence globales, identifiées par les mêmes ids entiers stables que ceux de l’app Gravl. Les listes d’exercices incluent aussi les exercices personnalisés de l’utilisateur qui autorise, jamais ceux des autres utilisateurs.
/api/v1/exercisesexercises:readpaginéLe catalogue d’exercices plus les exercices personnalisés de l’utilisateur, par ordre alphabétique.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
search | query | string | Recherche sur le nom, insensible à la casse (contient). 200 caractères max. |
Réponse : Exercice (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| id | integer | Id de catalogue stable : référencé par les séances, les records et les templates. |
| name | string | |
| description | string | null | |
| category | string | Enum en camelCase, p. ex. "barbell", "dumbbell". |
| type | string | Enum en camelCase, p. ex. "weight", "duration". |
| muscle | string | Muscle principal, en camelCase, p. ex. "chest". |
| secondaryMuscles | string[] | |
| equipment | string[] | Noms du matériel utilisable pour cet exercice. |
/api/v1/exercises/{id}exercises:readUn exercice par id de catalogue.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
idrequis | path | integer | Id de l’exercice au catalogue. |
Réponse : Exercice
| Champ | Type | Description |
|---|---|---|
| …identique à l’élément de liste |
/api/v1/equipmentexercises:readpaginéLe catalogue global de matériel, par ordre alphabétique.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
page | query | integer | Numéro de page (commence à 1). Défaut : 1 |
pageSize | query | integer | Éléments par page, 1–100. Défaut : 30 |
Réponse : Matériel (dans items[] de l’enveloppe de pagination)
| Champ | Type | Description |
|---|---|---|
| id | integer | Id de catalogue stable. |
| name | string | |
| subtitle | string | null | |
| category | string | Enum en camelCase. |
/api/v1/equipment/{id}exercises:readUn élément de matériel par id de catalogue.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
idrequis | path | integer | Id du matériel au catalogue. |
Réponse : Matériel
| Champ | Type | Description |
|---|---|---|
| …identique à l’élément de liste |
OAuth
Les endpoints de token sont encodés en formulaire (application/x-www-form-urlencoded) et suivent les RFC 6749/7009. Le flow complet est documenté sur la page Apps OAuth.
/oauth/tokenÉchange un code d’autorisation, ou fait tourner un refresh token, contre une paire access token / refresh token.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
grant_typerequis | form | string | "authorization_code" ou "refresh_token". |
client_idrequis | form | string | |
client_secret | form | string | Clients confidentiels uniquement ; les clients publics l’omettent (PKCE à la place). |
code | form | string | Grant authorization_code : le code gac_…. |
redirect_uri | form | string | Grant authorization_code : doit correspondre à l’URI pour laquelle le code a été émis. |
code_verifier | form | string | Grant authorization_code : le verifier PKCE de ton challenge. |
refresh_token | form | string | Grant refresh_token : le token grt_… (usage unique, remplacé en cas de succès). |
Réponse : Réponse token
| Champ | Type | Description |
|---|---|---|
| access_token | string | Token Bearer (gat_…), valable 6 heures. |
| token_type | "Bearer" | |
| expires_in | integer | Secondes avant l’expiration de l’access token. |
| refresh_token | string | Nouveau refresh token rotatif (grt_…), valable jusqu’à 180 jours. |
| scope | string | Scopes accordés, séparés par des espaces. |
{
"access_token": "gat_…",
"token_type": "Bearer",
"expires_in": 21600,
"refresh_token": "grt_…",
"scope": "workouts:read stats:read"
}/oauth/revokeRévoque un token (RFC 7009). Révoquer un refresh token révoque l’autorisation entière. Renvoie 200 même pour un token inconnu.
Paramètres
| Nom | Dans | Type | Description |
|---|---|---|---|
tokenrequis | form | string | L’access token ou le refresh token à révoquer. |
client_idrequis | form | string | |
client_secret | form | string | Clients confidentiels uniquement. |
Réponse : Vide
| Champ | Type | Description |
|---|---|---|
| (pas de corps) | 200 en cas de succès ; 401 invalid_client si les identifiants client sont invalides. |