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éthodeCheminScope
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

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

GET/api/v1/userprofile:read

Le profil de l’utilisateur qui autorise : champs d’affichage, unités préférées et objectif d’entraînement.

Réponse : Profil utilisateur

ChampTypeDescription
idstringIdentifiant utilisateur stable : indexe ton stockage dessus.
displayNamestring | null
usernamestring | 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
heightnumber | nullDans l’unité indiquée par heightUnit.
heightUnit"centimeters" | "feetInches"
goal"stronger" | "muscleMass" | "lean" | "generalFitness" | null
Exemple de réponse
{
  "id": "8fKq2ZbXvNc…",
  "displayName": "Alex",
  "username": "alexlifts",
  "weightUnit": "pounds",
  "distanceUnit": "kilometers",
  "gender": "male",
  "height": 183,
  "heightUnit": "centimeters",
  "goal": "muscleMass"
}

Séances

GET/api/v1/workoutsworkouts:readpaginé

L’historique d’entraînement de l’utilisateur, du plus récent au plus ancien.

Paramètres

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30
startDatequerystring (ISO 8601)Uniquement les séances commençant à cet instant ou après (inclus).
endDatequerystring (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)

ChampTypeDescription
idstring (uuid)
namestring
notesstring | null
startDatestring (ISO 8601, UTC)
endDatestring (ISO 8601, UTC)
durationMinutesinteger
typestringOrigine de la séance, p. ex. "custom", "external". De nouvelles valeurs peuvent apparaître.
volumenumberVolume total soulevé, en livres.
caloriesnumber
personalRecordCountintegerRecords battus pendant cette séance.
exerciseCountinteger
Exemple de réponse
{
  "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

Une séance complète : chaque exercice avec ses séries enregistrées, dans l’ordre d’exécution.

Paramètres

NomDansTypeDescription
idrequispathstring (uuid)L’id de séance renvoyé par l’endpoint de liste.

Réponse : Détail de séance

ChampTypeDescription
…tous les champs du résumé de séanceIdentiques à l’élément de liste, sans exerciseCount.
exercises[].exerciseIdintegerId de l’exercice au catalogue.
exercises[].exerciseNamestring
exercises[].supersetIdinteger | nullLes exercices partageant un même id ont été exécutés en superset.
exercises[].sets[].orderintegerPosition dans l’exercice (commence à 1).
exercises[].sets[].repsintegerRépétitions effectuées.
exercises[].sets[].weightnumberEn livres.
exercises[].sets[].durationinteger | nullEn secondes, pour les séries chronométrées.
exercises[].sets[].distanceinteger | nullEn mètres, pour les séries de distance.
exercises[].sets[].rpeinteger | nullEffort perçu (RPE, 1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
Exemple de réponse
{
  "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: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

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30

Réponse : Résumé de template (dans items[] de l’enveloppe de pagination)

ChampTypeDescription
idstring (uuid)
namestring
descriptionstring | null
exerciseCountinteger
exercisesarrayToujours vide dans les réponses de liste : utilise l’endpoint de détail.
GET/api/v1/workout-templates/{id}workouts:read

Un template avec ses exercices et séries prévus.

Paramètres

NomDansTypeDescription
idrequispathstring (uuid)L’id de template renvoyé par l’endpoint de liste.

Réponse : Détail de template

ChampTypeDescription
…tous les champs du résumé de template
exercises[].exerciseIdinteger
exercises[].exerciseNamestring
exercises[].supersetIdinteger | null
exercises[].sets[].orderintegerPosition dans l’exercice (commence à 1).
exercises[].sets[].repsinteger | nullRépétitions prévues.
exercises[].sets[].weightnumber | nullEn livres.
exercises[].sets[].durationinteger | nullEn secondes, pour les séries chronométrées.
exercises[].sets[].distanceinteger | nullEn mètres, pour les séries de distance.
exercises[].sets[].rpeinteger | nullEffort perçu (RPE, 1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
GET/api/v1/splitssplits:readpaginé

Les splits d’entraînement personnalisés de l’utilisateur, du plus récent au plus ancien.

Paramètres

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30

Réponse : Résumé de split (dans items[] de l’enveloppe de pagination)

ChampTypeDescription
idstring (uuid)
namestring | null
descriptionstring | null
sessionCountinteger
sessionsarrayToujours vide dans les réponses de liste : utilise l’endpoint de détail.
GET/api/v1/splits/{id}splits:read

Un split avec ses sessions dans l’ordre.

Paramètres

NomDansTypeDescription
idrequispathstring (uuid)L’id de split renvoyé par l’endpoint de liste.

Réponse : Détail de split

ChampTypeDescription
…tous les champs du résumé de split
sessions[].orderinteger
sessions[].type"muscles" | "workoutTemplate"
sessions[].musclesstring[]Muscles ciblés (camelCase, p. ex. "chest") ; renseigné quand type vaut muscles.
sessions[].workoutTemplateIdstring (uuid) | nullTemplate lié ; renseigné quand type vaut workoutTemplate.

Records et mensurations

GET/api/v1/personal-recordsrecords:readpaginé

Les records personnels de l’utilisateur, du plus récent au plus ancien.

Paramètres

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30
exerciseIdqueryintegerFiltre sur un exercice du catalogue.

Réponse : Record personnel (dans items[] de l’enveloppe de pagination)

ChampTypeDescription
exerciseIdinteger
exerciseNamestring
type"oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax"
repsinteger | null
weightnumber | nullEn livres.
durationinteger | nullEn secondes.
distanceinteger | nullEn mètres.
workoutIdstring (uuid) | nullSéance où le record a été battu, quand elle est connue.
achievedAtstring (ISO 8601, UTC)
Exemple de réponse
{
  "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:readpaginé

Les entrées de poids de corps et de mensurations, de la plus récente à la plus ancienne.

Paramètres

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30
startDatequerystring (ISO 8601)Uniquement les entrées datées de ce moment ou après (inclus).
endDatequerystring (ISO 8601)Uniquement les entrées datées de ce moment ou avant (inclus).

Réponse : Mensuration (dans items[] de l’enveloppe de pagination)

ChampTypeDescription
idstring (uuid)
datestring (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.
weightnumber | null
bodyFatnumber | nullEn pourcentage.
neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calfnumber | nullCirconférences dans le système d’unités de l’entrée.

Stats et trophées

GET/api/v1/statsstats:read

Les stats d’entraînement clés de l’utilisateur en un seul appel.

Réponse : Stats

ChampTypeDescription
streak.currentintegerStreak actuelle, en semaines.
streak.longestintegerPlus longue streak jamais atteinte, en semaines.
streak.workoutsThisWeekinteger
totalXpinteger
trophyCountinteger
Exemple de réponse
{
  "streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
  "totalXp": 12480,
  "trophyCount": 22
}
GET/api/v1/trophiesstats:readpaginé

Les trophées obtenus, du plus récent au plus ancien.

Paramètres

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30

Réponse : Trophée (dans items[] de l’enveloppe de pagination)

ChampTypeDescription
typestringType de trophée, en camelCase, p. ex. "workout100", "streak5", "calendar15". De nouvelles valeurs peuvent apparaître.
exerciseIdinteger | nullRenseigné pour les trophées liés à un exercice.
exerciseNamestring | null
achievedAtstring (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.

GET/api/v1/exercisesexercises:readpaginé

Le catalogue d’exercices plus les exercices personnalisés de l’utilisateur, par ordre alphabétique.

Paramètres

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30
searchquerystringRecherche sur le nom, insensible à la casse (contient). 200 caractères max.

Réponse : Exercice (dans items[] de l’enveloppe de pagination)

ChampTypeDescription
idintegerId de catalogue stable : référencé par les séances, les records et les templates.
namestring
descriptionstring | null
categorystringEnum en camelCase, p. ex. "barbell", "dumbbell".
typestringEnum en camelCase, p. ex. "weight", "duration".
musclestringMuscle principal, en camelCase, p. ex. "chest".
secondaryMusclesstring[]
equipmentstring[]Noms du matériel utilisable pour cet exercice.
GET/api/v1/exercises/{id}exercises:read

Un exercice par id de catalogue.

Paramètres

NomDansTypeDescription
idrequispathintegerId de l’exercice au catalogue.

Réponse : Exercice

ChampTypeDescription
…identique à l’élément de liste
GET/api/v1/equipmentexercises:readpaginé

Le catalogue global de matériel, par ordre alphabétique.

Paramètres

NomDansTypeDescription
pagequeryintegerNuméro de page (commence à 1). Défaut : 1
pageSizequeryintegerÉléments par page, 1–100. Défaut : 30

Réponse : Matériel (dans items[] de l’enveloppe de pagination)

ChampTypeDescription
idintegerId de catalogue stable.
namestring
subtitlestring | null
categorystringEnum en camelCase.
GET/api/v1/equipment/{id}exercises:read

Un élément de matériel par id de catalogue.

Paramètres

NomDansTypeDescription
idrequispathintegerId du matériel au catalogue.

Réponse : Matériel

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

GET/oauth/authorize

Écran de consentement hébergé : démarre le flow authorization-code. L’utilisateur se connecte et approuve ; son navigateur est redirigé avec ?code=gac_…&state=….

Paramètres

NomDansTypeDescription
client_idrequisquerystringLe client id de ton app.
redirect_urirequisquerystringDoit correspondre exactement à une redirect URI enregistrée.
scopequerystringScopes séparés par des espaces. Omets-le pour obtenir tous les scopes autorisés pour ton app.
statequerystringValeur aléatoire renvoyée telle quelle : vérifie-la au retour (protection CSRF).
code_challengequerystringChallenge PKCE S256. Requis pour les clients publics.
code_challenge_methodquerystringSeul "S256" est supporté.

Réponse : Redirection

ChampTypeDescription
codestringCode d’autorisation à usage unique (gac_…), valable 10 minutes. Renvoyé sur la redirection, pas en JSON.
statestringRenvoyé tel quel depuis la requête.
POST/oauth/token

Échange un code d’autorisation, ou fait tourner un refresh token, contre une paire access token / refresh token.

Paramètres

NomDansTypeDescription
grant_typerequisformstring"authorization_code" ou "refresh_token".
client_idrequisformstring
client_secretformstringClients confidentiels uniquement ; les clients publics l’omettent (PKCE à la place).
codeformstringGrant authorization_code : le code gac_….
redirect_uriformstringGrant authorization_code : doit correspondre à l’URI pour laquelle le code a été émis.
code_verifierformstringGrant authorization_code : le verifier PKCE de ton challenge.
refresh_tokenformstringGrant refresh_token : le token grt_… (usage unique, remplacé en cas de succès).

Réponse : Réponse token

ChampTypeDescription
access_tokenstringToken Bearer (gat_…), valable 6 heures.
token_type"Bearer"
expires_inintegerSecondes avant l’expiration de l’access token.
refresh_tokenstringNouveau refresh token rotatif (grt_…), valable jusqu’à 180 jours.
scopestringScopes accordés, séparés par des espaces.
Exemple de réponse
{
  "access_token": "gat_…",
  "token_type": "Bearer",
  "expires_in": 21600,
  "refresh_token": "grt_…",
  "scope": "workouts:read stats:read"
}
POST/oauth/revoke

Ré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

NomDansTypeDescription
tokenrequisformstringL’access token ou le refresh token à révoquer.
client_idrequisformstring
client_secretformstringClients confidentiels uniquement.

Réponse : Vide

ChampTypeDescription
(pas de corps)200 en cas de succès ; 401 invalid_client si les identifiants client sont invalides.