Compressed memory store 104 -> 71 files via four passes: - Syncro: 19 scattered feedback_syncro_* files merged into 3 rule files (api/billing/workflow) + an on-demand feedback_syncro_history.md for incident detail, quotes, and tech/product ID tables. - Four near-duplicate merges: Howard paste-safety, Pluto build server, Howard backend deferral, IX server access (ssh+tailscale). - Per-cluster rule/state/history split applied to GuruConnect (2->1), Dataforth (3->2), Cascades (7->3), GuruRMM (13->3). - New reference_resource_map.md: single auto-loaded cheatsheet for "do I have access to X and how do I connect from this machine?" - MEMORY.md rewritten to match the new layout. Health: broken backlinks 8->7, overlap clusters 12->5, orphans 17->0.
83 lines
3.9 KiB
Markdown
83 lines
3.9 KiB
Markdown
---
|
|
name: Syncro API plumbing — headers, endpoints, response shapes, idempotency
|
|
description: Technical mechanics for talking to the Syncro API — required Content-Type header, the no-idempotency rule (always GET before retry), response wrappers, the add_line_item endpoint shape, HTML rendering, and the (now historical) timer_entry response shape.
|
|
metadata:
|
|
type: feedback
|
|
---
|
|
|
|
Rules only. Incident detail, verbatim quotes, ticket numbers, and dates live in [[feedback_syncro_history]] — read on-demand when judging an edge case. Billing/product rules: [[feedback_syncro_billing]]. Workflow rules: [[feedback_syncro_workflow]].
|
|
|
|
---
|
|
|
|
## 1. Content-Type header is required on every POST/PUT
|
|
|
|
Always include `-H "Content-Type: application/json"`. Without it, curl sends `application/x-www-form-urlencoded` and Syncro returns a **400 HTML page** (not JSON). Applies to comments, tickets, line items, estimates, updates.
|
|
|
|
Ticket comment payloads also need the `subject` field:
|
|
```json
|
|
{"subject":"...","body":"...","hidden":true,"do_not_email":true}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. No idempotency — ALWAYS GET before retrying any POST
|
|
|
|
Syncro has **no idempotency on any endpoint**. One `POST` always creates one record, regardless of whether the client saw an error. A jq parse error, curl error, timeout, or weird-looking response does NOT mean the POST failed — verify first.
|
|
|
|
**Verification before retry:**
|
|
- Comments: `GET /tickets/{id}` and search `.ticket.comments[] | select(.subject == "…")`. Check ALL comments, not just `[-3:]`.
|
|
- Tickets: `GET /customers/{id}/tickets` before retrying.
|
|
- Line items: `GET /tickets/{id}` → `.ticket.line_items[]`.
|
|
|
|
**Response wrappers — CRITICAL for jq:**
|
|
- `POST /tickets` → `{"ticket": {...}}` → use `.ticket.id`.
|
|
- `POST /comment` → `{"comment": {...}}` → use `.comment.id`.
|
|
|
|
**Hardening:** Write payload JSON to a temp file (e.g. `tmp/syncro_comment.json`) before posting. Avoids shell quoting/encoding failures that masquerade as POST failures on requests that actually succeeded.
|
|
|
|
Comments cannot be deleted via API — duplicates require manual GUI removal.
|
|
|
|
---
|
|
|
|
## 3. HTML formatting in comments
|
|
|
|
Use `<br>` for line breaks. Do **NOT** use `<ul>` or `<li>` — Syncro's renderer collapses them into one line. For bulleted lists:
|
|
```html
|
|
<p>
|
|
- Item one<br>
|
|
- Item two<br>
|
|
- Item three
|
|
</p>
|
|
```
|
|
|
|
---
|
|
|
|
## 4. add_line_item endpoint
|
|
|
|
`POST /api/v1/tickets/{internal_ticket_id}/add_line_item`
|
|
|
|
- Path uses the **internal ticket ID** (e.g. `111387456`), NOT the ticket number (`32339`). Wrong-ID variants (`/line_item`, `/line_items`, `PUT line_items_attributes`) all 404.
|
|
- Required fields: `name`, `description`, `quantity`, `price`, `taxable` (and `product_id` for catalog items). Missing `name`/`description` → 422.
|
|
- Response is **flat** — parse `.id` directly (no wrapper).
|
|
|
|
Example:
|
|
```bash
|
|
curl -X POST "$BASE/tickets/111387456/add_line_item?api_key=$KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"product_id":1049360,"name":"Labor- Warranty work","description":"…","quantity":1,"price":0.0,"taxable":false}'
|
|
```
|
|
|
|
For ad-hoc API testing, use the internal ACG account only (customer ID `15353550`).
|
|
|
|
---
|
|
|
|
## 5. Timer response — HISTORICAL / SUPERSEDED
|
|
|
|
> Timers are no longer part of the ACG Syncro billing workflow (as of 2026-05-21 — see [[feedback_syncro_billing]] and [[feedback_syncro_history]]). Kept here for the rare manual-timer case.
|
|
|
|
`POST /tickets/{id}/timer_entry` returns a **FLAT** object — parse `.id`, NOT `.timer.id // .timer_entry.id` (both resolve to `null` and break `charge_timer_entry`, which can trigger a retry → duplicate). `charge_timer_entry` response is also flat: use `.ticket_line_item_id`.
|
|
|
|
Authoritative timer list for a ticket: `GET /tickets/{id}` → `.ticket.ticket_timers[]`. The standalone `/ticket_timers?ticket_id=N` returns global history, not filtered.
|
|
|
|
Cleanup duplicates: `POST /tickets/{id}/delete_timer_entry` with `{"timer_entry_id": N}`.
|