Before you start
- It is for clients only, and you only ever see your own data.
- It works at studios that have turned it on. If you get
api_disabled, ask your studio. - Trainer comments, recap notes, issue notes and trainer names are not included.
- Base URL:
https://app.everstrongsf.com/api/public/v1. All responses are JSON.
1. Get a token
Send your EverStrongSF username (or email) and password once. Store the tokens, not your password. The access token lasts 12 hours and the refresh token 30 days.
curl -X POST https://app.everstrongsf.com/api/public/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"username": "[email protected]", "password": "…"}'
{
"tokenType": "Bearer",
"accessToken": "…",
"refreshToken": "…",
"accessExpiresAt": "2026-10-20T22:00:00.000Z",
"refreshExpiresAt": "2026-11-19T10:00:00.000Z"
}When the access token expires, POST {"refreshToken": "…"} to /auth/refresh for a new pair. The old pair stops working. Six wrong passwords lock sign-in for 30 minutes, the same as the app.
2. Call the API
curl "https://app.everstrongsf.com/api/public/v1/workouts?from=2026-01-01&limit=50" \
-H "Authorization: Bearer $ACCESS_TOKEN"const res = await fetch("https://app.everstrongsf.com/api/public/v1/exercises", {
headers: { Authorization: `Bearer ${accessToken}` },
});
const { data } = await res.json();
for (const e of data) console.log(e.exercise, e.firstWeight, "→", e.bestWeight);Endpoints
| POST | /auth/token | Exchange your username and password for tokens |
| POST | /auth/refresh | Exchange a refresh token for a new pair |
| POST | /auth/revoke | Revoke a token pair |
| GET | /me | Your name, studio and weight unit |
| GET | /programs | Your current programs, with how each exercise is set up |
| GET | /exercises | Every exercise you have performed, with first, best and latest weight |
| GET | /workouts | Every recorded set, newest first, paginated |
| GET | /recaps | Dates of your sessions that have a recap, newest first |
The full schema is at openapi.json (OpenAPI 3.1).
Workouts
One row per recorded set. Optional filters: from and to (YYYY-MM-DD, inclusive), exercise (exact name), program (letter) and limit (1–500, default 100). When there are more rows, the response carries a nextCursor. Pass it back as cursor with the same filters.
{
"data": [
{
"id": "…",
"date": "2026-10-14",
"exercise": "Leg Press",
"machineId": "…",
"program": "A",
"weight": 310,
"reps": 9,
"time": "1:52",
"reachedFailure": true,
"nextWeight": 320,
"recordedAt": "2026-10-14T17:41:09.000Z"
}
],
"nextCursor": "…"
}| date | Session date, YYYY-MM-DD |
| exercise | Machine name |
| machineId | Stable machine id. Survives a machine being renamed |
| program | Program letter (A, B, …), or null |
| weight | Weight used, in the weightUnit from /me |
| reps | Repetitions completed, or null |
| time | Time under load, m:ss |
| reachedFailure | Whether the set reached momentary muscular failure |
| nextWeight | Weight planned for next time, or null |
| recordedAt | When the set was recorded, ISO 8601 |
Programs
Each exercise in your current programs, in order, with its prescription: protocol (failure, rep-max or drop-set), targetReps for a rep max, timeUnderTensionSeconds, the rep cadence, your machine setup (seat, back, hand, handle, pin, body position) and any notes, cues or warnings.
Errors and limits
Errors look like {"error": {"code": "…", "message": "…"}}. You can make 60 requests a minute.
| 400 | invalid_request | A parameter is missing or malformed |
| 401 | invalid_credentials / unauthorized / token_expired | Sign in again, or refresh |
| 403 | forbidden | Not a client account |
| 403 | api_disabled | Your studio has not turned the API on |
| 429 | rate_limited / too_many_attempts | Wait for the Retry-After seconds |
Keep it safe
A token reads your training data, so treat it like a password. Revoke one you no longer need by POSTing it to /auth/revoke. Changing your password does not revoke existing tokens. Revoke them too. Questions: [email protected].