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
- Verify HMAC signature
- Normalise raw Autotask payload → internal format
- Fetch full entity from API (webhook payloads are partial)
- Upsert into PostgreSQL
- Fire workflow engine (new tickets only, fire-and-forget)
- 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
- Add types to
lib/types/autotask.ts - Add a fetch method to
AutotaskClientor use the genericqueryEntityPaginated - Add sync logic in
lib/services/entity-sync.tsfollowing the incremental filter table above - Create a migration for the PostgreSQL table
- Add a
/api/data/<entity>route if UI needs it - Register a webhook in
autotask-webhook-manager.tsif 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/statusfor 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_eventstable)