API Reference
Public endpoints, request shape, response structure, and live schema access for Power Oracle.
Live Endpoints
- Live API base: https://api.workcapacity.io
- API catalog: https://api.workcapacity.io/.well-known/api-catalog
- OpenAPI schema: https://api.workcapacity.io/openapi.json
- Payment requirements: https://api.workcapacity.io/v1/payment-requirements
Agents and software systems should use the OpenAPI schema directly.
Browsable Swagger and ReDoc interfaces are local-development tooling and are not part of the deployed public contract.
The current public contract is versioned under /v1. External users should integrate against the /v1/* routes.
Current Public Endpoints
GET /v1/healthGET /v1/movementsGET /v1/payment-requirementsGET /v1/athletes/{athlete_uuid}/workoutsGET /v1/athletes/{athlete_uuid}/workouts/{workout_id}GET /v1/athletes/{athlete_uuid}/curvePOST /v1/compute-powerPOST /v1/workouts/\{workout_id\}/revisionsPOST /v1/workouts/\{workout_id\}/void
GET /v1/health
Use this as a liveness check.
Example:
curl https://api.workcapacity.io/v1/health
GET /v1/movements
This endpoint returns the public movement registry that clients should consult before building compute requests.
Use it to discover:
- supported movement names
- required inputs
- alternative input rules
- supported overrides
- public modeling assumptions
Why it matters:
- It tells you which movements the system supports right now.
- It tells you which inputs are required versus optional.
- It helps an agent refuse to guess when a movement or required input is missing.
- It keeps clients aligned with the current movement library instead of hard-coding stale assumptions.
Sample Response Fragment
{
"air_squat": {
"name": "air_squat",
"required_inputs": [],
"supported_overrides": ["height_coefficient"]
},
"thruster": {
"name": "thruster",
"required_inputs": [
{
"name": "external_load",
"required": true,
"allowed_units": ["kg", "lb"]
}
],
"supported_overrides": ["height_coefficient", "load_height_coefficient"]
},
"row": {
"name": "row",
"required_inputs": [],
"alternative_inputs": [
{
"description": "Provide either rowing distance or displayed calories.",
"options": [
{ "name": "travel_distance", "allowed_units": ["m", "ft"] },
{ "name": "calories", "allowed_units": ["kcal"] }
]
}
]
}
}
Required vs Optional Inputs
air_squatneeds no movement-specific input.thrusterrequiresinputs.external_load.rowrequires exactly one alternative input:travel_distanceorcalories.- Overrides are optional and should only be sent when the client explicitly intends to depart from the default assumption.
GET /v1/payment-requirements
This endpoint returns the current public payment metadata for paid routes.
Use it to discover:
- which routes are currently paid
- the public display price for each route
- the currently resolved L402 sat amount when a BTC/USD quote is available
- the route policy identifier
- any public Bazaar metadata that describes the paid route
Sample Response Fragment
{
"routes": [
{
"method": "POST",
"path": "/v1/compute-power",
"policy_id": "compute-power-v2",
"protocol": "l402",
"payment_mode": "single_use",
"price": "$0.10",
"price_sats": 100
},
{
"method": "POST",
"path": "/v1/compute-power",
"policy_id": "compute-power-v2",
"protocol": "x402",
"payment_mode": "single_use",
"price": "$0.10"
},
{
"method": "POST",
"path": "/v1/workouts/{workout_id}/revisions",
"policy_id": "workout-revision-v1",
"protocol": "l402",
"payment_mode": "single_use",
"price": "$0.04",
"price_sats": 40
},
{
"method": "POST",
"path": "/v1/workouts/{workout_id}/revisions",
"policy_id": "workout-revision-v1",
"protocol": "x402",
"payment_mode": "single_use",
"price": "$0.04"
}
]
}
GET /v1/athletes/{athlete_uuid}/workouts
Returns an athlete's active canonical completed workouts, newest first. The athlete_uuid is a capability token. No bearer token or payment challenge is required.
Query parameters:
limit: page size,1-100, default20cursor: opaque next-page cursor from the previous responsesince: only workouts on or after this performed dateuntil: only workouts on or before this performed datedomain:short,medium, orlongmovement: exact movement name fromGET /v1/movementsupdated_since: only workouts updated at or after this UTC timestamp
The response includes total, has_more, cursor, and items[]. total is the count of active canonical workouts matching the supplied filters.
Each item includes workout_id, revision_id, revision_number, performed_date, updated_at, duration, total work, active average power, split count, rest flag, movement names, and notes.
GET /v1/athletes/{athlete_uuid}/workouts/{workout_id}
Returns one active canonical workout detail record for the given athlete. This is the route agents should use before deciding whether a past workout needs a correction or void.
The response includes:
workoutmetadata with canonicalworkout_id,revision_id, andrevision_numberperformed_dateandupdated_atresults.sessionresults.splits[]results.summaryresults.movement_rollups[]notes
The route returns 404 when the athlete is unknown, the workout is unknown, the workout belongs to a different athlete, or the workout has been voided.
GET /v1/athletes/{athlete_uuid}/curve
Returns a bounded power-duration curve for active canonical completed workouts. This route is public and uuid-gated.
Query parameters:
since: only workouts on or after this performed dateuntil: only workouts on or before this performed datemax_points: maximum newest matching workout points to process and return, default500, maximum1000include_points:allorenvelopetrend_window_days: recent trend window, default14, maximum60trend_baseline_days: baseline window before the recent window, default90, range7-365
The response includes:
points: returned workout points sorted by duration ascendingenvelope_points: Pareto-optimal points sorted by duration ascendingwork_capacity_auc: area under the returned envelope in joules, using trapezoidal integration over linear durationdomain_slices: short, medium, and long bestsdomain_trends: recent-versus-baseline trends computed within each duration domain
work_capacity_auc is a Power Oracle work-capacity estimate over the selected response window. It is aligned with CrossFit's work-capacity curve framing, but it is only as complete as the athlete's logged coverage across time and modal domains.
POST /v1/compute-power
POST /v1/compute-power accepts a session-first, split-aware request body.
Required Top-Level Fields
athlete_uuidevaluation_contextduration_secondsusersplits
Context Rules
completedrequiresperformed_dateplannedrequiresplanned_for_dateplannedandhypotheticalmay includescenario_labelhypotheticalmust not includeperformed_dateorplanned_for_date
User Object
heightbody_mass- optional
age_years - optional
sex - optional
response_units
Each Split Includes
- optional
label - active
duration_seconds - optional
rest_seconds_after work.movements[]
Time Rules
- Top-level
duration_secondsis session elapsed time. - Split
duration_secondsis split active time. rest_seconds_afteris recovery after that split.- If session elapsed time exceeds accounted split time, the response returns
unattributed_duration_seconds.
Example Request
{
"athlete_uuid": "11111111-1111-1111-1111-111111111111",
"evaluation_context": "completed",
"performed_date": "2026-03-20",
"duration_seconds": 133,
"user": {
"height": { "value": 70, "unit": "in" },
"body_mass": { "value": 180, "unit": "lb" },
"age_years": 35,
"sex": "male"
},
"splits": [
{
"label": "21 thrusters",
"duration_seconds": 33,
"work": {
"movements": [
{
"movement": "thruster",
"reps": 21,
"inputs": {
"external_load": { "value": 95, "unit": "lb" }
},
"spec_overrides": {}
}
]
}
},
{
"label": "21 pull-ups",
"duration_seconds": 27,
"work": {
"movements": [
{
"movement": "pull_up",
"reps": 21,
"spec_overrides": {}
}
]
}
}
]
}
Response Shape
POST /v1/compute-power returns:
- optional
workout results.sessionresults.splits[]results.summaryresults.movement_rollups[]notespr_flags
Important semantics:
results.sessionreports session totals and elapsed/session-active powerresults.splits[]reports per-split work and active-time powerresults.summarycaptures peak, minimum, mean split power, dropoff, and consistencyresults.movement_rollups[]preserves movement totals without inventing per-movement timing inside grouped splitsnotesexplains denominator and modeling assumptionspr_flagslists any personal records the just-stored canonical event claimed against this athlete's prior canonical history — see Personal Records
Stored Completed Workout Metadata
When evaluation_context=completed, successful POST /v1/compute-power responses now include:
workout.workout_idworkout.revision_idworkout.revision_numberworkout.revision_statusworkout.supersedes_revision_id
Personal Records
Stored completed responses also carry a pr_flags array. Each entry describes a personal record this just-stored canonical event claimed against the athlete's prior canonical history. The array is empty by default — only stored completed workouts can claim PRs, and most workouts will not beat the current cache.
Example
"pr_flags": [
{
"pr_type": "best_signature",
"movement_name": null,
"domain": null,
"value_si": 28673.27,
"value_secondary_si": 80.0,
"prior_value_si": 28673.27,
"prior_value_secondary_si": 100.0,
"percent_improvement": 0.0,
"percent_improvement_secondary": 25.0
},
{
"pr_type": "domain_best",
"movement_name": null,
"domain": "short",
"value_si": 256.01,
"value_secondary_si": null,
"prior_value_si": 204.81,
"prior_value_secondary_si": null,
"percent_improvement": 25.0,
"percent_improvement_secondary": null
}
]
PR Types
best_avg_power— per movement, highestaverage_power_when_isolatedin watts. Emitted only for movements that appear in an isolated split, where movement-specific power is directly attributable. Fires at ≥ 3% over prior.movement_nameis set;domainis null.best_total_reps— per bodyweight movement only (movements with noexternal_load). Highest total reps for that movement in one session. Fires on any new max.movement_nameis set;domainis null.best_signature— percomparison_signature.value_siis joules of total work;value_secondary_siis the active duration in seconds. Fires on ≥ 2% more work OR ≤ 2% less duration.movement_nameanddomainare null.domain_best— per duration domain (short,medium,long).value_siis sessionaverage_power_activein watts. Fires on any genuine new domain best.domainis set;movement_nameis null.
Field Reference
value_si— the new winning value, always in SI units (watts, joules, or reps depending onpr_type). Display-unit overrides viaresponse_unitsdo not affect this field.value_secondary_si— second axis for compound metrics. Set only forbest_signature(active duration in seconds).prior_value_si/prior_value_secondary_si— the prior PR's values. Null on the athlete's first-ever PR for that natural key.percent_improvement/percent_improvement_secondary— signed percent change versusprior_value_*. Positive means improvement. Forvalue_sithe win direction is higher (more watts, more reps, more joules); forbest_signature's duration axis the win direction is shorter, and the field is already inverted so positive still means "faster." Clients pick whichever axis to display based on which improvement is meaningful. Abest_signaturePR claimed on the duration axis reportspercent_improvement: 0.0andpercent_improvement_secondaryas the headline number.
Empty pr_flags
pr_flags is empty when either (a) the event was not a new record on any tracked metric, or (b) the event was skipped because its performed_date is earlier than another canonical workout for the athlete. Backfilled history does not claim PRs against later attempts — otherwise the cache could silently demote a more-recent workout's records.
Revisions And Voids
Revisions and voids automatically re-derive the PR cache. When a canonical event is superseded or voided, the PR rows it set are invalidated and the next-best canonical attempt for each affected natural key is promoted as the new record. Revisions can themselves return new pr_flags if the corrected values beat prior history.
Additional Details
X-PowerOracle-Agentis an optional request header and is stored with successful compute events.api_versionandcompute_model_versionare server-managed and should not be sent by clients.- In MVP, only successful
completedrequests create stored workout history.plannedandhypotheticalrequests compute results but do not create stored workouts.
POST /v1/workouts/{workout_id}/revisions
Use this route to correct a stored completed workout.
Required fields:
supersedes_revision_idcompute_request
Optional fields:
correction_reason
Rules:
compute_request.evaluation_contextmust becompletedcompute_request.athlete_uuidmust match the athlete for the target workoutsupersedes_revision_idmust be the current canonical revision for that workout
If the supplied revision is stale, the route returns 409 Conflict.
Successful responses use the same workout + results + notes + pr_flags shape as POST /v1/compute-power. A revision can claim new PRs if its corrected values beat the prior canonical history; the PR cache is automatically reconciled across the revise/void cycle (see Personal Records).
POST /v1/workouts/{workout_id}/void
Use this route when the current canonical workout should stop counting and there is not yet a corrected replacement.
Required fields:
supersedes_revision_idvoid_reason
Behavior:
- the current canonical revision becomes
voided - the workout is removed from default history and analytics
- no new compute result is created
This route returns workout state only, not compute results.
Example response:
{
"workout": {
"workout_id": "11111111-1111-1111-1111-111111111111",
"workout_status": "voided",
"voided_revision_id": "22222222-2222-2222-2222-222222222222"
}
}
Paid Route Behavior
In production, compute-bearing routes are paid:
POST /v1/compute-powerPOST /v1/workouts/\{workout_id\}/revisions
Operational rules:
- first call may return
402 Payment Required - the x402 challenge arrives in
PAYMENT-REQUIRED - the L402 challenge arrives in
WWW-Authenticate: L402 ... - retry with
PAYMENT-SIGNATURE: <x402 proof>orAuthorization: L402 <token>:<preimage> - successful x402 responses include
PAYMENT-RESPONSE - route price is
$0.10forPOST /v1/compute-powerand$0.04forPOST /v1/workouts/\{workout_id\}/revisions - x402 uses the USD-denominated exact price directly
- L402 uses sats derived from the same USD route price with a cached live BTC/USD quote
- authorized
2xxand4xxresponses settle or consume the payment, while5xxresponses do not POST /v1/workouts/\{workout_id\}/voidis free- If the live challenge or server behavior differs, treat the live server behavior as authoritative.
See Workout History And Corrections for the rationale and workflow, Calling Power Oracle from a Client for the operational flow, and Payments for the payment contract.
