Open Workout

Development draft, v0. The schema may change; every change is in the changelog.

Open Workout API core

The routes every Open Workout server serves, from core.yaml (OpenAPI 3.1, version v0). Check a server against it with ows conformance; the quickstarts show it in use.

The REST API any Open Workout server serves over a person's workout log. Workouts are Open Workout Schema v0 documents (https://openworkout.dev/schema/v0/workout.json) whose exercises are Open Exercise IDs (https://openexercise.org).

A server is found from a 401: its WWW-Authenticate header names the OAuth protected resource metadata (RFC 9728), which names the authorization server (RFC 8414). Clients use OAuth 2.1 with PKCE. Errors are RFC 9457 problem details whose code says what went wrong. Requests that create things take an Idempotency-Key header, and a retry with the same key returns the first result with Idempotent-Replayed: true. Webhook deliveries are signed in OWS-Signature.

A server may add routes, fields and limits of its own; clients ignore what they don't know. ows conformance checks a server against this document's behaviour. Lifterclub (https://api.lifterclub.com) is the reference implementation.

Workouts

GET /v1/export

Download the whole log scope: workouts:read

Every workout in the log, newest first, as a file. json holds Open Workout Schema workouts, and a server may add more of the record beside them. csv has one row per set, and needs workouts:read; free text starting with =, +, - or @ gets a leading ', so spreadsheets don't run it. Deleted workouts aren't in workouts or the CSV. A server may limit how often it runs. If it fails part way the connection is cut, so an incomplete file never looks finished.

ParameterInTypeMeaning
formatquerystring
StatusMeaningBody
200The file, sent as an attachment.text/csv: string
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
429rate_limited.application/problem+json: Problem

GET /v1/workouts

List workouts, newest first scope: workouts:read

Totals cover the workouts on this page.

ParameterInTypeMeaning
fromquerystring (date)First day, YYYY-MM-DD in the user's timezone. Default 30 days before to.
toquerystring (date)Last day, inclusive. Default today.
exercisequerystringOnly workouts with this exercise (name or ID), showing just its entries.
limitqueryintegerWorkouts per page.
cursorquerystringnext_cursor from the previous page.
updated_sincequerystring (date-time)The change feed: every workout whose stored record changed at or after this time, oldest change first, ignoring from, to and exercise. For mirroring a log; see include_deleted.
include_deletedquerybooleanWith updated_since, also list the workouts deleted since then, as tombstones in deleted.
StatusMeaningBody
200A page of workouts.application/json: WorkoutList
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
422exercise_ambiguous (with candidates) or idempotency_conflict.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

POST /v1/workouts

Log a workout scope: workouts:write

ParameterInTypeMeaning
Idempotency-KeyheaderstringAny unique string, up to 200 characters. Retrying with the same key never writes twice.

Body: WorkoutInput

StatusMeaningBody
200An earlier request with the same Idempotency-Key; nothing new was stored.application/json: Logged
201Stored.application/json: Logged
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
422exercise_ambiguous (with candidates) or idempotency_conflict.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

GET /v1/workouts/{id}

Get a workout scope: workouts:read

ParameterInTypeMeaning
id requiredpathstring (uuid)
StatusMeaningBody
200The workout.application/json: Workout
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/json: BearerError
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

PATCH /v1/workouts/{id}

Change a workout's title, date or notes, or finish it scope: workouts:write

ParameterInTypeMeaning
id requiredpathstring (uuid)

Body: WorkoutPatch

StatusMeaningBody
200The workout after the change.application/json: Logged
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

DELETE /v1/workouts/{id}

Delete a workout scope: workouts:write

Returns an undo token that restores it for 10 minutes.

ParameterInTypeMeaning
id requiredpathstring (uuid)
StatusMeaningBody
200Deleted, with an undo token.application/json: Deleted
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/json: BearerError
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

Webhooks

GET /v1/webhooks

List this app's webhooks on the account scope: workouts:read

Without secrets. A webhook belongs to the grant the token came from, so each app sees only its own.

StatusMeaningBody
200The app's subscriptions.application/json: WebhookList
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
429rate_limited.application/problem+json: Problem

POST /v1/webhooks

Be told when the log changes scope: workouts:read

Subscribes the calling app to changes in this person's log. Every minute, each changed workout becomes one signed POST to url carrying the kind of change and the workout's ID, never its content; read the workout afterwards. The secret in the response is shown once. Only a connected app can subscribe: not a personal key, not the web app. At most 5 per app and account. Ten failed deliveries in a row pause the subscription; subscribe again to resume.

Body: WebhookInput

StatusMeaningBody
201Subscribed. Location names the subscription.application/json: Webhook
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/json: BearerError
429rate_limited.application/problem+json: Problem

DELETE /v1/webhooks/{id}

End a webhook scope: workouts:read

ParameterInTypeMeaning
id requiredpathstring
StatusMeaningBody
204Deleted.
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

Sets and entries

PATCH /v1/entries/{id}

Confirm an entry's exercise, or change its notes scope: workouts:write

Setting exercise_id resolves needs_confirmation, and the server may remember what the user meant.

ParameterInTypeMeaning
id requiredpathstring (uuid)

Body: EntryPatch

StatusMeaningBody
200The workout after the change.application/json: Logged
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

DELETE /v1/entries/{id}

Delete an exercise entry with its sets scope: workouts:write

ParameterInTypeMeaning
id requiredpathstring (uuid)
StatusMeaningBody
200Deleted, with an undo token.application/json: Deleted
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

POST /v1/entries/{id}/sets

Add sets to an exercise entry scope: workouts:write

ParameterInTypeMeaning
id requiredpathstring (uuid)
Idempotency-KeyheaderstringAny unique string, up to 200 characters. Retrying with the same key never writes twice.

Body: EntrySetsRequest

StatusMeaningBody
200An earlier request with the same Idempotency-Key; nothing new was stored.application/json: Logged
201The workout after the change.application/json: Logged
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
422exercise_ambiguous (with candidates) or idempotency_conflict.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

PATCH /v1/sets/{id}

Change a set scope: workouts:write

ParameterInTypeMeaning
id requiredpathstring (uuid)

Body: SetPatch

StatusMeaningBody
200The workout after the change.application/json: Logged
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

DELETE /v1/sets/{id}

Delete a set scope: workouts:write

ParameterInTypeMeaning
id requiredpathstring (uuid)
StatusMeaningBody
200Deleted, with an undo token.application/json: Deleted
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

POST /v1/undo

Restore what a delete removed scope: workouts:write

Body: UndoRequest

StatusMeaningBody
200The workout after the change.application/json: Logged
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
410The undo token is unknown, already used or expired.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

POST /v1/workouts/{id}/sets

Add sets for an exercise scope: workouts:write

Sets for an exercise the workout already has join its entry.

ParameterInTypeMeaning
id requiredpathstringA workout ID, or current for the session in progress (started if there is none in the last 4 hours).
Idempotency-KeyheaderstringAny unique string, up to 200 characters. Retrying with the same key never writes twice.

Body: AddSetsRequest

StatusMeaningBody
200An earlier request with the same Idempotency-Key; nothing new was stored.application/json: Logged
201The workout after the change.application/json: Logged
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
404not_found: no such resource in this account.application/problem+json: Problem
422exercise_ambiguous (with candidates) or idempotency_conflict.application/problem+json: Problem
429rate_limited.application/problem+json: Problem

History

GET /v1/exercises

Search the catalogue and the user's custom exercises scope: workouts:read

ParameterInTypeMeaning
query requiredquerystringName or shorthand, e.g. "rdl".
equipmentquerystring
musclequerystring
limitqueryinteger
StatusMeaningBody
200Best matches first.application/json: object
400invalid_input: field names what to fix.application/problem+json: Problem
401Missing, invalid or expired token, or the account no longer exists.application/problem+json: Problem
403The token lacks the scope (a bearer error), or the user hasn't given health-data consent (consent_required).application/problem+json: Problem
429rate_limited.application/problem+json: Problem

Schemas

AddSetsRequest

FieldTypeMeaning
sets requiredarray of SetInput
exercisestringAs the user named it.
exercise_idstring

BearerError

FieldTypeMeaning
error requiredone of invalid_request, invalid_token, insufficient_scope, server_error
error_descriptionstring

Candidate

FieldTypeMeaning
id requiredstring
is_custom requiredboolean
measurement_type requiredMeasurementType
name requiredstring
score requirednumber

Confirmation

An exercise to confirm with the user, then fix with PATCH /v1/entries/{entry_id}.

FieldTypeMeaning
candidates requiredarray of Candidate
entry_id requiredstring (uuid)
status requiredMatchStatus
text requiredstring

Date

string (date)

Deleted

FieldTypeMeaning
id requiredstring (uuid)
kind requiredone of workout, exercise, set
undo_expires_at requiredstring (date-time)
undo_token requiredstring

EntryPatch

FieldTypeMeaning
exercise_idstringFrom needs_confirmation candidates or /v1/exercises.
notesstring

EntrySetsRequest

FieldTypeMeaning
sets requiredarray of SetInput

ExerciseInput

One exercise and its sets: name and exercise_id, or an OWS entry's exercise and text.

FieldTypeMeaning
sets requiredarray of SetInput
exerciseobjectAn OWS entry's exercise. Its id is exercise_id; its name is used when neither name nor text is given; match is the server's and ignored.
exercise_idstringA catalogue or custom ID, when known; skips matching.
idstringA stored entry's ID, as an OWS document carries it. Ignored.
namestringThe exercise as the user named it.
notesstring
textstringAn OWS entry's text: what the person called the exercise. Used as name when name isn't given.

ExerciseMatch

FieldTypeMeaning
equipment requiredarray of string
id requiredstring
is_custom requiredboolean
measurement_type requiredMeasurementType
name requiredstring
primary_muscles requiredarray of string
score requirednumber

Load

load (../schema/v0/workout.json#/$defs/load)

Logged

The result of a write.

FieldTypeMeaning
needs_confirmation requiredarray of Confirmation
workout requiredWorkout
replayedboolean

MatchStatus

catalogue and custom are confirmed; close and unmatched need confirmation.

one of catalogue, close, custom, unmatched

MeasurementType

one of weight_reps, reps, duration, distance_duration, assisted_weight_reps

OWSExport

The log as the Open Workout core promises it, an OWS v0 log document (https://openworkout.dev/schema/v0/log.json). A server may add more of the person's record beside its keys.

log (../schema/v0/log.json)

Problem

FieldTypeMeaning
code requiredone of invalid_input, consent_required, exercise_ambiguous, not_found, undo_unavailable, rate_limited, idempotency_conflict, unauthenticated, internal_error
status requiredinteger
title requiredstring
type requiredstring (uri)
candidatesarray of CandidateFor exercise_ambiguous.
detailstring
fieldstringFor invalid_input.

ProgramRef

FieldTypeMeaning
id requiredstring
daystring
day_namestringAn OWS workout's program label. Ignored.
listedbooleanAn OWS workout's program label. Ignored.
namestringAn OWS workout's program label. Ignored.
versioninteger

SetInput

A set as a client describes it, or as an OWS workout document has it.

FieldTypeMeaning
caloriesintegerKilocalories, only as a machine, watch or app reported them.
distance_mnumber
duration_sinteger
estimatedbooleanThe values were inferred, not stated: read off a machine's screen, say.
heart_rate_bpmintegerAverage heart rate over the set, only as a watch or app reported it.
idstringA stored set's ID, as an OWS document carries it. Ignored.
loadnumber or LoadLeave out for bodyweight. A number with unit beside it, or an OWS load {value, unit}.
repsinteger
rpenumber
typeone of working, warmup, backoff, amrap, failure, dropThe OWS set type; working when left out. Only warm-ups are left out of records.
unitUnitLeave out for the user's unit.
warmupbooleanThe same as type warmup. Warm-ups don't count toward records.

SetPatch

FieldTypeMeaning
caloriesinteger
distance_mnumber
duration_sinteger
heart_rate_bpminteger
loadnumber
remove_loadbooleanMake it bodyweight.
repsinteger
rpenumber
unitUnit
warmupboolean

Tombstone

A deleted workout in the change feed, enough to remove a mirror's copy and nothing of what it held.

FieldTypeMeaning
deleted_at requiredstring (date-time)
id requiredstring
updated_at requiredstring (date-time)
merged_intostringThe workout it was merged into, when another app's record of the same session absorbed it.

UndoRequest

FieldTypeMeaning
undo_token requiredstring

Unit

one of kg, lb

Webhook

FieldTypeMeaning
consecutive_failures requiredinteger
created_at requiredstring (date-time)
events requiredarray of one of workout.created, workout.updated, workout.deleted
id requiredstring
url requiredstring
last_delivered_atstring (date-time)
last_statusintegerThe endpoint's last HTTP status; 0 when it couldn't be reached.
paused_atstring (date-time)Set after ten failed deliveries in a row. Nothing is delivered until the app subscribes again.
secretstringOnly in the creation response: the key the deliveries are signed with. OWS-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">.

WebhookInput

FieldTypeMeaning
events requiredarray of one of workout.created, workout.updated, workout.deleted
url requiredstringAn https URL on a public host, with no credentials. Checked again on every delivery, on the address actually reached.

WebhookList

FieldTypeMeaning
webhooks requiredarray of Webhook

Workout

workout (../schema/v0/workout.json)

WorkoutInput

A workout to log. An OWS workout document can be posted as it is: the members the server assigns (id, updated_at, merged_sources, source's kind and client, entries' and sets' id, an exercise's match, a program label's name, day_name and listed) are ignored.

FieldTypeMeaning
exercises requiredarray of ExerciseInput
$schemaany
dateDateDay in the workout's timezone, when started_at is not given. Default today. The start is then worked out and listed in estimates as /started_at.
estimatesarray of stringJSON pointers (RFC 6901) into this body to values you inferred rather than were told: a set, or a value in one (the set is marked), a set's /load/unit (the unit was assumed), or /started_at.
external_idstringYour app's own ID for this workout. A second POST with the same ID replays the first (200, Idempotent-Replayed: true) instead of logging twice; one the person has since deleted here answers 404 and stays deleted. Not for the web app.
idstringA stored workout's ID, as an OWS document carries it. Ignored.
merged_sourcesarray of objectIgnored: merges are the server's.
notesstring
programProgramRef
sourceobjectAn OWS document's source. Its external_id is read as external_id; kind and client come from your credential.
started_atstring (date-time)
timezonestringThe IANA time zone the workout happened in (America/Chicago), which decides its local day. Default the person's.
titlestring
updated_atstring (date-time)Ignored: the server's clock sets it.

WorkoutList

FieldTypeMeaning
truncated requiredbooleanMore workouts follow; pass next_cursor.
workouts requiredarray of Workout
deletedarray of TombstoneWorkouts deleted since updated_since, when include_deleted was asked for.
exercisestringThe exercise filter's name.
fromDateFirst day listed; absent in the change feed.
next_cursorstring
toDateLast day listed; absent in the change feed.
updated_sincestring (date-time)The change feed's start, when listing by updated_since.

WorkoutPatch

FieldTypeMeaning
dateDateMove the workout to this day.
ended_atstring (date-time)Finish the workout (a live session). Not before it started, nor in the future; POST /v1/workouts/current/sets then starts a new one.
notesstring
titlestring