From eb73a55442e0e82b68a73a3c0a972dd34528b137 Mon Sep 17 00:00:00 2001 From: Howard Enos Date: Tue, 5 May 2026 16:47:31 -0700 Subject: [PATCH] sync: auto-sync from HOWARD-HOME at 2026-05-05 16:47:31 Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-05-05 16:47:31 --- .claude/commands/syncro.md | 47 +++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/.claude/commands/syncro.md b/.claude/commands/syncro.md index 6e21727..6a5773b 100644 --- a/.claude/commands/syncro.md +++ b/.claude/commands/syncro.md @@ -486,12 +486,43 @@ This is the documented billing path. It records hours into Syncro's time-trackin | Charge timer (creates line item) | POST | `/tickets//charge_timer_entry` | | Update timer | PUT | `/tickets//update_timer_entry` | | Delete timer | POST | `/tickets//delete_timer_entry` | -| List timers | GET | `/ticket_timers?ticket_id=` | +| List timers (on a ticket) | GET | `/tickets/` → `.ticket.ticket_timers` | + +**CRITICAL — response shapes are FLAT:** Both `POST /timer_entry` and `POST /charge_timer_entry` return a flat object — `{"id": N, "ticket_id": ..., "product_id": ..., ...}` — NOT wrapped in `{"timer": {...}}` or `{"timer_entry": {...}}`. Parse as `.id` directly. The wrapped pattern silently returns `null`, breaks `charge_timer_entry` ("Not found"), and triggers a duplicate-timer retry. Hit on ticket #32253 on 2026-05-05; recovery via `delete_timer_entry`. Verified shape: + +```json +// POST /tickets/{id}/timer_entry response +{"id": 39031258, "ticket_id": 109895882, "user_id": 1750, "start_time": "...", "end_time": "...", + "recorded": false, "billable": true, "notes": "...", "product_id": 26118, + "comment_id": null, "ticket_line_item_id": null, "active_duration": 1800, ...} + +// POST /tickets/{id}/charge_timer_entry response (also flat) +{"id": 39031258, "recorded": true, "ticket_line_item_id": 42313052, ...} +``` + +**CRITICAL — duplicate prevention:** Syncro has no idempotency on `/timer_entry`. **Never retry the POST without first GET `/tickets/{id}` and inspecting `.ticket.ticket_timers[]`.** The standalone `GET /ticket_timers?ticket_id=N` query parameter does NOT filter — it returns the entire global timer history. Use the ticket object instead. + +```bash +# Verification pattern after ambiguous timer_entry response +curl -s "${BASE}/tickets/${ID}?api_key=${API_KEY}" | \ + jq '.ticket.ticket_timers[] | select(.recorded == false) | {id, start_time, end_time, product_id, notes}' +``` + +If duplicates exist, delete the older one(s) before charging: +```bash +curl -s -X POST "${BASE}/tickets/${ID}/delete_timer_entry?api_key=${API_KEY}" \ + -H "Content-Type: application/json" \ + --data-binary @- <<'JSON' +{"timer_entry_id": } +JSON +# Returns: {"success": true} +``` ```bash # 1. Create timer entry — records hours in Syncro's time-tracking system. # For warranty / no-charge work, set "billable": false (time still records). -curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KEY}" \ +# Capture .id directly — response is FLAT (see above). +TIMER_RESP=$(curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KEY}" \ -H "Content-Type: application/json" \ --data-binary @- <<'JSON' { @@ -502,13 +533,15 @@ curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KEY}" \ "product_id": 1190473 } JSON +) +TIMER_ID=$(echo "$TIMER_RESP" | jq -r '.id') # 2. Charge the timer — sets recorded:true and auto-generates a linked line # item with the timer's product_id and computed quantity (hours). curl -s -X POST "${BASE}/tickets/${ID}/charge_timer_entry?api_key=${API_KEY}" \ -H "Content-Type: application/json" \ - --data-binary @- <<'JSON' -{"timer_entry_id": N} + --data-binary @- </charge_timer_entry` | | Update timer | PUT | `/tickets//update_timer_entry` | | Delete timer | POST | `/tickets//delete_timer_entry` | -| List timers | GET | `/ticket_timers?ticket_id=` | +| List timers (on a ticket) | GET | `/tickets/` → `.ticket.ticket_timers` | + +Both `POST /timer_entry` and `POST /charge_timer_entry` return FLAT objects — parse `.id` directly. See "Billable Line Items → Default" above for the full response-shape note and duplicate-prevention pattern. #### Invoices @@ -716,7 +751,7 @@ TIMER_RESP=$(curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KE } JSON ) -TIMER_ID=$(echo "$TIMER_RESP" | jq -r '.timer.id // .timer_entry.id') +TIMER_ID=$(echo "$TIMER_RESP" | jq -r '.id') # response is FLAT — see "response shapes" note above # Step 3: Charge the timer — creates the linked line item automatically. curl -s -X POST "${BASE}/tickets/${ID}/charge_timer_entry?api_key=${API_KEY}" \