# 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: 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**.