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

# TeamCare APIs

TeamCare offers vendor APIs that allow seamless integration of your dental office data with your website. Leverage our HIPAA-compliant, opt-in APIs to access rich statistical insights generated by your healthcare practice.

## Getting Started

This step-by-step guide will help you get started with using the APIs to integrate them into your software solution.

These APIs are designed for consulting firms (aka client aka account aka teamcare-vendor aka agency tenant), which must first register on TeamCare. After registration, they can add associated practices (aka organizations aka practice tenant) under their account. Once set up, the firm can begin using the available API resources listed below.

For more information about a specific endpoint, click its name to view detailed documentation, including supported query parameters and usage instructions.

Follow Below Steps

* Step 1 : Register your consulting firm on TeamCare
* Step 2 : Add practices to your consulting firm
* Step 3 : Accept the API agreement and generate your API keys
* Step 4 : Fetching and displaying data

# Environments

The *Environment* dropdown represents the API environment from which data requests can be made. The API is deployed to the **production** as well as **staging (test)** environment, which points to `https://staging.teamcaredental.com`.

When an environment (like "production") is selected, all the environment variables defined for that environment are automatically resolved to their corresponding values. For example, a variable like `{teamcare-vendors}` would be cast to production environment. i.e. `{{teamcare-vendors}}`

If **no environment** is selected, the request will display the raw variable names (e.g., `{teamcare-vendors}`) instead of resolved values. This is standard behavior in tools like Postman.

# **Versioning Strategy**

Our APIs are versioned explicitly using the URL path (e.g., `/v1/`, `/v2/`) to avoid breaking changes. Each major version is treated as a stable interface.

# Output Formats

Currently, TeamCare APIs support only the JSON format.

# Error Codes

| HTTP Status Code | Error Description     |
| ---------------- | --------------------- |
| 422              | Unprocessable Entity  |
| 400              | Bad Request           |
| 500              | Internal Server Error |
| 401              | Unauthorized Error    |

# Pagination

We’ve implemented pagination to ensure consistent responses, even when data changes between requests. To improve reliability and efficiency, we’ve transitioned from page-based pagination to cursor-based pagination. Now, instead of using the `page` parameter, you should use the `after` parameter for **larger datasets** (i.e. appointments, treatments, patietns, ledger transactions and patient referrals), which should contain the ID of the last item from the previous page. This approach offers a more stable way to paginate through dynamic datasets. You can specify the number of **items** to return using a query parameter, with a maximum allowed value of **5000**. For rest we will keep using `page` parameter.

# Filteration

In the Teamcare APIs, you can filter results using the `q` parameter, which is a special JSON object placed in the request body. For example, if the `Appointment` model includes a `date` attribute and filtering is supported, you can use keys like `date_#{specifier}` within `q`. Supported specifiers include `gt`, `lt`, `gteq`, and `lteq` for numeric or date fields, `in` for array fields and `cont` or `eq` for string fields.

# Authentication

Your API keys are self-serve from your TeamCare dashboard:

1. Settings › Company › Agreement - accept the Developer API Agreement. Only the account owner can accept it, and the API Access tab stays hidden until then.
2. Settings › Company › API Access - click Generate TeamCare API Keys, then copy the client\_id and client\_secret. The secret is shown once. If you lose it, use Regenerate API Keys (this immediately invalidates the old keys and their tokens).

Don't share these keys, they uniquely identify your consulting firm.

Once you have these credentials, you will need to include them in the authentication request body in raw JSON format. This will grant your consulting firm access along with a JWT-secured authentication token. The token is valid for six hours; once it expires, you will need to regenerate it by making the same request.

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

# Quickstart

As mentioned above, once authentication is successful, the response will include a `token` field containing a Bearer Token. You must include this token as a **Bearer Token** in the `Authorization` header of all subsequent requests to access protected resources.

#### Organizations

Organization represent the list of accessible locations under a specific parent company owned by a dental group. To retrieve this list, you must make the request with a valid authentication token.

#### Locations

Each organization has multiple locations associated with it, which are accessible to the authenticated account.

#### Why support multiple locations per organization?

Sometimes a practice owner operates multiple locations under the same practice. To manage this, we treat each location as part of the same organization but store their data separately in our database. In most cases, an organization has a single location, which is the preferred setup. However, for multi-location practices, an organization can have multiple locations, each with its own distinct set of data.
