Discovered from GUI page source: comment[product_id] + comment[minutes_spent]
+ comment[bill_time_now] are fields on POST /tickets/{id}/comment. This is
how the GUI adds time — as part of the comment, not via separate timer_entry.
Updated billing workflow + added --time/--labor flags to comment command.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.9 KiB
/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
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:
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 commentproduct_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 Business26184— Labor - Emergency or After Hours Business9269129— Labor - Prepaid Project Labor9269124— Labor - Internal Labor26117— Fee - Travel Time68055— 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:
- Get ticket details
- Ask: "How many minutes + labor type?" (default: 60 min, Labor - Remote Business)
- Add comment with time:
POST /tickets/{id}/commentwithproduct_id,minutes_spent,bill_time_now: false, and work notes as body - Then create invoice:
POST /invoiceswith{"ticket_id": N, "customer_id": N, "category": "Standard"} - 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
--labormaps 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.