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

# Syncing a payroll or IT system

> Read the directory once, then keep a copy current from events and updated_since.

A payroll, identity or IT-asset system keeps its own copy of the people andteam holds. The API
gives it two ways to stay current, and a sound integration uses both: a **webhook** to hear that
something changed, and **`updated_since`** to be certain nothing was missed.

## 1. Mint a key for the system

Ask an andteam administrator for a key for this one system, holding:

* only the **companies** it serves;
* the **field groups** it needs. A directory sync needs Directory and Organisation (the default). Add
  Positions for reporting lines, and Personal contact only if the system has a lawful reason to hold
  home details;
* an **IP range** if it calls from a fixed address, and an expiry.

See [Authentication](https://docs.andteam.com/authentication.md) and [Field groups](https://docs.andteam.com/concepts/field-groups.md).

## 2. Read everything once

Note the time, then read every page of the lists you need. Use `limit=500`.

```python theme={null}
import requests
from datetime import datetime, timezone

API = "https://api.v3.andteam.com/v1"
H = {"Authorization": "Bearer atk_live_…"}

def read_all(path, **params):
    cursor = None
    while True:
        q = {"limit": 500, **params, **({"cursor": cursor} if cursor else {})}
        r = requests.get(f"{API}{path}", headers=H, params=q)
        r.raise_for_status()
        page = r.json()
        yield from page["data"]
        cursor = page["next_cursor"]
        if not cursor:
            return

started = datetime.now(timezone.utc).isoformat()
for e in read_all("/employees"):
    upsert(e["id"], e)           # key by id
```

Read the small lists once too (`/companies`, `/org-units`, `/locations`, `/lookups/departments`,
`/lookups/cost-centres`, `/lookups/grades`, `/lookups/job-titles`) and re-read them when an
`employee.changed` names a field that depends on them.

## 3. Listen, then re-read the person

Add a [webhook](https://docs.andteam.com/guides/webhooks.md) endpoint. A delivery is thin: it names the person and the fields that
changed, so when one arrives, **read the record**:

```python theme={null}
def on_event(event):
    # already verified, stored and answered
    emp_id = event["data"]["employeeId"]
    r = requests.get(f"{API}/employees/{emp_id}", headers=H)
    if r.status_code == 404:
        return            # not in this key's scope
    r.raise_for_status()
    upsert(emp_id, r.json())
```

A re-read is always current and always within the key's groups, so a late, duplicated or out-of-order
event does no harm: the record you read is the record as it is now.

If the system must not make a call per event, switch on **Include details** for the endpoint. The
payload then carries the record, limited to the endpoint's groups.

## 4. Catch up with updated\_since

Webhooks are at least once, but a receiver can be down for longer than the 27 and a half hours of
retries. So on a schedule, and always on start-up, read what changed:

```python theme={null}
since, started = started, datetime.now(timezone.utc).isoformat()
for e in read_all("/employees", updated_since=since):
    upsert(e["id"], e)
```

Set the window to when the **previous read began**. A record that changed while that read ran is then
read again, not lost. See [Pagination and syncing](https://docs.andteam.com/pagination.md).

## What each event means for the target

| Event | Typical action |
| - | - |
| `employee.hired` | Create the person, with no access yet. |
| `employee.started` | Enable access. 00:00 on the start date, company time. |
| `employee.changed` | Re-read the person and apply `changedFields`. |
| `employee.leaving` | Schedule the end of access for `data.lastDay`. |
| `employee.left` | End access now. |
| `employee.hire_withdrawn` | Remove the person who never started. |
| `employee.termination_cancelled` | Cancel the scheduled end of access. |

## Rules that keep it correct

* **Absent is not null.** A field missing from a response is outside the key's groups. Do not blank the
  value you hold. See [Field groups](https://docs.andteam.com/concepts/field-groups.md).
* **Match on `id`.** It is stable for the life of the record.
* **Ignore what you do not know.** New fields and event types arrive without notice. See
  [Versioning](https://docs.andteam.com/versioning.md).
* **Back off on a 429.** Wait for `Retry-After`. See [Rate limits](https://docs.andteam.com/rate-limits.md).
* **Read-only.** The API cannot write back. A value changed in your system does not reach andteam.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.