Autotask API Patterns
Autotask REST API fundamentals: header-based authentication, zone detection, the query/filter DSL (14 operators, logical grouping, includes), pagination, rate limits, and CRUD conventions across the 215+ entity PSA.
Install
npx skills add https://github.com/WYRE-AI/msp-claude-plugins/tree/main/msp-claude-plugins/kaseya/autotask/skills/api-patterns
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wyre-ai-msp-claude-plugins@llmmart
git clone https://github.com/WYRE-AI/msp-claude-plugins.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wyre-ai/msp-claude-plugins collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
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.
Anti-triggers
- Autotask tools missing, or present but refusing to run — a
lazy-loaded MCP connection exposes only four meta-tools. That is a
discovery problem, not an auth or query problem; use
autotask-tool-discovery. - Instance-specific status, priority, queue, or phase IDs — the
filter DSL will happily match an ID that does not exist in your
tenant and return nothing. Fetch the real values with
autotask-picklists.
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
Filters combine with implicit AND. Use "op": "or" / "op": "and" with an items array to group or nest conditions:
{
"filter": [
{"field": "companyID", "op": "eq", "value": 12345},
{
"op": "or",
"items": [
{"field": "priority", "op": "eq", "value": 1},
{"field": "status", "op": "eq", "value": 14}
]
}
]
}
See references/api.md for an AND-only example and a nested AND/OR example.
Field Includes
Retrieve related entity fields in a single request:
{
"filter": [{"field": "id", "op": "gt", "value": 0}],
"includeFields": [
"Company.companyName",
"AssignedResource.firstName",
"AssignedResource.lastName"
]
}
See references/api.md for the response shape returned when includes are used.
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
}
}
Page through results by looping while pageDetails.nextPageUrl is non-null. See references/examples.md for a full pagination loop implementation.
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 released as slots free up, so you won't see hard failures, but responses may be slower under load.
Multi-user / shared key risk: The 3-thread limit applies per integrationCode. If multiple users or teams share the same credentials, they compete for the same 3 slots. In a team deployment, give each team their own API user:
Support Team → integrationCode: SUPPORT_TEAM_CODE (3 threads, independent)
Projects Team → integrationCode: PROJECTS_TEAM_CODE (3 threads, independent)
Rate Limit Response
When the concurrent thread limit or hourly request limit is exceeded (HTTP 429):
{
"errors": [
{
"message": "Rate limit exceeded. Try again in 30 seconds."
}
]
}
Implement exponential backoff with jitter on 429 responses, honoring Retry-After when present. See references/examples.md for a retry-with-backoff implementation.
Query Different Entity Types in Parallel
To maximize throughput without hitting the per-endpoint thread limit, query different endpoints in parallel rather than the same endpoint multiple times — parallel requests to Tickets + Companies + Contacts each get their own 3-thread budget, while parallel requests to multiple pages of the same endpoint share one budget and queue.
For bulk write operations, batch requests in groups (e.g. 50 at a time) with a short delay between batches to avoid the hourly limit. See references/examples.md for parallel-query and batch-processing code.
Error Handling
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 201 | Created | Entity created successfully |
| 400 | Bad Request | Check request format/values |
| 401 | Unauthorized | Verify credentials |
| 403 | Forbidden | Check permissions |
| 404 | Not Found | Entity doesn't exist |
| 409 | Conflict | Resource locked/modified |
| 429 | Rate Limited | Implement backoff |
| 500 | Server Error | Retry with backoff |
Error Response Format
{
"errors": [
{
"message": "The value '999' is not valid for field 'status'.",
"field": "status",
"value": 999
}
]
}
When a field-level error is returned, cross-reference the field against /v1.0/<Entity>/entityInformation/fields (or /v1.0/Queues for queue IDs) to find valid values. See references/examples.md for a full validation-error handler.
Entity Information
Query field definitions and picklist values before writing to an unfamiliar entity:
GET /v1.0/Tickets/entityInformation/fields
GET /v1.0/Tickets/entityInformation/userDefinedFields
See references/api.md for the full response shape and CRUD (create/read/update/replace/delete) request examples.
Performance Optimization
Select Only Needed Fields
{
"filter": [{"field": "id", "op": "gt", "value": 0}],
"fields": ["id", "title", "status", "priority"]
}
Use Efficient Filters
Good - Uses indexed field:
{"field": "companyID", "op": "eq", "value": 12345}
Avoid - Full text search:
{"field": "description", "op": "contains", "value": "error"}
Cache Reference Data
Cache slowly-changing data — Queues, Resources, Issue Types, Priorities, Company lists — rather than re-fetching per request. See references/examples.md for a TTL-based cache pattern.
Best Practices
- Detect zone once - Cache the zone URL after initial detection
- Use includes - Avoid N+1 queries by including related data
- Paginate large results - Never fetch unbounded result sets
- Implement retry logic - Handle rate limits and transient errors
- Cache reference data - Reduce API calls for static lookups
- Select specific fields - Only request fields you need
- Use batch operations - Group related operations together
- One API key per team - Autotask limits 3 concurrent threads per
integrationCode. Each team using the integration should have their own API user so they don't compete for the same thread budget - Parallelize across endpoints, not within - To maximize throughput, query Tickets + Companies + Contacts simultaneously (different endpoints, independent thread budgets) rather than fetching multiple pages of the same endpoint in parallel
Related Skills
- Autotask Tickets - Ticket management
- Autotask CRM - Company and contact management
- Autotask Contracts - Service agreements
Files (msp-claude-plugins)
-
references
-
api.md 2.6 KB
# Autotask API Patterns — Extended Reference ## Complex Query Examples **AND conditions (default):** ```json { "filter": [ {"field": "companyID", "op": "eq", "value": 12345}, {"field": "priority", "op": "lte", "value": 2}, {"field": "status", "op": "in", "value": [1, 2, 5]} ] } ``` **Nested AND/OR:** ```json { "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 — Response Shape **Request:** ```json { "filter": [{"field": "id", "op": "gt", "value": 0}], "includeFields": [ "Company.companyName", "Company.phone", "AssignedResource.firstName", "AssignedResource.lastName", "Contact.emailAddress" ] } ``` **Response with includes:** ```json { "items": [ { "id": 54321, "title": "Email issue", "companyID": 12345, "companyName": "Acme Corporation", "companyPhone": "555-123-4567", "assignedResourceFirstName": "Jane", "assignedResourceLastName": "Tech" } ] } ``` ## Entity Information ### Get Field Definitions ```http GET /v1.0/Tickets/entityInformation/fields ``` **Response:** ```json { "fields": [ { "name": "status", "dataType": "Integer", "isRequired": true, "isPickList": true, "picklistValues": [ {"value": 1, "label": "New"}, {"value": 2, "label": "In Progress"}, {"value": 5, "label": "Complete"} ] } ] } ``` ### Get User-Defined Fields ```http GET /v1.0/Tickets/entityInformation/userDefinedFields ``` ## CRUD Operations ### Create (POST) ```http POST /v1.0/Tickets Content-Type: application/json { "companyID": 12345, "title": "New ticket", "status": 1, "priority": 2, "queueID": 8 } ``` ### Read (GET) **Single entity:** ```http GET /v1.0/Tickets/54321 ``` **Query:** ```http POST /v1.0/Tickets/query ``` ### Update (PATCH) ```http PATCH /v1.0/Tickets Content-Type: application/json { "id": 54321, "status": 2, "assignedResourceID": 29744150 } ``` ### Replace (PUT) ```http PUT /v1.0/Tickets/54321 Content-Type: application/json { "id": 54321, "companyID": 12345, "title": "Updated ticket", "status": 2, "priority": 2, "queueID": 8 } ``` ### Delete (DELETE) ```http DELETE /v1.0/Tickets/54321 ``` **Note:** Not all entities support DELETE. Check entity documentation. -
examples.md 3.5 KB
# Autotask API Patterns — Code Examples ## Efficient Pagination Pattern ```javascript 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; } ``` ## Retry Strategy (Rate Limits) ```javascript async function requestWithRetry(url, options, maxRetries = 5) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') || 30; const jitter = Math.random() * 1000; await sleep(retryAfter * 1000 + jitter); continue; } return response; } catch (error) { if (attempt === maxRetries - 1) throw error; // Exponential backoff with jitter const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000; await sleep(delay); } } } ``` ## Query Different Entity Types in Parallel ```javascript // Good: parallel requests to different endpoints — each has its own 3-thread budget const [tickets, companies, contacts] = await Promise.all([ client.tickets.query().where('status', 'in', [1, 5]).execute(), client.companies.query().where('companyType', 'eq', 1).execute(), client.contacts.query().where('isActive', 'eq', true).execute(), ]); // Avoid: parallel requests to the SAME endpoint — they share 3 threads // (will queue automatically, but adds latency) const [page1, page2, page3] = await Promise.all([ client.tickets.query().pageNumber(1).execute(), // ← same endpoint client.tickets.query().pageNumber(2).execute(), // ← same endpoint client.tickets.query().pageNumber(3).execute(), // ← same endpoint ]); ``` ## Batch Processing ```javascript async function batchProcess(items, batchSize = 50, delayMs = 1000) { const results = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await Promise.all( batch.map(item => processItem(item)) ); results.push(...batchResults); if (i + batchSize < items.length) { await sleep(delayMs); } } return results; } ``` ## Validation Error Handling ```javascript function handleApiError(response) { if (!response.errors) return; response.errors.forEach(error => { console.log(`Error: ${error.message}`); if (error.field) { console.log(` Field: ${error.field}`); console.log(` Invalid Value: ${error.value}`); // Suggest fix based on field if (error.field === 'status') { console.log(' Suggestion: Query /v1.0/Tickets/entityInformation/fields for valid status IDs'); } else if (error.field === 'queueID') { console.log(' Suggestion: Query /v1.0/Queues for valid queue IDs'); } } }); } ``` ## Cache Reference Data ```javascript const cache = new Map(); async function getQueues() { if (!cache.has('queues') || cache.get('queues').expires < Date.now()) { const queues = await fetchQueues(); cache.set('queues', { data: queues, expires: Date.now() + 5 * 60 * 1000 // 5 minutes }); } return cache.get('queues').data; } ```
-
-
SKILL.md 12.4 KB
--- name: "Autotask API Patterns" description: > Autotask REST API fundamentals: header-based authentication, zone detection, the query/filter DSL (14 operators, logical grouping, includes), pagination, rate limits, and CRUD conventions across the 215+ entity PSA. when_to_use: >- When working with authentication, query building, pagination, includes, rate limiting, and error handling in the Autotask REST API. Use when: autotask api, autotask query, autotask authentication, api filter, query builder, autotask pagination, api rate limit, autotask zone, api error, or autotask rest. --- # 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. ## Anti-triggers - **Autotask tools missing, or present but refusing to run** — a lazy-loaded MCP connection exposes only four meta-tools. That is a discovery problem, not an auth or query problem; use `autotask-tool-discovery`. - **Instance-specific status, priority, queue, or phase IDs** — the filter DSL will happily match an ID that does not exist in your tenant and return nothing. Fetch the real values with `autotask-picklists`. ## Authentication ### Header-Based Authentication Autotask uses header-based authentication (NOT Basic Auth): ```http 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 ```bash 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: ```http GET https://webservices.autotask.net/atservicesrest/v1.0/ZoneInformation UserName: your-api-user@domain.com ``` **Response:** ```json { "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":** ```json { "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:** ```json { "filter": [ {"field": "createDate", "op": "eq", "value": "2026-04-13"} ] } ``` Always compute "tomorrow" dynamically. For example: ```javascript 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 ```http POST /v1.0/Tickets/query Content-Type: application/json ``` ```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 Filters combine with implicit AND. Use `"op": "or"` / `"op": "and"` with an `items` array to group or nest conditions: ```json { "filter": [ {"field": "companyID", "op": "eq", "value": 12345}, { "op": "or", "items": [ {"field": "priority", "op": "eq", "value": 1}, {"field": "status", "op": "eq", "value": 14} ] } ] } ``` See [references/api.md](references/api.md) for an AND-only example and a nested AND/OR example. ### Field Includes Retrieve related entity fields in a single request: ```json { "filter": [{"field": "id", "op": "gt", "value": 0}], "includeFields": [ "Company.companyName", "AssignedResource.firstName", "AssignedResource.lastName" ] } ``` See [references/api.md](references/api.md) for the response shape returned when includes are used. ## Pagination ### Request Pagination ```json { "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 ```json { "items": [...], "pageDetails": { "count": 100, "nextPageUrl": "/v1.0/Tickets/query?pageNumber=2", "prevPageUrl": null, "requestCount": 2847 } } ``` Page through results by looping while `pageDetails.nextPageUrl` is non-null. See [references/examples.md](references/examples.md) for a full pagination loop implementation. ## 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 released as slots free up, so you won't see hard failures, but responses may be slower under load. **Multi-user / shared key risk**: The 3-thread limit applies per `integrationCode`. If multiple users or teams share the same credentials, they compete for the same 3 slots. In a team deployment, give each team their own API user: ``` Support Team → integrationCode: SUPPORT_TEAM_CODE (3 threads, independent) Projects Team → integrationCode: PROJECTS_TEAM_CODE (3 threads, independent) ``` ### Rate Limit Response When the concurrent thread limit or hourly request limit is exceeded (HTTP 429): ```json { "errors": [ { "message": "Rate limit exceeded. Try again in 30 seconds." } ] } ``` Implement exponential backoff with jitter on 429 responses, honoring `Retry-After` when present. See [references/examples.md](references/examples.md) for a retry-with-backoff implementation. ### Query Different Entity Types in Parallel To maximize throughput without hitting the per-endpoint thread limit, query **different endpoints in parallel** rather than the same endpoint multiple times — parallel requests to Tickets + Companies + Contacts each get their own 3-thread budget, while parallel requests to multiple pages of the same endpoint share one budget and queue. For bulk write operations, batch requests in groups (e.g. 50 at a time) with a short delay between batches to avoid the hourly limit. See [references/examples.md](references/examples.md) for parallel-query and batch-processing code. ## Error Handling ### HTTP Status Codes | Code | Meaning | Action | |------|---------|--------| | 200 | Success | Process response | | 201 | Created | Entity created successfully | | 400 | Bad Request | Check request format/values | | 401 | Unauthorized | Verify credentials | | 403 | Forbidden | Check permissions | | 404 | Not Found | Entity doesn't exist | | 409 | Conflict | Resource locked/modified | | 429 | Rate Limited | Implement backoff | | 500 | Server Error | Retry with backoff | ### Error Response Format ```json { "errors": [ { "message": "The value '999' is not valid for field 'status'.", "field": "status", "value": 999 } ] } ``` When a field-level error is returned, cross-reference the field against `/v1.0/<Entity>/entityInformation/fields` (or `/v1.0/Queues` for queue IDs) to find valid values. See [references/examples.md](references/examples.md) for a full validation-error handler. ## Entity Information Query field definitions and picklist values before writing to an unfamiliar entity: ```http GET /v1.0/Tickets/entityInformation/fields GET /v1.0/Tickets/entityInformation/userDefinedFields ``` See [references/api.md](references/api.md) for the full response shape and CRUD (create/read/update/replace/delete) request examples. ## Performance Optimization ### Select Only Needed Fields ```json { "filter": [{"field": "id", "op": "gt", "value": 0}], "fields": ["id", "title", "status", "priority"] } ``` ### Use Efficient Filters **Good - Uses indexed field:** ```json {"field": "companyID", "op": "eq", "value": 12345} ``` **Avoid - Full text search:** ```json {"field": "description", "op": "contains", "value": "error"} ``` ### Cache Reference Data Cache slowly-changing data — Queues, Resources, Issue Types, Priorities, Company lists — rather than re-fetching per request. See [references/examples.md](references/examples.md) for a TTL-based cache pattern. ## Best Practices 1. **Detect zone once** - Cache the zone URL after initial detection 2. **Use includes** - Avoid N+1 queries by including related data 3. **Paginate large results** - Never fetch unbounded result sets 4. **Implement retry logic** - Handle rate limits and transient errors 5. **Cache reference data** - Reduce API calls for static lookups 6. **Select specific fields** - Only request fields you need 7. **Use batch operations** - Group related operations together 8. **One API key per team** - Autotask limits 3 concurrent threads per `integrationCode`. Each team using the integration should have their own API user so they don't compete for the same thread budget 9. **Parallelize across endpoints, not within** - To maximize throughput, query Tickets + Companies + Contacts simultaneously (different endpoints, independent thread budgets) rather than fetching multiple pages of the same endpoint in parallel ## Related Skills - [Autotask Tickets](../tickets/SKILL.md) - Ticket management - [Autotask CRM](../crm/SKILL.md) - Company and contact management - [Autotask Contracts](../contracts/SKILL.md) - Service agreements
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.