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

# ATS Sync Controls

> Support-only controls for enabling ATS sync and setting lookback cursors from the Health Monitor

<Warning>
  **Restricted Access**: This documentation is only accessible to @tenzo.ai and @salv.ai email addresses.
</Warning>

## Overview

ATS Sync Controls let support users with **full access** enable or disable per-type ATS sync and adjust sync cursors (last-synced timestamps) while impersonating an org. Controls live in the regular app under **Admin Settings → ATS Integrations → Health Monitor**, in **Org View** only.

Supported sync types:

| Sync type  | Toggle column            | Cursor column               |
| ---------- | ------------------------ | --------------------------- |
| Jobs       | `job_sync_enabled`       | `jobs_last_synced_at`       |
| Applicants | `applicant_sync_enabled` | `applicants_last_synced_at` |
| Candidates | `candidate_sync_enabled` | `candidates_last_synced_at` |

Tearsheets, placements and notes also keep cursors (`tearsheets_last_synced_at`, `placements_last_synced_at`, `notes_last_synced_at`) but have no toggle or lookback control in the UI.

### How the cursor advances

Setting a cursor here only chooses where the next cycle starts. Whether a cycle then moves the cursor forward is decided by a single rule shared by all sync types: the cursor is held whenever the window was not fully enumerated or any retryable failure occurred, and it is advanced when the window was empty, everything succeeded, or the only failures were permanent per-item failures in queue mode, which are recorded and skipped rather than retried from the cursor. The cursor moves to the sync start time, never the end time. The full table is in [ATS Sync Alerts](/internal/ats-sync-alerts#cursor-advance-rule).

## Access requirements

* User must be a support user with **full access** enabled (`auth.is_full_access`).
* User must be impersonating the target org. Endpoints scope changes to `auth.org_id`; integrations belonging to other orgs return 403.
* Sync controls are hidden in **Global View** on the Health Monitor. Switch to **Org View** to see them.

## UI behavior

Each sync type shows:

* A toggle (Enabled / Disabled) with a confirmation modal.
* Lookback preset buttons: **Now**, **1 day**, **2 days**, **1 week**, **1 month**.

### Enable

When enabling sync, the UI shows a confirmation modal with a lookback selector.

| Cursor state              | Modal behavior                                           | Default cursor on confirm                       |
| ------------------------- | -------------------------------------------------------- | ----------------------------------------------- |
| **Null** (never synced)   | Yellow warning: "Review sync start point"                | `now`                                           |
| **Stale** (> 30 days old) | Yellow warning + lookback selector (defaults to **Now**) | `now` (unless another lookback is chosen)       |
| **Fresh** (≤ 30 days old) | Message that existing timestamp will be kept             | Unchanged (unless a non-Now lookback is chosen) |

If the user selects a lookback preset in the enable modal, that preset is applied even when the cursor is fresh.

### Disable

Disabling sync only flips the toggle. The last-synced timestamp is **never** changed.

### Standalone lookback

Lookback buttons update the cursor without changing the enable/disable state. Each click opens a confirmation modal.

## API endpoints

| Endpoint                                   | Purpose                                                     |
| ------------------------------------------ | ----------------------------------------------------------- |
| `POST /api/ats/sync-controls/set-toggle`   | Enable/disable a sync type; optionally set cursor on enable |
| `POST /api/ats/sync-controls/set-lookback` | Set cursor to `now - lookback` without toggling             |

Lookback presets map to: `NOW` (0 days), `ONE_DAY`, `TWO_DAYS`, `ONE_WEEK`, `ONE_MONTH` (30 days). Maximum lookback is 30 days.

When enabling with a stale cursor, the API may return a `warning` string indicating the old timestamp will not be used.

## Cursor resolution logic

Pure logic lives in `server/ats/sync_control_logic.py`:

* **Disable** → no cursor change.
* **Enable + explicit lookback** → cursor = `now - lookback`; stale warning if prior cursor was stale.
* **Enable + null cursor** → cursor = `now`.
* **Enable + stale cursor, no lookback** → cursor = `now` + stale warning.
* **Enable + fresh cursor, no lookback** → cursor unchanged.

## Testing

### Automated tests

```bash theme={null}
cd server && ./bin/test tests/unit/ats/test_sync_control_logic.py
cd server && ./bin/test tests/unit/ats/test_ats_sync_controls_api.py
```

* `test_sync_control_logic.py` — 20 unit tests for cursor classification and enable/lookback resolution.
* `test_ats_sync_controls_api.py` — 8 unit tests for full-access auth, org scoping, and DB side effects.

### Manual E2E verification

Verified locally against Docker Postgres (`salv-postgres-dev`, database `salv_local`) and the Health Monitor UI at `/settings/ats_integrations/ats_health` in **Org View** with full-access impersonation.

Test integration: `recruitcrm:433964ef-2d90-4d91-810c-62ee716ded3d`

| # | Action                                                                  | Expected UI                                          | Expected Postgres                                                              |
| - | ----------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------ |
| 1 | Enable **Jobs** with stale cursor, default **Now**                      | Stale warning + lookback selector                    | `job_sync_enabled=true`, `jobs_last_synced_at` → now                           |
| 2 | **Candidates → 1 week** lookback (toggle unchanged)                     | Lookback confirm modal                               | `candidate_sync_enabled` unchanged, `candidates_last_synced_at` → \~7 days ago |
| 3 | Enable **Applicants** with stale cursor, **1 day** lookback             | Stale warning, 1d selected                           | `applicant_sync_enabled=true`, `applicants_last_synced_at` → \~1 day ago       |
| 4 | Disable **Jobs**                                                        | "Timestamp will not be changed" modal                | `job_sync_enabled=false`, `jobs_last_synced_at` unchanged                      |
| 5 | Re-enable **Jobs** with fresh cursor, default **Now**                   | "Existing timestamp will be kept" (no stale warning) | `job_sync_enabled=true`, `jobs_last_synced_at` unchanged                       |
| 6 | **Jobs → 2 days** lookback while enabled                                | Lookback confirm modal                               | `jobs_last_synced_at` → \~2 days ago, toggle stays enabled                     |
| 7 | Enable **Candidates** with fresh cursor (\~6 days old), default **Now** | Keep-existing-timestamp message                      | `candidate_sync_enabled=true`, `candidates_last_synced_at` unchanged           |
| 8 | Enable **Candidates** with null cursor, default **Now**                 | Null/stale warning                                   | `candidate_sync_enabled=true`, `candidates_last_synced_at` → now               |

After each action, the Health Monitor UI refreshed to show updated toggle labels and last-synced timestamps.

### Edge cases covered

* Null cursor on enable defaults to `now`.
* Stale cursor (> 30 days) warns and defaults to `now` unless a lookback is chosen.
* Fresh cursor is preserved on enable when lookback is **Now**.
* Explicit lookback on enable or via standalone buttons updates cursor independently of toggle state.
* Disable never mutates cursor columns.
* Org scoping and full-access gating enforced at the API layer (unit tested).
