mako

Partner API

Read-only HTTPS access to your organization's MakoSwim data: workouts as written and as swum, races with splits and stroke rates, attendance, test sets and wellness. This page documents v1: authentication, conventions, the twelve endpoints, and the OpenAPI contract.

mako partner api
#

Overview

The API is organized around twelve list resources under /v1. Every endpoint returns rows in the same JSON envelope, authenticates with the same bearer key, and uses standard HTTP response codes. GET is the only method. No SDK is required.

base url
https://europe-west3-makoswim-prod-9ab4d.cloudfunctions.net/dataApi/v1

Workouts are exposed in three layers: the text exactly as the coach wrote it (workout-content), the parsed structure of every line (structured=true), and the per-athlete volumes the training engine computed when the session was saved (workouts, workout-segments, workout-set-volumes). Attribution is joint: a segment row carries zone, stroke, style and equipment together, so "fly kick at zone 2 in July, per athlete" is one filter and one group-by on workout-segments.

#

Getting access

The Data API ships with the MakoSwim federation tier. Your federation authorizes access in writing, and we issue an organization-scoped key with exactly the scopes you request. Keys are shown once at issuance and stored only as a SHA-256 hash on our side, so treat the key like a password and put it straight into your secrets manager.

A key carries only the scopes that were requested:

ScopeUnlocks
rosterAthletes and teams
workoutsWorkout aggregates, segments, content, set volumes, terminology
attendanceSession attendance
performancesRace results with splits, stroke counts and rates
metricsTest-set definitions and results
wellnessDaily wellness surveys. Health data: never granted by default, only on explicit written instruction from your federation

To request access or add a scope, write to eric@makoapp.io from your federation account. To roll a key, email us: we issue the replacement first, and revoke the old one after you switch.

#

Authentication

Send the key as a bearer token on every request. Keys look like mk_live_ followed by 43 to 60 characters, and identify your organization on our side: the API never takes an org identifier from the caller, so a key can only ever read the organization it was issued for.

A missing, malformed, unknown or revoked key returns 401 unauthorized. A valid key whose organization is not on the federation tier returns 403 forbidden_federation. A valid key used outside its scopes returns 403 forbidden_scope and names the missing scope.

request
curl -H "Authorization: Bearer mk_live_YOUR_KEY" \
  "$BASE/v1/athletes?limit=50"
401 · problem+json
{
  "code": "unauthorized",
  "title": "Unauthorized",
  "detail": "Provide an API key: Authorization: Bearer mk_live_…",
  "status": 401
}
#

Envelope & pagination

Every list response is the same shape: data holds the rows, has_more tells you whether to keep going, and next_cursor is an opaque token you pass back as cursor. Walk until has_more is false.

Cursors are opaque on purpose. Do not parse or construct them; a tampered cursor returns 400 invalid_cursor. Page size is limit, from 1 to 500, defaulting to 100. Every endpoint accepts limit and cursor; the endpoint sections list only resource-specific parameters.

Attributes
dataarray
The rows.
has_moreboolean
Whether more rows exist beyond this page.
next_cursornullable string
Opaque cursor for the next page; null on the last page.
every response
{
  "data": [ …rows… ],
  "has_more": true,
  "next_cursor": "eyJkIjoiMjAyNi0wOC0xMiIsImlkIjoi…"
}
next page
curl -H "Authorization: Bearer $KEY" \
  "$BASE/v1/workouts?cursor=eyJkIjoiMjAyNi0wOC0xMiIsImlkIjoi…"
#

Filters & dates

Where a resource has dates, from and to are inclusive YYYY-MM-DD bounds. Most resources also filter by teamId and athleteId. Training dates are calendar dates, deliberately without timezone; timestamps, where present, are RFC 3339 UTC.

Responses are gzip-compressed when the client sends Accept-Encoding: gzip, and carry Cache-Control: no-store because rows contain personal data.

#

Rate limits

Per key: a burst of 20 requests, refilling at 10 per second, inside a cap of 3,000 per hour. Every response carries the headers below; a 429 adds Retry-After. Back off with jitter. At 500 rows per page, the hourly cap covers 1.5 million rows.

response headers
X-RateLimit-Limit: 3000
X-RateLimit-Remaining: 2984
X-RateLimit-Reset: 2711
Retry-After: 1        # on 429 only
#

Errors

Errors are RFC 9457 application/problem+json, with a stable machine-readable code. Branch on the code; log the detail for debugging.

The error object
typestring
URI identifying the error class (https://api.makoswim.io/problems/<code>).
titlestring
Short summary of the error class.
statusinteger
HTTP status code, repeated in the body.
codeenum
Stable machine-readable code; the full list is below.
detailstring
Human-readable explanation of this occurrence.
paramstring
The query parameter at fault; present on invalid_param and invalid_cursor.
StatusCodeMeaning
400invalid_paramA query parameter failed validation. The response names it.
400invalid_cursorThe cursor is not one we issued. Restart the walk.
401unauthorizedMissing, malformed, unknown or revoked key.
403forbidden_federationThe organization is not on the federation tier.
403forbidden_scopeThe key does not carry the required scope.
404not_foundUnknown endpoint. The response lists the known ones.
405method_not_allowedThe API is read-only. Use GET.
429rate_limitedRate limit exceeded. Honor Retry-After before retrying.
500internalUnexpected server error, logged on our side with the request.

#

GET/v1/athletesscope roster

One row per athlete: name, birth date, main stroke, and team and training-group membership. Other resources reference people by athleteId and join here. Contact fields are never exposed.

Attributes
idstring
Unique identifier for the athlete within the organization.
firstNamenullable string
The athlete's first name.
lastNamenullable string
The athlete's last name.
gendernullable enum
M, F, or null when not recorded.
dateOfBirthnullable date
Date of birth (YYYY-MM-DD).
mainStrokenullable string
The athlete's main stroke, as set on the roster.
teamIdnullable string
Identifier of the team the athlete belongs to.
trainingGroupIdnullable string
Identifier of the athlete's training group within the team.
statusnullable string
Roster status, as shown in the app (for example active).
hasAccountboolean
Whether the athlete has a linked app account.
Query parameters
teamIdLimit to one team
request
curl -H "Authorization: Bearer $KEY" \
  "$BASE/v1/athletes?teamId=t8xK2p&limit=100"
row
{
  "id": "aQ71Lm",
  "firstName": "Lena",
  "lastName": "Fischer",
  "gender": "F",
  "dateOfBirth": "2008-03-21",
  "mainStroke": "fly",
  "teamId": "t8xK2p",
  "trainingGroupId": "g_sprint",
  "status": "active",
  "hasAccount": true
}
#

GET/v1/teamsscope roster

Teams with their site and training groups. In a federation setup a site is typically a training base and teams are the squads inside it. Training-group ids from here appear on athlete rows and workout rosters.

Attributes
idstring
Unique identifier for the team.
namenullable string
Team name.
descriptionnullable string
Team description.
siteIdnullable string
Identifier of the site (training base) the team belongs to.
siteNamenullable string
Name of that site.
trainingGroupsarray of objects
The team's training groups.
idstring
Training-group identifier, referenced by athletes and workout rosters.
namenullable string
Group name.
colornullable string
Display color (hex).
row
{
  "id": "t8xK2p",
  "name": "National Training Base Berlin",
  "siteId": "s_berlin",
  "siteName": "Berlin",
  "trainingGroups": [
    { "id": "g_sprint", "name": "Sprint", "color": "#596AF7" }
  ]
}
#

GET/v1/workoutsscope workouts

One row per athlete per workout: total volume, training load, and marginal breakdowns by energy zone, stroke, training style and equipment, as computed by the engine when the session was saved.

Attributes
workoutIdstring
Identifier of the workout this row aggregates.
datenullable date
Session date (YYYY-MM-DD).
sessionnullable string
Session label (for example AM).
sessionIndexnullable integer
Ordinal of the session within the day.
teamIdnullable string
Identifier of the team.
poolTypenullable enum
SCY, SCM, or LCM.
athleteIdstring
Identifier of the athlete.
totalVolumenumber
Total distance swum by this athlete, in the pool's units.
trainingLoadnullable number
Training load computed for this athlete for the session.
byEnergymap
Distance per energy-zone id.
byStrokemap
Distance per stroke id.
byTrainingStylemap
Distance per training-style id.
byEquipmentmap
Distance per equipment id.
estimatedDurationSecondsnullable number
Estimated session duration for this athlete, in seconds.
Query parameters
from, toInclusive date bounds
teamIdOne team
athleteIdOne athlete
row
{
  "workoutId": "w_2026-08-12_am",
  "date": "2026-08-12",
  "session": "AM",
  "poolType": "LCM",
  "athleteId": "aQ71Lm",
  "totalVolume": 5400,
  "trainingLoad": 312,
  "byEnergy": { "BZ1": 2600, "BZ2": 2000, "BZ5": 800 },
  "byStroke": { "free": 4200, "fly": 1200 },
  "byTrainingStyle": { "swim": 4600, "kick": 800 },
  "estimatedDurationSeconds": 6930
}
#

GET/v1/workout-segmentsscope workouts

The attribution behind those marginals, in long format: one row per athlete, workout and segment, carrying volume, zone, stroke, style and equipment together. Marginals cannot answer cross-dimension questions (800m of kick and 800m at BZ2 may or may not be the same 800); segment rows can.

medley=resolved (default) distributes IM across its strokes; medley=preserved keeps it as im.

Attributes
workoutIdstring
Identifier of the workout the segment belongs to.
datenullable date
Session date (YYYY-MM-DD).
sessionnullable string
Session label (for example AM).
teamIdnullable string
Identifier of the team.
athleteIdstring
Identifier of the athlete.
volumenumber
Distance of this segment, in the pool's units.
zonenullable string
Energy-zone id; resolves via /v1/terminology.
strokenullable string
Stroke id. With medley=resolved, IM is distributed across its strokes.
stylenullable string
Training-style id (for example swim, kick, pull).
equipmentarray of strings
Equipment ids attached to the segment.
Query parameters
from, toInclusive date bounds (YYYY-MM-DD)
teamIdLimit to one team
athleteIdLimit to one athlete
medleyresolved (default) or preserved
fly kick at bz2, per athlete
curl -H "Authorization: Bearer $KEY" \
  "$BASE/v1/workout-segments?from=2026-07-01&to=2026-07-31"
row
{
  "workoutId": "w_2026-08-12_am",
  "date": "2026-08-12",
  "athleteId": "aQ71Lm",
  "volume": 400,
  "zone": "BZ2",
  "stroke": "fly",
  "style": "kick",
  "equipment": ["fins"]
}
#

GET/v1/workout-contentscope workouts

The prescription itself, one row per workout: every set exactly as the coach wrote it, with rounds, set type, and which groups or athletes each set targets. Set text is byte-verbatim, including leading whitespace: indentation encodes nesting depth and line breaks delimit lines. Never normalize it.

The row also carries the resolved roster state: reassignments between training groups and per-athlete stroke overrides, keyed by athleteId. Names never appear here; join the roster. Coach post-workout reflections are private and are not exposed.

With structured=true, every set also carries a parsed, line-by-line structure; see the next section.

Attributes
workoutIdstring
Unique identifier for the workout.
datenullable date
Session date (YYYY-MM-DD).
sessionnullable string
Session label (for example AM).
sessionIndexnullable integer
Ordinal of the session within the day.
teamIdnullable string
Identifier of the team.
poolTypenullable string
Pool type the workout was written for.
namenullable string
Workout name, as titled by the coach.
workoutTypenullable string
Identifier of the workout type; resolves via /v1/terminology.
workoutTypeNamenullable string
Display name of the workout type.
isLiveboolean
Whether the workout is currently running in live mode.
seasonCycleIdnullable string
Identifier of the season cycle the workout belongs to.
weekNumbernullable integer
Week number within that cycle.
trainingGroupsarray of strings
Training-group ids the workout targets.
setsarray of objects
The sets, in order.
idnullable string
Set identifier; joins to /v1/workout-set-volumes.
ordernullable integer
Position of the set within the workout.
typenullable string
Set type (group, individual, or dryland).
roundsinteger
Number of rounds. Lines are not multiplied out.
groupsarray of strings
Training-group ids this set targets.
athletesarray of strings
Athlete ids this set targets directly.
contentstring
The set text, byte-verbatim.
structurednullable array
Parsed lines. Present only with structured=true; null when the team has no terminology config.
messagesarray of objects
Coach messages attached to the workout.
rosterobject
Resolved roster state.
useExplicitRosterboolean
Whether the workout uses an explicit athlete list instead of group membership.
assignedAthleteIdsarray of strings
Athlete ids explicitly assigned.
guestAthleteIdsarray of strings
Athlete ids attending as guests from other teams.
flexRostermap
Per-athlete group reassignment for this workout: athleteId to { assignedGroup, originalGroup }.
strokeFlexAssignmentsmap
Per-set stroke overrides: setId to athleteId to { strokeOverride }.
Query parameters
from, toInclusive date bounds
teamIdOne team
structuredtrue to parse each set with the production engine
row (trimmed)
{
  "workoutId": "w_2026-08-12_am",
  "name": "Main aerobic block",
  "workoutTypeName": "Endurance",
  "sets": [{
    "id": "set_1",
    "type": "main",
    "rounds": 3,
    "groups": ["g_sprint"],
    "content": "8 x 50 fly BZ2 @0:50\n  4 x 25 kick\n> 200 easy"
  }],
  "roster": {
    "flexRoster": { "aQ71Lm": { "assignedGroup": "g_dist", "originalGroup": "g_sprint" } }
  }
}
#

Structured parsing

With structured=true, each set's text is parsed by the same engine the app itself uses, against your organization's own terminology. Every line comes back with its engine-computed volume, its attribution segments, intervals, skill counts and flags for post-round lines and nesting depth.

The accuracy contract: every field is either produced by the engine or proven against it before it ships, and anything unprovable is null. reps and distance are emitted only when their product equals the engine's volume for that line. A sum line like 500 + 5x100 + 500 therefore keeps its volume (1500) and its three segments while both scalars stay null; no single reps-and-distance pair describes that line. segments is always the authoritative decomposition.

  • text stays verbatim; indent reports nesting depth.
  • intervals lists every interval on the line with parsed seconds; intervalSeconds is set only when there is exactly one, never collapsed from many.
  • flags.postRound marks lines that execute once after all rounds; rounds stays a set-level fact and lines are never multiplied out.
  • Skill lines (# 8 Starts @1:00) carry volume: 0, matching the app, which does not count skill work as swim volume. The skill meters live in skillVolume, with per-skill counts alongside.
  • A team without a terminology config gets structured: null; the verbatim text is still returned.
The line object
textstring
The line, byte-verbatim, including leading whitespace.
indentinteger
Leading-whitespace length; encodes nesting depth.
volumenumber
Engine-computed distance for the line, in the pool's units. 0 for skill lines.
repsnullable integer
Repetition count. Emitted only when reps times distance equals volume; null otherwise.
distancenullable number
Distance per repetition. Same emission rule as reps.
intervalsarray of objects
Every interval on the line.
patternstring
The interval token as written (for example @0:50).
secondsnullable number
Parsed seconds.
intervalSecondsnullable number
Parsed seconds when the line has exactly one interval; null otherwise.
segmentsarray of objects
The authoritative attribution decomposition.
volumenumber
Segment distance.
zonenullable string
Energy-zone id.
strokenullable string
Stroke id.
stylenullable string
Training-style id.
equipmentarray of strings
Equipment ids.
skillCountsarray of objects
Counts for skill lines.
skillIdnullable string
Skill id; resolves via /v1/terminology.
termnullable string
Skill term as matched.
countnullable number
Repetitions.
restnullable string
Rest token, as written.
skillVolumenullable number
Skill meters (count times distancePerRep). Separate from volume because the app does not count skill work as swim volume.
notesnullable string
Trailing note text, when the line carries one.
flagsobject
Line flags.
postRoundboolean
The line executes once after all rounds.
skillLineboolean
The line is a skill line.
restSecondsnullable number
Seconds for timed-break lines.
"500 + 5x100 + 500" · parsed
{
  "text": "500 + 5x100 + 500",
  "volume": 1500,
  "reps": null,
  "distance": null,
  "segments": [
    { "volume": 500, "zone": "BZ1", "stroke": "free" },
    { "volume": 500, "zone": "BZ1", "stroke": "free" },
    { "volume": 500, "zone": "BZ1", "stroke": "free" }
  ],
  "flags": { "postRound": false, "skillLine": false }
}
"8 x 50 fly BZ2 @0:50" · parsed
{
  "volume": 400,
  "reps": 8,
  "distance": 50,
  "intervalSeconds": 50,
  "segments": [{ "volume": 400, "zone": "BZ2", "stroke": "fly", "style": "swim" }]
}
#

GET/v1/workout-set-volumesscope workouts

One row per athlete, workout and set: total volume, training load and breakdowns for that set alone. setId joins to the sets in /v1/workout-content, so you can compare the prescribed set with what each athlete swam.

Attributes
workoutIdstring
Identifier of the workout.
setIdstring
Set identifier; joins to sets in /v1/workout-content.
datenullable date
Session date (YYYY-MM-DD).
sessionnullable string
Session label (for example AM).
teamIdnullable string
Identifier of the team.
athleteIdstring
Identifier of the athlete.
totalVolumenumber
Distance swum by this athlete in this set, in the pool's units.
trainingLoadnullable number
Training load for this set alone.
byEnergymap
Distance per energy-zone id, for this set.
byStrokemap
Distance per stroke id, for this set.
byTrainingStylemap
Distance per training-style id, for this set.
byEquipmentmap
Distance per equipment id, for this set.
Query parameters
from, toInclusive date bounds (YYYY-MM-DD)
teamIdLimit to one team
athleteIdLimit to one athlete
row
{
  "workoutId": "w_2026-08-12_am",
  "setId": "set_1",
  "athleteId": "aQ71Lm",
  "totalVolume": 1800,
  "trainingLoad": 96,
  "byEnergy": { "BZ2": 1800 }
}
#

GET/v1/terminologyscope workouts

Your organization's terminology: energy zones with RPE ranges, strokes, training styles, skills and workout types, each with the aliases coaches actually write. Zone, stroke and style ids used in segment rows and parsed lines resolve here.

Attributes
idstring
Unique identifier for the terminology config.
namenullable string
Config name.
teamIdnullable string
Owning team, when team-scoped.
versionnullable string
Config schema version.
defaultEnergyZonenullable string
Energy-zone id applied when a line names none.
energySystemsarray of objects
Energy zones.
idnullable string
Zone id, as used in segment rows and parsed lines.
termnullable string
Short term (for example BZ2).
fullNamenullable string
Full name.
colornullable string
Display color (hex).
rpeMinnullable number
Lower bound of the RPE range.
rpeMaxnullable number
Upper bound of the RPE range.
aliasesarray of strings
Alternative spellings coaches write.
strokesarray of objects
Strokes, with the same shape as energy zones minus the RPE range.
idnullable string
Stroke id.
termnullable string
Short term.
fullNamenullable string
Full name.
colornullable string
Display color (hex).
aliasesarray of strings
Alternative spellings.
stylesarray of objects
Training styles and equipment.
idnullable string
Style id.
termnullable string
Short term.
fullNamenullable string
Full name.
colornullable string
Display color (hex).
aliasesarray of strings
Alternative spellings.
categorynullable string
training or equipment.
skillsarray of objects
Count-based skills (starts, turns, and similar).
idnullable string
Skill id.
termnullable string
Short term.
fullNamenullable string
Full name.
colornullable string
Display color (hex).
aliasesarray of strings
Alternative spellings.
linkedEnergyZonenullable string
Zone credited for skill work.
distancePerRepnullable number
Meters credited per repetition.
workoutTypesarray of objects
Workout types.
idnullable string
Workout-type id.
termnullable string
Short term.
fullNamenullable string
Full name.
colornullable string
Display color (hex).
descriptionnullable string
Description.
row (trimmed)
{
  "name": "Federation terms",
  "energySystems": [{
    "id": "es_bz2",
    "term": "BZ2",
    "rpeMin": 3, "rpeMax": 5,
    "aliases": ["GA1", "aerobic"]
  }],
  "strokes": [  ],
  "skills": [  ]
}

#

GET/v1/attendancescope attendance

One row per athlete per session. status is one of present, late, absent, excused, planned_absence or made_up, with the training group at the time and the make-up date where relevant. made_up counts as present in the app's own summaries.

Attributes
attendanceIdstring
Identifier of the attendance record (one per team session).
teamIdnullable string
Identifier of the team.
datenullable date
Session date (YYYY-MM-DD).
sessionnullable string
Session label (for example am).
sessionIndexnullable integer
Ordinal of the session within the day.
workoutIdnullable string
Workout linked to the session, when one exists.
athleteIdstring
Identifier of the athlete.
statusnullable enum
present, late, absent, excused, planned_absence, or made_up.
trainingGroupIdnullable string
The athlete's training group at the time.
notesnullable string
Coach note on the record.
madeUpOnnullable date
Date the session was made up, for made_up records.
isGuestboolean
Whether the athlete attended as a guest from another team.
Query parameters
from, toInclusive date bounds (YYYY-MM-DD)
teamIdLimit to one team
athleteIdLimit to one athlete
row
{
  "date": "2026-08-12",
  "session": "am",
  "athleteId": "aQ71Lm",
  "status": "made_up",
  "madeUpOn": "2026-08-14",
  "workoutId": "w_2026-08-12_am"
}
#

GET/v1/performancesscope performances

Event swims with total time, reaction time and AquaPoints, plus race-analysis detail where it exists: cumulative taggedTimes at marked distances, with strokeCounts and strokeRates at measurement points. type separates competition swims from training swims, timed race-style efforts done at practice.

Attributes
idstring
Unique identifier for the swim.
athleteIdnullable string
Identifier of the athlete.
teamIdnullable string
Identifier of the team.
datenullable date
Swim date (YYYY-MM-DD).
typenullable enum
competition, or training for race-style efforts at practice.
eventnullable string
Event name (for example 100 Fly).
strokenullable string
Stroke id.
poolTypenullable string
Pool type of the swim.
totalTimeSecondsnullable number
Final time, in seconds.
reactionTimeSecondsnullable number
Reaction time, in seconds.
startTypenullable string
Start type recorded for the swim.
suitTypenullable string
Suit recorded for the swim.
aquaPointsnullable number
AquaPoints score for the swim.
competitionIdnullable string
Identifier of the competition.
competitionNamenullable string
Competition name.
isManualEntryboolean
Whether the swim was entered by hand rather than imported or tagged.
taggedTimesmap
Cumulative time at tagged distances, in seconds (for example "15m": 9.89).
strokeCountsmap
Stroke count at measurement points.
strokeRatesmap
Stroke rate at measurement points, in strokes per minute.
Query parameters
from, toInclusive date bounds (YYYY-MM-DD)
teamIdLimit to one team
athleteIdLimit to one athlete
typecompetition or training
row (trimmed)
{
  "athleteId": "aQ71Lm",
  "date": "2026-07-19",
  "type": "competition",
  "event": "100 Fly",
  "poolType": "LCM",
  "totalTimeSeconds": 59.84,
  "reactionTimeSeconds": 0.68,
  "aquaPoints": 812,
  "taggedTimes": { "15m": 6.91, "50m": 27.93 },
  "strokeRates": { "25m": 52.4 }
}
#

GET/v1/metricsscope metrics

The catalog of coach-defined test sets and measurements: time or count, unit, distance, pool type, and for composite tests the full rep structure. Read this once to interpret /v1/metric-results.

Attributes
idstring
Unique identifier for the metric definition.
namenullable string
Metric name.
descriptionnullable string
Metric description.
typenullable enum
time or count.
unitnullable string
Unit for count metrics.
distancenullable number
Distance of the test, when distance-based.
poolTypenullable string
Pool type the metric is defined for.
scopenullable enum
account (all teams) or team.
teamIdnullable string
Owning team for team-scoped metrics.
isActiveboolean
Whether the metric is currently in use.
isCompositeboolean
Whether the metric has a multi-rep structure.
compositeStructurenullable object
Rep-group structure for composite metrics: distances, reps, rest, stroke, and split configuration.
row
{
  "id": "m_7x200",
  "name": "7 x 200 step test",
  "type": "time",
  "poolType": "LCM",
  "isComposite": true,
  "compositeStructure": { "repGroups": [  ] }
}
#

GET/v1/metric-resultsscope metrics

Recorded results against those definitions, including per-rep values and split times for composite tests. Filter by metricId to pull one test's full history across the squad.

Attributes
idstring
Unique identifier for the result.
athleteIdnullable string
Identifier of the athlete.
teamIdnullable string
Identifier of the team.
metricIdnullable string
Definition this result was recorded against.
metricNamenullable string
Definition name at the time of recording.
datenullable date
Recording date (YYYY-MM-DD).
sessionnullable string
Session label, when recorded in a session.
workoutIdnullable string
Workout the result was recorded in, when applicable.
valuenullable number
Numeric value; seconds for time metrics.
displayValuenullable string
Formatted value as shown in the app.
typenullable string
Copied from the definition (time or count).
unitnullable string
Copied from the definition.
poolTypenullable string
Pool type at recording.
strokenullable string
Stroke, when recorded.
suitnullable string
Suit, when recorded.
notesnullable string
Note on the result.
isCompositeboolean
Whether the result carries per-rep detail.
compositeStructurenullable object
The rep-group structure the result was recorded against.
repValuesarray of numbers
Per-rep values for composite results.
repSplitTimesmap
Split times per rep, when recorded.
totalDistancenullable number
Total distance of the test.
createdAtnullable timestamp
When the result was recorded (RFC 3339 UTC).
Query parameters
from, toInclusive date bounds (YYYY-MM-DD)
teamIdLimit to one team
athleteIdLimit to one athlete
metricIdLimit to one metric definition
row (trimmed)
{
  "athleteId": "aQ71Lm",
  "metricId": "m_7x200",
  "date": "2026-08-05",
  "displayValue": "2:14.6 avg",
  "repValues": [139.2, 137.8, 136.1, 134.6, 133.0, 131.4, 128.9]
}
#

GET/v1/wellnessscope wellness

Daily athlete surveys: energy, sleep hours and quality, muscle soreness, life stress and willingness to train on 1 to 5 scales (soreness and stress read lower-is-better), plus the composite wellnessScore from 0 to 100 and the mood quadrant.

Wellness rows are health data and are handled separately from everything else. Rows are name-free: you get athleteId and join the roster only where your legal basis allows it. Athletes whose consent is withheld or withdrawn are absent from every response, including direct queries for their id. The wellness scope is granted only on explicit written instruction from your federation.

Attributes
idstring
Identifier of the survey (one per athlete per day).
athleteIdnullable string
Identifier of the athlete. Rows carry no names.
teamIdnullable string
Identifier of the team.
datenullable date
Survey date (YYYY-MM-DD).
energynullable number
1 to 5.
sleepHoursnullable number
Hours slept.
sleepQualitynullable number
1 to 5.
muscleSorenessnullable number
1 to 5; lower is better.
lifeStressnullable number
1 to 5; lower is better.
willingnessToTrainnullable number
1 to 5.
wellnessScorenullable number
Composite score, 0 to 100.
moodQuadrantnullable string
Mood quadrant from the daily check-in.
submittedAtnullable timestamp
When the survey was submitted (RFC 3339 UTC).
Query parameters
from, toInclusive date bounds (YYYY-MM-DD)
teamIdLimit to one team
athleteIdLimit to one athlete
row
{
  "athleteId": "aQ71Lm",
  "date": "2026-08-12",
  "energy": 4,
  "sleepHours": 7.5,
  "sleepQuality": 4,
  "muscleSoreness": 2,
  "lifeStress": 1,
  "willingnessToTrain": 5,
  "wellnessScore": 87,
  "moodQuadrant": "green"
}

#

Pulling into pandas

Rows are long-format. The cursor walk is identical for every resource; the helper below works for all twelve endpoints.

python
import requests, pandas as pd

BASE = "https://europe-west3-makoswim-prod-9ab4d.cloudfunctions.net/dataApi/v1"
H = {"Authorization": "Bearer mk_live_YOUR_KEY"}

def pull(resource, **params):
    rows, cursor = [], None
    while True:
        r = requests.get(f"{BASE}/{resource}", headers=H,
                         params={**params, "cursor": cursor, "limit": 500}).json()
        rows += r["data"]
        if not r["has_more"]: return pd.DataFrame(rows)
        cursor = r["next_cursor"]

segments = pull("workout-segments", **{"from": "2026-07-01", "to": "2026-07-31"})
fly_kick = segments.query("stroke == 'fly' and style == 'kick'") \
                   .groupby("athleteId").volume.sum()
#

Data protection

This section summarizes the processing posture. The full annex, with technical and organizational measures, is part of your federation agreement.

ResidencyThe API and its database run in Frankfurt (europe-west3). Your request and your data stay on EU infrastructure end to end.
Read-onlyGET is the only method. The API cannot modify, delete or write anything.
TenancyA key reads exactly one organization, resolved server-side from the key itself. No caller parameter can move it.
KeysIssued once, stored only as SHA-256 hashes, revocable in one call, scoped deny-by-default.
Health dataWellness is a separate scope granted only on written instruction. Rows are name-free and per-athlete consent withdrawal is enforced in the API itself.
AuditEvery request is logged: key prefix, endpoint, parameters, row count, status and origin, retained for 365 days for your controller audits.
MinimizationSerializers are explicit whitelists; contact details, account identifiers and free-text coach notes are never emitted.

For the current annex, write to eric@makoapp.io.

#

OpenAPI spec

The v1 contract in OpenAPI 3.1: every endpoint, parameter, row schema and error shape. Use it to generate a typed client or to wire the API into your tooling.

openapi.yaml