Skip to main content

Batch Operations

Batch endpoints let you perform multiple operations in a single API call. Use these when you need to process a training week, onboard a team, or import historical activities. The examples read your key from a SATURDAY_API_KEY environment variable, as in Quickstart. Sandbox keys are issued with their own base URL; using one against api.saturday.fit returns 401 invalid_api_key.

Batch calculate

Calculate prescriptions for multiple scenarios at once. Ideal for building “training week” views or “what if” comparisons.
Each scenario is a full calculate request, so athlete_id goes on the scenario, not at the top level. Results come back in request order, and there is no per-scenario label: match results to inputs by position.

Response format

Counts and the request ID sit at the top level; there is no metadata object. Each entry in results is the same body the single calculate endpoint returns, so it carries tier, safety, and attribution and does not carry an index.
Failed scenarios appear in errors as { "index", "code", "message" }, and the successful ones are still returned: batch operations do not fail atomically. Because failures are dropped from results rather than nulled, results is shorter than scenarios when anything fails, and a result’s position no longer matches its scenario index. When you need that mapping and failures are possible, read the failed indexes from errors first and reconstruct alignment from them.

Limits

Exceeding 50 returns 400 with the code batch_too_large, and an empty scenarios array returns 400 with empty_batch.

Sizing the wait

A batch takes as long as the sum of its scenarios. Two things let you show real progress instead of a spinner: the response carries an X-Batch-Estimated-Ms header, flushed before processing begins, and sending "estimate_only": true returns the same estimate immediately without running any calculations, consuming quota, or returning results.

Bulk athlete create

Onboard multiple athletes in one call. Useful for team imports or platform migrations.
This endpoint takes a partner API key only. An athlete-delegated OAuth token is rejected up front with 403 rather than partway through, so a scope mistake never leaves some athletes created and others not.

Limits

Each athlete is also validated on weight_kg (must be positive), year_of_birth (1900 to the current year), and sex (male, female, or intersex). A violation fails that item only. The batch is also checked against your account’s total athlete quota before processing. If you are at the cap the whole request returns 403 with the code resource_limit, rather than partially filling.
external_id is not deduplicated. Saturday does not reject or merge an athlete whose external_id you have already used; every create mints a new athlete with a new UUID. Retrying a batch that partially succeeded will therefore create duplicates of the athletes that succeeded the first time. Track the returned IDs against your own external_id values and retry only the items that failed.

Activity import

Import multiple activities for an athlete at once. Useful for backfilling historical data from other platforms.
An imported activity accepts these fields and no others. Unknown keys are ignored silently, so check this list rather than assuming a field was stored:
There is no timestamp field on import. Imported activities are stamped with the time Saturday created them, so a backfill does not preserve the original activity dates. Keep your own date mapping via external_id if you need it.
Up to 200 activities per import. Exceeding that returns 400 with batch_too_large. The import is also checked against the athlete’s activity quota before processing, returning 403 with resource_limit if it would exceed the cap.

Calculating prescriptions during import

Set calculate: true at the top level to calculate a prescription for every imported activity, or on individual activities to calculate selectively:
Each calculation runs the same tier-aware path as the single calculate endpoint: full-tier and in-trial athletes get exact prescriptions, stored on the activity and mirrored on imported[].prescription, while teaser-tier athletes get per-hour ranges with a subscription_cta. Trial athletes debit their daily call allowance per calculated activity, so a large import can exhaust the day’s allowance mid-batch, after which the remaining items return teaser ranges. Per-item outcomes ride a prescriptions array in the response, each with index, activity_id, and the tier-aware result. A failed calculation never fails the import: the activity is still created, and the failure appears on that item’s code and message. Imports count against rate limits per item, like batch calculate.

Error handling in batch operations

Batch operations use partial success semantics. If 3 of 5 items succeed and 2 fail, the 3 successes are committed, the 2 failures are returned in errors, and the HTTP status is 200 rather than 400 because some items succeeded. Every batch response uses the same envelope. The success array is named for the operation (results, created, or imported), and alongside it sit errors, total, succeeded, failed, and request_id. Each error is a flat object with index, code, and message:
Read both arrays. index on an error refers to the item’s position in your request, which is the only reliable way to tell which input failed. Unlike the resource IDs above, request_id is not a UUID: it is req_ followed by 12 hex characters. Log it. It is what support needs to trace a specific call.