Autotask API Patterns
Overview
The Autotask REST API provides access to 215+ entities across the PSA. This skill covers authentication, query building, pagination, error handling, and performance optimization patterns.
Authentication
Header-Based Authentication
Autotask uses header-based authentication (NOT Basic Auth):
GET /v1.0/Tickets
ApiIntegrationCode: YOUR_INTEGRATION_CODE
UserName: your-api-user@domain.com
Secret: YOUR_SECRET
Content-Type: application/json
Required Headers: | Header | Description | |--------|-------------| | ApiIntegrationCode | Your Autotask integration code | | UserName | API username (email address) | | Secret | API secret/password | | Content-Type | application/json |
Environment Variables
export AUTOTASK_USERNAME="your-api-user@domain.com"
export AUTOTASK_INTEGRATION_CODE="YOUR_INTEGRATION_CODE"
export AUTOTASK_SECRET="YOUR_SECRET"
Automatic Zone Detection
Autotask operates in multiple zones. The API can automatically detect your zone:
GET https://webservices.autotask.net/atservicesrest/v1.0/ZoneInformation
UserName: your-api-user@domain.com
Response:
{
"url": "https://webservices5.autotask.net/atservicesrest",
"webUrl": "https://ww5.autotask.net"
}
Common Zones: | Zone | API URL | |------|---------| | webservices | https://webservices.autotask.net/atservicesrest | | webservices1 | https://webservices1.autotask.net/atservicesrest | | webservices2 | https://webservices2.autotask.net/atservicesrest | | webservices5 | https://webservices5.autotask.net/atservicesrest | | webservices6 | https://webservices6.autotask.net/atservicesrest |
Query Builder
Query Operators
The Autotask API supports 14 query operators:
| Operator | Description | Example | |----------|-------------|---------| | eq | Equals | {"field": "status", "op": "eq", "value": 1} | | ne / noteq | Not equals | {"field": "status", "op": "noteq", "value": 5} | | gt | Greater than | {"field": "priority", "op": "gt", "value": 2} | | gte | Greater than or equal | {"field": "createDate", "op": "gte", "value": "2024-01-01"} | | lt | Less than | {"field": "priority", "op": "lt", "value": 3} | | lte | Less than or equal | {"field": "dueDateTime", "op": "lte", "value": "2024-02-15T17:00:00Z"} | | contains | Contains substring | {"field": "title", "op": "contains", "value": "email"} | | startsWith | Starts with | {"field": "companyName", "op": "startsWith", "value": "Acme"} | | endsWith | Ends with | {"field": "email", "op": "endsWith", "value": "@acme.com"} | | in | In array | {"field": "status", "op": "in", "value": [1, 2, 5]} | | notIn | Not in array | {"field": "status", "op": "notIn", "value": [5, 10]} | | isNull | Is null | {"field": "assignedResourceId", "op": "isNull"} | | isNotNull | Is not null | {"field": "dueDateTime", "op": "isNotNull"} | | between | Between range | {"field": "createDate", "op": "between", "value": ["2024-01-01", "2024-01-31"]} |
Date Filtering: "Today" Queries
CRITICAL: To filter for records from "today", you must use a range — gte today's date AND lt tomorrow's date. Using only today's date (e.g. eq or a single filter on today) returns zero results. "Today" in Autotask means: created on or after today's midnight, but before tomorrow's midnight.
Correct — "tickets created today":
{
"filter": [
{"field": "createDate", "op": "gte", "value": "2026-04-13T00:00:00Z"},
{"field": "createDate", "op": "lt", "value": "2026-04-14T00:00:00Z"}
]
}
Wrong — returns NO results:
{
"filter": [
{"field": "createDate", "op": "eq", "value": "2026-04-13"}
]
}
Always compute "tomorrow" dynamically. For example:
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const todayISO = today.toISOString(); // "2026-04-13T00:00:00.000Z"
const tomorrowISO = tomorrow.toISOString(); // "2026-04-14T00:00:00.000Z"
This pattern applies to all datetime fields (createDate, lastActivityDate, dueDateTime, startDateTime, endDateTime, dateWorked, etc.) across all entities — not just tickets.
Query Structure
POST /v1.0/Tickets/query
Content-Type: application/json
{
"filter": [
{"field": "companyID", "op": "eq", "value": 12345},
{"field": "status", "op": "noteq", "value": 5}
],
"maxRecords": 50,
"includeFields": ["Company.companyName", "AssignedResource.firstName"]
}
Complex Queries with Logical Grouping
AND conditions (default):
{
"filter": [
{"field": "companyID", "op": "eq", "value": 12345},
{"field": "priority", "op": "lte", "value": 2},
{"field": "status", "op": "in", "value": [1, 2, 5]}
]
}
OR conditions with items array:
{
"filter": [
{
"op": "or",
"items": [
{"field": "priority", "op": "eq", "value": 1},
{"field": "status", "op": "eq", "value": 14}
]
}
]
}
Nested AND/OR:
{
"filter": [
{"field": "companyID", "op": "eq", "value": 12345},
{
"op": "or",
"items": [
{"field": "priority", "op": "in", "value": [3, 4]},
{
"op": "and",
"items": [
{"field": "status", "op": "eq", "value": 1},
{"field": "estimatedHours", "op": "gt", "value": 10}
]
}
]
}
]
}
Field Includes
Retrieve related entity fields in a single request:
{
"filter": [{"field": "id", "op": "gt", "value": 0}],
"includeFields": [
"Company.companyName",
"Company.phone",
"AssignedResource.firstName",
"AssignedResource.lastName",
"Contact.emailAddress"
]
}
Response with includes:
{
"items": [
{
"id": 54321,
"title": "Email issue",
"companyID": 12345,
"companyName": "Acme Corporation",
"companyPhone": "555-123-4567",
"assignedResourceFirstName": "Jane",
"assignedResourceLastName": "Tech"
}
]
}
Pagination
Request Pagination
{
"filter": [{"field": "id", "op": "gt", "value": 0}],
"maxRecords": 100,
"pageNumber": 1
}
Pagination Fields: | Field | Description | Max | |-------|-------------|-----| | maxRecords | Records per page | 500 | | pageNumber | Current page (1-based) | - |
Response Structure
{
"items": [...],
"pageDetails": {
"count": 100,
"nextPageUrl": "/v1.0/Tickets/query?pageNumber=2",
"prevPageUrl": null,
"requestCount": 2847
}
}
Efficient Pagination Pattern
async function fetchAllTickets(filter) {
const allItems = [];
let pageNumber = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch('/v1.0/Tickets/query', {
method: 'POST',
body: JSON.stringify({
filter,
maxRecords: 500,
pageNumber
})
});
const data = await response.json();
allItems.push(...data.items);
hasMore = data.pageDetails.nextPageUrl !== null;
pageNumber++;
}
return allItems;
}
Rate Limiting
Autotask API Hard Limits
Autotask enforces two hard limits:
| Limit | Value | Scope | |-------|-------|-------| | Concurrent threads per endpoint | 3 | Per API tracking identifier (your integrationCode) | | Total requests per hour | 10,000 | Per Autotask tenant database (all integrations combined) |
Concurrent thread limit is the most common cause of slowdowns in LLM-driven integrations. When Claude issues several tool calls in parallel (e.g., tickets search + companies search + contacts search), all three may target the Tickets endpoint simultaneously and hit the 3-thread cap.
When using the MCP server or autotask-node SDK, this is handled automatically — excess requests are queued and