Open API

Getting started with the Plyomat Open API

The Plyomat Open API gives you secure, read-only access to your organization's training data: athletes, groups, devices, test sessions, individual jumps, and assessment results. It is a plain REST API that returns JSON, so you can sync Plyomat into your own dashboards, athlete-management system, or data warehouse. Included free for Plyomat owners.

REST
JSON over HTTPS
Read-only
Safe by design
v1
Current version
Free
For Plyomat owners
The basics

What you can read

The API is read-only by design. It exposes the same training data you see in the app, structured as clean REST resources you can pull on your own schedule.

👤

Athletes & Groups

Your roster and how it is organized: names, gender, body weight, external IDs, and the groups each athlete belongs to.

📱

Devices

The mats and controllers registered to your organization, so you can attribute data back to the hardware that captured it.

📊

Sessions & Sets

Every test session and the sets inside it. Each set embeds its individual jumps (reps) with flight time, contact time, jump height, and RSI.

Assessments

Computed assessment results, ready to drop into reports, leaderboards, or longitudinal tracking on your side.

👑

Org members

The coaches and staff on your account, including archived members when you ask for them.

🔄

One source of truth

Pull it once or sync it continuously. Either way the data matches what your coaches captured on the mat, to 0.001 seconds.

Setup

Base URL & authentication

Every endpoint hangs off a single versioned base URL.

https://api.plyomat.com/v1

Every request needs your API key sent as a Bearer token in the Authorization header:

Authorization: Bearer pk_live_xxxxxxxxxxxx

Treat the key like a password: it grants read access to your whole organization. You can rotate or revoke it anytime from the Plyomat dashboard under Settings › Advanced › API keys. Keys are scoped to read-only access, so they can never modify or delete your data.

Getting your key. API keys are self-serve from your own organization account, no request needed:

  1. Sign in to your Plyomat dashboard at app.plyomat.com.
  2. Open SettingsAdvancedAPI keys.
  3. Generate your API key and copy it. You can rotate or revoke it from the same screen at any time.
Hello, world

Your first call

List your athletes with a single authenticated request.

curl https://api.plyomat.com/v1/athletes \
  -H "Authorization: Bearer pk_live_xxxxxxxxxxxx"

Every list endpoint returns the same response envelope: a data array of records plus a total.

{
  "data": [
    {
      "id": "...",
      "first_name": "...",
      "last_name": "...",
      "gender": "...",
      "body_weight_kg": 0,
      "external_id": null,
      "external_source": null,
      "created_at": "...",
      "updated_at": "..."
    }
  ],
  "total": 1,
  "limit": 100,
  "offset": 0,
  "has_more": false
}
Reference

Endpoints

Eight resources, each a list and (where it makes sense) a detail endpoint, with a few filters to keep responses small.

ResourceListDetailFilters
AthletesGET /athletesGET /athletes/{id}group_id
GroupsGET /groupsGET /groups/{id}None
DevicesGET /devicesGET /devices/{id}None
SessionsGET /sessionsGET /sessions/{id}athlete_id, since
SetsGET /setsGET /sets/{id}athlete_id, session_id, since, mode
AssessmentsGET /assessmentsNoneNone
Org membersGET /org/membersNoneinclude_archived

Each set embeds its reps (rep_index, flight_time_ms, contact_time_ms, jump_height_cm, rsi, side, is_kept, captured_at), so a single GET /sets?session_id=... gives you a session's full jump-by-jump detail in one call.

Mapping

Test names & display modes

The single most important thing to get right when you map Plyomat data into your own model.

display_mode is not the test name. It is the measurement mode: what the mat measures and which metric is primary. The test name lives on the assessment.

A set carries an assessment_id. Resolve it against GET /v1/assessments to get the real protocol:

set.assessment_id  →  GET /v1/assessments  →  { name, display_mode, sidedness, rep_count, reps_kept, drop_height_cm }

Why this matters: display_mode: "rsi" is a family, not a test. A 10-rep rebound protocol and a single-leg 5-hop both report rsi, but they are completely different assessments. Map on assessment.name for the test, and use display_mode for the category.

What each mode measures

display_modeWhat the mat measuresPrimary metric
verticalFlight time of a vertical jumpjump_height_cm
contactGround contact time, repeated hopscontact_time_ms
rsiRebound jumps, flight ÷ contactrsi
drop_jumpDrop from a set box height, then reboundrsi
ppsPower, using body mass (and added load)jump_height_cm + body weight
bounce_factorTwo-jump bounce protocolrsi
timerOpen recording on a countdownnot a test
freeRaw flight + contact capturenot a test

Treat display_mode as an open string, not a fixed enum. New modes are added over time; an integration that hard-fails on an unrecognized value will break. Fall back to the assessment name and carry the raw value through.

Full test-type mapping guide

Assessment names are org-defined

Plyomat ships a set of built-in assessments (Standing Vertical Jump, 10/5 RSI, Max RSI, Drop Jump DRI at several box heights, and more), but every organization can create and name its own. If a coach calls their test CMJ, that is exactly the string assessment.name returns.

So: never hard-code a list of test names. Read GET /v1/assessments per organization and map dynamically. The is_system flag tells you whether a row is a Plyomat built-in or that org's own.

When assessment_id is null

A null assessment_id is normal and expected. It means the coach ran a display mode directly from the mat screen rather than selecting a formal assessment: a quick check, a warm-up, an ad-hoc jump. Those sets are real captured data, but they are not a named test.

Handle it explicitly. We recommend treating those as display_mode only, and either labeling them as ad-hoc or excluding them from formal test reporting, depending on what your product needs:

test_name = assessment_id
  ? assessments[assessment_id].name   // a named test
  : null                              // ad-hoc capture, use display_mode

A note on units and laterality: rep values are metric (jump_height_cm, milliseconds). Unilateral assessments carry sidedness: "unilateral" and each rep carries side: "left" | "right"; bilateral reps have side: null. Reps the coach discarded come through with is_kept: false, so filter on that before computing bests or averages.

At scale

Paging, limits & syncing

Every list endpoint is paginated. Add ?limit=N (default 100, max 500) and ?offset=N (default 0). The envelope tells you everything:

{
  "data": [ /* ...rows... */ ],
  "total": 525,      // TRUE total across all pages, not just this page
  "limit": 100,
  "offset": 0,
  "has_more": true   // more rows exist past this page
}

Page by incrementing offset (0, 100, 200, ...) until has_more is false.

To pull large datasets reliably, filter rather than page. Two patterns cover almost everything:

Incremental sync

Poll the since filter for anything new since your last run.

curl "https://api.plyomat.com/v1/sets?since=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer pk_live_xxxxxxxxxxxx"

Run the same call against /sessions?since=... and you have a complete, low-cost incremental sync.

Backfill

For a one-time historical pull, loop your athletes and request their sets one athlete at a time, which keeps every call well under the row limit.

curl "https://api.plyomat.com/v1/sets?athlete_id=<id>" \
  -H "Authorization: Bearer pk_live_xxxxxxxxxxxx"
Push, not pull

Webhooks

The REST API is pull: you ask for data. Webhooks are push: Plyomat POSTs to your endpoint the moment something happens. Most integrations use both, a webhook tells you when something changed, then you pull the full detail from the API using the ids.

Set up a subscription

  1. In the Plyomat app, open Settings › Advanced › Webhook subscriptions › Add subscription.
  2. Enter a name, an HTTPS target URL, and tick the events you want.
  3. On save, copy your signing secret (whsec_...). It is shown once, so store it now: it is what verifies delivery signatures.

Events

Subscribe to any of these. Set.completed is the workhorse: it fires the moment a coach saves a set.

Sets & sessions
set.completedsession.startedsession.ended
Athletes
athlete.createdathlete.updatedathlete.archived
Assessments
assessment.createdassessment.updatedassessment.archived
Other
group.deletedleaderboard_share.createdleaderboard_share.revoked

The delivery payload

Every delivery is a JSON POST to your URL:

{
  "event_type": "set.completed",
  "event_id": "<uuid>",
  "source_version": 1,
  "delivery_id": "<uuid>",
  "payload": { /* the changed record */ }
}

The payload is the record that changed. Pull richer detail (for example, a set's reps) from the read API using the ids.

Delivery headers

Every delivery carries these headers:

X-Plyomat-Event:        set.completed
X-Plyomat-Delivery-Id:  <uuid>
X-Plyomat-Timestamp:    1733590000
X-Plyomat-Signature:    t=1733590000,v1=<hex>

Verify the signature

Signatures are Stripe-style. Compute HMAC-SHA256(signing_secret, "<timestamp>.<raw_request_body>") and compare the hex digest to v1 from the X-Plyomat-Signature header. Reject the delivery if the timestamp is more than 5 minutes old.

// Node
const crypto = require("crypto");

function verify(rawBody, sigHeader, secret) {
  const parts = Object.fromEntries(sigHeader.split(",").map(p => p.split("=")));
  const expected = crypto.createHmac("sha256", secret)
    .update(parts.t + "." + rawBody).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(parts.v1));
}
# Python
import hmac, hashlib, time

def verify(raw_body: bytes, sig_header: str, secret: str) -> bool:
    parts = dict(p.split("=") for p in sig_header.split(","))
    signed = f"{parts['t']}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return fresh and hmac.compare_digest(expected, parts["v1"])

Timing & reliability

Deliveries are near-real-time, typically under a minute. Set.completed has a roughly 30-second coalesce window, so quick rep edits do not double-fire.

Failed deliveries retry with backoff (1m, 5m, 15m, 1h, 6h). After repeated failures the subscription auto-pauses, and resumes on the next successful delivery.

Questions

Open API FAQ

Does Plyomat have an API?
Yes. Plyomat offers a public, read-only Open API: a REST API that returns your organization's training data (athletes, groups, devices, sessions, individual jumps, and assessments) as JSON over HTTPS. It is included free for Plyomat owners. The base URL is https://api.plyomat.com/v1, and the full interactive reference lives at app.plyomat.com/developers.
What is the Plyomat Open API?
A secure, read-only REST API that gives you access to your organization's training data: athletes, groups, devices (mats), test sessions, individual jumps (reps), and assessment results. It returns JSON over HTTPS, so you can sync Plyomat into your own dashboards, athlete-management system, or data warehouse.
How do I authenticate?
Send your API key as a Bearer token in the Authorization header: Authorization: Bearer pk_live_xxxxxxxxxxxx. Treat the key like a password, since it grants read access to your whole organization. Rotate or revoke it anytime from the dashboard under Settings then Advanced.
Is the API read-only?
Yes. Every key is scoped to read-only access, so an integration can pull your data but can never modify or delete it. That makes it safe to wire into reporting tools and third-party dashboards.
How do I sync large datasets without hitting limits?
Filter rather than page. For incremental sync, poll GET /sessions?since=<ISO8601> and GET /sets?since=<ISO8601> for anything new since your last run. For a full backfill, loop your athletes and call GET /sets?athlete_id=<id> per athlete.
How do I get an API key?
Generate it yourself from your organization account. Sign in to your Plyomat dashboard at app.plyomat.com, open Settings then Advanced, and create your key. You can copy, rotate, or revoke it anytime from the same screen, with no request needed.
Is there an interactive reference?
Yes. The full, interactive API reference, with every endpoint, field, and live request builder, lives at app.plyomat.com/developers.
Keep reading

All developer docs

  • Open API overview (this page): what you can read, authentication, endpoints, paging, syncing, and webhooks.
  • Mapping test types: Display Mode vs Assessment, why display_mode is not the test name, tags, and how to handle a null assessment_id.
  • Interactive API reference: every endpoint, parameter, and response shape, with a live Try it.

Build on your own data.

The Plyomat Open API is included free for owners. Pull your athletes, sessions, and every jump into your own tools, with a read-only key you control.

Open the interactive reference