{"slug":"autotask-api-patterns","title":"Autotask API Patterns","summary":"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.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-21T18:26:34.735557Z","repo":{"url":"https://github.com/WYRE-AI/msp-claude-plugins","stars":46,"forks":26,"license":"Apache-2.0","updatedAt":"2026-09-21T16:51:26Z"},"bodyHtml":"<hr>\n<h2>name: \"Autotask API Patterns\"\ndescription: &gt;\nAutotask REST API fundamentals: header-based authentication, zone\ndetection, the query/filter DSL (14 operators, logical grouping,\nincludes), pagination, rate limits, and CRUD conventions across the\n215+ entity PSA.\nwhen_to_use: &gt;-\nWhen working with authentication, query building, pagination, includes, rate limiting, and error\nhandling in the Autotask REST API. Use when: autotask api, autotask query, autotask\nauthentication, api filter, query builder, autotask pagination, api rate limit, autotask zone,\napi error, or autotask rest.</h2>\n<h1>Autotask API Patterns</h1>\n<h2>Overview</h2>\n<p>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.</p>\n<h2>Anti-triggers</h2>\n<ul>\n<li><strong>Autotask tools missing, or present but refusing to run</strong> — a\nlazy-loaded MCP connection exposes only four meta-tools. That is a\ndiscovery problem, not an auth or query problem; use\n<code>autotask-tool-discovery</code>.</li>\n<li><strong>Instance-specific status, priority, queue, or phase IDs</strong> — the\nfilter DSL will happily match an ID that does not exist in your\ntenant and return nothing. Fetch the real values with\n<code>autotask-picklists</code>.</li>\n</ul>\n<h2>Authentication</h2>\n<h3>Header-Based Authentication</h3>\n<p>Autotask uses header-based authentication (NOT Basic Auth):</p>\n<pre><code>GET /v1.0/Tickets\nApiIntegrationCode: YOUR_INTEGRATION_CODE\nUserName: your-api-user@domain.com\nSecret: YOUR_SECRET\nContent-Type: application/json\n</code></pre>\n<p><strong>Required Headers:</strong></p>\n<table>\n<thead>\n<tr>\n<th>Header</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ApiIntegrationCode</code></td>\n<td>Your Autotask integration code</td>\n</tr>\n<tr>\n<td><code>UserName</code></td>\n<td>API username (email address)</td>\n</tr>\n<tr>\n<td><code>Secret</code></td>\n<td>API secret/password</td>\n</tr>\n<tr>\n<td><code>Content-Type</code></td>\n<td><code>application/json</code></td>\n</tr>\n</tbody>\n</table>\n<h3>Environment Variables</h3>\n<pre><code>export AUTOTASK_USERNAME=\"your-api-user@domain.com\"\nexport AUTOTASK_INTEGRATION_CODE=\"YOUR_INTEGRATION_CODE\"\nexport AUTOTASK_SECRET=\"YOUR_SECRET\"\n</code></pre>\n<h3>Automatic Zone Detection</h3>\n<p>Autotask operates in multiple zones. The API can automatically detect your zone:</p>\n<pre><code>GET https://webservices.autotask.net/atservicesrest/v1.0/ZoneInformation\nUserName: your-api-user@domain.com\n</code></pre>\n<p><strong>Response:</strong></p>\n<pre><code>{\n  \"url\": \"https://webservices5.autotask.net/atservicesrest\",\n  \"webUrl\": \"https://ww5.autotask.net\"\n}\n</code></pre>\n<p><strong>Common Zones:</strong></p>\n<table>\n<thead>\n<tr>\n<th>Zone</th>\n<th>API URL</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>webservices</td>\n<td><code>https://webservices.autotask.net/atservicesrest</code></td>\n</tr>\n<tr>\n<td>webservices1</td>\n<td><code>https://webservices1.autotask.net/atservicesrest</code></td>\n</tr>\n<tr>\n<td>webservices2</td>\n<td><code>https://webservices2.autotask.net/atservicesrest</code></td>\n</tr>\n<tr>\n<td>webservices5</td>\n<td><code>https://webservices5.autotask.net/atservicesrest</code></td>\n</tr>\n<tr>\n<td>webservices6</td>\n<td><code>https://webservices6.autotask.net/atservicesrest</code></td>\n</tr>\n</tbody>\n</table>\n<h2>Query Builder</h2>\n<h3>Query Operators</h3>\n<p>The Autotask API supports 14 query operators:</p>\n<table>\n<thead>\n<tr>\n<th>Operator</th>\n<th>Description</th>\n<th>Example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>eq</code></td>\n<td>Equals</td>\n<td><code>{\"field\": \"status\", \"op\": \"eq\", \"value\": 1}</code></td>\n</tr>\n<tr>\n<td><code>ne</code> / <code>noteq</code></td>\n<td>Not equals</td>\n<td><code>{\"field\": \"status\", \"op\": \"noteq\", \"value\": 5}</code></td>\n</tr>\n<tr>\n<td><code>gt</code></td>\n<td>Greater than</td>\n<td><code>{\"field\": \"priority\", \"op\": \"gt\", \"value\": 2}</code></td>\n</tr>\n<tr>\n<td><code>gte</code></td>\n<td>Greater than or equal</td>\n<td><code>{\"field\": \"createDate\", \"op\": \"gte\", \"value\": \"2024-01-01\"}</code></td>\n</tr>\n<tr>\n<td><code>lt</code></td>\n<td>Less than</td>\n<td><code>{\"field\": \"priority\", \"op\": \"lt\", \"value\": 3}</code></td>\n</tr>\n<tr>\n<td><code>lte</code></td>\n<td>Less than or equal</td>\n<td><code>{\"field\": \"dueDateTime\", \"op\": \"lte\", \"value\": \"2024-02-15T17:00:00Z\"}</code></td>\n</tr>\n<tr>\n<td><code>contains</code></td>\n<td>Contains substring</td>\n<td><code>{\"field\": \"title\", \"op\": \"contains\", \"value\": \"email\"}</code></td>\n</tr>\n<tr>\n<td><code>startsWith</code></td>\n<td>Starts with</td>\n<td><code>{\"field\": \"companyName\", \"op\": \"startsWith\", \"value\": \"Acme\"}</code></td>\n</tr>\n<tr>\n<td><code>endsWith</code></td>\n<td>Ends with</td>\n<td><code>{\"field\": \"email\", \"op\": \"endsWith\", \"value\": \"@acme.com\"}</code></td>\n</tr>\n<tr>\n<td><code>in</code></td>\n<td>In array</td>\n<td><code>{\"field\": \"status\", \"op\": \"in\", \"value\": [1, 2, 5]}</code></td>\n</tr>\n<tr>\n<td><code>notIn</code></td>\n<td>Not in array</td>\n<td><code>{\"field\": \"status\", \"op\": \"notIn\", \"value\": [5, 10]}</code></td>\n</tr>\n<tr>\n<td><code>isNull</code></td>\n<td>Is null</td>\n<td><code>{\"field\": \"assignedResourceId\", \"op\": \"isNull\"}</code></td>\n</tr>\n<tr>\n<td><code>isNotNull</code></td>\n<td>Is not null</td>\n<td><code>{\"field\": \"dueDateTime\", \"op\": \"isNotNull\"}</code></td>\n</tr>\n<tr>\n<td><code>between</code></td>\n<td>Between range</td>\n<td><code>{\"field\": \"createDate\", \"op\": \"between\", \"value\": [\"2024-01-01\", \"2024-01-31\"]}</code></td>\n</tr>\n</tbody>\n</table>\n<h3>Date Filtering: \"Today\" Queries</h3>\n<p><strong>CRITICAL:</strong> To filter for records from \"today\", you must use a <strong>range</strong> — <code>gte</code> today's date AND <code>lt</code> tomorrow's date. Using only today's date (e.g. <code>eq</code> or a single filter on today) returns <strong>zero results</strong>. \"Today\" in Autotask means: created on or after today's midnight, but before tomorrow's midnight.</p>\n<p><strong>Correct — \"tickets created today\":</strong></p>\n<pre><code>{\n  \"filter\": [\n    {\"field\": \"createDate\", \"op\": \"gte\", \"value\": \"2026-04-13T00:00:00Z\"},\n    {\"field\": \"createDate\", \"op\": \"lt\", \"value\": \"2026-04-14T00:00:00Z\"}\n  ]\n}\n</code></pre>\n<p><strong>Wrong — returns NO results:</strong></p>\n<pre><code>{\n  \"filter\": [\n    {\"field\": \"createDate\", \"op\": \"eq\", \"value\": \"2026-04-13\"}\n  ]\n}\n</code></pre>\n<p>Always compute \"tomorrow\" dynamically. For example:</p>\n<pre><code>const today = new Date();\ntoday.setHours(0, 0, 0, 0);\nconst tomorrow = new Date(today);\ntomorrow.setDate(tomorrow.getDate() + 1);\n\nconst todayISO = today.toISOString(); // \"2026-04-13T00:00:00.000Z\"\nconst tomorrowISO = tomorrow.toISOString(); // \"2026-04-14T00:00:00.000Z\"\n</code></pre>\n<p>This pattern applies to <strong>all datetime fields</strong> (<code>createDate</code>, <code>lastActivityDate</code>, <code>dueDateTime</code>, <code>startDateTime</code>, <code>endDateTime</code>, <code>dateWorked</code>, etc.) across all entities — not just tickets.</p>\n<h3>Query Structure</h3>\n<pre><code>POST /v1.0/Tickets/query\nContent-Type: application/json\n</code></pre>\n<pre><code>{\n  \"filter\": [\n    {\"field\": \"companyID\", \"op\": \"eq\", \"value\": 12345},\n    {\"field\": \"status\", \"op\": \"noteq\", \"value\": 5}\n  ],\n  \"maxRecords\": 50,\n  \"includeFields\": [\"Company.companyName\", \"AssignedResource.firstName\"]\n}\n</code></pre>\n<h3>Complex Queries with Logical Grouping</h3>\n<p>Filters combine with implicit AND. Use <code>\"op\": \"or\"</code> / <code>\"op\": \"and\"</code> with an <code>items</code> array to group or nest conditions:</p>\n<pre><code>{\n  \"filter\": [\n    {\"field\": \"companyID\", \"op\": \"eq\", \"value\": 12345},\n    {\n      \"op\": \"or\",\n      \"items\": [\n        {\"field\": \"priority\", \"op\": \"eq\", \"value\": 1},\n        {\"field\": \"status\", \"op\": \"eq\", \"value\": 14}\n      ]\n    }\n  ]\n}\n</code></pre>\n<p>See <a href=\"references/api.md\">references/api.md</a> for an AND-only example and a nested AND/OR example.</p>\n<h3>Field Includes</h3>\n<p>Retrieve related entity fields in a single request:</p>\n<pre><code>{\n  \"filter\": [{\"field\": \"id\", \"op\": \"gt\", \"value\": 0}],\n  \"includeFields\": [\n    \"Company.companyName\",\n    \"AssignedResource.firstName\",\n    \"AssignedResource.lastName\"\n  ]\n}\n</code></pre>\n<p>See <a href=\"references/api.md\">references/api.md</a> for the response shape returned when includes are used.</p>\n<h2>Pagination</h2>\n<h3>Request Pagination</h3>\n<pre><code>{\n  \"filter\": [{\"field\": \"id\", \"op\": \"gt\", \"value\": 0}],\n  \"maxRecords\": 100,\n  \"pageNumber\": 1\n}\n</code></pre>\n<p><strong>Pagination Fields:</strong></p>\n<table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Description</th>\n<th>Max</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>maxRecords</code></td>\n<td>Records per page</td>\n<td>500</td>\n</tr>\n<tr>\n<td><code>pageNumber</code></td>\n<td>Current page (1-based)</td>\n<td>-</td>\n</tr>\n</tbody>\n</table>\n<h3>Response Structure</h3>\n<pre><code>{\n  \"items\": [...],\n  \"pageDetails\": {\n    \"count\": 100,\n    \"nextPageUrl\": \"/v1.0/Tickets/query?pageNumber=2\",\n    \"prevPageUrl\": null,\n    \"requestCount\": 2847\n  }\n}\n</code></pre>\n<p>Page through results by looping while <code>pageDetails.nextPageUrl</code> is non-null. See <a href=\"references/examples.md\">references/examples.md</a> for a full pagination loop implementation.</p>\n<h2>Rate Limiting</h2>\n<h3>Autotask API Hard Limits</h3>\n<p>Autotask enforces two hard limits:</p>\n<table>\n<thead>\n<tr>\n<th>Limit</th>\n<th>Value</th>\n<th>Scope</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Concurrent threads per endpoint</strong></td>\n<td><strong>3</strong></td>\n<td>Per API tracking identifier (your <code>integrationCode</code>)</td>\n</tr>\n<tr>\n<td><strong>Total requests per hour</strong></td>\n<td><strong>10,000</strong></td>\n<td>Per Autotask tenant database (all integrations combined)</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Concurrent thread limit</strong> 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.</p>\n<p>When using the MCP server or <code>autotask-node</code> 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.</p>\n<p><strong>Multi-user / shared key risk</strong>: The 3-thread limit applies per <code>integrationCode</code>. 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:</p>\n<pre><code>Support Team  → integrationCode: SUPPORT_TEAM_CODE  (3 threads, independent)\nProjects Team → integrationCode: PROJECTS_TEAM_CODE (3 threads, independent)\n</code></pre>\n<h3>Rate Limit Response</h3>\n<p>When the concurrent thread limit or hourly request limit is exceeded (HTTP 429):</p>\n<pre><code>{\n  \"errors\": [\n    {\n      \"message\": \"Rate limit exceeded. Try again in 30 seconds.\"\n    }\n  ]\n}\n</code></pre>\n<p>Implement exponential backoff with jitter on 429 responses, honoring <code>Retry-After</code> when present. See <a href=\"references/examples.md\">references/examples.md</a> for a retry-with-backoff implementation.</p>\n<h3>Query Different Entity Types in Parallel</h3>\n<p>To maximize throughput without hitting the per-endpoint thread limit, query <strong>different endpoints in parallel</strong> 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.</p>\n<p>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 <a href=\"references/examples.md\">references/examples.md</a> for parallel-query and batch-processing code.</p>\n<h2>Error Handling</h2>\n<h3>HTTP Status Codes</h3>\n<table>\n<thead>\n<tr>\n<th>Code</th>\n<th>Meaning</th>\n<th>Action</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>200</td>\n<td>Success</td>\n<td>Process response</td>\n</tr>\n<tr>\n<td>201</td>\n<td>Created</td>\n<td>Entity created successfully</td>\n</tr>\n<tr>\n<td>400</td>\n<td>Bad Request</td>\n<td>Check request format/values</td>\n</tr>\n<tr>\n<td>401</td>\n<td>Unauthorized</td>\n<td>Verify credentials</td>\n</tr>\n<tr>\n<td>403</td>\n<td>Forbidden</td>\n<td>Check permissions</td>\n</tr>\n<tr>\n<td>404</td>\n<td>Not Found</td>\n<td>Entity doesn't exist</td>\n</tr>\n<tr>\n<td>409</td>\n<td>Conflict</td>\n<td>Resource locked/modified</td>\n</tr>\n<tr>\n<td>429</td>\n<td>Rate Limited</td>\n<td>Implement backoff</td>\n</tr>\n<tr>\n<td>500</td>\n<td>Server Error</td>\n<td>Retry with backoff</td>\n</tr>\n</tbody>\n</table>\n<h3>Error Response Format</h3>\n<pre><code>{\n  \"errors\": [\n    {\n      \"message\": \"The value '999' is not valid for field 'status'.\",\n      \"field\": \"status\",\n      \"value\": 999\n    }\n  ]\n}\n</code></pre>\n<p>When a field-level error is returned, cross-reference the field against <code>/v1.0/&lt;Entity&gt;/entityInformation/fields</code> (or <code>/v1.0/Queues</code> for queue IDs) to find valid values. See <a href=\"references/examples.md\">references/examples.md</a> for a full validation-error handler.</p>\n<h2>Entity Information</h2>\n<p>Query field definitions and picklist values before writing to an unfamiliar entity:</p>\n<pre><code>GET /v1.0/Tickets/entityInformation/fields\nGET /v1.0/Tickets/entityInformation/userDefinedFields\n</code></pre>\n<p>See <a href=\"references/api.md\">references/api.md</a> for the full response shape and CRUD (create/read/update/replace/delete) request examples.</p>\n<h2>Performance Optimization</h2>\n<h3>Select Only Needed Fields</h3>\n<pre><code>{\n  \"filter\": [{\"field\": \"id\", \"op\": \"gt\", \"value\": 0}],\n  \"fields\": [\"id\", \"title\", \"status\", \"priority\"]\n}\n</code></pre>\n<h3>Use Efficient Filters</h3>\n<p><strong>Good - Uses indexed field:</strong></p>\n<pre><code>{\"field\": \"companyID\", \"op\": \"eq\", \"value\": 12345}\n</code></pre>\n<p><strong>Avoid - Full text search:</strong></p>\n<pre><code>{\"field\": \"description\", \"op\": \"contains\", \"value\": \"error\"}\n</code></pre>\n<h3>Cache Reference Data</h3>\n<p>Cache slowly-changing data — Queues, Resources, Issue Types, Priorities, Company lists — rather than re-fetching per request. See <a href=\"references/examples.md\">references/examples.md</a> for a TTL-based cache pattern.</p>\n<h2>Best Practices</h2>\n<ol>\n<li><strong>Detect zone once</strong> - Cache the zone URL after initial detection</li>\n<li><strong>Use includes</strong> - Avoid N+1 queries by including related data</li>\n<li><strong>Paginate large results</strong> - Never fetch unbounded result sets</li>\n<li><strong>Implement retry logic</strong> - Handle rate limits and transient errors</li>\n<li><strong>Cache reference data</strong> - Reduce API calls for static lookups</li>\n<li><strong>Select specific fields</strong> - Only request fields you need</li>\n<li><strong>Use batch operations</strong> - Group related operations together</li>\n<li><strong>One API key per team</strong> - Autotask limits 3 concurrent threads per <code>integrationCode</code>. Each team using the integration should have their own API user so they don't compete for the same thread budget</li>\n<li><strong>Parallelize across endpoints, not within</strong> - To maximize throughput, query Tickets + Companies + Contacts simultaneously (different endpoints, independent thread budgets) rather than fetching multiple pages of the same endpoint in parallel</li>\n</ol>\n<h2>Related Skills</h2>\n<ul>\n<li><a href=\"../tickets/SKILL.md\">Autotask Tickets</a> - Ticket management</li>\n<li><a href=\"../crm/SKILL.md\">Autotask CRM</a> - Company and contact management</li>\n<li><a href=\"../contracts/SKILL.md\">Autotask Contracts</a> - Service agreements</li>\n</ul>\n","files":[{"path":"references/api.md","sizeBytes":2668,"isText":true},{"path":"references/examples.md","sizeBytes":3576,"isText":true},{"path":"SKILL.md","sizeBytes":12652,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-21T18:26:58.807694Z","sha256":"C1A0C803DD26659487B9E5CFECD092FCAF8E20D698590683499D0C60301AA97D","sizeBytes":7355},"review":null,"source":{"repositoryUrl":"https://github.com/WYRE-AI/msp-claude-plugins","path":"msp-claude-plugins/kaseya/autotask/skills/api-patterns","license":"Apache-2.0","commit":"147da75a83127bb77f9551efdbf66adf62823c05","subtreeSha":"FBCFEB60833FECAA8BF79A2AE0824E4D97BA73DA49342625A8FDD1698771068B","lastSyncedAt":"2026-09-21T18:26:31.557491Z"},"reviewedAt":"2026-09-21T18:27:50.230212Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/WYRE-AI/msp-claude-plugins/tree/main/msp-claude-plugins/kaseya/autotask/skills/api-patterns"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wyre-ai-msp-claude-plugins@llmmart"},{"target":"git","command":"git clone https://github.com/WYRE-AI/msp-claude-plugins.git"}]}