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>
7.3 KiB
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:
ATServicesRestis 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
itemwrapper
Example shape:
issueTypeis atitem.issueTypesubIssueTypeis atitem.subIssueTypesourceis atitem.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:
statusis 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
-
Retrieve current ticket state
- GET /Tickets/{TicketID}
- Read fields from
item.*(see section 2.1)
-
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
-
Change only the
resolutionfield -
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:
- GET /Tickets/{TicketID}
- POST /Tickets/{TicketID}/Notes
- PUT /Tickets (update
resolutiononly)
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:
- GET /Tickets/{TicketID}
- POST /Tickets/{TicketID}/Notes
- PUT /Tickets (update
resolution) - 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.