feat: ConnectWise PSA integration (#106)
PSA abstraction layer with provider pattern, ConnectWise integration (connection management, ticket linking, note posting, status updates, member mapping), Integrations page UI, Fernet credential encryption, in-memory TTL cache, 6 DB migrations, ConnectWise API reference docs.
This commit was merged in pull request #106.
This commit is contained in:
303215
docs/connectwise/All.json
Normal file
303215
docs/connectwise/All.json
Normal file
File diff suppressed because it is too large
Load Diff
267
docs/connectwise/CONNECTWISE-API-REFERENCE.md
Normal file
267
docs/connectwise/CONNECTWISE-API-REFERENCE.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# ConnectWise PSA API — Integration Quick Reference
|
||||
## For ResolutionFlow Development (Claude Code)
|
||||
|
||||
> **Source:** ConnectWise PSA OpenAPI Spec v2025.16
|
||||
> **Full extracted spec:** `docs/connectwise/connectwise-psa-resolutionflow-reference.json`
|
||||
> **Full original spec:** `docs/connectwise/All.json` (7.6MB, 1838 endpoints, 842 schemas)
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Method:** API Key (Public/Private key pair per API Member)
|
||||
- **Headers required on every request:**
|
||||
- `Authorization: Basic {base64(companyId+publicKey:privateKey)}`
|
||||
- `clientId: {your_connectwise_client_id}` (assigned via developer program)
|
||||
- **Accept header:** `application/vnd.connectwise.com+json; version=2025.16`
|
||||
- **Base URL pattern:** `https://{site}/v4_6_release/apis/3.0`
|
||||
- **Date format:** ISO 8601 `yyyy-MM-ddTHH:mm:ssZ`
|
||||
- **SSL required** on production servers
|
||||
|
||||
## Pagination & Query
|
||||
|
||||
- Default page size: 25, max: 1000
|
||||
- Query params: `conditions`, `orderBy`, `fields`, `page`, `pageSize`, `childConditions`, `customFieldConditions`
|
||||
- Condition syntax: `fieldName="value"`, supports `AND`, `OR`, `like`, `contains`, `>`, `<`, `!=`
|
||||
- No documented hard rate limits (design respectfully)
|
||||
|
||||
---
|
||||
|
||||
## TIER 1 — Core Integration
|
||||
|
||||
### Service Tickets (`/service/tickets`)
|
||||
**The central entity. 120 fields. 33 endpoints.**
|
||||
|
||||
Key endpoints:
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `/service/tickets` | List tickets (with conditions filter) |
|
||||
| POST | `/service/tickets` | Create a ticket |
|
||||
| GET | `/service/tickets/{id}` | Get single ticket |
|
||||
| PATCH | `/service/tickets/{id}` | Update ticket fields |
|
||||
| GET | `/service/tickets/{parentId}/notes` | List ticket notes |
|
||||
| POST | `/service/tickets/{parentId}/notes` | **Add note to ticket** ⭐ |
|
||||
| GET | `/service/tickets/{parentId}/configurations` | Get attached devices |
|
||||
| POST | `/service/tickets/{parentId}/configurations` | Attach a device |
|
||||
| GET | `/service/tickets/{parentId}/tasks` | Get ticket tasks |
|
||||
| POST | `/service/tickets/{parentId}/tasks` | Add task to ticket |
|
||||
| GET | `/service/tickets/{parentId}/allNotes` | Get ALL note types |
|
||||
| POST | `/service/tickets/search` | Advanced search |
|
||||
| GET | `/service/tickets/calculateSla` | Get SLA times |
|
||||
| POST | `/service/tickets/{parentId}/merge` | Merge tickets |
|
||||
| POST | `/service/tickets/{id}/copy` | Copy a ticket |
|
||||
| POST | `/service/tickets/{parentId}/convert` | Convert to project |
|
||||
|
||||
Key Ticket fields for ResolutionFlow:
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `id` | integer | |
|
||||
| `summary` | string | Max length: 100; |
|
||||
| `board` | BoardReference | |
|
||||
| `status` | ServiceStatusReference | |
|
||||
| `company` | CompanyReference | |
|
||||
| `contact` | ContactReference | |
|
||||
| `contactName` | string | Max length: 62; |
|
||||
| `contactEmailAddress` | string | Max length: 250; |
|
||||
| `site` | SiteReference | |
|
||||
| `type` | ServiceTypeReference | |
|
||||
| `subType` | ServiceSubTypeReference | |
|
||||
| `item` | ServiceItemReference | |
|
||||
| `team` | ServiceTeamReference | |
|
||||
| `owner` | MemberReference | |
|
||||
| `priority` | PriorityReference | |
|
||||
| `severity` | string | Required On Updates; |
|
||||
| `impact` | string | Required On Updates; |
|
||||
| `source` | ServiceSourceReference | |
|
||||
| `sla` | SLAReference | |
|
||||
| `slaStatus` | string | |
|
||||
| `isInSla` | boolean | |
|
||||
| `agreement` | AgreementReference | |
|
||||
| `initialDescription` | string | Only available for POST, will not be returned in the response. |
|
||||
| `initialInternalAnalysis` | string | Only available for POST, will not be returned in the response. |
|
||||
| `initialResolution` | string | Only available for POST, will not be returned in the response. |
|
||||
| `closedFlag` | boolean | |
|
||||
| `closedDate` | string | |
|
||||
| `closedBy` | string | |
|
||||
| `dateResolved` | string | |
|
||||
| `dateResponded` | string | |
|
||||
| `actualHours` | number | |
|
||||
| `budgetHours` | number | |
|
||||
| `resources` | string | |
|
||||
| `parentTicketId` | integer | |
|
||||
| `externalXRef` | string | Max length: 100; |
|
||||
| `knowledgeBaseLinkId` | integer | |
|
||||
| `customFields` | array | |
|
||||
| `processNotifications` | boolean | Can be set to false to skip notification processing when adding or updating a... |
|
||||
| `skipCallback` | boolean | |
|
||||
| `recordType` | string | |
|
||||
| `respondMinutes` | integer | To obtain the current SLA times for an active ticket, please use the /service... |
|
||||
| `resPlanMinutes` | integer | To obtain the current SLA times for an active ticket, please use the /service... |
|
||||
| `resolveMinutes` | integer | To obtain the current SLA times for an active ticket, please use the /service... |
|
||||
|
||||
### ServiceNote / TicketNote Schema
|
||||
**The schema for notes added to tickets. This is where session documentation lands.**
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| `id` | integer | Note ID |
|
||||
| `ticketId` | integer | Parent ticket |
|
||||
| `text` | string | **Note content — session documentation goes here** |
|
||||
| `detailDescriptionFlag` | boolean | Mark as "Description" note |
|
||||
| `internalAnalysisFlag` | boolean | Mark as "Internal Analysis" note ⭐ |
|
||||
| `resolutionFlag` | boolean | Mark as "Resolution" note ⭐ |
|
||||
| `issueFlag` | boolean | Mark as issue note |
|
||||
| `internalFlag` | boolean | Internal-only (not visible to customer) |
|
||||
| `externalFlag` | boolean | Visible to customer |
|
||||
| `member` | MemberReference | Who wrote the note |
|
||||
| `contact` | ContactReference | Contact associated |
|
||||
| `processNotifications` | boolean | Trigger notification workflows |
|
||||
| `dateCreated` | string | When created |
|
||||
| `createdBy` | string | Creator |
|
||||
| `sentimentScore` | number | AI sentiment (ServiceNote only) |
|
||||
|
||||
**ResolutionFlow mapping:** Post session documentation as an **internal analysis note** (`internalAnalysisFlag: true, internalFlag: true`), and optionally post a resolution summary (`resolutionFlag: true`) when the session resolves the issue.
|
||||
|
||||
### Companies (`/company/companies`)
|
||||
**72 fields. Client/customer context for sessions.**
|
||||
|
||||
Key fields: `id`, `identifier`, `name`, `status`, `addressLine1`, `city`, `state`, `zip`, `phoneNumber`, `website`, `territory`, `market`, `defaultContact`, `parentCompany`, `customFields`
|
||||
|
||||
Key endpoints:
|
||||
- `GET /company/companies` — List/search companies
|
||||
- `GET /company/companies/{id}` — Get company detail
|
||||
- `GET /company/companies/{parentId}/sites` — Get company sites
|
||||
- `GET /company/companies/{parentId}/notes` — Get company notes
|
||||
- `GET /company/companies/{parentId}/groups` — Get company groups
|
||||
- `GET /company/companies/{parentId}/teams` — Get company teams
|
||||
|
||||
### Contacts (`/company/contacts`)
|
||||
**62 fields. The person reporting the issue.**
|
||||
|
||||
Key fields: `id`, `firstName`, `lastName`, `company`, `site`, `title`, `department`, `inactiveFlag`, `defaultPhoneType`, `defaultBillingFlag`, `communicationItems`
|
||||
|
||||
### Configurations / Assets (`/company/configurations`)
|
||||
**58 fields. Devices and assets — critical MSP context.**
|
||||
|
||||
Key fields: `id`, `name`, `type`, `status`, `company`, `contact`, `site`, `deviceIdentifier`, `serialNumber`, `modelNumber`, `tagNumber`, `purchaseDate`, `installationDate`, `warrantyExpirationDate`, `vendorNotes`, `osType`, `osInfo`, `cpuSpeed`, `ram`, `lastBackupDate`, `ipAddress`, `macAddress`, `lastLoginName`, `customFields`
|
||||
|
||||
### Boards & Statuses (`/service/boards`)
|
||||
**65-field Board model. 23-field BoardStatus model. 63 endpoints.**
|
||||
|
||||
Boards define service desk structure. Each board has statuses, types, subtypes, items, teams, notifications.
|
||||
|
||||
Key endpoints:
|
||||
- `GET /service/boards` — List all boards
|
||||
- `GET /service/boards/{id}` — Get board detail
|
||||
- `GET /service/boards/{parentId}/statuses` — Get board statuses
|
||||
- `GET /service/boards/{parentId}/types` — Get ticket types for board
|
||||
- `GET /service/boards/{parentId}/subtypes` — Get subtypes
|
||||
- `GET /service/boards/{parentId}/items` — Get items
|
||||
- `GET /service/boards/{parentId}/teams` — Get board teams
|
||||
|
||||
### Callbacks / Webhooks (`/system/callbacks`)
|
||||
**Real-time event notifications.**
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `/system/callbacks` | List registered callbacks |
|
||||
| POST | `/system/callbacks` | **Register a new callback** |
|
||||
| GET | `/system/callbacks/{id}` | Get callback detail |
|
||||
| PATCH | `/system/callbacks/{id}` | Update callback |
|
||||
| DELETE | `/system/callbacks/{id}` | Remove callback |
|
||||
|
||||
CallbackEntry fields: `id`, `description`, `url`, `objectId`, `type`, `level`, `memberId`, `payloadVersion`, `inactiveFlag`
|
||||
|
||||
**ResolutionFlow usage:** Register callbacks for ticket creation/update events to suggest relevant Flows in real-time.
|
||||
|
||||
---
|
||||
|
||||
## TIER 2 — High Value Add-ons
|
||||
|
||||
### Knowledge Base (`/service/knowledgeBaseArticles`)
|
||||
**12 fields. Maps directly to Flows.**
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| `id` | integer | Article ID |
|
||||
| `title` | string | Article title |
|
||||
| `issue` | string | **Problem description** |
|
||||
| `resolution` | string | **Solution** |
|
||||
| `board` | BoardReference | Associated board |
|
||||
| `categoryId` | integer | KB category |
|
||||
| `subCategoryId` | integer | KB subcategory |
|
||||
|
||||
**ResolutionFlow mapping:** Completed sessions → auto-generate KB articles. Existing KB articles → inform FlowPilot AI suggestions.
|
||||
|
||||
### Time Entries (`/time/entries`)
|
||||
**62 fields. Auto-log time from session duration.**
|
||||
|
||||
Key fields: `id`, `company`, `chargeToId`, `chargeToType`, `member`, `workType`, `workRole`, `timeStart`, `timeEnd`, `actualHours`, `notes`, `internalNotes`
|
||||
|
||||
### Documents (`/system/documents`)
|
||||
**Attach session exports to tickets.**
|
||||
|
||||
- `POST /system/documents` — Upload document (multipart form data with `recordType=Ticket&recordId={id}`)
|
||||
- `GET /system/documents/{id}/download` — Download document
|
||||
|
||||
### Members (`/system/members`)
|
||||
**126 fields. Map CW members to ResolutionFlow users.**
|
||||
|
||||
Key fields: `id`, `identifier`, `firstName`, `lastName`, `emailAddress`, `photo`, `title`, `securityRole`, `defaultLocation`, `defaultDepartment`
|
||||
|
||||
### SLAs (`/service/SLAs`)
|
||||
**23 fields. Pull SLA context into sessions.**
|
||||
|
||||
Fields include response/resolution hours and percentages by impact/urgency matrix.
|
||||
|
||||
### Priorities (`/service/priorities`)
|
||||
Fields: `id`, `name`, `color`, `sortOrder`, `defaultFlag`, `level`
|
||||
|
||||
---
|
||||
|
||||
## TIER 3 — Future Expansion
|
||||
|
||||
### Projects (`/project/projects`)
|
||||
**For larger MSP engagements. 11 endpoints.**
|
||||
|
||||
Key endpoints: CRUD on projects, plus `/project/projects/{parentId}/contacts`, `/project/projects/{parentId}/notes`, `/project/projects/{parentId}/teamMembers`
|
||||
|
||||
### Project Tickets (`/project/tickets`)
|
||||
**80 fields. 24 endpoints. Separate from service tickets but similar structure.**
|
||||
|
||||
Includes own notes system at `/project/ticketNote`
|
||||
|
||||
### Workflows (`/system/workflows`)
|
||||
**46 endpoints. ConnectWise's internal automation engine.**
|
||||
|
||||
Could trigger ResolutionFlow sessions automatically based on ticket events.
|
||||
|
||||
### Activities (`/sales/activities`)
|
||||
**Track follow-ups post-session.**
|
||||
|
||||
### Agreements (`/finance/agreements`)
|
||||
**44 endpoints. Billing/SLA context per client.**
|
||||
|
||||
---
|
||||
|
||||
## Integration Architecture Notes
|
||||
|
||||
### Session → Ticket Note (Primary Flow)
|
||||
1. Engineer opens session, optionally links to CW ticket ID
|
||||
2. Session documentation auto-generates during troubleshooting
|
||||
3. On session complete: POST to `/service/tickets/{ticketId}/notes` with `internalAnalysisFlag: true`
|
||||
4. Optionally POST resolution note with `resolutionFlag: true`
|
||||
5. Optionally update ticket status via PATCH `/service/tickets/{id}`
|
||||
|
||||
### Ticket Context → Session (Reverse Flow)
|
||||
1. Engineer enters CW ticket ID or session is launched from CW callback
|
||||
2. GET `/service/tickets/{id}` for ticket details
|
||||
3. GET `/service/tickets/{id}/configurations` for attached devices
|
||||
4. GET `/company/companies/{companyId}` for client context
|
||||
5. Feed all context to FlowPilot AI for informed troubleshooting
|
||||
|
||||
### Callback-Driven Flow Suggestions
|
||||
1. Register callback via POST `/system/callbacks`
|
||||
2. Receive ticket creation events at ResolutionFlow webhook endpoint
|
||||
3. Analyze ticket summary/type/board
|
||||
4. Suggest relevant Flows to the assigned engineer
|
||||
1114
docs/connectwise/Developer-Guide.md
Normal file
1114
docs/connectwise/Developer-Guide.md
Normal file
File diff suppressed because it is too large
Load Diff
381
docs/connectwise/best-practices/Bundled-Requests.md
Normal file
381
docs/connectwise/best-practices/Bundled-Requests.md
Normal file
@@ -0,0 +1,381 @@
|
||||
1. Last updated
|
||||
|
||||
Mar 31, 2023
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/1350/pdf/Bundled%2bRequests.pdf "Export page as a PDF")
|
||||
|
||||
#### Purpose
|
||||
|
||||
The Bundle endpoint is so that the end users can post multiple distinct requests at the same time. An example is if you need data from a number of endpoints for a single screen, instead of making each individual call you would bundle them together and make one request. This will save time and bandwidth.
|
||||
|
||||
## Request
|
||||
|
||||
When sending a bundled request to the server, you will send an array of the same objects you would send in a singular request.
|
||||
|
||||
|`01`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`02`|`"requests"``: [`|
|
||||
|---|---|
|
||||
|
||||
|`03`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`04`|`"Version"``:` `"2019.3"``,`|
|
||||
|---|---|
|
||||
|
||||
|`05`|`"SequenceNumber"``:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`06`|`"ResourceType"``:` `"ticket"``,`|
|
||||
|---|---|
|
||||
|
||||
|`07`|`"ApiRequest"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`08`|`"Filters"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`09`|`"conditions"``:` `"summary like '%a%' and status/name like '%new%'and board/name='Help Desk'"`|
|
||||
|---|---|
|
||||
|
||||
|`10`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`11`|`"Page"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`12`|`"page"``:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`13`|`"pageSize"``:` `1000`|
|
||||
|---|---|
|
||||
|
||||
|`14`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`15`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`16`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`17`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`18`|`"Version"``:` `"2019.2"``,`|
|
||||
|---|---|
|
||||
|
||||
|`19`|`"SequenceNumber"``:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`20`|`"ResourceType"``:` `"Company"``,`|
|
||||
|---|---|
|
||||
|
||||
|`21`|`"ApiRequest"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`22`|`"Filters"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`23`|`"conditions"``:` `"companyName = 'Connectwise'"``,`|
|
||||
|---|---|
|
||||
|
||||
|`24`|`"childConditions"``:` `""``,`|
|
||||
|---|---|
|
||||
|
||||
|`25`|`"customFieldConditions"``:` `""`|
|
||||
|---|---|
|
||||
|
||||
|`26`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`27`|`"Page"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`28`|`"pageSize"``:` `25``,`|
|
||||
|---|---|
|
||||
|
||||
|`29`|`"pageId"``:` `2`|
|
||||
|---|---|
|
||||
|
||||
|`30`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`31`|`"Id"``:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`32`|`"ParentId"``:` `2``,`|
|
||||
|---|---|
|
||||
|
||||
|`33`|`"GrandParentId"``:` `3`|
|
||||
|---|---|
|
||||
|
||||
|`34`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`35`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`36`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`37`|`"Version"``:` `"2019.2"``,`|
|
||||
|---|---|
|
||||
|
||||
|`38`|`"SequenceNumber"``:` `2``,`|
|
||||
|---|---|
|
||||
|
||||
|`39`|`"ResourceName"``:` `"Member"``,`|
|
||||
|---|---|
|
||||
|
||||
|`40`|`"ApiRequest"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`41`|`"Id"``:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`42`|`"ParentId"``:` `2``,`|
|
||||
|---|---|
|
||||
|
||||
|`43`|`"GrandParentId"``:` `3``,`|
|
||||
|---|---|
|
||||
|
||||
|`44`|`"Filters"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`45`|`"conditions"``:` `"memberName = 'John Smith'"``,`|
|
||||
|---|---|
|
||||
|
||||
|`46`|`"childConditions"``:` `""``,`|
|
||||
|---|---|
|
||||
|
||||
|`47`|`"customFieldConditions"``:` `""`|
|
||||
|---|---|
|
||||
|
||||
|`48`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`49`|`"Page"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`50`|`"page"``:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`51`|`"pageSize"``:` `25`|
|
||||
|---|---|
|
||||
|
||||
|`52`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`53`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`54`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`55`|`]`|
|
||||
|---|---|
|
||||
|
||||
|`56`|`}`|
|
||||
|---|---|
|
||||
|
||||
### Example: Line Items from PO
|
||||
|
||||
This example is a GET on Line Items for purchase order 1. Note the ParentId specifies the purchase order recId.
|
||||
|
||||
|`01`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`02`|`"requests"``:`|
|
||||
|---|---|
|
||||
|
||||
|`03`|`[`|
|
||||
|---|---|
|
||||
|
||||
|`04`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`05`|`"Version"``:``"2019.5"``,`|
|
||||
|---|---|
|
||||
|
||||
|`06`|`"SequenceNumber"``:``1``,`|
|
||||
|---|---|
|
||||
|
||||
|`07`|`"ResourceType"``:``"purchaseorderlineitem"``,`|
|
||||
|---|---|
|
||||
|
||||
|`08`|`"ApiRequest"``:{`|
|
||||
|---|---|
|
||||
|
||||
|`09`|`"Filters"``:`|
|
||||
|---|---|
|
||||
|
||||
|`10`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`11`|`"conditions"``:``""``,`|
|
||||
|---|---|
|
||||
|
||||
|`12`|`"childConditions"` `:` `""``,`|
|
||||
|---|---|
|
||||
|
||||
|`13`|`"customFieldConditions"``:` `""`|
|
||||
|---|---|
|
||||
|
||||
|`14`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`15`|
|
||||
|---|
|
||||
|
||||
|`16`|`"Page"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`17`|`"page"` `:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`18`|`"pageSize"``:` `1000`|
|
||||
|---|---|
|
||||
|
||||
|`19`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`20`|`"parentId"``:``1`|
|
||||
|---|---|
|
||||
|
||||
|`21`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`22`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`23`|`]`|
|
||||
|---|---|
|
||||
|
||||
|`24`|`}`|
|
||||
|---|---|
|
||||
|
||||
## Response
|
||||
|
||||
The response object that is returned is slightly different from what they normally get back from the singular endpoints. This is because we need to track the success status of each object individually. Because of this, there will be a wrapper class around each returned object that will have status information.
|
||||
|
||||
Furthermore, since each record may have different statuses, we will respond with different status codes depending on what the outcomes are.
|
||||
|
||||
The wrapper will have 5 properties:
|
||||
|
||||
- success: This property is a boolean that indicates if the response was a success or an error occurred
|
||||
|
||||
- sequenceNumber: This is the sequence number passed in with the array. This is so you can map the returned object back to the original sent-in object so you can sync id’s and all other information
|
||||
|
||||
- statusCode: This will be filled in with what the status code would have been if the user had called the individual endpoint (e.g. 200, 400, 403)
|
||||
|
||||
- error: If success is false, this will be populated with the error response that you would have received if you called the individual endpoint; Else this will be null and not returned.
|
||||
|
||||
- data: If success is true, this will be populated with the returned object as if the called the individual endpoint; Else this will be null and not returned
|
||||
|
||||
|
||||
|`01`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`02`|`"results"``: [`|
|
||||
|---|---|
|
||||
|
||||
|`03`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`04`|`"sequenceNumber"``:` `1``,`|
|
||||
|---|---|
|
||||
|
||||
|`05`|`"resourceType"``:` `"Company"``,`|
|
||||
|---|---|
|
||||
|
||||
|`06`|`"entities"``: [`|
|
||||
|---|---|
|
||||
|
||||
|`07`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`08`|`"record"``:` `"Company1data"`|
|
||||
|---|---|
|
||||
|
||||
|`09`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`10`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`11`|`"record2"``:` `"Company2data"`|
|
||||
|---|---|
|
||||
|
||||
|`12`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`13`|`],`|
|
||||
|---|---|
|
||||
|
||||
|`14`|`"count"``:` `2``,`|
|
||||
|---|---|
|
||||
|
||||
|`15`|`"success"``:` `true``,`|
|
||||
|---|---|
|
||||
|
||||
|`16`|`"statusCode"``:` `200`|
|
||||
|---|---|
|
||||
|
||||
|`17`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`18`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`19`|`"sequenceNumber"``:` `2``,`|
|
||||
|---|---|
|
||||
|
||||
|`20`|`"resouceType"``:` `"ServiceNote"``,`|
|
||||
|---|---|
|
||||
|
||||
|`21`|`"count"``:` `0``,`|
|
||||
|---|---|
|
||||
|
||||
|`22`|`"success"``:` `false``,`|
|
||||
|---|---|
|
||||
|
||||
|`23`|`"statusCode"``:` `404``,`|
|
||||
|---|---|
|
||||
|
||||
|`24`|`"error"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`25`|`"code"``:` `"NotFound"``,`|
|
||||
|---|---|
|
||||
|
||||
|`26`|`"message"``:` `"ServiceNote123notfound"`|
|
||||
|---|---|
|
||||
|
||||
|`27`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`28`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`29`|`],`|
|
||||
|---|---|
|
||||
|
||||
|`30`|`"_info"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`31`|`"failure"``:` `"1"``,`|
|
||||
|---|---|
|
||||
|
||||
|`32`|`"success"``:` `"1"``,`|
|
||||
|---|---|
|
||||
|
||||
|`33`|`"total"``:` `"2"`|
|
||||
|---|---|
|
||||
|
||||
|`34`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`35`|`}`|
|
||||
|---|---|
|
||||
360
docs/connectwise/best-practices/PSA-API-Requests.md
Normal file
360
docs/connectwise/best-practices/PSA-API-Requests.md
Normal file
@@ -0,0 +1,360 @@
|
||||
1. Last updated
|
||||
|
||||
Mar 31, 2023
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/1291/pdf/PSA%2bAPI%2bRequests.pdf "Export page as a PDF")
|
||||
|
||||
## HTTP Methods
|
||||
|
||||
- POST
|
||||
- Create an entity or any non-CRUD action
|
||||
- GET
|
||||
- Return entity or list of entities
|
||||
- PUT
|
||||
- Replace all fields on an entity with supplied fields
|
||||
- PATCH
|
||||
- Update specific fields on an entity
|
||||
- DELETE
|
||||
- Remove entity
|
||||
|
||||
## HTTP Response Statuses
|
||||
|
||||
- 201 Created
|
||||
- The response will be the record that was created as well as the path to it.
|
||||
- 204 NoContent
|
||||
- Will be returned by successful delete requests
|
||||
- 400 Bad Request
|
||||
- The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications
|
||||
- 401 Unauthorized
|
||||
- The supplied authentication is incorrect
|
||||
- 403 Forbidden
|
||||
- Improper security role settings for the supplied authentication
|
||||
- 404 Not Found
|
||||
- Resource URL not found, this could mean the record was deleted or moved
|
||||
- 405 Method Not Allowed
|
||||
- This will occur if you try to use an HTTP method that is not supported by the URL specified
|
||||
- 409 Conflict
|
||||
- Record possibly in use or another conflict with the record
|
||||
- 415 Unsupported Media Type
|
||||
- This can occur when using the documents API if you are sending the file as a JSON object
|
||||
- 429 Too Many Requests
|
||||
- This will occur if request volume exceeds capacity limits and will include a Retry-After header with a numeric value that indicates the number of seconds to wait before retrying (see Retry Policy below)
|
||||
- 500 Server Error
|
||||
- Server errors can occur oftentimes due to a network fault or other non-application-related error, however, In some cases, a 500 error may occur when another error message has not been defined for an error
|
||||
- These errors should follow the retry logic we recommend and if it appears to be an undefined error, should be reported
|
||||
|
||||
> PSA Specific - Are you getting a 404 error or "Could not get any response" on cloud? You may have incorrect authentication instead of the resource not being found. To confirm, change your base URL from v4\_6\_release to your ConnectWise PSA version /201x\_x/.
|
||||
|
||||
## Formatting Each Method
|
||||
|
||||
### Get
|
||||
|
||||
Get requests are used for finding both individual records or a listing of records. In order to grab an individual entity you must specify an id within the request URL.
|
||||
|
||||
```
|
||||
https://api-na.myconnectwise.net/v4_6_release/apis/3.0/service/tickets/5000
|
||||
```
|
||||
|
||||
When requesting a grouping of records, do not include an id record. Optionally include parameters at the end of the URL to instead grab a specific set of records.
|
||||
|
||||
```
|
||||
https://api-na.myconnectwise.net/v4_6_release/apis/3.0/service/tickets?conditions=board/name="Integration"%20and%20status/name="new"&page=1&pageSize=10
|
||||
```
|
||||
|
||||
When looking at an endpoint, the parameters section will tell you which fields can be used to filter the request. In addition to the below parameters, you can use the Fields and Columns to manipulate the response dataset.
|
||||
|
||||
#### URL Max Length
|
||||
|
||||
The HTTP standards document does not impost a max URL length. However various browsers, as well as applications, have different limits in place. In addition search engines favor URLs that are around 2000 characters. As a URL grows in length it no longer remains user-friendly and becomes unusable or readable by users. This causes issues with troubleshooting and leads to the overall confusion. We recommend keeping the URL length of each request to a maximum of 2000 characters. This will ensure there are no compatibility issues with various servers and configurations. Please keep in mind that the max length of a domain name can reach 255 characters. **With that in mind, the safe max length of a URL would be around 1745 characters.**
|
||||
|
||||
This can vary based on environment configurations. There will often be times when the URL could be significantly longer. There may also be one of the server configurations based on premise that restricts the URL to a lower length. However, it is recommended to keep the URL length to a max of 2000 characters including the domain name.
|
||||
|
||||
#### Query String Parameters
|
||||
|
||||
|**Parameter**|**Description**|**Example**|
|
||||
|---|---|---|
|
||||
|[conditions](https://developer.connectwise.com/Best_Practices/PSA_API_Requests?mt-learningpath=manage#Conditions "Manage API Requests")|Search results based on the fields returned in a GET|board/name="Integration"
|
||||
summary="xyz"
|
||||
board/id in (3, 2, 4)
|
||||
lastUpdated > \[2016-08-20T18:04:26Z\]
|
||||
Only fields returned in a GET request can be used|\=, !=, <, <=, >, >=, contains, like, in, not|
|
||||
|childConditions|Allows searching arrays on endpoints that list childConditions under parameters|/company/contacts?childconditions=communicationItems/value like "[john@Outlook.com](mailto:%john@Outlook.com% "mailto:%john@Outlook.com%")" AND communicationItems/communicationType="Email"|\=, !=, <, <=, >, >=, contains, like, not|
|
||||
|[customFieldConditions](https://developer.connectwise.com/Products/ConnectWise_PSA/Developer_Guide#Custom_Fields "Developer Guide")|Allows searching custom fields when customFieldConditions is listed in the parameters|/company/contacts?customFieldConditions=caption="TomNumber" AND value !=null|\=, !=, <, <=, >, >=, contains, like, not|
|
||||
|orderBy|Choose which field to sort the results by|contact/name asc|asc or desc|
|
||||
|[fields](https://developer.connectwise.com/Products/ConnectWise_PSA/Developer_Guide#Partial_Responses "Developer Guide")|Limits which information is returned in the response|company/companies?fields=id,name,status/id|Not available on the reporting endpoints|
|
||||
|[columns](https://developer.connectwise.com/Products/ConnectWise_PSA/Developer_Guide#Partial_Responses "Developer Guide")|Limits which information is returned in the response|system/reports/service?columns=id,summary,name|Only used for the Reporting Endpoints|
|
||||
|[page](https://developer.connectwise.com/Products/ConnectWise_PSA/Developer_Guide#Pagination "Developer Guide")|Used in pagination to cycle through results|
|
||||
|[pageSize](https://developer.connectwise.com/Products/ConnectWise_PSA/Developer_Guide#Pagination "Developer Guide")|Number of results returned per page (Defaults to 25)|Max Size = 1,000\*|
|
||||
|\*Max page size was increased to 1,000 in 2016.2.|
|
||||
|
||||
#### Conditions
|
||||
|
||||
|Strings|Must be surrounded by quotes|Summary = "This is my string" (Accepts \*'s for Wild Cards)|
|
||||
|---|---|---|
|
||||
|Integers|No formatting required|Board/Id = 123|
|
||||
|Boolean|No formatting is required but must be True or False|ClosedFlag = True|
|
||||
|Datetimes|Must be surrounded by square brackets|LastUpdated = \[2016-08-20T18:04:26Z\]|
|
||||
|Operators|<, <=, =, !=, >, >=, contains, like, in, not|Summary Not Contains "Low Priority"|
|
||||
|Logic Operators|Supported operators include:
|
||||
- AND
|
||||
- OR|board/name="integration" and summary="xyz"
|
||||
board/name="integration" or board/name="professional services"|
|
||||
|Reference\*|Must have a / followed by the field under the reference you would like to use|manufacturer/name|
|
||||
|
||||
#### Using the /Search endpoints
|
||||
|
||||
If your request URL is going to be over 10,000 characters long you can instead use the /Search path for certain endpoints. This allows you to enter the conditions in the body of the request.
|
||||
|
||||
```
|
||||
POST /v4_6_release/apis/3.0/service/tickets/search HTTP/1.1
|
||||
Host: YOURCONNECTWISESITE
|
||||
Authorization: Basic AUTHKEY=
|
||||
Content-Type: application/json
|
||||
|
||||
Body:
|
||||
{
|
||||
"conditions": "summary like 'test'"
|
||||
}
|
||||
```
|
||||
|
||||
> Successful get requests will return a 200 status response and a content body of the record(s).
|
||||
|
||||
### Patch
|
||||
|
||||
Patch requests enable the ability to update individual fields on an entity. When formatting the request you can specify multiple fields to be updated. When working with a patch, the entire object is part of an array and must be surrounded by square brackets as per the example below. If you do not have the square brackets, you will run into errors. In addition, when you are updating a reference, you can only update it through the use of unique values. Many times Name is not considered unique.
|
||||
|
||||
```
|
||||
https://api-na.myconnectwise.net/v4_6_release/apis/3.0/service/tickets/5000
|
||||
```
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"op": "string",
|
||||
"path": "string",
|
||||
"value": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|op|The update operation used in the request|add|
|
||||
|---|---|---|
|
||||
|path|Pathway for the updated field (Case Sensitive)|summary
|
||||
company|
|
||||
|value|The new value if doing a replace
|
||||
Refer to [escaping characters](https://developer.connectwise.com/Products/ConnectWise_PSA/Developer_Guide#Escaping_Characters "Developer Guide")
|
||||
When working with custom fields, you must pass the entire array of custom fields.|String: "Here is my Summary"
|
||||
Object: { "identifier": "connectwise" }|
|
||||
|
||||
|`01`|`[`|
|
||||
|---|---|
|
||||
|
||||
|`02`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`03`|`"op"``:` `"replace"``,`|
|
||||
|---|---|
|
||||
|
||||
|`04`|`"path"``:` `"summary"``,`|
|
||||
|---|---|
|
||||
|
||||
|`05`|`"value"``:` `"New Summary"`|
|
||||
|---|---|
|
||||
|
||||
|`06`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`07`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`08`|`"op"``:` `"replace"``,`|
|
||||
|---|---|
|
||||
|
||||
|`09`|`"path"``:` `"company"``,`|
|
||||
|---|---|
|
||||
|
||||
|`10`|`"value"``: {`|
|
||||
|---|---|
|
||||
|
||||
|`11`|`"identifier"``:` `"New Company"`|
|
||||
|---|---|
|
||||
|
||||
|`12`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`13`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`14`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`15`|`"op"``:` `"replace"``,`|
|
||||
|---|---|
|
||||
|
||||
|`16`|`"path"``:` `"customFields"``,`|
|
||||
|---|---|
|
||||
|
||||
|`17`|`"value"``: [`|
|
||||
|---|---|
|
||||
|
||||
|`18`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`19`|`"id"``: 5,`|
|
||||
|---|---|
|
||||
|
||||
|`20`|`"caption"``:` `"CloudPlus"``,`|
|
||||
|---|---|
|
||||
|
||||
|`21`|`"type"``:` `"Checkbox"``,`|
|
||||
|---|---|
|
||||
|
||||
|`22`|`"entryMethod"``:` `"EntryField"``,`|
|
||||
|---|---|
|
||||
|
||||
|`23`|`"numberOfDecimals"``: 0,`|
|
||||
|---|---|
|
||||
|
||||
|`24`|`"value"``:` `false`|
|
||||
|---|---|
|
||||
|
||||
|`25`|`},`|
|
||||
|---|---|
|
||||
|
||||
|`26`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`27`|`"id"``: 28,`|
|
||||
|---|---|
|
||||
|
||||
|`28`|`"caption"``:` `"test"``,`|
|
||||
|---|---|
|
||||
|
||||
|`29`|`"type"``:` `"Text"``,`|
|
||||
|---|---|
|
||||
|
||||
|`30`|`"entryMethod"``:` `"List"``,`|
|
||||
|---|---|
|
||||
|
||||
|`31`|`"numberOfDecimals"``: 0,`|
|
||||
|---|---|
|
||||
|
||||
|`32`|`"value"``:` `"test"`|
|
||||
|---|---|
|
||||
|
||||
|`33`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`34`|`]`|
|
||||
|---|---|
|
||||
|
||||
|`35`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`36`|`]`|
|
||||
|---|---|
|
||||
|
||||
> When updating an Object such as Company, you cannot specify a location inside of the object. You have to replace the whole object like the example above. (Do not use "path":"company/identifier") If you try to update the Object incorrectly, you may receive a false 200 message.
|
||||
>
|
||||
> Successful patch requests will return a 200 status response for success
|
||||
|
||||
### Delete
|
||||
|
||||
Delete requests are used for removing records from the ConnectWise PSA system. Please take care when removing items as they cannot be recovered. The id for the record to be deleted needs to be included in the request URL.
|
||||
|
||||
```
|
||||
https://api-na.myconnectwise.net/v4_6_release/apis/3.0/service/tickets/5000
|
||||
```
|
||||
|
||||
> Successful delete requests will return a 204 status response for No Content. 204 is a success response.
|
||||
|
||||
### Post
|
||||
|
||||
Post is used when creating new records. The body of the request must be sent in JSON format. When sending a Post the response body will include the newly created record id as well as a Get request for the record. If you pass a post request without filling out every possible value, the system will attempt to default all required information, and anything that does not require a value will be set to Null.
|
||||
|
||||
### Put
|
||||
|
||||
Put requests are designed to completely replace an entity. They work the in the same manner as a Post request with the exception that you must specify an already created entity in your URL. When you use Put to update a record, any field that has not been specified will be overridden with the system defaults or set to Null.
|
||||
|
||||
### Escaping Characters
|
||||
|
||||
When working with JSON, you may find that you need to use characters that require escaping. This comes into play primarily when quotes are involved as we do not support many of the characters that require escaping. Refer to the chart below:
|
||||
|
||||
|Character|Escaped|Displayed in the UI|
|
||||
|---|---|---|
|
||||
|Double Quotes|\\"|"|
|
||||
|Backslash|\\\\|\\|
|
||||
|Tab|\\t|Tabbed space equal to four spaces|
|
||||
|Backspace (Not Supported)|\\b|An invisible character that doesn't take up any space|
|
||||
|Carriage Return (Not Supported)|\\r|Single space which is returned as a space in the API instead of an escaped character|
|
||||
|Newline (Not Supported)|\\n|Single space which is returned as a space in the API instead of an escaped character|
|
||||
|Form Feed (Not Supported)|\\f|An invisible character that doesn't take up any space|
|
||||
|**Applies to JSON Bodies Only:** Please note that Single Quotes ', do not require escaping as they should not be used as a container for your strings. If you are using single contains around string values, you must switch to doubles.|
|
||||
|
||||
URL Encoding is required when using conditions and other URL parameters that have symbols that would denote new query parameters or strings.
|
||||
|
||||
|Character|Formatting|
|
||||
|---|---|
|
||||
|&|%26|
|
||||
|"|%22|
|
||||
|'|%27|
|
||||
|\*|%2A|
|
||||
|%|%25|
|
||||
|+|%2B|
|
||||
|\[string\]|\[\[\]string\]|
|
||||
|**This applies to URL Parameters Only**|
|
||||
|
||||
### Partial Responses
|
||||
|
||||
The API allows you to specify which information you want to be returned. You do this by listing the request fields or columns on your endpoint URL. Partial responses work for both GET and POST requests.
|
||||
|
||||
|Fields|company/companies?fields=company/id,company/name,phoneNumber|
|
||||
|---|---|
|
||||
|Columns (Reporting API only)|system/reports/service?columns=id,summary,company/name|
|
||||
|
||||
### Custom Fields
|
||||
|
||||
Screens that have custom fields in the ConnectWise PSA UI will have an array of fields on the respective endpoint. This array can be both queried and updated via the API. When updating custom fields, you must pass in the entire array object, which means that you cannot patch a single custom field record. Wondering if an endpoint supports custom fields? Supported endpoints will have customFields(CustomFieldValue\[\]) listed at the end of the documentation.
|
||||
|
||||
There are two methods of adding new custom fields to the ConnectWise PSA environment. The first method will require access to the ConnectWise PSA thick client and the second method is available in the system/userDefinedFields endpoint within the REST API.
|
||||
|
||||
> Refer to the [GET](https://developer.connectwise.com/Products/ConnectWise_PSA/Developer_Guide#Get "Developer Guide") section on how to search custom fields. Searching custom fields uses customFieldConditions instead of conditions.
|
||||
|
||||
In order to access the custom fields via the thick client, navigate to System > Setup Tables > Custom fields,
|
||||
|
||||
#### Field Options and Parameters
|
||||
|
||||

|
||||
|
||||
|**Field**|**Description**|
|
||||
|---|---|
|
||||
|Field Caption|Enter a custom field caption for this required field; you are limited to 12 characters.|
|
||||
|Help Text|The text entered in this field will be displayed when you hover over the help button next to the custom field on the screen.
|
||||
**Note:** This has a limit of 1000 characters, however only 512 characters will display on the opportunity screen.|
|
||||
|Field Type|Select one of the following options:
|
||||
- **Button** This field allows you to add a hyperlink in the **Button URL** field. There is a character limit of 1000.
|
||||
- **Checkbox** This field is used for simple "Yes/No" or "On/Off" answers. This will display as a checkbox.
|
||||
- **Date** This field displays a standard date picker field.
|
||||
- **Hyperlink** This field displays a text entry field to enter a URL and will be accompanied by a button to visit the hyperlink.
|
||||
- **Number** This field displays numerical values only. You will be prompted with an error message that displays 'Option value must be a number' if the text is entered.
|
||||
- **Percent** This field displays numerical values only. You will be prompted with an error message that displays 'Option value must be a number' if the text is entered.
|
||||
- **Text** This field displays alpha-numeric values. There is a character limit of 100.
|
||||
- **Text** **Area** This field displays alpha-numeric values. There is a character limit of 1000. The **List** and **Dropdown** method of entry is unavailable for this option.
|
||||
|
||||
**Note:** If you change a **Date** field to a **Text** or **Text Area** field, the date entered will save as plain text.
|
||||
|
||||
If you change a **Text** or **Text Area** field to a **Date** field, the system may not automatically save the date. If this occurs, and it is a required field, the page-level validation will alert the user to enter a date in the field.|
|
||||
|Number of Decimals|This required field is only available for the Number and Percent Field Type. You can add up to 5 decimal places.|
|
||||
|Method of Entry|Select one of the following options:
|
||||
- **Entry Field** This field is available for all field types.
|
||||
- **List (Drop-down)** This field is available for the following field types: Number, Percent, and Text. You are able to enter option values once this entry is selected.
|
||||
- **Option (Radio)** This field is available for the following field types: Number, Percent, and Text. You are able to enter option values once this entry is selected.|
|
||||
|Sequence #|Enter a sequence number. This value must be between 1 and 50.|
|
||||
|Required Field?|Select this checkbox if you would like to make this a required custom field. This will display as a blue asterisk.|
|
||||
|Display on Screen?|Select this checkbox if you would like this to display on the Opportunities screen. This is selected by default.|
|
||||
|Read Only?|This field is used for the API. The **Required Field** checkbox cannot be marked for this field.|
|
||||
|Include on List View?|Select this checkbox if you would like to include this custom field in the My Opportunities list view screen.
|
||||
**Note:** There is a limit of 10 fields that can be displayed on the list view screen. You can have an unlimited number of custom fields, but the check box will not display if there are already 10 list items. You can unselect this check box at any time.|
|
||||
|Button URL|Enter the URL if you are using the Field Type Button. The field only appends URLs, no other form of entry will generate content.|
|
||||
|Select the Locations that will use this Custom Field|This is a required field. Opportunities that have the specified location(s) will display this custom field. Select at least one location where the custom field will be used.|
|
||||
|Select the Departments that will use this Custom Field|This is not a required field. Opportunities that have the specified department(s) will display this custom field.|
|
||||
231
docs/connectwise/best-practices/PSA-Callbacks.md
Normal file
231
docs/connectwise/best-practices/PSA-Callbacks.md
Normal file
@@ -0,0 +1,231 @@
|
||||
1. Last updated
|
||||
|
||||
Feb 4, 2026
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/859/pdf/PSA%2bCallbacks.pdf "Export page as a PDF")
|
||||
|
||||
ConnectWise PSA callbacks are payloads of information that are similar to webhooks. When a record is saved within PSA, a summarized payload is sent to a specified location.
|
||||
|
||||
### Levels and Types
|
||||
|
||||
The REST APIs allow a more granular approach to callbacks. Levels and types open the ability to report on specific boards or tickets without getting unnecessary results.
|
||||
|
||||
|**Type**|**Level**|**Description**|
|
||||
|---|---|---|
|
||||
|Activities \-|
|
||||
|Activity|Owner|When set to owner, all ConnectWise PSA activities are returned.|
|
||||
|Activity|Status|Receive callbacks for activities in the specified status.|
|
||||
|Activity|Type|Receive callbacks for activities in the specified type.|
|
||||
|Activity|Company|Receive callbacks for activities under a specific company.|
|
||||
|Activity|Activity|Receive callbacks for specific activity.|
|
||||
|Agreements \-|
|
||||
|Agreement|Owner|When set to owner, all ConnectWise PSA agreements are returned.|
|
||||
|Agreement|Type|Receive callbacks for agreements in the specified type.|
|
||||
|Agreement|Company|Receive callbacks for agreements under a specific company.|
|
||||
|Agreement|Agreement|Recieve callbacks for specific agreement.|
|
||||
|Companies \-|
|
||||
|Company|Owner|When set to owner, all ConnectWise PSA companies are returned.|
|
||||
|Company|Status|Receive callbacks for companies in the specified status.|
|
||||
|Company|Type|Receive callbacks for companies in the specified type.|
|
||||
|Company|Territory|Receive callbacks for companies in the specified territory.|
|
||||
|Company|Company|Receive callbacks for specific company regardless of territory.|
|
||||
|Company|IntegratorTag|Tag companies to only get updates for that company with one callback record|
|
||||
|Contacts \-|
|
||||
|Contact|Owner|When set to owner, all ConnectWise PSA contacts are returned.|
|
||||
|Contact|Type|Receive callbacks for contacts in the specified type.|
|
||||
|Contact|Territory|Receive callbacks for contacts in the specified territory.|
|
||||
|Contact|Company|Receive callbacks for contacts under a specific company.|
|
||||
|Contact|Contact|Receive callbacks for specific contact.|
|
||||
|Configurations \-|
|
||||
|Configuration|Owner|When set to owner, all ConnectWise PSA configurations are returned.|
|
||||
|Configuration|Type|Receive callbacks for configurations in the specified type.|
|
||||
|Configuration|Status|Receive callbacks for contacts in the specified status.|
|
||||
|Configuration|Configuration|Receive callbacks for a specific configuration.|
|
||||
|Invoice \-|
|
||||
|Invoice|Owner|When set to owner, all ConnectWise PSA invoices are returned.|
|
||||
|Invoice|Status|Receive callbacks for invoices in the specified status.|
|
||||
|Invoice|Company|Receive callbacks for invoices under a specific company.|
|
||||
|Invoice|Invoice|Receive callbacks for specific invoice.|
|
||||
|Expense \-|
|
||||
|Expense|Owner|When set to owner, all ConnectWise PSA expenses are returned.|
|
||||
|Expense|ChargeToType|Receive callbacks for expenses under the specified ChargeToType.|
|
||||
|Expense|Type|Receive callbacks for expenses under the specified Type.|
|
||||
|Expense|Class|Receive callbacks for expenses under the specified Class.|
|
||||
|Expense|Company|Receive callbacks for expenses under the specified Company.|
|
||||
|Expense|Expense|Receive calbacks for specific expense.|
|
||||
|Member \-|
|
||||
|Member|Owner|When set to owner, changes to all ConnectWise PSA member records are returned.|
|
||||
|Member|Location|Receive callbacks for member records associated with the specified Location.|
|
||||
|Member|SecurityRole|Receive callbacks for member records associated with the specified Security Role.|
|
||||
|Member|Member|Receive callbacks for changes made to a specified member record.|
|
||||
|Opportunities \-|
|
||||
|Opportunity|Owner|When set to owner, all ConnectWise PSA opportunities are returned.|
|
||||
|Opportunity|StatusId|Receive callbacks for opportunities in the specified status.|
|
||||
|Opportunity|Company|Receive callbacks for opportunities under a specific company.|
|
||||
|Opportunity|Opportunity|Receive callbacks for specific opportunity.|
|
||||
|Product Catalog \-|
|
||||
|ProductCatalog|Owner|When set to owner, all ConnectWise PSA product catalog items are returned.|
|
||||
|Projects \-|
|
||||
|Project|Owner|When set to owner, all ConnectWise PSA projects are returned.|
|
||||
|Project|Status|Receive callbacks for projects in the specified status.|
|
||||
|Project|Board|Receive callbacks for projects on the specified board.|
|
||||
|Project|Project|Receive callbacks for specific project.|
|
||||
|Purchase Orders \-|
|
||||
|PurchaseOrder|Owner|When set to owner, all ConnectWise PSA purchase orders are returned.|
|
||||
|PurchaseOrder|Status|Receive callbacks for purchase orders in the specified status.|
|
||||
|PurchaseOrder|Vendor|Receive callbacks for purchase orders under the specified Vendor.|
|
||||
|PurchaseOrder|Company|Receive callbacks for purchase orders under a specific company.|
|
||||
|PurchaseOrder|PurchaseOrder|Receive callbacks for specific purchase orders.|
|
||||
|Schedule Entries \-|
|
||||
|Schedule|Owner|When set to owner, all ConnectWise PSA schedule entries are returned.|
|
||||
|Schedule|Status|Receive callbacks for schedule entries under a specific status.|
|
||||
|Schedule|Type|Receive callbacks for schedule entries under a specific type.|
|
||||
|Schedule|Location|Receive callbacks for schedule entries under a specific location.|
|
||||
|Schedule|Member|Receive callbacks for schedule entries under a specific member.|
|
||||
|Schedule|Schedule|Receive callbacks for specific schedule entries.|
|
||||
|Sites \-|
|
||||
|Site|Owner|When set to owner, all ConnectWise PSA sites are returned.|
|
||||
|Site|Territory|Receive callbacks for sites in specified territory.|
|
||||
|Site|Company|Receive callbacks for sites associated with a specific company.|
|
||||
|Site|Site|Receive callbacks for specific site.|
|
||||
|Tickets \-|
|
||||
|Ticket|Owner|When set to owner, all ConnectWise PSA tickets are returned.|
|
||||
|Ticket|Board|Receive callbacks for tickets on the specified board.|
|
||||
|Ticket|Project|Receive callbacks for tickets attached to a specific project.|
|
||||
|Ticket|Phase|Receive callbacks for tickets attached to a specific project phase.|
|
||||
|Ticket|Status|Receive callbacks for tickets in the specified status.|
|
||||
|Ticket|Ticket|Receive callbacks for specific tickets.|
|
||||
|Ticket|IntegratorTag|Tag tickets to only get updates for that ticket with one callback record|
|
||||
|Time Entries \-|
|
||||
|Time|Owner|When set to owner, all ConnectWise PSA time entries are returned.|
|
||||
|Time|Company|Receive callbacks for tickets for the specified company.|
|
||||
|Time|Time|Receive callbacks for specific time entries.|
|
||||
|
||||
### Configuring the Callbacks
|
||||
|
||||
More information can be found within the REST API documentation [Here](https://developer.connectwise.com/Products/ConnectWise_PSA/REST#/CallbackEntries "https://developer.connectwise.com/Products/Manage/REST#/CallbackEntries") with the endpoint system/callbacks.
|
||||
|
||||
|**Fields**|**Description**|
|
||||
|---|---|
|
||||
|Id|The database record id of the callback; is automatically assigned.|
|
||||
|Description|This is used to label the callback's usage.|
|
||||
|URL|This is the URL PSA will send the POST payload to.
|
||||
**Note**: PSA appends the record id, and action is taken, to the specified callback URL.
|
||||
For example, a callback on ticket 7601, turns the callback URL into: [https://mycallbacksite.com/e355a80bc2c107e32](https://mycallbacksite.com/e355a80bc2c107e32 "https://mycallbacksite.com/e355a80bc2c107e32")_?7601&action=updated_
|
||||
Typically, this is not an issue. However, when it is necessary to add custom parameters to your callback URL, this can cause a problem. For this reason, it is recommended that partners append _&recordId=_ to the end of their callback URL, like: [https://mycallbacksite.com/e355a80bc2c107e32](https://mycallbacksite.com/e355a80bc2c107e32 "https://mycallbacksite.com/e355a80bc2c107e32")**_&recordId=_**. This will ensure that the record id does not interfere with any custom parameters.
|
||||
Example:
|
||||
Desired callback URL: [https://api.mycallbackendpoint.com?param1=5¶m2=6](https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fapi.mycallbackendpoint.com%2F%3Fparam1%3D5%26param2%3D6&data=05%7C01%7CRBodford%40connectwise.com%7C3deeae87cf1245c0a98008db0301ab7e%7Cf2ddb62f83354cc99886175b834e4bf3%7C0%7C0%7C638107077950837811%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=6dopNZzGSRoM5HMUn%2BzhlV0UV3DeZizIxiwMajv5eXE%3D&reserved=0 "Original URL: https://api.mycallbackendpoint.com/?param1=5¶m2=6. Click or tap if you trust this link.")
|
||||
Currently, a callback on ticket number 2475 turns the callback URL into:
|
||||
[https://api.mycallbackendpoint.com?param1=5¶m2=62475&action=updated](https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fapi.mycallbackendpoint.com%2F%3Fparam1%3D5%26param2%3D62475%26action%3Dupdated&data=05%7C01%7CRBodford%40connectwise.com%7C3deeae87cf1245c0a98008db0301ab7e%7Cf2ddb62f83354cc99886175b834e4bf3%7C0%7C0%7C638107077950837811%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=2BxWDta4VObPj81GAHDOWTxyrtYmj3AxTo8MYp3mxv4%3D&reserved=0 "Original URL: https://api.mycallbackendpoint.com/?param1=5¶m2=62475&action=updated. Click or tap if you trust this link.")
|
||||
Notice the value of param2 is now changed from 6 to 62475 because the ticket number was appended.
|
||||
This results in the following URL:
|
||||
[https://api.mycallbackendpoint.com?param1=5¶m2=6&recordId=2475&action=updated](https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fapi.mycallbackendpoint.com%2F%3Fparam1%3D5%26param2%3D6%26recordId%3D2475%26action%3Dupdated&data=05%7C01%7CRBodford%40connectwise.com%7C3deeae87cf1245c0a98008db0301ab7e%7Cf2ddb62f83354cc99886175b834e4bf3%7C0%7C0%7C638107077950994009%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=GwH9Y77%2Be5wgOxXwy%2FD1HXx4XRII%2FgE8eFCu7pyLafw%3D&reserved=0 "Original URL: https://api.mycallbackendpoint.com/?param1=5¶m2=6&recordId=2475&action=updated. Click or tap if you trust this link.")|
|
||||
|ObjectId|The ObjectId should be the Id of whatever record you are subscribing to. This should be set to 1 when using a level of Owner.|
|
||||
|Type|This is the specific type of record such as Company, Ticket, Contact, etc... See the associated table for all values.|
|
||||
|Level|The level is used to determine how granular the callback subscription will be. See the associated table for all values.|
|
||||
|MemberId|This is a read-only value that shows who initially created the Callback.|
|
||||
|InactiveFlag|Used to determine if the callback is active and sending requests.|
|
||||
|\_Info|This section has additional metadata about the record and is included on all API requests as read-only data.|
|
||||
|
||||
|`01`|`POST /v4_6_release/apis/3.0/system/callbacks`|
|
||||
|---|---|
|
||||
|
||||
|`02`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`03`|`"id"``: 0,`|
|
||||
|---|---|
|
||||
|
||||
|`04`|`"description"``:` `"maxLength = 100"``,`|
|
||||
|---|---|
|
||||
|
||||
|`05`|`"url"``:` `"Sample string"``,`|
|
||||
|---|---|
|
||||
|
||||
|`06`|`"objectId"``: 0,`|
|
||||
|---|---|
|
||||
|
||||
|`07`|`"type"``:` `"Sample string"``,`|
|
||||
|---|---|
|
||||
|
||||
|`08`|`"level"``:` `"Sample string"``,`|
|
||||
|---|---|
|
||||
|
||||
|`09`|`"memberId"``: 0,`|
|
||||
|---|---|
|
||||
|
||||
|`10`|`"inactiveFlag"``:` `"false"``,`|
|
||||
|---|---|
|
||||
|
||||
|`11`|`"_info "``: {` `"lastUpdated"``:` `""``,` `"updatedBy"``:` `""` `}`|
|
||||
|---|---|
|
||||
|
||||
|`12`|`}`|
|
||||
|---|---|
|
||||
|
||||
### Testing Callbacks
|
||||
|
||||
#### Webhook.Site
|
||||
|
||||
When testing the ConnectWise PSA callbacks there are a number of useful browser-based tools. One of note is [https://webhook.site](https://webhook.site/ "https://webhook.site").
|
||||
|
||||
Simply use the New button to create a URL and then the Copy URL button to save the URL generated to your clipboard and add this as your ConnectWise PSA callback URL.
|
||||
|
||||
```
|
||||
https://webhook.site/xxxxxxx-xxxx-xxxx-af13-855ab85aebae
|
||||
```
|
||||
|
||||
### When Callbacks Fail to POST to Target Host
|
||||
|
||||
When a callback receives an error when attempting to POST the callback payload to the host specified in the callback URL, ConnectWise PSA will retry the POST for any 404, 409, 419, or 429 error responses. ConnectWise PSA retries twice, once two seconds after the initial POST attempt and again four seconds after the first retry attempt. Requests will timeout after five seconds.
|
||||
|
||||
The system counts how many consecutive days the callback fails, and after three consecutive days of failed attempts, ConnectWise PSA will disable the callback
|
||||
|
||||
Any 2xx response is considered successful.
|
||||
|
||||
For partners with an on-premise instance of ConnectWise PSA, when troubleshooting why your callback POSTs are receiving error response from the remote host, logs for the service are found on the ConnectWise PSA frontend server at C:\\Program Files\\ConnectWise\\ApiCallbackService\\logs\\server.log
|
||||
|
||||
To enable verbose logging, change the minlevel value to minlevel=”Info” in the C:\\Program Files\\ConnectWise\\ApiCallbackService\\ApiCallbackService.exe.nlog
|
||||
|
||||
### Verifying the Callback Source
|
||||
|
||||
Callbacks contain a key\_url in the metadata section that can be used to verify the source of the callback. The key\_url returns the signing key which then can be used in conjunction with the below code sample and the x-content-signature.
|
||||
|
||||
|`1`|`using` `(var sha =` `new` `SHA256Managed())`|
|
||||
|---|---|
|
||||
|
||||
|`2`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`3`|`var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(sharedSecretKey));`|
|
||||
|---|---|
|
||||
|
||||
|`4`|`using` `(var hmac =` `new` `HMACSHA256(hash))`|
|
||||
|---|---|
|
||||
|
||||
|`5`|`{`|
|
||||
|---|---|
|
||||
|
||||
|`6`|`return` `Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(jsonPayload)));`|
|
||||
|---|---|
|
||||
|
||||
|`7`|`}`|
|
||||
|---|---|
|
||||
|
||||
|`8`|`}`|
|
||||
|---|---|
|
||||
|
||||
### Callback Changelog
|
||||
|
||||
|**Supported Version**|**Changes**|
|
||||
|---|---|
|
||||
|ConnectWise PSA 2020.2|Added callbacks for Configurations|
|
||||
|ConnectWise PSA 2016.6|Added callbacks for Invoice
|
||||
Added callbacks for Projects
|
||||
Added callbacks for Activities|
|
||||
|ConnectWise PSA 2016.5|Company Callbacks: Added Status and Type levels
|
||||
Contact Callbacks: Added Type level
|
||||
Ticket Callbacks: Added levels for tracking Project Tickets|
|
||||
|ConnectWise PSA 2016.4|Added callbacks for Opportunities|
|
||||
|ConnectWise PSA 2016.3|Added callbacks for Companies and Contacts|
|
||||
|ConnectWise PSA 2015.6|Added callbacks for Tickets|
|
||||
51
docs/connectwise/best-practices/PSA-Cloud-URL-Formatting.md
Normal file
51
docs/connectwise/best-practices/PSA-Cloud-URL-Formatting.md
Normal file
@@ -0,0 +1,51 @@
|
||||
1. Last updated
|
||||
|
||||
Apr 8, 2025
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/959/pdf/PSA%2bCloud%2bURL%2bFormatting.pdf "Export page as a PDF")
|
||||
|
||||
When making calls to the ConnectWise PSA API, we have a URL that will give you the exact codebase to target in place of v4\_6\_release. This is useful to see if someone is on a cloud environment programmatically. Additionally, the request can be routed directly to the correct PSA version for the partner without it going through another source.
|
||||
|
||||
## Calling Company Info
|
||||
|
||||
```
|
||||
"https://" + ConnectWiseSite + "/login/companyinfo/" + LoginCompanyId
|
||||
https://na.myconnectwise.net/login/companyinfo/connectwise
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```
|
||||
{
|
||||
"CompanyName":"ConnectWise",
|
||||
"Codebase":"v2017_3/",
|
||||
"VersionCode":"v2017.3",
|
||||
"VersionNumber":"v4.6.38842",
|
||||
"CompanyID":"CW",
|
||||
"IsCloud":"True" *Added in 2016.5
|
||||
}
|
||||
```
|
||||
|
||||
## API Request URL Format
|
||||
|
||||
```
|
||||
"https://" + ConnectWiseSite + "/" + codebase + "apis/3.0/company/companies"
|
||||
https://api-my.myconnectwise.net/v2017_3/apis/3.0/company/companies
|
||||
```
|
||||
|
||||
## Cloud vs Premise
|
||||
|
||||
A cloud environment will return a codebase with the PSA version. On-Premise does not use URL redirection and will return v4\_6\_release/. If your returned codebase contains anything other than v4\_6\_release/, you will need to ensure your request is prefixed by API-
|
||||
|
||||
## Cloud URLs
|
||||
|
||||
These are the most commonly used URLs for the cloud.
|
||||
|
||||
```
|
||||
https://na.myconnectwise.net
|
||||
https://eu.myconnectwise.net
|
||||
https://au.myconnectwise.net
|
||||
https://aus.myconnectwise.net
|
||||
https://za.myconnectwise.net
|
||||
https://staging.connectwisedev.com
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
1. Last updated
|
||||
|
||||
Mar 31, 2023
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/1259/pdf/PSA%2bCompany%2bSynchronization.pdf "Export page as a PDF")
|
||||
|
||||
## Overview
|
||||
|
||||
The single most resource-intensive part of a typical integration is company synchronization. Many times integrations will cycle through company records that are irrelevant in terms of the integration itself. This could be cycling through prospects, former clients, or even duplicate records.
|
||||
|
||||
## What are Companies?
|
||||
|
||||
Companies are the primary key for many data sets within the ConnectWise PSA system. ConnectWise PSA is multi-tenant in that each company record inside of it is a representation of a client that they support or want to support. What this means is that if you wanted to create a service ticket, you have to specify what company it would belong to. You cannot have a service ticket without it being associated with a company. Similarly, you cannot have an agreement, opportunity, invoice, configuration, or any major record type without specifying what company it belongs to.
|
||||
|
||||
### Business Case
|
||||
|
||||
There are many benefits to ensuring that your integration has an optimization company synchronization flow. These range from a faster integration to a much easier-to-utilize integration as you won't be displaying irrelevant information.
|
||||
|
||||
- Reduce the number of API Calls made
|
||||
- Speed up the process of mapping to PSA Companies
|
||||
- Add value to your integration that will impress partners
|
||||
|
||||
## Implementation
|
||||
|
||||
First you need to identify what the is the record in your system that would be tied to a company in ConnectWise PSA. Once you know what record you are going to sync across you need to start with making a method for the partner to correspond the records together. There are a number of ways that integrators have done this in the past. Some of these are more advanced than others and in order to become a certified integrator, you cannot use any methods that require manual entry of data. It must be transmitted by the API. There are a number of recommended approaches that should be taken for each integration.
|
||||
|
||||
- Add a field to your equivalent screen to select the ConnectWise PSA company
|
||||
- Add a new screen that is a mass association tool that lists each of your records with a field to set the ConnectWise PSA company
|
||||
- Add an option to import ConnectWise PSA companies
|
||||
- Add an option to export companies to ConnectWise PSA
|
||||
|
||||
After you know what areas you are going to add the synchronization to, you have to work on filtering the data you are looking for to be usable by your integration. This is where many people make a mistake and they don't filter the data they are looking for and instead end up with many records that are unnecessary or irrelevant for the integration. Many times this means that when someone goes to select a company to map, integrators may load all 50,000 companies in ConnectWise PSA to pick from, whereas the partner only has 1,500 that are actually active clients. The rest of the results could be prospects, old clients, and duplicates.
|
||||
|
||||
To begin filtering records, you need to know how a company is categorized within ConnectWise PSA. There are two fields in the UI that control this, which, are the Status and Type fields. Each company can only have one status but many types. The status is used primarily as an indicator of it being Active, Inactive, or even in Credit Hold. The type is used to determine the specific type of customer, client, or prospect as well as many other options.
|
||||
|
||||

|
||||
|
||||
_Fig.1 - The company status and type fields are found on the company finance screen within ConnectWise PSA._
|
||||
|
||||
> Statuses and Types are customizable. You cannot compare them between environments or expect every environment to have the same values or ids. Each integration should be using API calls to grab a listing of each per environment in order to dynamically filter data.
|
||||
23
docs/connectwise/best-practices/PSA-Data-Protection.md
Normal file
23
docs/connectwise/best-practices/PSA-Data-Protection.md
Normal file
@@ -0,0 +1,23 @@
|
||||
1. Last updated
|
||||
|
||||
Mar 31, 2023
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/1653/pdf/PSA%2bData%2bProtection.pdf "Export page as a PDF")
|
||||
|
||||
## Protecting your Information
|
||||
|
||||
The ConnectWise PSA API uses security roles to determine access to environments. By selecting ALL instead of My or None you give integrators access to every piece of information. For certain areas, this may make sense. If you are adding your existing companies to their solution, or if you are sending tickets to them from ConnectWise PSA. However, in other cases such as an AV Alert or Backup Failure alert, integrations don’t need access to tickets they haven’t created themselves. Integrations that impact agreements, don’t need to see additions that they don’t manage. By providing access to ALL, integrations may be reading data that you wouldn’t expect, or updating information that they don’t own. It is always best practice to select MY access for all security roles unless it makes sense to enable ALL access. Integrations should justify requests for ALL access. There is never a situation in which integration needs “admin” access. This is often the default request if they don’t know the exact roles required. [API Logging](https://docs.connectwise.com/ConnectWise_Documentation/090/040/010/040/040?psa=1 "https://docs.connectwise.com/ConnectWise_Documentation/090/040/010/040/040?psa=1") will tell you if you restrict integrations too far.
|
||||
|
||||
> Want to learn more about [ConnectWise PSA Security Roles](https://docs.connectwise.com/ConnectWise_Documentation/090/025#Security_Role_Levels_Setting "https://docs.connectwise.com/ConnectWise_Documentation/090/025#Security_Role_Levels_Setting")?
|
||||
|
||||
## How is data stored?
|
||||
|
||||
ConnectWise does not store any information passed via the API outside of respective partner environments. We do not have a central API Service, data is sent directly to the front ends of each environment. The data from each partner is separate from every other partner, just because you can access one partner, does not mean you can access another. This means that if any integration has access to the APIs, they can only see data relating to the exact partner that gave them access to the APIs. This also means if you are an integrator, you must be given individual API Keys for every environment you wish to access. Similarly, no other vendors can see anything relating to your data if a partner hasn’t specifically given them API keys.
|
||||
|
||||

|
||||
|
||||
In this example Vendor A only has access to Partner A's information, because only Partner A has provided them with API Keys. Partner B on the other hand, is using both Vendor A and Vendor B. Depending on the security role permissions, this means Vendor B and Vendor A could see information from each other Partner environment A. If Partner A sets each integration to MY level access, then neither integration could see the other.
|
||||
|
||||
## Can another vendor access my information?
|
||||
|
||||
All information passed into a partner environment is only accessible if the partner has explicitly also given that other vendor access to that data. This means that they had to provide API Keys to the vendor and give them a security role other than MY or NONE.
|
||||
70
docs/connectwise/best-practices/PSA-Markdown.md
Normal file
70
docs/connectwise/best-practices/PSA-Markdown.md
Normal file
@@ -0,0 +1,70 @@
|
||||
## Endpoints
|
||||
|
||||
ConnectWise PSA supports Markdown formatting for notes such as on Ticket Detailed Descriptions. Here are the supported formatting options.
|
||||
|
||||
|Formatting Type|API Markdown|Outcome in Ticket Notes|
|
||||
|---|---|---|
|
||||
|Bold|\*\*This is bold\*\*|**This is bold**|
|
||||
|Italic|\*This is italic\*|_This is italic_|
|
||||
|Underlined|\_\_This is underlined\_\_|<u>This is underlined</u>|
|
||||
|Bold Italic|\*\*\*This is Bold Italic\*\*\*|_**This is Bold Italic**_|
|
||||
|Bold Underlined|\*\*\_\_This is Bold Underlined\_\_\*\*|**<u>This is Bold Underlined</u>**|
|
||||
|Underlined Italic|\*\_\_This is Underlined Italic\_\_\*|_<u>This is Underlined Italic</u>_|
|
||||
|Bold Underlined Italic|\*\*\*\_\_This is Bold Italic Underlined\_\_\*\*\*|_<u><strong>This is Bold Italic Underlined</strong></u>_|
|
||||
|Unformatting Formatted
|
||||
text|\\\\\*We have 10 pallets of rock \\n\*We have 5 rocks per pallet|\*We have 10 pallets of rock
|
||||
\*We have 5 rocks per pallet|
|
||||
|Ordered Lists|1\. Regular Ordered List 1\\n2. Regular Ordered List 2|1\. Regular Ordered List 1
|
||||
2\. Regular Ordered List 2|
|
||||
|Italic Ordered Lists|1\. \*Italics Ordered List 1\*\\n2. \*Italics Ordered List 2\*|1. _Italics Ordered List 1_
|
||||
2. _Italics Ordered List 2_|
|
||||
|Bold Ordered Lists|1\. \*\*Bold Ordered List 1\*\*\\n2. \*\*Bold Ordered List 2\*\*|1. **Bold** **Ordered List 1**
|
||||
2. **Bold** **Ordered List 2**|
|
||||
|Underlined Ordered Lists|1\. \_\_Underlined Ordered List 1\_\_\\n2. \_\_Underlined Ordered List 2\_\_|1. <u>Ordered List 1</u>
|
||||
2. <u>Ordered List 2</u>|
|
||||
|Underlined Link - Ordered List|1\. \_\_\[Ordered List 1\]([www.google.com/](http://www.google.com/))\_\_\\n2. \_\_\[Ordered List 2\]([www.google.com/](http://www.google.com/))\_\_|1. <u><a href="https://www.google.com/" rel="nofollow">Ordered List 1</a></u>
|
||||
2. <u><a href="https://www.google.com/" rel="nofollow">Ordered List 2</a></u>|
|
||||
|Italic Link - Ordered List|1\. \*\_\_\[Italic Ordered List 1\]([www.google.com/](http://www.google.com/))\_\_\*\\n2. \*\_\_\[Italic Ordered List 2\]([www.google.com/](http://www.google.com/))\_\_\*|1. _<u><a href="http://www.google.com/" rel="nofollow">Italic Ordered List 1</a></u>_
|
||||
2. _<u><a href="http://www.google.com/" rel="nofollow">Italic Ordered List 2</a></u>_|
|
||||
|Bold Link - Ordered List|1\. \*\*\_\_\[Bold Ordered List 1\]([www.google.com/](http://www.google.com/))\_\_\*\*\\n2. \*\*\_\_\[Bold Ordered List 2\]([www.google.com/](http://www.google.com/))\_\_\*\*|1. **<u><a href="http://www.google.com/" rel="nofollow">Bold Ordered List 1</a></u>**
|
||||
2. **<u><a href="http://www.google.com/" rel="nofollow">Bold Ordered List 2</a></u>**|
|
||||
|Bold Italic Link - Ordered List|1\. \*\*\*\_\_\[Bold Italic Ordered List 1\]([www.google.com/](http://www.google.com/))\_\_\*\*\*\\n2. \*\*\*\_\_\[Bold Italic Ordered List 2\]([www.google.com/](http://www.google.com/))\_\_\*\*\*|1. **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Ordered List 1</a></u>_**
|
||||
2. **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Ordered List 2</a></u>_**|
|
||||
|Bold Underlined Link - Ordered List|1\. \*\*\_\_\[Bold Underlined Ordered List 1\]([www.google.com/](http://www.google.com/))\_\_\*\*\\n2. \*\*\_\_\[Bold Underlined Ordered List 2\]([www.google.com/](http://www.google.com/))\_\_\*\*|1. **<u><a href="http://www.google.com/" rel="nofollow">Bold Underlined Ordered List 1</a></u>**
|
||||
|
||||
2. **<u><a href="http://www.google.com/" rel="nofollow">Bold Underlined Ordered List 2</a></u>**|
|
||||
|Italic Underlined Link - Ordered List|1\. \*\_\_\[Italic Underlined Link Ordered List 1\]([www.google.com/](http://www.google.com/))\_\_\*\\n2. \*\_\_\[Italic Underlined Link Ordered List 2\]([www.google.com/](http://www.google.com/))\_\_\*|1. _<u><a href="http://www.google.com/" rel="nofollow">Italic Underlined Link Ordered List 1</a></u>_
|
||||
|
||||
2. _<u><a href="http://www.google.com/" rel="nofollow">Italic Underlined Link Ordered List 2</a></u>_|
|
||||
|Regular Unordered List|\* Regular Unordered List\\n\* Regular Unordered List|- Regular Unordered List
|
||||
- Regular Unordered List|
|
||||
|Bold Unordered List|\* \*\*Bold Unordered List\*\*\\n\* \*\*Bold Unordered List\*\*|- **Bold Unordered List**
|
||||
- **Bold Unordered List**|
|
||||
|Italic Unordered List|\* \*Italic Unordered List\*\\n\* \*Italic Unordered List\*|- _Italic Unordered List_
|
||||
- _Italic Unordered List_|
|
||||
|Underlined Unordered List|\* \_\_Underlined Unordered List\_\_\\n\* \_\_Underlined Unordered List\_\_|- <u>Underlined Unordered List</u>
|
||||
- <u>Underlined Unordered List</u>|
|
||||
|Bold Link - Unordered List|\* \*\*\_\_\[Bold Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\\n\* \*\*\_\_\[Bold Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*|- **<u><a href="http://www.google.com/" rel="nofollow">Bold Link Unordered List</a></u>**
|
||||
- **<u><a href="http://www.google.com/" rel="nofollow">Bold Link Unordered List</a></u>**|
|
||||
|Italic Link - Unordered List|\* \*\_\_\[Italic Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\\n\* \*\_\_\[Italic Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*|- _<u><a href="http://www.google.com/" rel="nofollow">Italic Link Unordered List</a></u>_
|
||||
- _<u><a href="http://www.google.com/" rel="nofollow">Italic Link Unordered List</a></u>_|
|
||||
|Underlined Link – Unordered List|\* \_\_Underlined Unordered List\_\_\\n\* \_\_Underlined Unordered List\_\_|- <u>Underlined Link Unordered List</u>
|
||||
- <u>Underlined Link Unordered List</u>|
|
||||
|Bold Italic Link - Unordered List|\* \*\*\*\_\_\[Bold Italic Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\*\\n\* \*\*\*\_\_\[Bold Italic Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\*|- **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Unordered List</a></u>_**
|
||||
- **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Unordered List</a></u>_**|
|
||||
|Bold Underlined Link - Unordered List|\* \*\*\_\_\[Bold Underlined Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\\n\* \*\*\_\_\[Bold Underlined Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*|- **<u><a href="http://www.google.com/" rel="nofollow">Bold Underlined Unordered List</a></u>**
|
||||
- **<u><a href="http://www.google.com/" rel="nofollow">Bold Underlined Unordered List</a></u>**|
|
||||
|Italic Underlined Link - Unordered List|\* \*\_\_\[Italic Underlined Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\\n\* \*\_\_\[Italic Underlined Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*|- _<u><a href="http://www.google.com/" rel="nofollow">Italic Underlined Unordered List</a></u>_
|
||||
- _<u><a href="http://www.google.com/" rel="nofollow">Italic Underlined Unordered List</a></u>_|
|
||||
|Link|\_\_\[link\]([www.google.com](http://www.google.com/))\_\_|<u><a href="http://www.google.com/" rel="nofollow">link</a></u>|
|
||||
|Bold link|\*\*\_\_\[Bold link\]([www.google.com](http://www.google.com/))\_\_\*\*|**<u><a href="http://www.google.com/" rel="nofollow">Bold link</a></u>**|
|
||||
|Italics link|\*\_\_\[Italics link\]([www.google.com](http://www.google.com/))\_\_\*|_<u><a href="http://www.google.com/" rel="nofollow">Italics link</a></u>_|
|
||||
|Underlined link|\_\_\[Underlined link\]([www.google.com](http://www.google.com/))\_\_|<u><a href="http://www.google.com/" rel="nofollow">Underlined link</a></u>|
|
||||
|Bold Italic link|\*\*\*\_\_\[Bold Italic link\]([www.google.com](http://www.google.com/))\_\_\*\*\*|**_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic link</a></u>_**|
|
||||
|Bold Underlined link|\*\*\_\_\[Bold Underlined link\]([www.google.com](http://www.google.com/))\_\_\*\*|**<u><a href="http://www.google.com/" rel="nofollow">Bold Underlined link</a></u>**|
|
||||
|Italics Underlined link|\*\_\_\[Italics Underlined link\]([www.google.com](http://www.google.com/))\_\_\*|_<u><a href="http://www.google.com/" rel="nofollow">Italics Underlined link</a></u>_|
|
||||
|Bold Italic Underlined Link - Unordered List|\* \*\*\*\_\_\[Bold Italic Underlined Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\*\\n\* \*\*\*\_\_\[Bold Italic Underlined Unordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\*|- **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Underlined Unordered List</a></u>_**
|
||||
- **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Underlined Unordered List</a></u>_**|
|
||||
|Bold Italic Underlined Link - Ordered List|1\. \*\*\*\_\_\[Bold Italic Underlined Ordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\*\\n2. \*\*\*\_\_\[Bold Italic Underlined Ordered List\]([www.google.com/](http://www.google.com/))\_\_\*\*\*|1. **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Underlined Ordered List</a></u>_**
|
||||
2. **_<u><a href="http://www.google.com/" rel="nofollow">Bold Italic Underlined Ordered List</a></u>_**|
|
||||
|Image|!\[\]([https://media.giphy.com/media/LVrlKiw0VCl5lEMt4f/giphy.gif](https://media.giphy.com/media/EldfH1VJdbrwY/giphy.gif))|
|
||||
122
docs/connectwise/best-practices/PSA-Pagination.md
Normal file
122
docs/connectwise/best-practices/PSA-Pagination.md
Normal file
@@ -0,0 +1,122 @@
|
||||
1. Last updated
|
||||
|
||||
Mar 31, 2023
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/1266/pdf/PSA%2bPagination.pdf "Export page as a PDF")
|
||||
|
||||
## Overview
|
||||
|
||||
When working with ConnectWise PSA instances, you will run into a significant amount of data. This guide should help you to work with and manipulate that data for your integration.
|
||||
|
||||
## Pagination Defined
|
||||
|
||||
The concept of pagination is similar to that of pages within a book or when you are navigating a website or forum and there is a button to go to the next page. Simply put, when you have a large set of data, pagination breaks it up into different pages for consumption. The below is a perfect example of the pagination concept that you have probably seen before.
|
||||
|
||||

|
||||
|
||||
Pagination with APIs works a little bit differently but has the same underlying concept. When you try to retrieve a list of objects that is too large, either for the computational costs associated with consumption or any of the plethora of networking concerns (waiting for a few gigs of data from an API call can have unexpected effects on your application), the API will respond with information about how to access the next page of results. Instead of flipping through pages either physically or with a navigation tree, the API has a series of Link headers that allow you to do it programmatically without human interaction.
|
||||
|
||||
Business Case
|
||||
|
||||
ConnectWise products store a significant amount of data and often times developers may not understand the impact of their integration on a given environment. We have set a hard limit of 1,000 records for the protection of the Partner environments. Many integrations do not need data past this point and if we didn't have a set limit, it allow for the API to potentially lock records or cause overall performance problems.
|
||||
|
||||
## Implementation
|
||||
|
||||
There are two different methods to Pagination, each has it's own benefit or drawback depending on the type of integration you are creating.
|
||||
|
||||
|Type|Pros|Cons|Usage|
|
||||
|---|---|---|---|
|
||||
|Navigable|Allows you to create a UI that allows for human interaction in going back and forth between records.|Uses a large number of resources and results are returned slower as you get into the higher page sizes.|This example returns the first 100 results.
|
||||
service/tickets?pagesize=100&page=1|
|
||||
|Forward Only|Each call will return results at the same speed as the last. Data is returned from an indexed location.|There is no way to page backward. This means you can't design a navigable UI around it.|This example returns the first 100 results after Ticket#40941
|
||||
service/tickets?pagesize=100&conditions=id>1231|
|
||||
|
||||
Each version of paging will return a series of Link headers to help with navigation. Let's dive into each Pagination Method and how to utilize them.
|
||||
|
||||
## Navigable Pagination
|
||||
|
||||
The ConnectWise PSA API provides a vast wealth of information for developers to consume. Most of the time, you will find that there is far more information than is needed for integration. In order to ensure server availability, the API automatically sets the page size to 25 results. The maximum page size for any request can be up to 1,000 records if properly specified in the request URL.
|
||||
|
||||
|pagesize|The number of results returned by each call. Defaulted to 25 and has a maximum of 1,000. These values cannot be changed.|
|
||||
|---|---|
|
||||
|page|Starting with page 1, is the number of pages available based on the current pagesize.|
|
||||
|
||||
When using paging, the response will include a [Link header](https://tools.ietf.org/html/rfc5988 "https://tools.ietf.org/html/rfc5988") that looks like this:
|
||||
|
||||
|`1`|`/v``4``_``6``_release/apis/``3.0``/company/companies?pagesize=``50``&page=``1`|
|
||||
|---|---|
|
||||
|
||||
If you notice, the header contains two separate URLs in this instance. The first one is "next" and the second one is "last". These refer to the next set of results and the final set of results. If we navigate to the next page, we will get some additional URLs in the response Link header.
|
||||
|
||||
|`1`|`/v``4``_``6``_release/apis/``3.0``/company/companies?pagesize=``50``&page=``2`|
|
||||
|---|---|
|
||||
|
||||
Now that we have navigated to the second page, we will find two additional URLs. These are "prev" and "first" and in total we now have four including the original "next" and "last".
|
||||
|
||||
|First|This link will display the first page available based on the current page size|
|
||||
|---|---|
|
||||
|Prev\*|This link will display the previous page based on the current page position and page size|
|
||||
|Next\*|This link will display the next available page based on the current page position and page size|
|
||||
|Last|This link will always display the final page available based on the current page size|
|
||||
|
||||
\*We will not return the next or prev links if there isn't a next or prev respectively.
|
||||
|
||||
In the above examples, we have four pages of results and depending on the page size, that number can change. For instance, let's say we switch to the default page size of 25. Instead of returning four pages, we actually get back 7 pages as there are only 160 results.
|
||||
|
||||
The goal of pagination is not to pull every possible result, every single time. Instead, it is designed to be in conjunction with a UI component to create a set of navigation links.
|
||||
|
||||

|
||||
|
||||
> Navigable Pagination closely follows [RFC 5988](https://tools.ietf.org/html/rfc5988 "https://tools.ietf.org/html/rfc5988").
|
||||
|
||||
## Forward-Only Pagination
|
||||
|
||||
Released in 2018.5
|
||||
|
||||
Unlike Navigable Pagination, forward-only requires that you pass in a header to identify the type of pagination you would like to use. For instance, we now accept a header called pagination-type, and when you set that to forward-only you will get to utilize the new features. In addition to the new header, there is a new query parameter called pageId. The pageId is the record in which you would like to begin with for paging.
|
||||
|
||||
- If you do not include the new header in your request, it will use the default paging method of navigable.
|
||||
- The Page query parameter will be ignored with forward-only paging as all results are technically page 1.
|
||||
- You cannot use an Order By query parameter with forward-only as it must be ordered by the ID.
|
||||
- There will always be a link header in the response for the next pageId.
|
||||
- The pageId query parameter is treated like an additional condition of Id > pageId.
|
||||
- The following pageId in the header will be the last Id you got in the request.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
> Forward-Only pagination does not work with the Audit Trail endpoints at this time.
|
||||
|
||||
## How to Get All Records?
|
||||
|
||||
In most cases, when you experience pagination, you likely want all of the items in the list and aren’t very happy about the extra work required to page through the results. Here are some best practices to make it a little less painful to get all of your results:
|
||||
|
||||
### Avoid it
|
||||
|
||||
Although this doesn't sound ideal, conceptually this is how you will be able to get the most performance. Many APIs allow you to query for items based on specific criteria or only return certain subsets of the data. The PSA API is no different and for example, allows you to query based on any number of fields including time ranges. If you know anything about the data you are looking for, you can narrow down the result set from the beginning, in many cases eliminating pagination and giving you a performance boost in comparison to over-fetching and then filtering in your application.
|
||||
|
||||
### Pay Attention to the Headers
|
||||
|
||||
We only return the paging headers if there is something to the page. If you don't see the next page, don't try to query for the next page number. Use the paging headers programmatically to achieve your results and avoid trying to make them on your own.
|
||||
|
||||
### While looping
|
||||
|
||||
Depending on your programming language of choice, pagination can be a wonderful use case for While. The basic workflow is that while you are getting a pagination token in your response, keep making subsequent requests. In pseudo-code that might look like this:
|
||||
|
||||
```
|
||||
Page = GetPageOfItems();
|
||||
//process the data from the page, or add it to a larger array, etc.
|
||||
```
|
||||
|
||||
```
|
||||
while( Page->cursor )
|
||||
Page = GetPageOfItems(Page->cursor);
|
||||
//process the data again
|
||||
end
|
||||
```
|
||||
|
||||
As soon as your API stops responding with a pagination cursor, then you can stop looping and continue execution with the complete set of data. There are a couple of important points to remember with an approach like this:
|
||||
|
||||
- Any errors from the API that don’t return a cursor might prematurely stop your execution, so always check that you get a good response from the API.
|
||||
- You probably want to include some checks to make sure that you stop bringing in more information at some upper limit, just in case the API responds with much more information than you expect and keeps paging, and paging, and paging…
|
||||
48
docs/connectwise/best-practices/PSA-Service-Tickets.md
Normal file
48
docs/connectwise/best-practices/PSA-Service-Tickets.md
Normal file
@@ -0,0 +1,48 @@
|
||||
1. Last updated
|
||||
|
||||
Mar 31, 2023
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/1258/pdf/PSA%2bService%2bTickets.pdf "Export page as a PDF")
|
||||
|
||||
## Overview
|
||||
|
||||
Anything that is an actionable item should become a ticket. Today, the natural course of business is to send tasks and get things done via email. If you want to stick with email, you can send the email in to create a service ticket on the service board.
|
||||
|
||||
## Service Tickets
|
||||
|
||||
|Tickets are crucial to the success of any ConnectWise PSA integration. There are many different types of tickets and reasons for creating them:
|
||||
|---|
|
||||
1. Anything that requires work that you would spend time on
|
||||
2. Alert
|
||||
3. Portal request
|
||||
4. Client question
|
||||
5. Client request
|
||||
6. Voicemail
|
||||
7. Email
|
||||
8. Internal request
|
||||
When everything is a ticket, you are creating that accountability loop to ensure that tasks get completed. Keep in mind that tickets are the building blocks for projects, and they can be imported into a project.
|
||||
> Want to learn more? Watch the <u><a href="https://www.youtube.com/watch?v=5TQU5scQ8eA" rel="external nofollow" target="_blank" title="https://www.youtube.com/watch?v=5TQU5scQ8eA">Everything is a Ticket</a></u> video.|
|
||||
|
||||
## Business Case
|
||||
|
||||
|When it comes to ConnectWise, all roads lead to Rome. This concept means all things should be in ConnectWise. As an integrator, the pathway to mutual success is creating tickets. Tickets can come from:
|
||||
|---|
|
||||
1. Calls
|
||||
2. Emails
|
||||
3. Alerts
|
||||
4. Issues
|
||||
There are many intricacies with service tickets and the available fields that can be used to categorize, classify and add to the existing workflows of our mutual partners. From the figures provided, you can see that there are a large number of fields available for creating a tight integration experience.|
|
||||
|
||||
## Fields
|
||||
|
||||
|When it comes to actually creating and updating tickets there are many fields that can be utilized in order to create the best integration experience for the mutual partner.
|
||||
|---|
|
||||
When you are going through the initial integration setup, it is a good idea to map to as many of recommended fields as possible. A baseline integration is simply going to update a summary line and select a single service board.
|
||||
A best-in-class integration is going to be able to take all or most of the recommended fields and be able to build upon those in order to ensure that the integration is compatible with each partner's workflow.|**Summary** \- The subject line or description line for the ticket.
|
||||
**Board** \- The service board determines which team will be working on the service ticket after creation. This also helps to determine billing options.
|
||||
**Status** \- Statuses should be changed as the work for the service ticket progresses. Statuses are defined in the Status Tab of the Service Board setup table
|
||||
**Type** \- The type of service you are performing for this service ticket. Service types are defined in the Types Tab of the Service Board setup table.
|
||||
**Subtype** \- Subtypes are used to provide further detail to your main service type.
|
||||
**Item** \- Items are used to provide even further detail to your main service types and subtypes. Partners can use items to set up automated task additions to tickets as they are created based on service templates.
|
||||
**Priority** \- The impact and urgency are defined as a singular value to determine the priority of a ticket. Typically defined by a color.
|
||||
**Source** \- Where the ticket originated. This is usually something such as Email Connector, Phone, or from Integration.|
|
||||
50
docs/connectwise/best-practices/PSA-Versioning.md
Normal file
50
docs/connectwise/best-practices/PSA-Versioning.md
Normal file
@@ -0,0 +1,50 @@
|
||||
1. Last updated
|
||||
|
||||
Mar 31, 2023
|
||||
|
||||
2. [Save as PDF](https://developer.connectwise.com/@api/deki/pages/1118/pdf/PSA%2bVersioning.pdf "Export page as a PDF")
|
||||
|
||||
Starting with 2019.1 the API is now tied to the ConnectWise PSA release cycle. Previously the API was versioned as it's own entity and followed a format such as 3.0.1. To provide clarity around the API Versioning and to make it easier for consumers, we have replaced 3.0.0 with the specific ConnectWise PSA version that the API model was released on.
|
||||
|
||||
If you develope your API against 2019.1 and pass in the version 2019.1, you will continue to get the models for 2019.1 even if we change them in 2019.2. What this means is that each time we release a breaking change, you have a period of time to switch over to the new version. We do not release new models every release, however you can still target a version that did not have any changes. For instance, lets say in 2019.1 we have a specific ticket model called A. In 2019.4 we update tickets to have model B. If you pass 2019.1, 2019.2 or 2019.3, you will get model A, which is 2019.1.
|
||||
|
||||
By default all requests will receive the latest version of our API endpoint. As breaking changes are released, each individual API endpoint, may receive an updated version. In order to prevent your integration from breaking, we encourage you to request a specific version using an Accept header. For all production level integrations we recommend using this Accept header concept and for all development and testing work, we recommend using the latest version of the API. We will regularly deprecate old models of the API.
|
||||
|
||||
### What is a breaking change?
|
||||
|
||||
Breaking changes are defined as any change to the APIs that could result in an error being returned by the SDK. The SDK is a JSON interpreter and as long as you develop your code the way a modern JSON interpreter would, you shouldn't have any issues. If you hard code every field, you will have issues that we don't consider breaking.
|
||||
|
||||
#### **Examples of breaking changes:**
|
||||
|
||||
- Renaming a field (fixing a typo)
|
||||
|
||||
- Changing a field's data type (such as from an int to a decimal, or from editable to read only)
|
||||
|
||||
- Changing the response type (such as from an object to an array of objects)
|
||||
|
||||
- Validation changes (fields now required that previously were not)
|
||||
|
||||
|
||||
#### **NOT a breaking change:**
|
||||
|
||||
- Adding new fields
|
||||
|
||||
- Adding new endpoints
|
||||
|
||||
- Making a read only field editable
|
||||
|
||||
- Making a field no longer required if it was previously required
|
||||
|
||||
|
||||
> As we release breaking changes the individual endpoint will be versioned when applicable. The previous version will be immediately be considered deprecated but supported for 12 months. Please note that some endpoints cannot be versioned.
|
||||
|
||||
### Formatting Version Header
|
||||
|
||||
We try to make it as simple as possible to format your version header. We use an accept header that lists our JSON schema as well as the version following. Accept headers tell the server what type of information you are expecting to get back. This makes it a perfect fit to add our version to as this tells the server that you want xyz version back.
|
||||
|
||||
|`1`|`Accept: application/vnd.connectwise.com+json; version=``2019.1`|
|
||||
|---|---|
|
||||
|
||||
### Per Endpoint
|
||||
|
||||
If you are not ready to update to the latest API Models for your entire integration, you can change your accept header to be on an endpoint by endpoint basis. We recommend that you keep a singular version across your integration, but will support it either way. Anytime you test your integration and validate it against a version, update your version header even if we are not deprecating the version you are on. We may do so without warning, or give you very little time to make changes.
|
||||
303215
docs/connectwise/connectwise-psa-openapi-full.json
Normal file
303215
docs/connectwise/connectwise-psa-openapi-full.json
Normal file
File diff suppressed because it is too large
Load Diff
111573
docs/connectwise/connectwise-psa-resolutionflow-reference.json
Normal file
111573
docs/connectwise/connectwise-psa-resolutionflow-reference.json
Normal file
File diff suppressed because it is too large
Load Diff
1482
docs/plans/2026-03-14-connectwise-psa-integration-plan.md
Normal file
1482
docs/plans/2026-03-14-connectwise-psa-integration-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user