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 aSATURDAY_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.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 nometadata 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.
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 anX-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.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.
Activity import
Import multiple activities for an athlete at once. Useful for backfilling historical data from other platforms.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.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
Setcalculate: true at the top level to calculate a prescription for every imported activity, or on individual activities to calculate selectively:
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 inerrors, 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:
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.