AgentSkillsCN

Api Patterns

当您需要对接Autotask REST API时,可使用此技能:掌握身份认证、查询语句构建、分页查询、包含字段、速率限制与错误处理等核心功能。本技能全面覆盖14种查询运算符,支持区域检测、头部认证、重试策略,以及API集成的最佳实践。

SKILL.md
--- frontmatter
description: >
  Use this skill when working with the Autotask REST API - authentication,
  query building, pagination, includes, rate limiting, and error handling.
  Covers all 14 query operators, zone detection, header authentication,
  retry strategies, and best practices for API integration.
triggers:
  - autotask api
  - autotask query
  - autotask authentication
  - api filter
  - query builder
  - autotask pagination
  - api rate limit
  - autotask zone
  - api error
  - 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.

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:

HeaderDescription
ApiIntegrationCodeYour Autotask integration code
UserNameAPI username (email address)
SecretAPI secret/password
Content-Typeapplication/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:

ZoneAPI URL
webserviceshttps://webservices.autotask.net/atservicesrest
webservices1https://webservices1.autotask.net/atservicesrest
webservices2https://webservices2.autotask.net/atservicesrest
webservices5https://webservices5.autotask.net/atservicesrest
webservices6https://webservices6.autotask.net/atservicesrest

Query Builder

Query Operators

The Autotask API supports 14 query operators:

OperatorDescriptionExample
eqEquals{"field": "status", "op": "eq", "value": 1}
ne / noteqNot equals{"field": "status", "op": "noteq", "value": 5}
gtGreater than{"field": "priority", "op": "gt", "value": 2}
gteGreater than or equal{"field": "createDate", "op": "gte", "value": "2024-01-01"}
ltLess than{"field": "priority", "op": "lt", "value": 3}
lteLess than or equal{"field": "dueDateTime", "op": "lte", "value": "2024-02-15T17:00:00Z"}
containsContains substring{"field": "title", "op": "contains", "value": "email"}
startsWithStarts with{"field": "companyName", "op": "startsWith", "value": "Acme"}
endsWithEnds with{"field": "email", "op": "endsWith", "value": "@acme.com"}
inIn array{"field": "status", "op": "in", "value": [1, 2, 5]}
notInNot in array{"field": "status", "op": "notIn", "value": [5, 10]}
isNullIs null{"field": "assignedResourceId", "op": "isNull"}
isNotNullIs not null{"field": "dueDateTime", "op": "isNotNull"}
betweenBetween range{"field": "createDate", "op": "between", "value": ["2024-01-01", "2024-01-31"]}

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

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]}
  ]
}

OR conditions with items array:

json
{
  "filter": [
    {
      "op": "or",
      "items": [
        {"field": "priority", "op": "eq", "value": 1},
        {"field": "status", "op": "eq", "value": 14}
      ]
    }
  ]
}

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

Retrieve related entity fields in a single 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"
    }
  ]
}

Pagination

Request Pagination

json
{
  "filter": [{"field": "id", "op": "gt", "value": 0}],
  "maxRecords": 100,
  "pageNumber": 1
}

Pagination Fields:

FieldDescriptionMax
maxRecordsRecords per page500
pageNumberCurrent page (1-based)-

Response Structure

json
{
  "items": [...],
  "pageDetails": {
    "count": 100,
    "nextPageUrl": "/v1.0/Tickets/query?pageNumber=2",
    "prevPageUrl": null,
    "requestCount": 2847
  }
}

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;
}

Rate Limiting

Rate Limit Headers

Autotask returns rate limit information in response headers:

HeaderDescription
X-RateLimit-LimitMax requests per minute
X-RateLimit-RemainingRemaining requests
X-RateLimit-ResetSeconds until reset

Rate Limit Response

When rate limited (HTTP 429):

json
{
  "errors": [
    {
      "message": "Rate limit exceeded. Try again in 30 seconds."
    }
  ]
}

Retry Strategy

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);
    }
  }
}

Batch Processing

For bulk operations, batch requests to avoid rate limits:

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;
}

Error Handling

HTTP Status Codes

CodeMeaningAction
200SuccessProcess response
201CreatedEntity created successfully
400Bad RequestCheck request format/values
401UnauthorizedVerify credentials
403ForbiddenCheck permissions
404Not FoundEntity doesn't exist
409ConflictResource locked/modified
429Rate LimitedImplement backoff
500Server ErrorRetry with backoff

Error Response Format

json
{
  "errors": [
    {
      "message": "The value '999' is not valid for field 'status'.",
      "field": "status",
      "value": 999
    }
  ]
}

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');
      }
    }
  });
}

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.

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
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;
}

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. Monitor rate limits - Track remaining requests
  9. Log API calls - Enable debugging and audit trails
  10. Validate before sending - Check required fields client-side

Related Skills