# Autotask Sync — Reference Guide ## Overview Pulse maintains a local PostgreSQL mirror of Autotask data in the `pulse_autotask` database. Three mechanisms keep it current: **scheduled syncs**, **real-time webhooks**, and **manual/API-triggered syncs**. --- ## Automatic Syncs (Scheduled) All schedules are managed via the **Admin → Sync Scheduler** UI or directly in the `sync_schedules` table. They can be enabled/disabled individually. | Schedule ID | Name | Cron (UTC) | Local (EST) | Type | Enabled | |---|---|---|---|---|---| | `sync-6am` | Morning Sync | `0 11 * * *` | 6:00 AM daily | incremental | ✅ | | `sync-830am` | Morning Sync | `30 13 * * *` | 8:30 AM daily | incremental | ✅ | | `sync-11am` | Midday Sync | `0 16 * * *` | 11:00 AM daily | incremental | ✅ | | `sync-230pm` | Afternoon Sync | `30 19 * * *` | 2:30 PM daily | incremental | ✅ | | `weekly-full` | Weekly Full Sync | `0 3 * * 0` | 3:00 AM Sunday | full | ✅ | | `contract-services` | Contract Services | `0 4 * * *` | 4:00 AM daily | contract-services | ✅ | ### Incremental vs Full **Incremental sync** — pulls only records modified since the last successful sync timestamp (stored in `sync_history`). Much faster. Runs 4× per day. **Full sync** — pulls everything regardless of modification date, going back `yearsBack` years (default: 2). Runs every Sunday at 3 AM. Also used for initial setup or repair. ### What incremental syncs cover Every incremental run syncs all entities in dependency order: 1. Companies, Resources, Statuses, Issue Types, Sub-Issue Types, Work Types, Queues, Priorities, Ticket Categories *(no dependencies)* 2. Contacts *(requires Companies)* 3. Projects *(requires Companies, Resources)* 4. Tickets *(requires Companies, Resources, Contacts)* 5. Tasks *(requires Resources, Projects, Tickets)* 6. Configuration Items *(requires Companies, Contacts)* 7. Contracts *(requires Companies, Contacts)* 8. Autotask Services *(standalone)* 9. Billing Items *(requires Companies, Tasks, Tickets, Projects)* 10. Time Entries *(requires Companies, Resources, Contacts, Projects, Tasks, Tickets)* 11. Ticket Notes *(requires Tickets)* 12. Tag Groups, Tags, Ticket Tag Associations > **Note:** Companies and Resources do not support date-based filtering in the Autotask API, so they always do a full pull on every run (fast — ~57 resources, ~236 companies). ### Time-windowed entities Some entities are too large to sync in full and are filtered by date: | Entity | Filter field | Default window | |---|---|---| | Tickets | `createDate` | Last 2 years | | Tasks | `createDate` | Last 2 years | | Time Entries | `dateWorked` | Last 2 years | | Billing Items | `itemDate` | Last 2 years | The `yearsBack` parameter (default `2`) controls how far back these go. The full sync and chunked sync accept a custom `yearsBack` value. --- ## Real-Time Webhooks Autotask pushes change events to `/api/webhooks/autotask` immediately when a ticket (or other entity) is created or updated. The webhook handler fetches the full record from the Autotask API and upserts it directly — no waiting for the next scheduled sync. This means tickets assigned, updated, or closed in Autotask appear in Pulse within seconds. The webhook path **bypasses** the resource ID validation step, so it always writes exactly what Autotask sends. --- ## Manual / API-Triggered Syncs ### Via Admin UI **Admin → Sync** — the sync overview page has buttons to trigger syncs per integration. ### Via API endpoints All endpoints are POST and non-blocking (fire-and-forget) — they return immediately and run in the background. | Endpoint | Payload | Description | |---|---|---| | `POST /api/sync/full` | `{ yearsBack?, triggeredBy? }` | Full sync of all entities | | `POST /api/sync/entity` | `{ entities: [...], syncType?, yearsBack? }` | Sync specific entity types only | | `POST /api/sync/tickets-chunked` | `{ yearsBack? }` | Chunked ticket sync with progress logging (good for large date ranges) | **Example — sync only tickets and resources:** ```bash curl -X POST http://localhost:3100/api/sync/entity \ -H "Content-Type: application/json" \ -d '{"entities": ["tickets", "resources"], "triggeredBy": "manual"}' ``` **Example — full sync going back 3 years:** ```bash curl -X POST http://localhost:3100/api/sync/full \ -H "Content-Type: application/json" \ -d '{"yearsBack": 3, "triggeredBy": "manual"}' ``` ### Via OpenClaw (external agent API) ```bash # Incremental sync POST /api/openclaw/sync/autotask/incremental # Full sync POST /api/openclaw/sync/autotask/full { "yearsBack": 2 } # Specific entities POST /api/openclaw/sync/autotask/entity { "entities": ["tickets"] } ``` Auth: `x-openclaw-key: ` --- ## Valid Entity Names Use these string values in the `entities` array: ``` companies, resources, statuses, issue_types, sub_issue_types, work_types, queues, priorities, ticket_categories, contacts, projects, tickets, tasks, configuration_items, contracts, contract_services, autotask_services, billing_items, time_entries, ticket_notes, tag_groups, tags ``` --- ## Sync History & Status Every sync run writes to `sync_history`. You can query it directly: ```sql -- Last sync per entity type SELECT entity_type, sync_type, status, records_added, records_updated, completed_at FROM sync_history WHERE status = 'completed' ORDER BY completed_at DESC; -- Check when tickets last synced successfully SELECT completed_at FROM sync_history WHERE entity_type = 'tickets' AND status = 'completed' ORDER BY completed_at DESC LIMIT 1; ``` The incremental sync uses `completed_at` from `sync_history` to determine how far back to pull. **If a sync fails, the timestamp does not advance** — the next run re-fetches from the last successful point. --- ## Known Constraints & Gotchas - **One sync at a time.** If a sync is already running, new requests return `409 Conflict`. - **`assigned_resource_id` is the only resource assignment on a ticket.** There is no separate "primary resource" — `assigned_resource_id` is it. Other resource fields (`first_response_assigned_resource_id`, `last_activity_resource_id`, `creator_resource_id`) are audit/SLA tracking fields. - **BIGINT IDs come back as strings from pg.** The sync code uses `Number()` coercion when building validation sets to avoid false-negative ID comparisons. - **FK constraints on tickets are deferrable.** `tickets_company_id_fkey` and all resource FKs are `DEFERRABLE INITIALLY DEFERRED ON DELETE SET NULL`. This means a ticket can be inserted even if its referenced company or resource isn't in the local DB yet — the field is set to NULL rather than rejecting the row. - **Webhook and scheduled sync can race.** If a webhook fires during a scheduled sync batch, it may be overwritten by the batch. This is harmless — both write the same Autotask data.