Referência

Endpoints

Tudo vive sob https://api.gravl.ai e devolve JSON. Os dados de utilizador são identificados por GUIDs estáveis; os recursos de catálogo por ids inteiros. Os valores enum são strings em camelCase: trate valores desconhecidos como compatíveis com versões futuras.

Todos os endpoints

MétodoCaminhoScope
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

O envelope de paginação

Os endpoints marcados abaixo como paginado envolvem os seus itens num envelope padrão:

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

Todos os endpoints exigem o respetivo scope; um scope em falta devolve 403 com problem details. Os erros e limites de pedidos estão documentados em convenções.

Utilizador

GET/api/v1/userprofile:read

O perfil do utilizador que autorizou o acesso: campos de apresentação, unidades preferidas e objetivo de treino.

Resposta: Perfil do utilizador

CampoTipoDescrição
idstringIdentificador estável do utilizador. Use-o como chave do seu armazenamento.
displayNamestring | null
usernamestring | null
weightUnit"kilograms" | "pounds"Unidade de apresentação preferida. Os pesos da API são sempre libras.
distanceUnit"kilometers" | "miles"
gender"male" | "female" | "other" | null
heightnumber | nullNa unidade indicada por heightUnit.
heightUnit"centimeters" | "feetInches"
goal"stronger" | "muscleMass" | "lean" | "generalFitness" | null
Exemplo de resposta
{
  "id": "8fKq2ZbXvNc…",
  "displayName": "Alex",
  "username": "alexlifts",
  "weightUnit": "pounds",
  "distanceUnit": "kilometers",
  "gender": "male",
  "height": 183,
  "heightUnit": "centimeters",
  "goal": "muscleMass"
}

Treinos

GET/api/v1/workoutsworkouts:readpaginado

O histórico de treinos do utilizador, do mais recente para o mais antigo.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30
startDatequerystring (ISO 8601)Apenas treinos iniciados neste instante ou depois (inclusive).
endDatequerystring (ISO 8601)Apenas treinos iniciados neste instante ou antes (inclusive).

Resposta: Resumo do treino (como items[] no envelope de paginação)

CampoTipoDescrição
idstring (uuid)
namestring
notesstring | null
startDatestring (ISO 8601, UTC)
endDatestring (ISO 8601, UTC)
durationMinutesinteger
typestringOrigem do treino, p. ex. "custom", "external". Podem surgir novos valores.
volumenumberVolume total levantado, em libras.
caloriesnumber
personalRecordCountintegerRecordes pessoais alcançados neste treino.
exerciseCountinteger
Exemplo de resposta
{
  "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

Um treino completo: cada exercício com as séries registadas, pela ordem em que foram realizadas.

Parâmetros

NomeEmTipoDescrição
idobrigatóriopathstring (uuid)O id do treino devolvido pelo endpoint de listagem.

Resposta: Detalhe do treino

CampoTipoDescrição
…todos os campos do resumo do treinoIgual ao item da listagem, sem o exerciseCount.
exercises[].exerciseIdintegerId do exercício no catálogo.
exercises[].exerciseNamestring
exercises[].supersetIdinteger | nullExercícios com o mesmo id foram realizados em superset.
exercises[].sets[].orderintegerPosição dentro do exercício, a começar em 1.
exercises[].sets[].repsintegerRepetições realizadas.
exercises[].sets[].weightnumberLibras.
exercises[].sets[].durationinteger | nullSegundos, para séries por tempo.
exercises[].sets[].distanceinteger | nullMetros, para séries por distância.
exercises[].sets[].rpeinteger | nullPerceção subjetiva de esforço (1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
Exemplo de resposta
{
  "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:readpaginado

Os templates de treino guardados do utilizador, do mais recente para o mais antigo. As séries são valores planeados, não registos.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30

Resposta: Resumo do template (como items[] no envelope de paginação)

CampoTipoDescrição
idstring (uuid)
namestring
descriptionstring | null
exerciseCountinteger
exercisesarraySempre vazio nas respostas de listagem. Use o endpoint de detalhe.
GET/api/v1/workout-templates/{id}workouts:read

Um template com os seus exercícios e séries planeados.

Parâmetros

NomeEmTipoDescrição
idobrigatóriopathstring (uuid)O id do template devolvido pelo endpoint de listagem.

Resposta: Detalhe do template

CampoTipoDescrição
…todos os campos do resumo do template
exercises[].exerciseIdinteger
exercises[].exerciseNamestring
exercises[].supersetIdinteger | null
exercises[].sets[].orderintegerPosição dentro do exercício, a começar em 1.
exercises[].sets[].repsinteger | nullRepetições planeadas.
exercises[].sets[].weightnumber | nullLibras.
exercises[].sets[].durationinteger | nullSegundos, para séries por tempo.
exercises[].sets[].distanceinteger | nullMetros, para séries por distância.
exercises[].sets[].rpeinteger | nullPerceção subjetiva de esforço (1–10).
exercises[].sets[].setType"normal" | "warmup" | "dropSet" | "failure"
GET/api/v1/splitssplits:readpaginado

Os splits de treino personalizados do utilizador, do mais recente para o mais antigo.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30

Resposta: Resumo do split (como items[] no envelope de paginação)

CampoTipoDescrição
idstring (uuid)
namestring | null
descriptionstring | null
sessionCountinteger
sessionsarraySempre vazio nas respostas de listagem. Use o endpoint de detalhe.
GET/api/v1/splits/{id}splits:read

Um split com as suas sessões por ordem.

Parâmetros

NomeEmTipoDescrição
idobrigatóriopathstring (uuid)O id do split devolvido pelo endpoint de listagem.

Resposta: Detalhe do split

CampoTipoDescrição
…todos os campos do resumo do split
sessions[].orderinteger
sessions[].type"muscles" | "workoutTemplate"
sessions[].musclesstring[]Músculos alvo (camelCase, p. ex. "chest"); definido quando type é muscles.
sessions[].workoutTemplateIdstring (uuid) | nullTemplate associado; definido quando type é workoutTemplate.

Recordes e medições

GET/api/v1/personal-recordsrecords:readpaginado

Os recordes pessoais do utilizador, do mais recente para o mais antigo.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30
exerciseIdqueryintegerFiltra por um exercício do catálogo.

Resposta: Recorde pessoal (como items[] no envelope de paginação)

CampoTipoDescrição
exerciseIdinteger
exerciseNamestring
type"oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax"
repsinteger | null
weightnumber | nullLibras.
durationinteger | nullSegundos.
distanceinteger | nullMetros.
workoutIdstring (uuid) | nullTreino em que o recorde foi alcançado, quando conhecido.
achievedAtstring (ISO 8601, UTC)
Exemplo de resposta
{
  "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:readpaginado

Registos de peso corporal e perímetros, do mais recente para o mais antigo.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30
startDatequerystring (ISO 8601)Apenas registos com data igual ou posterior a esta (inclusive).
endDatequerystring (ISO 8601)Apenas registos com data igual ou anterior a esta (inclusive).

Resposta: Medição (como items[] no envelope de paginação)

CampoTipoDescrição
idstring (uuid)
datestring (ISO 8601 date)
weightUnit"kilograms" | "pounds"Sistema de unidades deste registo: os perímetros são em polegadas com libras e em centímetros com quilogramas.
weightnumber | null
bodyFatnumber | nullPercentagem.
neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calfnumber | nullPerímetros no sistema de unidades do registo.

Estatísticas e troféus

GET/api/v1/statsstats:read

As principais estatísticas de treino do utilizador numa única chamada.

Resposta: Estatísticas

CampoTipoDescrição
streak.currentintegerSequência atual, em semanas.
streak.longestintegerSequência mais longa de sempre, em semanas.
streak.workoutsThisWeekinteger
totalXpinteger
trophyCountinteger
Exemplo de resposta
{
  "streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
  "totalXp": 12480,
  "trophyCount": 22
}
GET/api/v1/trophiesstats:readpaginado

Troféus conquistados, do mais recente para o mais antigo.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30

Resposta: Troféu (como items[] no envelope de paginação)

CampoTipoDescrição
typestringTipo de troféu, em camelCase, p. ex. "workout100", "streak5", "calendar15". Podem surgir novos valores.
exerciseIdinteger | nullDefinido para troféus específicos de um exercício.
exerciseNamestring | null
achievedAtstring (ISO 8601, UTC)

Catálogo de exercícios e equipamento

Dados de referência globais, identificados pelos mesmos ids inteiros estáveis que a app Gravl usa. As listagens de exercícios também incluem os exercícios personalizados do utilizador que autorizou o acesso, nunca os de outros utilizadores.

GET/api/v1/exercisesexercises:readpaginado

O catálogo de exercícios mais os exercícios personalizados do utilizador, por ordem alfabética.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30
searchquerystringCorrespondência no nome sem distinguir maiúsculas (contém). Máximo 200 caracteres.

Resposta: Exercício (como items[] no envelope de paginação)

CampoTipoDescrição
idintegerId estável do catálogo, referenciado por treinos, recordes e templates.
namestring
descriptionstring | null
categorystringEnum em camelCase, p. ex. "barbell", "dumbbell".
typestringEnum em camelCase, p. ex. "weight", "duration".
musclestringMúsculo principal, em camelCase, p. ex. "chest".
secondaryMusclesstring[]
equipmentstring[]Nomes de equipamento utilizável neste exercício.
GET/api/v1/exercises/{id}exercises:read

Um exercício pelo id de catálogo.

Parâmetros

NomeEmTipoDescrição
idobrigatóriopathintegerId do exercício no catálogo.

Resposta: Exercício

CampoTipoDescrição
…igual ao item da listagem
GET/api/v1/equipmentexercises:readpaginado

O catálogo global de equipamento, por ordem alfabética.

Parâmetros

NomeEmTipoDescrição
pagequeryintegerNúmero da página, a começar em 1. Por omissão: 1
pageSizequeryintegerItens por página, 1–100. Por omissão: 30

Resposta: Equipamento (como items[] no envelope de paginação)

CampoTipoDescrição
idintegerId estável do catálogo.
namestring
subtitlestring | null
categorystringEnum em camelCase.
GET/api/v1/equipment/{id}exercises:read

Um item de equipamento pelo id de catálogo.

Parâmetros

NomeEmTipoDescrição
idobrigatóriopathintegerId do equipamento no catálogo.

Resposta: Equipamento

CampoTipoDescrição
…igual ao item da listagem

OAuth

Os endpoints de token recebem dados form-encoded (application/x-www-form-urlencoded) e seguem as RFC 6749/7009. O fluxo completo está documentado na página de apps OAuth.

GET/oauth/authorize

Ecrã de consentimento alojado que inicia o fluxo de código de autorização. O utilizador inicia sessão e aprova; o browser é redirecionado de volta com ?code=gac_…&state=….

Parâmetros

NomeEmTipoDescrição
client_idobrigatórioquerystringO client id da sua app.
redirect_uriobrigatórioquerystringTem de corresponder exatamente a uma redirect URI registada.
scopequerystringScopes separados por espaços. Omita para todos os scopes permitidos à sua app.
statequerystringValor aleatório devolvido tal e qual. Verifique-o no regresso (proteção CSRF).
code_challengequerystringChallenge PKCE S256. Obrigatório para clientes públicos.
code_challenge_methodquerystringApenas "S256" é suportado.

Resposta: Redirecionamento

CampoTipoDescrição
codestringCódigo de autorização de utilização única (gac_…), válido por 10 minutos. Devolvido no redirecionamento, não como JSON.
statestringDevolvido tal como enviado no pedido.
POST/oauth/token

Troca um código de autorização, ou roda um refresh token, por um par de access/refresh token.

Parâmetros

NomeEmTipoDescrição
grant_typeobrigatórioformstring"authorization_code" ou "refresh_token".
client_idobrigatórioformstring
client_secretformstringApenas clientes confidenciais; os clientes públicos omitem-no (PKCE em alternativa).
codeformstringGrant authorization_code: o código gac_….
redirect_uriformstringGrant authorization_code: tem de corresponder à URI para a qual o código foi emitido.
code_verifierformstringGrant authorization_code: o verifier PKCE do seu challenge.
refresh_tokenformstringGrant refresh_token: o token grt_… (utilização única; rodado em caso de sucesso).

Resposta: Resposta de token

CampoTipoDescrição
access_tokenstringBearer token (gat_…), válido por 6 horas.
token_type"Bearer"
expires_inintegerSegundos até o access token expirar.
refresh_tokenstringNovo refresh token rotativo (grt_…), válido até 180 dias.
scopestringScopes concedidos, separados por espaços.
Exemplo de resposta
{
  "access_token": "gat_…",
  "token_type": "Bearer",
  "expires_in": 21600,
  "refresh_token": "grt_…",
  "scope": "workouts:read stats:read"
}
POST/oauth/revoke

Revoga um token (RFC 7009). Revogar um refresh token revoga o grant inteiro. Devolve 200 mesmo para tokens desconhecidos.

Parâmetros

NomeEmTipoDescrição
tokenobrigatórioformstringO access token ou refresh token a revogar.
client_idobrigatórioformstring
client_secretformstringApenas clientes confidenciais.

Resposta: Vazio

CampoTipoDescrição
(sem corpo)200 em caso de sucesso; 401 invalid_client para credenciais inválidas.