backupchecks/docs/autotask_rest_api.md
Ivo Oskamp a9a60e3315 Restore Autotask PSA integration
Restored from branch v20260203-13-autotask-resolution-item-wrapper with
the following functionality:

- Autotask REST API client with support for tickets, notes, companies,
  and reference data (queues, sources, priorities, statuses)
- Safe ticket updates preserving stabilizing fields per API contract
- Database models for Autotask settings, customer mapping, and ticket linkage
- Settings UI for Autotask configuration with connection test
- Run Checks integration for ticket creation
- Customers page with Autotask company mapping
- Ticket linking during mail import

Also preserved the require_daily_dashboard_visit setting from the
current branch.

Added docs/autotask_rest_api.md with validated API contract from Postman tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 14:43:44 +01:00

7.3 KiB
Raw Blame History

Autotask REST API Postman Test Contract

Reference Sources

Primary external reference used during testing:

This document combines:

  • Empirically validated Postman test results
  • Official Autotask Postman collection references (where applicable)

Purpose

This document captures validated Autotask REST API behaviour based on hands-on Postman testing.

This is an authoritative test contract:

  • General-purpose
  • Product-agnostic
  • Based on proven results, not assumptions

If implementation deviates from this document, the document is correct and the code is wrong.


0. Base URLs

Sandbox

https://webservices19.autotask.net/ATServicesRest/V1.0

Production

https://webservices19.autotask.net/ATServicesRest/V1.0

Notes:

  • ATServicesRest is case-sensitive
  • Other casing variants are invalid

0.1 Global Invariants (Do Not Violate)

  • TicketID (numeric) is the only authoritative identifier for single-ticket operations
  • TicketNumber is display-only and must be resolved to TicketID first
  • PATCH is not supported for Tickets
  • PUT /Tickets is always a full update, never partial
  • Never guess fields or values

1. Ticket Lookup

Resolve TicketNumber → TicketID

Endpoint: POST /Tickets/query

Filter:

  • field: ticketNumber
  • op: eq
  • value:

Result:

  • items[0].id → TicketID

2. Authoritative Ticket Retrieval

Endpoint: GET /Tickets/{TicketID}

Always required before any update.

2.1 Response Envelope (Critical)

Validated response shape for single-ticket retrieval:

  • The ticket object is returned under the item wrapper

Example shape:

  • issueType is at item.issueType
  • subIssueType is at item.subIssueType
  • source is at item.source

Implementation rule:

  • Always read stabilising fields from item.* in the GET response.
  • Do not read these fields from the response root.

Note:

  • PUT payloads are not wrapped in item. They use the plain ticket object fields at the request body root.

Commonly required stabilising fields:

  • id
  • ticketNumber
  • companyID
  • queueID
  • title
  • priority
  • status
  • dueDateTime
  • ticketCategory
  • issueType
  • subIssueType
  • source
  • organizationalLevelAssociationID
  • completedDate
  • resolvedDateTime
  • lastTrackedModificationDateTime

3. Ticket Status Picklist (ID → Label)

Endpoint: GET /Tickets/entityInformation/fields

Validated behaviour:

  • status is an integer picklist
  • Picklist values (ID → label) are returned inline
  • Status IDs and semantics are tenant-dependent

Do not assume lifecycle meaning based on label alone.


4. Ticket Update Behaviour

PATCH

  • Not supported
  • Returns error indicating unsupported HTTP method

PUT /Tickets

Validated behaviour:

  • Full update required
  • Missing fields cause hard failures
  • Partial updates are rejected

Implementation rule:

  • Always copy required fields from a fresh GET
  • Change only the intended field(s)

5. Status Semantics (Validated Example)

Observed in test tenant:

  • Status = 8 (label: "Completed")

    • Status updates
    • completedDate = null
    • resolvedDateTime = null
  • Status = 5 (label: "Complete")

    • Status updates
    • completedDate populated
    • resolvedDateTime populated

Conclusion:

  • Resolution timestamps depend on status ID, not label
  • Validate per tenant before relying on timestamps

6. Time Entry Existence Check (Decisive)

Entity: TimeEntries

Endpoint: POST /TimeEntries/query

Filter:

  • field: ticketID
  • op: eq
  • value:

Decision:

  • count = 0 → no time entries
  • count > 0 → time entries exist

7. Ticket Notes via REST Capability vs Endpoint Reality

Although entityInformation reports TicketNote as creatable, entity-level create does not work.

Non-working endpoints

  • POST /TicketNotes → 404
  • POST /TicketNote → 404

Working endpoint (only supported method)

POST /Tickets/{TicketID}/Notes

Required fields:

  • Title
  • Description
  • NoteType
  • Publish

Result:

  • Note is created and immediately visible

Query endpoint works:

  • POST /TicketNotes/query

8. Resolution Field Update (Validated Test)

Test scope

This section documents the explicit Postman tests performed to validate how the resolution field can be updated safely.

Field characteristics (validated)

  • name: resolution
  • dataType: string
  • max length: 32000
  • isReadOnly: false

Critical constraint (proven)

Because PATCH is not supported and PUT /Tickets is a full update, the resolution field cannot be updated in isolation.

Sending an incomplete payload results in unintended changes to:

  • classification
  • routing
  • status
  • organizational structure

Validated update pattern

  1. Retrieve current ticket state

    • GET /Tickets/{TicketID}
    • Read fields from item.* (see section 2.1)
  2. Construct PUT payload by copying current values of stabilising fields (explicitly validated in tests):

    • id
    • companyID
    • queueID
    • title
    • priority
    • status
    • dueDateTime
    • ticketCategory
    • issueType
    • subIssueType
    • source
    • organizationalLevelAssociationID
  3. Change only the resolution field

  4. Execute update

    • PUT /Tickets

Test result

  • Resolution text becomes visible in the Autotask UI
  • No unintended changes occur

This behaviour was reproduced consistently and is considered authoritative.


9. Ticket Resolution Workflow (Validated Tests)

This section captures the end-to-end resolution tests performed via Postman.

Test 1 Resolution without status change

Steps:

  1. GET /Tickets/{TicketID}
  2. POST /Tickets/{TicketID}/Notes
  3. PUT /Tickets (update resolution only)

Result:

  • Resolution text is updated
  • Ticket status remains unchanged
  • completedDate and resolvedDateTime remain null

Conclusion:

  • Resolution text alone does not resolve a ticket

Test 2 Conditional resolution based on time entries

Steps:

  1. GET /Tickets/{TicketID}
  2. POST /Tickets/{TicketID}/Notes
  3. PUT /Tickets (update resolution)
  4. POST /TimeEntries/query (filter by ticketID)

Decision logic (validated):

  • If no time entries exist:

    • PUT /Tickets with status = 5
    • completedDate is set
    • resolvedDateTime is set
  • If time entries exist:

    • Status is NOT changed
    • Ticket remains open in Autotask

Key conclusions

  • Notes, resolution, and status are independent operations
  • Status 5 is the only validated status that sets resolution timestamps
  • Status changes must always be explicit and conditional

Non-Negotiable Implementation Rules

  • Always GET before PUT
  • Never guess stabilising fields
  • Never use PATCH
  • Never change status implicitly
  • Notes and resolution must be explicit

10. API Contract Summary

Hard rules for code:

  • Always resolve TicketNumber → TicketID via POST /Tickets/query
  • Always GET /Tickets/{TicketID} before any update
  • Never attempt PATCH for Ticket updates
  • Use PUT /Tickets with a full, stabilised payload
  • Validate per tenant which status values set completedDate/resolvedDateTime
  • Check time entries via POST /TimeEntries/query when status decisions depend on them
  • Create ticket notes only via /Tickets/{TicketID}/Notes

If code deviates from this document, the document is correct and the code is wrong.