This commit consolidates all development work from the following branch series: - v20260203-* (13 branches): Initial Autotask integration, graph config UI improvements - v20260204-* (3 branches): Dashboard redirect setting, additional refinements - v20260205-* (13 branches): Autotask resolution improvements, changelog restructuring Key features merged: - Autotask PSA integration with ticket creation, resolution, and search - Graph/mail configuration UI improvements with credential testing - Daily dashboard redirect setting (optional navigation control) - Changelog restructuring with improved Python structure - Various bug fixes and UI enhancements All functionality has been consolidated from the final state of branch v20260205-13-changelog-python-structure to preserve working features. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
326 lines
7.3 KiB
Markdown
326 lines
7.3 KiB
Markdown
# Autotask REST API – Postman Test Contract
|
||
|
||
## Reference Sources
|
||
|
||
Primary external reference used during testing:
|
||
- https://github.com/AutotaskDevelopment/REST-Postman/blob/main/2020.10.29%20-%20Autotask%20Collecction%20-%20Must%20fill%20in%20variables%20on%20collection.postman_collection.json
|
||
|
||
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: <TicketNumber>
|
||
|
||
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: <TicketID>
|
||
|
||
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**.
|