wulf-pulse/docs/AUTOTASK_API_GUIDE.md

12 KiB

Autotask API — Claude Code Skill Guide

A reference for working with the Autotask REST API in this codebase. Read this before adding, modifying, or debugging any Autotask integration.


Quick Reference

Item Value
Base URL https://webservices1.autotask.net/atservicesrest/v1.0
Auth style Custom headers (not Basic Auth)
Query method POST /Entity/query with JSON body
Rate limit 10 req/s (enforced internally)
Page size 500 records max
Client file lib/services/autotask-client.ts
Factory lib/services/autotask-factory.ts
Types lib/types/autotask.ts

Authentication

All requests use three custom headers — not a Bearer token or Basic Auth.

Username: <AUTOTASK_USERNAME>
Secret: <AUTOTASK_SECRET>
APIIntegrationcode: <AUTOTASK_API_INTEGRATION_CODE>
Content-Type: application/json
Accept: application/json

Optional delegation header:

ImpersonationResourceID: <resourceId>

Env vars required:

AUTOTASK_API_URL=https://webservices1.autotask.net/atservicesrest/v1.0
AUTOTASK_USERNAME=user@domain.com
AUTOTASK_SECRET=api-password
AUTOTASK_API_INTEGRATION_CODE=your-integration-code
AUTOTASK_WEBHOOK_SECRET=webhook-hmac-secret
WEBHOOK_BASE_URL=https://your-public-domain.com

Client Usage

Always get the client via the factory — never instantiate AutotaskClient directly.

import { getAutotaskClient } from '@/lib/services/autotask-factory';

const client = getAutotaskClient();

isMsgraphConfigured() pattern does not apply here — Autotask client always initialises if env vars are present.


Entity Endpoints

Standard CRUD

Operation Method Path
Query / list POST /Entity/query
Get by ID GET /Entity/{id}
Create POST /Entity
Update PUT /Entity
Delete DELETE /Entity/{id}
Field metadata GET /Entity/entityInformation/fields
Attachments GET/POST /Entity/{id}/Attachments

Supported Entities

Companies           Tickets             TicketNotes
Tasks               Projects            Resources
Contacts            ConfigurationItems  Contracts
ContractServices    Services            TimeEntries
TicketTagAssociations  TagGroups        Tags
Statuses            IssueTypes          SubIssueTypes
WorkTypes           Queues              Priorities
TicketCategories    BillingItems        ClassificationIcons

Webhook Entities (separate registration endpoints)

CompanyWebhooks           ContactWebhooks
ConfigurationItemWebhooks TicketWebhooks
TicketNoteWebhooks

/{WebhookEntity}/{id}/Fields
/{WebhookEntity}/{id}/ExcludedResources

Querying

All list/search operations use POST /Entity/query with a JSON body — not query string parameters.

Request shape

const params = {
  MaxRecords: 500,
  filter: [
    { op: 'eq',  field: 'isActive',        value: true },
    { op: 'gte', field: 'lastActivityDate', value: '2024-01-01T00:00:00Z' }
  ]
};

const result = await client.queryEntity<Company>('Companies', params);
// result.items: Company[]
// result.pageDetails: { count, requestCount, nextPageUrl, prevPageUrl }

Filter operators

Operator Meaning
eq Equal
noteq Not equal
gt / gte Greater than / or equal
lt / lte Less than / or equal
contains String contains
beginsWith String starts with
endsWith String ends with
exist Field is not null

Common filter fields

Field Used on
isActive Companies, Resources, ConfigItems, Contacts, Services, Tags
status Tickets, Tasks, Projects, Contracts
companyID Most child entities
assignedResourceID Tickets, Tasks
createDate / createDateTime Tickets, Tasks
lastActivityDate / lastModifiedDateTime Most entities
resourceID / ticketID / taskID / projectID TimeEntries
id Any entity

Pagination

Use queryEntityPaginated — it handles nextPageUrl automatically and yields all records.

const allTickets = await client.queryEntityPaginated<Ticket>('Tickets', {
  MaxRecords: 500,
  filter: [{ op: 'eq', field: 'status', value: 1 }]
});

Manual pagination: check result.pageDetails.nextPageUrl and issue a GET to that URL.


Response Shapes

Single entity (GET by ID)

{ "item": { "id": 123, ... } }

Query / list (POST /query)

{
  "items": [ { "id": 1, ... }, { "id": 2, ... } ],
  "pageDetails": {
    "count": 500,
    "requestCount": 1,
    "nextPageUrl": "https://...?pageDetails=...",
    "prevPageUrl": null
  }
}

Create / update

{ "itemId": 456, "item": { ... } }

Error

{
  "message": "Human-readable error",
  "errors": [
    { "message": "Detail", "field": "fieldName" }
  ]
}

Client Methods

Generic CRUD

client.queryEntity<T>(entity, params)                    // single page
client.queryEntityPaginated<T>(entity, params, pageSize) // all pages
client.getEntityById<T>(entity, id)
client.createEntity<T>(entity, data)
client.updateEntity<T>(entity, id, data)                 // PUT then GET
client.deleteEntity(entity, id)
client.getFieldInfo(entity)                              // field metadata

Typed convenience methods

// Resources
client.getResourceByEmail(email)
client.getAllResources()

// Tickets
client.getOpenTicketsByResource(resourceId)
client.getTicketsByCompany(companyId)
client.createTicket(data)
client.updateTicket(id, data)

// Tasks
client.getTasksByResource(resourceId)
client.getTasksByProject(projectId)
client.createTask(data)
client.updateTask(id, data)

// Companies
client.getAllCompanies()
client.getCompanyById(id)

// Configuration Items
client.getConfigurationItemsByCompany(companyId)
client.getAllConfigurationItems()
client.getConfigurationItemById(id)
client.createConfigurationItem(data)
client.updateConfigurationItem(id, data)

// Time Entries
client.getTimeEntriesByResource(resourceId)
client.getTimeEntriesByTicket(ticketId)
client.getTimeEntriesByTask(taskId)
client.getTimeEntriesByProject(projectId)
client.getTimeEntriesByCompany(companyId)
client.getTimeEntriesByDateRange(start, end)
client.createTimeEntry(data)
client.updateTimeEntry(id, data)
client.deleteTimeEntry(id)

// Picklists
client.getPicklistValues(entity, field)
client.getTicketStatusPicklist()
client.getTicketPriorityPicklist()
client.getTaskStatusPicklist()

// Attachments
client.uploadAttachment(entity, id, file, filename, contentType)
client.getAttachments(entity, id)

// Misc
client.getClassificationIcons()

Entity Sync

The sync layer lives in lib/services/entity-sync.ts. It handles both full and incremental syncs from Autotask → PostgreSQL.

Sync types

Type Description
Full Fetch all records (with active/status filters)
Incremental Fetch only records changed since last sync timestamp

Incremental filter fields by entity

Entity Incremental field Active filter Date window
Companies lastActivityDate isActive=true None
Tickets lastActivityDate None createDate (2 yr)
Tasks lastActivityDateTime None createDateTime (2 yr)
TimeEntries lastModifiedDateTime None lastModifiedDateTime (2 yr)
Contracts lastModifiedDateTime status=1 None
ContractServices lastModifiedDate None None
ConfigurationItems lastModifiedTime isActive=true None
Resources lastModifiedDate isActive=true No incremental
Tags lastModifiedDateTime isActive=true None
TagGroups lastModifiedDate isActive=true None
TicketTagAssociations N/A id > 0 N/A
BillingItems itemDate None itemDate (2 yr)
Contacts lastModifiedDate isActive=true None
Projects lastActivityDateTime status != 5 None
Picklists (Statuses, etc.) lastModifiedDate isActive=true None

Sync order (dependency chain)

1. Companies          — parent entity
2. Resources          — parent entity
3. Priorities, Statuses, IssueTypes, SubIssueTypes, WorkTypes, Queues, TicketCategories
4. Contacts, Projects, Tickets, Tasks
5. ConfigurationItems, Contracts, TimeEntries
6. ContractServices, BillingItems
7. TagGroups, Tags, TicketTagAssociations

Webhooks

Incoming webhook

  • Route: POST /api/webhooks/autotask
  • Verification: HMAC-SHA1 of raw body, header x-hook-signature: sha1=<base64>
  • Service: lib/services/webhook-service.ts

Processing pipeline

  1. Verify HMAC signature
  2. Normalise raw Autotask payload → internal format
  3. Fetch full entity from API (webhook payloads are partial)
  4. Upsert into PostgreSQL
  5. Fire workflow engine (new tickets only, fire-and-forget)
  6. Log event

Webhook payload (raw from Autotask)

{
  "Action": "Create | Update | Delete",
  "Guid": "unique-event-guid",
  "EntityType": "Ticket | Company | Contact | ConfigurationItem | TicketNote",
  "Id": 123,
  "Fields": { "status": "1", "title": "..." },
  "EventTime": "2024-03-21T10:30:00Z",
  "SequenceNumber": 1,
  "PersonId": 456
}

Supported webhook entities

  • Companies, Contacts, ConfigurationItems, Tickets, TicketNotes
  • Not supported (returns 404): TimeEntries, Tasks, Projects, Contracts

Registration

POST   /api/webhooks/register             — register all webhooks
POST   /api/webhooks/register?entity=Tickets  — register single entity
DELETE /api/webhooks/register             — deregister all
GET    /api/webhooks/status               — view registered webhooks

Webhook manager: lib/services/autotask-webhook-manager.ts


Rate Limiting

Built into AutotaskClient via a RateLimiter class — do not add external throttling.

  • Limit: 10 requests/second
  • Implementation: token bucket tracking request timestamps
  • Behaviour: auto-delays if limit is hit; transparent to callers

Internal API Routes

Sync

POST /api/sync/full           — trigger full sync of all entities
POST /api/sync/incremental    — trigger incremental sync
GET  /api/sync/status         — current sync status
GET  /api/sync/history        — sync history log

Cached data (read from PostgreSQL, not Autotask directly)

GET /api/data/tickets
GET /api/data/companies
GET /api/data/resources
GET /api/data/tasks
GET /api/data/configuration-items
GET /api/data/time-entries
GET /api/data/contacts
GET /api/data/contracts
GET /api/data/billing-items

Key File Map

Purpose File
Main API client lib/services/autotask-client.ts
Client factory (singleton) lib/services/autotask-factory.ts
Webhook registration lib/services/autotask-webhook-manager.ts
Sync orchestration lib/services/entity-sync.ts
Webhook receiver + verifier lib/services/webhook-service.ts
Sync helpers (filters, dates) lib/utils/sync-helpers.ts
Entity field mapper lib/utils/entity-mapper.ts
TypeScript types lib/types/autotask.ts
Webhook types lib/types/webhook.ts
Webhook route handler app/api/webhooks/autotask/route.ts
Webhook register route app/api/webhooks/register/route.ts
Tags migration migrations/057_create_autotask_tags_tables.sql

Common Patterns

Adding a new synced entity

  1. Add types to lib/types/autotask.ts
  2. Add a fetch method to AutotaskClient or use the generic queryEntityPaginated
  3. Add sync logic in lib/services/entity-sync.ts following the incremental filter table above
  4. Create a migration for the PostgreSQL table
  5. Add a /api/data/<entity> route if UI needs it
  6. Register a webhook in autotask-webhook-manager.ts if the entity supports it

Adding a new client method

Follow the pattern in autotask-client.ts — use this.queryEntityPaginated() for lists, this.getEntityById() for single records, this.updateEntity() for writes. Rate limiting and auth headers are applied automatically.

Debugging sync issues

  • Check GET /api/sync/status for in-progress or failed syncs
  • Incremental syncs use the last sync timestamp stored in PostgreSQL — if stale, run a full sync
  • Webhook delivery failures appear in the webhook event log (webhook_events table)