API & MCP. Build on top of Workout Chat.
A remote MCP server you can plug into Claude, ChatGPT, Cursor, and other AI clients — plus a simple JSON REST API for workouts, exercises, weigh-ins, injuries, and goals. OAuth for MCP, Bearer tokens for REST, no versioning ceremony either way.
MCP server
Workout Chat speaks the Model Context Protocol. Add it to any MCP-capable client and the AI you're already talking to can log your workout, record a weigh-in, check on an injury, set a goal, or look up an exercise — using your Workout Chat account. Same tools the in-app assistant uses.
Endpoint
https://workout.chat/mcp
Streamable HTTP transport. Protocol version 2025-03-26.
Authentication
OAuth 2.1 with PKCE and dynamic client registration (RFC 7591). The first time your client connects, it'll open a browser tab to log into Workout Chat and approve access — there's no API key to copy or rotate. The same tokens work for the REST API.
Available tools
log_workout— create a workout from natural-language exerciseslist_workouts— recent sessions, paginated and date-filterableupdate_workout·update_exercise— fix titles, notes, sets/reps/weightweighin·update_profile— log bodyweight, change unit systemanalyze_progress·lookup_exercise— trends, PRs, canonical exercise lookuplog_injury·update_injury·checkin_injury— track and update injuriesset_goal·list_goals·abandon_goal— capture and review training targets
Set up in Claude Desktop
In Settings → Connectors → Add custom connector, paste:
https://workout.chat/mcp
Or, if you prefer the config file
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"workout-chat": {
"url": "https://workout.chat/mcp"
}
}
}
Restart Claude. The first message that touches Workout Chat will pop a browser tab for OAuth — log in, approve, and you're connected.
Set up in ChatGPT
On a plan that supports MCP connectors: Settings → Connectors → Add MCP server. Server URL:
https://workout.chat/mcp
ChatGPT will run the OAuth flow on first use.
Set up in Cursor, Cline, Continue, Goose, etc.
Drop the same JSON snippet into your client's MCP config (the file path varies — check your client's docs):
{
"mcpServers": {
"workout-chat": {
"url": "https://workout.chat/mcp"
}
}
}
Discovery URLs
For clients that auto-discover OAuth metadata:
/.well-known/oauth-protected-resource/mcp # RFC 9728 /.well-known/oauth-authorization-server # RFC 8414 /.well-known/mcp/server-card.json # SEP-1649 server card
Try it
Once connected, ask your AI things like:
"Log bench 3x8 at 185, OHP 3x10 at 95 to my workout chat" "What did I train this week?" "Set a goal to bench 225 by July" "My left shoulder felt tight after OHP — log that" "How am I progressing on squat?"
REST API. Build your own client.
Bearer-token JSON for everything in the app — workouts, exercises, weigh-ins, injuries, goals, and live chat streaming. Same auth as MCP; if you've already connected, the token works here too.
Getting started
Base URL
https://workout.chat/api
Authentication
Every request requires an Authorization: Bearer <token>
header. Tokens are issued through the same OAuth 2.1 flow the
MCP server uses — if you've already
connected via Claude Desktop / ChatGPT / Cursor, that token works
here too. Visit /.well-known/oauth-authorization-server
for discovery.
curl https://workout.chat/api/profile \
-H "Authorization: Bearer YOUR_TOKEN"
Response shape
Every successful response wraps its payload in data:
{
"data": { ... } // single resource
}
{
"data": [ ... ], // list
"meta": {
"total": 142,
"page": 1,
"per_page": 50
}
}
Errors
{
"error": {
"code": "validation_failed",
"message": "Name can't be blank",
"details": { "name": [{"error": "blank"}] }
}
}
Standard HTTP status codes: 401 unauthorized,
404 not found, 422 validation failed,
400 bad request. Resources scoped to another user
return 404 rather than 403.
Pagination
List endpoints accept ?page=N&per_page=M. Defaults:
page=1, per_page=50. Max
per_page=200.
Index / ping
/api
Returns API metadata and the authenticated user. Useful as a "is my token working?" ping.
{
"name": "Workout Chat API",
"docs_url": "https://workout.chat/api_docs",
"authenticated_as": { "id": 1, "email": "you@example.com", "name": "…" },
"resources": {
"profile": "/api/profile",
"stats": "/api/stats",
"weighins": "/api/weighins",
"workouts": "/api/workouts",
"injuries": "/api/injuries"
}
}
Stats
/api/stats
Aggregated, high-level KPIs for the authenticated user — workout counts across rolling windows, exercise totals, injury status breakdown, latest weigh-in, and top personal records. Useful for building your own dashboard without walking every resource.
{
"data": {
"workouts": {
"total": 142,
"last_7_days": 4,
"last_30_days": 15,
"last_90_days": 42,
"first_performed_at": "2025-09-14T00:00:00Z",
"last_performed_at": "2026-04-18T17:30:00Z"
},
"exercises": {
"total": 920,
"unique_names": 48,
"last_30_days": 120
},
"weighins": {
"total": 22,
"latest": { "date": "2026-04-18", "weight_kg": 81.2 }
},
"injuries": {
"total": 2,
"active": 1,
"recovering": 1,
"healed": 0
},
"personal_records": {
"count": 15,
"top": [
{ "name": "Deadlift", "weight_kg": 142.88 },
{ "name": "Squat", "weight_kg": 136.08 },
{ "name": "Bench Press", "weight_kg": 83.91 }
]
}
}
}
Profile
/api/profile
Returns the authenticated user's profile.
/api/profile
Updates name, email, unit system (metric / imperial), birth year, height, or gender.
curl -X PATCH https://workout.chat/api/profile \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"profile": {"system": "metric", "height_cm": 178}}'
Weigh-ins
/api/weighins
List weigh-ins, newest first. Filter with
?since=YYYY-MM-DD and/or ?until=YYYY-MM-DD.
/api/weighins
weight_kg is required. date defaults to today if omitted.
curl -X POST https://workout.chat/api/weighins \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"weighin": {"date": "2026-04-18", "weight_kg": 84.1}}'
/api/weighins/:id
Workouts
/api/workouts
List workouts, most recent first. Supports
?since= / ?until= date filters.
Each workout includes its exercise_count.
/api/workouts/:id
Returns the workout with its full list of exercises nested under
exercises.
/api/workouts
performed_at defaults to the current time if omitted.
Accepts title, notes, performed_at,
raw_text.
curl -X POST https://workout.chat/api/workouts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workout": {
"title": "Upper Body Push",
"notes": "Bench focus day",
"performed_at": "2026-04-18T17:30:00Z"
}
}'
/api/workouts/:id
/api/workouts/:id
Exercises
/api/workouts/:workout_id/exercises
List exercises on a workout, in order.
/api/workouts/:workout_id/exercises
Appends an exercise to a workout. position auto-assigns
to the next slot if omitted. raw_line defaults to the
exercise name when blank. Accepts name,
sets, reps, weight_kg,
entered_weight, entered_weight_unit,
duration_seconds, distance_km,
kind, equipment,
muscle_area, notes, position,
raw_line.
curl -X POST https://workout.chat/api/workouts/123/exercises \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"exercise": {
"name": "Bench Press",
"sets": 3,
"reps": 8,
"entered_weight": 185,
"entered_weight_unit": "lbs"
}
}'
/api/exercises/:id
/api/exercises/:id
/api/exercises/:id
Injuries
/api/injuries
List injuries, newest first. Filter with
?status=active, recovering, or
healed.
/api/injuries/:id
Returns the injury with its check-in history nested under checkins.
/api/injuries
occurred_on defaults to today if omitted.
severity defaults to moderate,
status defaults to active.
curl -X POST https://workout.chat/api/injuries \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"injury": {
"name": "Left shoulder impingement",
"body_area": "shoulder",
"side": "left",
"injury_type": "impingement",
"severity": "moderate",
"exercises_to_avoid": ["OHP", "Lateral Raise"]
}
}'
/api/injuries/:id
/api/injuries/:id
Chat
The chat loop is a two-step flow: you POST a user message to enqueue it, and the assistant's tokens stream back over a WebSocket as the job runs. Same pipeline the web app uses.
Send a message
/api/chat/messages
Creates the user message and enqueues the AI turn. Returns
202 Accepted immediately — listen on the WebSocket
for the streamed response.
curl -X POST https://workout.chat/api/chat/messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": {"content": "bench 3x8 at 185, OHP 3x10 at 95"}}'
{
"data": {
"id": 123,
"role": "user",
"content": "bench 3x8 at 185, OHP 3x10 at 95",
"created_at": "2026-04-18T17:30:00Z"
},
"meta": {
"status": "queued",
"channel": "ChatChannel",
"stream_scope": "user"
}
}
Stream responses over WebSocket
Connect to the ActionCable WebSocket and subscribe to
ChatChannel. Authenticate by passing your bearer token
as a query param:
wss://workout.chat/cable?token=YOUR_TOKEN
Once connected, send the standard ActionCable subscribe frame:
{
"command": "subscribe",
"identifier": "{\"channel\":\"ChatChannel\"}"
}
The server pushes three kinds of payloads as the job runs. Token
frames arrive as the model streams; a single done frame
signals completion.
{ "type": "token", "content": "Logged " }
{ "type": "token", "content": "2 exercises." }
{ "type": "done" }
// on failure:
{ "type": "error", "content": "Something went wrong." }
The channel is scoped to the authenticated user — you'll only receive your own chat's stream. Full typical flow from an iOS client:
1. Open WebSocket to wss://workout.chat/cable?token=$TOKEN 2. Send the `subscribe` frame above 3. POST /api/chat/messages with the user's input 4. Concatenate incoming `token` payloads into the assistant bubble 5. When `done` arrives, finalize the bubble (render markdown, etc.)
Injury check-ins
/api/injuries/:injury_id/checkins
List check-ins on an injury, newest first.
/api/injuries/:injury_id/checkins
Log a check-in. date defaults to today and
status defaults to "same" if omitted.
If status is "healed" the parent injury
auto-updates to healed and records healed_on.
If "improving" and the injury is still
active, it moves to recovering.
curl -X POST https://workout.chat/api/injuries/42/checkins \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"checkin": {
"date": "2026-04-18",
"severity": 4,
"sensation": "tightness",
"status": "improving",
"notes": "Feels 80% better"
}
}'