Timer entries use POST /tickets/{id}/timer_entry with labor product IDs
(not invoice products). "Make Invoice" converts timers to invoice.
Documented 7 common labor products with IDs. Fixed line_items path to
/invoices/{id}/line_items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
6.2 KiB
Markdown
163 lines
6.2 KiB
Markdown
# /syncro — Syncro PSA ticket management
|
|
|
|
Create, update, close, comment on, and bill tickets in Syncro PSA.
|
|
|
|
## Usage
|
|
|
|
```
|
|
/syncro Show open tickets summary
|
|
/syncro ticket <number> View ticket details + comments
|
|
/syncro create <customer> <subject> Create new ticket
|
|
/syncro update <number> <status> Update ticket status
|
|
/syncro close <number> Close/resolve a ticket
|
|
/syncro comment <number> <text> Add a comment to a ticket
|
|
/syncro bill <number> Create invoice from ticket time entries
|
|
/syncro search <query> Search tickets by subject/customer
|
|
/syncro customers <query> Search customers
|
|
```
|
|
|
|
## API Configuration
|
|
|
|
**Base URL:** `https://computerguru.syncromsp.com/api/v1`
|
|
**API Key:** SOPS vault `msp-tools/syncro.sops.yaml` → `credentials.credential`
|
|
**Rate limit:** 180 requests/minute per IP
|
|
**Docs:** https://api-docs.syncromsp.com/
|
|
|
|
## Implementation
|
|
|
|
When invoked, use the Syncro REST API via `curl`. All requests include `?api_key=<key>` as query parameter (NOT in header — Syncro uses query param auth).
|
|
|
|
### Get API key
|
|
|
|
```bash
|
|
API_KEY=$(bash D:/vault/scripts/vault.sh get-field msp-tools/syncro.sops.yaml credentials.credential)
|
|
BASE="https://computerguru.syncromsp.com/api/v1"
|
|
```
|
|
|
|
If `vault.sh get-field` fails (yq not installed), fall back to:
|
|
```bash
|
|
API_KEY=$(sops -d D:/vault/msp-tools/syncro.sops.yaml | python -c "import sys,yaml; print(yaml.safe_load(sys.stdin)['credentials']['credential'])")
|
|
```
|
|
|
|
### Endpoints reference
|
|
|
|
#### Tickets
|
|
|
|
| Operation | Method | Endpoint | Body |
|
|
|---|---|---|---|
|
|
| List tickets | GET | `/tickets?status=<status>&per_page=25` | — |
|
|
| Get ticket | GET | `/tickets/<id>` | — |
|
|
| Create ticket | POST | `/tickets` | `{"customer_id": N, "subject": "...", "problem_type": "...", "status": "New"}` |
|
|
| Update ticket | PUT | `/tickets/<id>` | `{"status": "In Progress", "priority": "..."}` |
|
|
| Delete ticket | DELETE | `/tickets/<id>` | — |
|
|
|
|
**Ticket statuses:** `New`, `In Progress`, `Waiting on Customer`, `Waiting on Vendor`, `Scheduled`, `Resolved`, `Invoiced`, `Closed`
|
|
|
|
**Ticket fields (create/update):**
|
|
- `customer_id` (required for create)
|
|
- `subject` (required for create)
|
|
- `problem_type` (string, free-form)
|
|
- `status` (string, one of the statuses above)
|
|
- `priority` (string)
|
|
- `due_date` (ISO date)
|
|
- `user_id` (assign to tech)
|
|
- `contact_id` (customer contact)
|
|
- `ticket_type_id` (ticket category)
|
|
|
|
#### Comments
|
|
|
|
| Operation | Method | Endpoint | Body |
|
|
|---|---|---|---|
|
|
| Add comment | POST | `/tickets/<id>/comment` | `{"subject": "Update", "body": "...", "hidden": false, "do_not_email": false}` |
|
|
|
|
**Comment fields:**
|
|
- `subject` — comment header (e.g., "Update", "Resolution", "Internal Note")
|
|
- `body` — comment text (HTML supported)
|
|
- `hidden` — if true, internal-only (customer can't see)
|
|
- `do_not_email` — if true, don't email customer about this comment
|
|
|
|
#### Customers
|
|
|
|
| Operation | Method | Endpoint |
|
|
|---|---|---|
|
|
| List/search | GET | `/customers?query=<search>&per_page=25` |
|
|
| Get customer | GET | `/customers/<id>` |
|
|
| Create customer | POST | `/customers` |
|
|
|
|
#### Timer Entries (add time to ticket)
|
|
|
|
| Operation | Method | Endpoint | Body |
|
|
|---|---|---|---|
|
|
| Add time | POST | `/tickets/<id>/timer_entry` | `{"start_at": "ISO8601", "end_at": "ISO8601", "notes": "...", "billable": true, "product_id": N}` |
|
|
| List timers | GET | `/ticket_timers?ticket_id=<id>` |
|
|
|
|
**IMPORTANT:** `product_id` must be a **labor product**, not an invoice product. Common labor products:
|
|
- `1190473` — Labor - Remote Business (standard remote work)
|
|
- `26118` — Labor - Onsite Business
|
|
- `26184` — Labor - Emergency or After Hours Business
|
|
- `9269129` — Labor - Prepaid Project Labor
|
|
- `9269124` — Labor - Internal Labor
|
|
- `26117` — Fee - Travel Time
|
|
- `68055` — Labor - Website Labor
|
|
|
|
#### Invoices
|
|
|
|
| Operation | Method | Endpoint | Body |
|
|
|---|---|---|---|
|
|
| List invoices | GET | `/invoices?per_page=25` |
|
|
| Get invoice | GET | `/invoices/<id>` |
|
|
| Create from ticket | POST | `/invoices` | `{"ticket_id": N, "customer_id": N, "category": "Standard"}` |
|
|
| Delete invoice | DELETE | `/invoices/<id>` | — |
|
|
|
|
**"Make Invoice" flow:** Timer entries on the ticket become invoice line items when you POST `/invoices` with the ticket_id. This is the equivalent of clicking "Make Invoice" in the GUI.
|
|
|
|
#### Invoice Line Items
|
|
|
|
| Operation | Method | Endpoint | Body |
|
|
|---|---|---|---|
|
|
| Add line item | POST | `/invoices/<id>/line_items` | `{"item": "...", "quantity": 1, "price": 125.00, "product_id": N}` |
|
|
|
|
### Display formatting
|
|
|
|
When showing ticket lists, format as:
|
|
|
|
```
|
|
#32164 New Jerry Burger Own cloud thing again
|
|
#32163 New LeeAnn Parkinson Remote - Jim cant access his email
|
|
#32162 Invoiced Len's Auto Brokerage Server upgrade
|
|
```
|
|
|
|
When showing ticket detail, include:
|
|
- Ticket number, subject, status, priority
|
|
- Customer name + contact
|
|
- Created date, due date, last updated
|
|
- Assigned tech
|
|
- Comments (most recent first, truncated to last 5)
|
|
- Time entries if any
|
|
- Billing status
|
|
|
|
### Billing workflow
|
|
|
|
When `/syncro bill <number>` is called:
|
|
1. Get ticket details + existing time entries
|
|
2. If no timer entries exist, ask: "Add time? How many hours + labor type?"
|
|
3. Add timer entry via `POST /tickets/{id}/timer_entry` with correct labor product_id
|
|
4. Show summary: total billable time, labor product, estimated total
|
|
5. Ask for confirmation: "Create invoice? [yes/no]"
|
|
6. On yes: `POST /invoices` with `{"ticket_id": N, "customer_id": N, "category": "Standard"}`
|
|
7. Update ticket status to "Invoiced"
|
|
|
|
**The flow is: timer entries → Make Invoice → Syncro auto-creates line items from timers.**
|
|
Do NOT create line items manually unless adding non-labor charges (parts, products, etc.).
|
|
|
|
### Error handling
|
|
|
|
- 401: API key invalid or expired
|
|
- 404: ticket/customer/invoice not found
|
|
- 422: validation error (show the error message from response body)
|
|
- 429: rate limited (wait 60s and retry)
|
|
|
|
### Integration with session logs
|
|
|
|
When closing a ticket (`/syncro close`), offer to create a session log entry in `clients/<customer>/session-logs/` documenting what was resolved. Pull the ticket subject, comments, and resolution into a structured log.
|