For developers

Data import API

Send training sessions, morning wellness and jump tests from your own systems straight into a VitalForg workspace, and read them back.

Getting a key

A workspace admin creates keys in Settings › API keys, choosing only the permissions a system needs:

PermissionAllows
athletes:readList athletes
athletes:writeAdd athletes
data:readRead sessions, wellness and jump tests
data:writeAdd sessions, wellness and jump tests

The key is shown once. VitalForg keeps only a fingerprint of it, so store it in your system’s secret settings. A key works only for the workspace it was created in. Revoking it stops it at once.

Making requests

Send every request over HTTPS to https://www.vitalforg.com/api/v1/ with the key in the Authorization header. Request and response bodies are JSON.

curl https://www.vitalforg.com/api/v1/status \
  -H "Authorization: Bearer vf_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

If your server cannot use short paths, use /api/v1/index.php?resource=status instead.

RequestPermissionWhat it does
GET statusanyShows the workspace, the key’s permissions and the limits
GET athletesathletes:readLists athletes: code, name, team, position
POST athletesathletes:writeAdds athletes. Teams that do not exist yet are created
POST sessions
POST wellness
POST tests
data:writeAdds records for athletes already in the workspace
GET sessions
GET wellness
GET tests
data:readReads records by date: from, to (YYYY-MM-DD, default the last 30 days), limit (up to 1,000) and after_id for the next page

Sending records

Send up to 1,000 records per request in a records array. Field names and rules are the same as the CSV import columns in the app.

curl https://www.vitalforg.com/api/v1/sessions \
  -H "Authorization: Bearer vf_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: gps-export-2026-09-10" \
  -d '{"records": [
        {"athlete_code": "P01", "date": "2026-09-10", "start_time": "2026-09-10T17:00:00+01:00",
         "type": "Training", "duration_min": 75, "rpe": 6, "distance_m": 6400, "hsr_m": 520,
         "source_gps": "GPS vest 12"}
      ]}'
HTTP/1.1 201 Created
{"received": 1, "inserted": 1, "duplicates": [], "warnings": [], "import_id": 418}
TypeRequired fieldsOptional fields
athletesathlete_code, name, team, date_of_birthposition
sessionsathlete_code, date, start_time, type, duration_min, rpe (0–10)distance_m, hsr_m, source_gps
wellnessathlete_code, date, recorded_at, and at least one of the metricshrv_rmssd_ms, resting_hr_bpm, sleep_h, soreness_1to5, source_sensor
testsathlete_code, date, recorded_at, cmj_height_cmtrials, source

Timestamps use ISO 8601 with a time zone. Numbers must be within plausible ranges, for example RPE 0–10 and sleep 2–14 hours. VitalForg is for adults only: athletes under 18 are refused.

All or nothing, and no duplicates

  • If any record in a request is invalid, nothing from that request is saved, and the error lists every problem by record position (index, counting from 0) and field.
  • Records that are already stored (same athlete and start time and type for sessions, same athlete and date for wellness and tests, same code for athletes) are skipped and listed in duplicates. Sending the same batch twice never stores it twice.
  • Add an Idempotency-Key header (8–100 letters, numbers, dots, colons, dashes or underscores) to make retries completely safe: the same key with the same body returns the original answer with Idempotent-Replayed: true. The same key with a different body is refused.

Limits

Each key can make 60 requests a minute. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Bodies can be up to 2 MB. Repeated requests with an invalid key from one address are blocked for the rest of the minute.

Errors

Errors always look like this:

HTTP/1.1 422 Unprocessable Entity
{"error": {"code": "validation_failed", "message": "Some records are not valid. Nothing was saved.",
  "details": [{"index": 3, "field": "rpe", "message": "14 CR-10 is outside the plausible range 0–10."}]}}
StatusCodeMeaning
400invalid_json, invalid_idempotency_keyThe body or header could not be read
401unauthorizedMissing, wrong or revoked key
403insufficient_scopeThe key does not have the permission named in required_scope
403real_data_disabledThis VitalForg installation accepts synthetic test data only; include "synthetic": true and use generated data
404not_foundUnknown address
409idempotency_conflict, duplicate_conflictKey reused with a different body, or another request saved the same records at the same moment (retry it)
413payload_too_large, too_many_recordsOver 2 MB or over 1,000 records
415unsupported_media_typeSend Content-Type: application/json
422validation_failedSee details; nothing was saved
429rate_limitedWait for the number of seconds in Retry-After
500server_errorNothing was saved; try again later

Wearables and other systems

VitalForg does not connect directly to wearable or force-plate vendors’ accounts. Most systems can export CSV files, which you can import in the app, or a short script can read your vendor’s export and send it to this API.