> ## Documentation Index
> Fetch the complete documentation index at: https://docs.saturday.fit/llms.txt
> Use this file to discover all available pages before exploring further.

# Batch Operations

> Batch calculations, bulk athlete creation, and activity imports

# 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.

## Batch calculate

Calculate prescriptions for multiple scenarios at once. Ideal for building "training week" views or "what if" comparisons.

```bash theme={null}
POST /v1/nutrition/calculate/batch
```

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.saturday.fit/v1/nutrition/calculate/batch",
      headers={"Authorization": "Bearer sk_test_abc123def456"},
      json={
          "athlete_id": "ath_abc123",
          "scenarios": [
              {
                  "label": "Monday easy run",
                  "activity_type": "run",
                  "duration_min": 45,
                  "intensity_level": 3,
              },
              {
                  "label": "Wednesday intervals",
                  "activity_type": "run",
                  "duration_min": 60,
                  "intensity_level": 7,
              },
              {
                  "label": "Saturday long ride",
                  "activity_type": "bike",
                  "duration_min": 180,
                  "intensity_level": 5,
                  "thermal_stress_level": 7,
              },
          ],
      },
  )

  results = response.json()
  for result in results["results"]:
      print(f"{result['label']}: {result['carb_g_per_hr']}g carbs/hr")
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    "https://api.saturday.fit/v1/nutrition/calculate/batch",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_test_abc123def456",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        athlete_id: "ath_abc123",
        scenarios: [
          {
            label: "Monday easy run",
            activity_type: "run",
            duration_min: 45,
            intensity_level: 3,
          },
          {
            label: "Wednesday intervals",
            activity_type: "run",
            duration_min: 60,
            intensity_level: 7,
          },
          {
            label: "Saturday long ride",
            activity_type: "bike",
            duration_min: 180,
            intensity_level: 5,
            thermal_stress_level: 7,
          },
        ],
      }),
    }
  );

  const data = await response.json();
  data.results.forEach((r: any) =>
    console.log(`${r.label}: ${r.carb_g_per_hr}g carbs/hr`)
  );
  ```
</CodeGroup>

### Response format

```json theme={null}
{
  "results": [
    {
      "index": 0,
      "label": "Monday easy run",
      "carb_g_per_hr": 30,
      "sodium_mg_per_hr": 300,
      "fluid_ml_per_hr": 400,
      "safety": { "confidence_score": 0.65, "warnings": [], "not_instructions": true }
    },
    {
      "index": 1,
      "label": "Wednesday intervals",
      "carb_g_per_hr": 55,
      "sodium_mg_per_hr": 400,
      "fluid_ml_per_hr": 500,
      "safety": { "confidence_score": 0.65, "warnings": [], "not_instructions": true }
    }
  ],
  "errors": [],
  "metadata": {
    "total_scenarios": 3,
    "successful": 3,
    "failed": 0,
    "request_id": "req_abc123def456"
  }
}
```

If individual scenarios fail, they appear in `errors` with the index and error detail. Successful scenarios are still returned — batch operations don't fail atomically.

### Limits

| Constraint              | Value                             |
| ----------------------- | --------------------------------- |
| Max scenarios per batch | 50                                |
| Rate limiting           | Counts as 1 API call per scenario |

## Bulk athlete create

Onboard multiple athletes in one call. Useful for team imports or platform migrations.

```bash theme={null}
POST /v1/athletes/batch
```

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(
      "https://api.saturday.fit/v1/athletes/batch",
      headers={"Authorization": "Bearer sk_test_abc123def456"},
      json={
          "athletes": [
              {
                  "external_id": "user-001",
                  "name": "Alice Runner",
                  "weight_kg": 58,
                  "fitness_level": "advanced",
                  "primary_sport": "run",
              },
              {
                  "external_id": "user-002",
                  "name": "Bob Cyclist",
                  "weight_kg": 75,
                  "fitness_level": "intermediate",
                  "primary_sport": "bike",
              },
              {
                  "external_id": "user-003",
                  "name": "Carol Triathlete",
                  "weight_kg": 65,
                  "fitness_level": "elite",
                  "primary_sport": "bike",
              },
          ],
      },
  )

  results = response.json()
  for athlete in results["created"]:
      print(f"Created: {athlete['name']} -> {athlete['id']}")
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.saturday.fit/v1/athletes/batch", {
    method: "POST",
    headers: {
      Authorization: "Bearer sk_test_abc123def456",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      athletes: [
        {
          external_id: "user-001",
          name: "Alice Runner",
          weight_kg: 58,
          fitness_level: "advanced",
          primary_sport: "run",
        },
        {
          external_id: "user-002",
          name: "Bob Cyclist",
          weight_kg: 75,
          fitness_level: "intermediate",
          primary_sport: "bike",
        },
      ],
    }),
  });

  const results = await response.json();
  results.created.forEach((a: any) => console.log(`Created: ${a.name} -> ${a.id}`));
  ```
</CodeGroup>

### Limits

| Constraint              | Value                                |
| ----------------------- | ------------------------------------ |
| Max athletes per batch  | 100                                  |
| Duplicate `external_id` | Error for that item, others continue |

## Activity import

Import multiple activities for an athlete at once. Useful for backfilling historical data from other platforms.

```bash theme={null}
POST /v1/athletes/{athlete_id}/activities/import
```

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(
      f"https://api.saturday.fit/v1/athletes/ath_abc123/activities/import",
      headers={"Authorization": "Bearer sk_test_abc123def456"},
      json={
          "activities": [
              {
                  "type": "run",
                  "name": "New Year's Day 10K",
                  "duration_min": 48,
                  "intensity_level": 7,
                  "scheduled_at": "2025-01-01T09:00:00Z",
                  "thermal_stress_level": 2,
              },
              {
                  "type": "bike",
                  "name": "Weekend group ride",
                  "duration_min": 150,
                  "intensity_level": 5,
                  "scheduled_at": "2025-01-04T08:00:00Z",
              },
          ],
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    `https://api.saturday.fit/v1/athletes/ath_abc123/activities/import`,
    {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_test_abc123def456",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        activities: [
          {
            type: "run",
            name: "New Year's Day 10K",
            duration_min: 48,
            intensity_level: 7,
            scheduled_at: "2025-01-01T09:00:00Z",
            thermal_stress_level: 2,
          },
          {
            type: "bike",
            name: "Weekend group ride",
            duration_min: 150,
            intensity_level: 5,
            scheduled_at: "2025-01-04T08:00:00Z",
          },
        ],
      }),
    }
  );
  ```
</CodeGroup>

### 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:

```json theme={null}
{
  "calculate": true,
  "activities": [
    { "type": "run", "duration_min": 48 },
    { "type": "bike", "duration_min": 150, "calculate": true }
  ]
}
```

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`); teaser-tier athletes get per-hour ranges with a `subscription_cta`. Trial athletes debit their daily call allowance per calculated activity — a large import can use up the day's allowance mid-batch, after which remaining items return teaser ranges.

Per-item outcomes ride a `prescriptions` array in the response (`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`/`message`.

Imports also count against rate limits per item, like batch calculate.

## Error handling in batch operations

Batch operations use partial success semantics. If 3 out of 5 items succeed and 2 fail:

* The 3 successful items are committed
* The 2 failed items are returned in the `errors` array with index, error type, and message
* The HTTP status is `200` (not `400`) because some items succeeded

```json theme={null}
{
  "created": [
    { "index": 0, "id": "ath_abc123", "name": "Alice Runner" },
    { "index": 2, "id": "ath_def456", "name": "Carol Triathlete" }
  ],
  "errors": [
    {
      "index": 1,
      "error": {
        "type": "conflict",
        "code": "duplicate_external_id",
        "message": "An athlete with external_id 'user-002' already exists"
      }
    }
  ]
}
```

Check both `created` (or `results`) and `errors` arrays to handle the response correctly.
