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étodo | Caminho | 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 | — |
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
/api/v1/userprofile:readO perfil do utilizador que autorizou o acesso: campos de apresentação, unidades preferidas e objetivo de treino.
Resposta: Perfil do utilizador
| Campo | Tipo | Descrição |
|---|---|---|
| id | string | Identificador estável do utilizador. Use-o como chave do seu armazenamento. |
| displayName | string | null | |
| username | string | 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 | |
| height | number | null | Na unidade indicada por 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"
}Treinos
/api/v1/workoutsworkouts:readpaginadoO histórico de treinos do utilizador, do mais recente para o mais antigo.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
startDate | query | string (ISO 8601) | Apenas treinos iniciados neste instante ou depois (inclusive). |
endDate | query | string (ISO 8601) | Apenas treinos iniciados neste instante ou antes (inclusive). |
Resposta: Resumo do treino (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| notes | string | null | |
| startDate | string (ISO 8601, UTC) | |
| endDate | string (ISO 8601, UTC) | |
| durationMinutes | integer | |
| type | string | Origem do treino, p. ex. "custom", "external". Podem surgir novos valores. |
| volume | number | Volume total levantado, em libras. |
| calories | number | |
| personalRecordCount | integer | Recordes pessoais alcançados neste treino. |
| 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:readUm treino completo: cada exercício com as séries registadas, pela ordem em que foram realizadas.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
idobrigatório | path | string (uuid) | O id do treino devolvido pelo endpoint de listagem. |
Resposta: Detalhe do treino
| Campo | Tipo | Descrição |
|---|---|---|
| …todos os campos do resumo do treino | Igual ao item da listagem, sem o exerciseCount. | |
| exercises[].exerciseId | integer | Id do exercício no catálogo. |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | Exercícios com o mesmo id foram realizados em superset. |
| exercises[].sets[].order | integer | Posição dentro do exercício, a começar em 1. |
| exercises[].sets[].reps | integer | Repetições realizadas. |
| exercises[].sets[].weight | number | Libras. |
| exercises[].sets[].duration | integer | null | Segundos, para séries por tempo. |
| exercises[].sets[].distance | integer | null | Metros, para séries por distância. |
| exercises[].sets[].rpe | integer | null | Perceção subjetiva de esforço (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:readpaginadoOs 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
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
Resposta: Resumo do template (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| id | string (uuid) | |
| name | string | |
| description | string | null | |
| exerciseCount | integer | |
| exercises | array | Sempre vazio nas respostas de listagem. Use o endpoint de detalhe. |
/api/v1/workout-templates/{id}workouts:readUm template com os seus exercícios e séries planeados.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
idobrigatório | path | string (uuid) | O id do template devolvido pelo endpoint de listagem. |
Resposta: Detalhe do template
| Campo | Tipo | Descrição |
|---|---|---|
| …todos os campos do resumo do template | ||
| exercises[].exerciseId | integer | |
| exercises[].exerciseName | string | |
| exercises[].supersetId | integer | null | |
| exercises[].sets[].order | integer | Posição dentro do exercício, a começar em 1. |
| exercises[].sets[].reps | integer | null | Repetições planeadas. |
| exercises[].sets[].weight | number | null | Libras. |
| exercises[].sets[].duration | integer | null | Segundos, para séries por tempo. |
| exercises[].sets[].distance | integer | null | Metros, para séries por distância. |
| exercises[].sets[].rpe | integer | null | Perceção subjetiva de esforço (1–10). |
| exercises[].sets[].setType | "normal" | "warmup" | "dropSet" | "failure" |
/api/v1/splitssplits:readpaginadoOs splits de treino personalizados do utilizador, do mais recente para o mais antigo.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
Resposta: Resumo do split (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| id | string (uuid) | |
| name | string | null | |
| description | string | null | |
| sessionCount | integer | |
| sessions | array | Sempre vazio nas respostas de listagem. Use o endpoint de detalhe. |
/api/v1/splits/{id}splits:readUm split com as suas sessões por ordem.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
idobrigatório | path | string (uuid) | O id do split devolvido pelo endpoint de listagem. |
Resposta: Detalhe do split
| Campo | Tipo | Descrição |
|---|---|---|
| …todos os campos do resumo do split | ||
| sessions[].order | integer | |
| sessions[].type | "muscles" | "workoutTemplate" | |
| sessions[].muscles | string[] | Músculos alvo (camelCase, p. ex. "chest"); definido quando type é muscles. |
| sessions[].workoutTemplateId | string (uuid) | null | Template associado; definido quando type é workoutTemplate. |
Recordes e medições
/api/v1/personal-recordsrecords:readpaginadoOs recordes pessoais do utilizador, do mais recente para o mais antigo.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
exerciseId | query | integer | Filtra por um exercício do catálogo. |
Resposta: Recorde pessoal (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| exerciseId | integer | |
| exerciseName | string | |
| type | "oneRepMax" | "weightMax" | "volumeMax" | "repetitionMax" | "durationMax" | "distanceMax" | |
| reps | integer | null | |
| weight | number | null | Libras. |
| duration | integer | null | Segundos. |
| distance | integer | null | Metros. |
| workoutId | string (uuid) | null | Treino em que o recorde foi alcançado, quando conhecido. |
| 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:readpaginadoRegistos de peso corporal e perímetros, do mais recente para o mais antigo.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
startDate | query | string (ISO 8601) | Apenas registos com data igual ou posterior a esta (inclusive). |
endDate | query | string (ISO 8601) | Apenas registos com data igual ou anterior a esta (inclusive). |
Resposta: Medição (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| id | string (uuid) | |
| date | string (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. |
| weight | number | null | |
| bodyFat | number | null | Percentagem. |
| neck / shoulder / chest / bicep / forearm / abdomen / waist / glutes / thigh / calf | number | null | Perímetros no sistema de unidades do registo. |
Estatísticas e troféus
/api/v1/statsstats:readAs principais estatísticas de treino do utilizador numa única chamada.
Resposta: Estatísticas
| Campo | Tipo | Descrição |
|---|---|---|
| streak.current | integer | Sequência atual, em semanas. |
| streak.longest | integer | Sequência mais longa de sempre, em semanas. |
| streak.workoutsThisWeek | integer | |
| totalXp | integer | |
| trophyCount | integer |
{
"streak": { "current": 6, "longest": 14, "workoutsThisWeek": 3 },
"totalXp": 12480,
"trophyCount": 22
}/api/v1/trophiesstats:readpaginadoTroféus conquistados, do mais recente para o mais antigo.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
Resposta: Troféu (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| type | string | Tipo de troféu, em camelCase, p. ex. "workout100", "streak5", "calendar15". Podem surgir novos valores. |
| exerciseId | integer | null | Definido para troféus específicos de um exercício. |
| exerciseName | string | null | |
| achievedAt | string (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.
/api/v1/exercisesexercises:readpaginadoO catálogo de exercícios mais os exercícios personalizados do utilizador, por ordem alfabética.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
search | query | string | Correspondência no nome sem distinguir maiúsculas (contém). Máximo 200 caracteres. |
Resposta: Exercício (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| id | integer | Id estável do catálogo, referenciado por treinos, recordes e templates. |
| name | string | |
| description | string | null | |
| category | string | Enum em camelCase, p. ex. "barbell", "dumbbell". |
| type | string | Enum em camelCase, p. ex. "weight", "duration". |
| muscle | string | Músculo principal, em camelCase, p. ex. "chest". |
| secondaryMuscles | string[] | |
| equipment | string[] | Nomes de equipamento utilizável neste exercício. |
/api/v1/exercises/{id}exercises:readUm exercício pelo id de catálogo.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
idobrigatório | path | integer | Id do exercício no catálogo. |
Resposta: Exercício
| Campo | Tipo | Descrição |
|---|---|---|
| …igual ao item da listagem |
/api/v1/equipmentexercises:readpaginadoO catálogo global de equipamento, por ordem alfabética.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
page | query | integer | Número da página, a começar em 1. Por omissão: 1 |
pageSize | query | integer | Itens por página, 1–100. Por omissão: 30 |
Resposta: Equipamento (como items[] no envelope de paginação)
| Campo | Tipo | Descrição |
|---|---|---|
| id | integer | Id estável do catálogo. |
| name | string | |
| subtitle | string | null | |
| category | string | Enum em camelCase. |
/api/v1/equipment/{id}exercises:readUm item de equipamento pelo id de catálogo.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
idobrigatório | path | integer | Id do equipamento no catálogo. |
Resposta: Equipamento
| Campo | Tipo | Descriçã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.
/oauth/tokenTroca um código de autorização, ou roda um refresh token, por um par de access/refresh token.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
grant_typeobrigatório | form | string | "authorization_code" ou "refresh_token". |
client_idobrigatório | form | string | |
client_secret | form | string | Apenas clientes confidenciais; os clientes públicos omitem-no (PKCE em alternativa). |
code | form | string | Grant authorization_code: o código gac_…. |
redirect_uri | form | string | Grant authorization_code: tem de corresponder à URI para a qual o código foi emitido. |
code_verifier | form | string | Grant authorization_code: o verifier PKCE do seu challenge. |
refresh_token | form | string | Grant refresh_token: o token grt_… (utilização única; rodado em caso de sucesso). |
Resposta: Resposta de token
| Campo | Tipo | Descrição |
|---|---|---|
| access_token | string | Bearer token (gat_…), válido por 6 horas. |
| token_type | "Bearer" | |
| expires_in | integer | Segundos até o access token expirar. |
| refresh_token | string | Novo refresh token rotativo (grt_…), válido até 180 dias. |
| scope | string | Scopes concedidos, separados por espaços. |
{
"access_token": "gat_…",
"token_type": "Bearer",
"expires_in": 21600,
"refresh_token": "grt_…",
"scope": "workouts:read stats:read"
}/oauth/revokeRevoga um token (RFC 7009). Revogar um refresh token revoga o grant inteiro. Devolve 200 mesmo para tokens desconhecidos.
Parâmetros
| Nome | Em | Tipo | Descrição |
|---|---|---|---|
tokenobrigatório | form | string | O access token ou refresh token a revogar. |
client_idobrigatório | form | string | |
client_secret | form | string | Apenas clientes confidenciais. |
Resposta: Vazio
| Campo | Tipo | Descrição |
|---|---|---|
| (sem corpo) | 200 em caso de sucesso; 401 invalid_client para credenciais inválidas. |