# /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 (with optional time entry)

| Operation | Method | Endpoint | Body |
|---|---|---|---|
| Add comment | POST | `/tickets/<id>/comment` | `{"subject": "Update", "body": "...", "hidden": false, "do_not_email": false}` |
| Add comment + time | POST | `/tickets/<id>/comment` | Same as above, PLUS: `"product_id": N, "minutes_spent": 60, "bill_time_now": 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
- `product_id` — labor product ID (see labor products table below). Adds billable time to the ticket.
- `minutes_spent` — integer, minutes of work (60 = 1hr minimum in most cases)
- `bill_time_now` — if true, immediately creates a charge (equivalent to "Charge now" checkbox in GUI)

**This is the primary way to log time.** Comment + time in one call mirrors the GUI workflow exactly. Timer entries (`/tickets/{id}/timer_entry`) exist but are rarely used.

#### 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
2. Ask: "How many minutes + labor type?" (default: 60 min, Labor - Remote Business)
3. Add comment with time: `POST /tickets/{id}/comment` with `product_id`, `minutes_spent`, `bill_time_now: false`, and work notes as body
4. Then create invoice: `POST /invoices` with `{"ticket_id": N, "customer_id": N, "category": "Standard"}`
5. Update ticket status to "Invoiced"

**The flow mirrors the GUI: add comment with time attached → Make Invoice.**

When `/syncro comment <number> <text> --time 60 --labor remote` is called:
- Post the comment with time in one API call
- `--labor` maps to product IDs: `remote` → 1190473, `onsite` → 26118, `emergency` → 26184, `project` → 9269129, `internal` → 9269124, `travel` → 26117, `website` → 68055

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