Syncing many customers

A reliable polling loop for every connected customer: per-customer checkpoints, shared catalogs, rate limits, and the errors that should pause a customer.

The API has no webhooks and no "changed since" filter, so integrations poll. This page sets up a polling loop for a platform with many customers.

One job per customer

Treat each customer key as an independent sync with its own state:

Store per customerWhy
The integration key, encryptedIts only credential.
The organization id, from GET /organizationTo link Vitruve data to your customer account.
A checkpoint: the largest completedAt syncedWhere the next run starts.
Status: active, paused (plan) or disconnected (key revoked)To skip customers that cannot answer.

The rate limit is 30 requests per minute per customer key, 403 answers included, so one large customer cannot slow down the others. Run customers in parallel with a modest concurrency, and keep each customer's own requests sequential.

The loop

For each active customer:

  1. GET /workouts?from=<checkpoint>&limit=100, paging with cursor while meta.hasMore is true. Completed workouts are filtered and sorted by completedAt, newest first.
  2. Skip workouts whose id you already stored. from is inclusive, so the workout at the checkpoint comes back on every run.
  3. GET /workouts/{id} for each new workout to get its sets and repetitions.
  4. When every page succeeded, store the new largest completedAt as the checkpoint.

To pick up coaches' corrections to recent workouts, also re-read a short window on each run, for example the last 7 days, and upsert by id. Edits do not change completedAt, and deleted workouts simply stop being returned.

Catalogs

Metrics, units, prescription types and muscles are global: the same for every customer. Load them once with any key and cache them for a day. Exercises combine Vitruve's global library with each customer's own exercises: cache the global ones (source=global) once, and each customer's (source=organization) per customer.

Errors that change a customer's status

AnswerAction
401 Invalid API keyMark the customer disconnected. Stop calling the key: repeated 401s use up your secret's failed-authentication budget, and past it every customer answers 429 until the minute ends.
401 Organization is not activeMark the customer disconnected and remove the key.
403 Your plan no longer includes API accessMark the customer paused and retry once a day.
429 Rate limit of 30 requests per minute exceededWait Retry-After seconds for that customer, then continue from where you stopped.

Some answers are about your platform, not the customer: every customer would answer the same. Stop the whole sync instead of marking customers, and fix the cause first.

AnswerCause
401 Partner credentials required or Invalid partner credentialsYour secret is missing, wrong or revoked.
403 Request origin not allowed for this integrationYour egress address changed or is not registered.
403 This integration is not availableYour platform's integration is disabled. Contact Vitruve.
429 Too many failed authentication attemptsYour secret, or your address, spent its failed-authentication budget. Wait Retry-After seconds.
503 Rate limiter unavailableRetry with exponential backoff.
5xxRetry with exponential backoff. Do not move the checkpoint.

The full list is in Error codes. Keep the X-Request-Id of a failed request: it lets Vitruve support trace it.

How often

Hourly polling suits most integrations. A poll with nothing new costs one request per customer. An initial backfill is the expensive part: each workout detail is one request, so 10,000 workouts take at least 34 minutes for that customer. Spread backfills over time and run them separately from incremental syncs.

On this page