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

# Concurrency Limits

To ensure reliable service for all practices, the TeamCare Vendor API limits how many **locations** a vendor integration can be pulling data for at the same moment. Requests that exceed a limit are rejected with `HTTP 429 Too Many Requests` and are not queued. Your integration must implement the client-side handling described below.

The limit applies to **concurrent locations, not request rate**. There is no cap on how many requests you may send to a single location, how quickly you may page through it, or how many locations you may sync in total.

## Limits

| Scope                | Limit                 | Applies to                                                     |
| -------------------- | --------------------- | -------------------------------------------------------------- |
| **Initial sync**     | 5 locations in flight | Location-scoped requests **without** `q[fetch_modified_since]` |
| **Incremental sync** | 5 locations in flight | Location-scoped requests **with** `q[fetch_modified_since]`    |

Limits apply to every endpoint under `/api/v1/vendors/locations/{location_id}/`. The locations list, the organizations endpoint, and the token endpoint are not limited.

* **The unit is the location, not the request.** A location occupies one slot while it has *any* request executing. Any number of concurrent requests for the *same* location share that slot.
* **Slots free immediately.** A location's slot is released the instant its last in-flight request finishes — success or error. There are no fixed windows and no idle timers.
* **The two pools are independent.** Incremental polling never blocks initial syncs, and the same location may hold one slot in each pool at the same time.
* **Budgets are per credential.** Limits are counted per API credential across all your processes and servers combined. Other vendors' traffic never affects yours.
* **Rejected requests cost you nothing.** A `429` request is not executed, occupies no slot, and is not recorded against you. Nothing was read and no partial response was produced, so retrying is always safe.
* **Sequential clients are never limited.** If you sync one location at a time, you cannot reach these limits.

### Throttled Response Contract

When a limit is exceeded, the API responds as follows:

```text theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 1
{
  "errors": "Too many locations syncing concurrently. At most 5 locations may have initial sync requests in flight at a time.",
  "sync_type": "initial",
  "max_concurrent_locations": 5,
  "active_location_ids": [101, 102, 103, 104, 105],
  "retry_after_seconds": 1
}
```

| Field                      | Meaning                                                                                                                         |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `max_concurrent_locations` | The current limit. Its presence identifies this response as the concurrency limit; other throttled responses do not include it. |
| `sync_type`                | Which pool was full: `initial` or `incremental`.                                                                                |
| `active_location_ids`      | Which of your locations currently hold slots in that pool.                                                                      |
| `retry_after_seconds`      | Seconds to wait before retrying. Also sent as the `Retry-After` header.                                                         |

## Required Client Behavior

* **MUST** limit concurrent locations to **5 per sync mode**. Initial and incremental are counted separately, so you may run up to 5 of each.
* **MUST** treat `HTTP 429` as a retryable condition and wait at least `retry_after_seconds` (or the `Retry-After` header) before retrying.
* **MUST NOT** retry in a tight loop without a delay.
* **MUST** cache the access token and reuse it. Tokens are valid for 6 hours; request a new one only when the current token expires.
* **SHOULD** detect this limit by checking the response body for `max_concurrent_locations` rather than assuming every `429` is a concurrency rejection.
* **SHOULD** read the limit from `max_concurrent_locations` rather than hardcoding `5`, so a future change on our side requires no change on yours.
* **SHOULD** parallelize freely *within* a location. Fetching one location's patients, appointments, and treatments simultaneously consumes a single slot and is the most efficient way to sync.
* **SHOULD** set `items` explicitly when paging. Cursor pagination defaults to **10** records per page and accepts up to **5000**; leaving it at the default makes large pulls far slower than necessary.
* **SHOULD** add random jitter to retry delays if you run many workers, so they do not retry in lockstep.
* **SHOULD** use `q[fetch_modified_since]` for routine polling. It returns only records with `updated_at >=` your timestamp and routes the request to the incremental pool, so polling never competes with initial syncs.

If you are seeing this `429` regularly, your effective location fan-out is above 5. Correct the fan-out rather than relying on retries.

### Reference Implementation

Minimal retry handling (Python; the same pattern applies in any language):

```text theme={null}
import time
import requests
def vendor_get(session, url, headers, max_attempts=5):
    for attempt in range(max_attempts):
        response = session.get(url, headers=headers)
        if response.status_code != 429:
            return response
        try:
            body = response.json()
        except ValueError:
            body = {}
        if "max_concurrent_locations" in body:
            # Location concurrency limit: a slot frees as soon as any active
            # location's last in-flight request completes.
            wait = body.get("retry_after_seconds", 1)
        else:
            # Any other throttled response: honor the header.
            wait = int(response.headers.get("Retry-After", 5))
        time.sleep(wait)
    raise RuntimeError("throttled after %d attempts" % max_attempts)
```

A worker pool is the natural structure for the limit: give each worker one location at a time, and run at most 5 workers per sync mode. The number of locations you sync overall is unlimited — 50 or 500 locations flow through the 5-wide pipe without any additional coordination on your side.

## Verifying Your Integration

**The limit triggers as documented.** Start pulls for 6 distinct locations simultaneously in the same pool. Requests for the first 5 locations succeed; requests for the 6th return `429` with `max_concurrent_locations` in the body. Once one of the active locations finishes, the 6th is admitted on retry.

**Same-location parallelism is unrestricted.** Fire several endpoints for one location at once. None are rejected — they share a single slot.

**The pools are independent.** With 5 locations already active in the initial pool, a request carrying `q[fetch_modified_since]` for a 6th location still succeeds, because it draws on the incremental pool.

**Normal operation is clean.** Run your usual sync with fan-out capped at 5 per mode and confirm you see no `429` responses at all. A correctly capped client should never trigger this limit in steady state.
