Back to blog
FILE 0x4D·THE 8 MINUTES MSP TECHS SPEND WRITING THE SAME TIME ENTRY EV

The 8 minutes MSP techs spend writing the same time entry every day

June 9, 2026 · msp, ticketscope, connectwise, python, chrome-extension, ai

Here's a thing every MSP tech does every day: they close a ticket, go to the Time tab, stare at the notes they wrote at 2 PM when they were knee-deep in the problem, and spend 5-10 minutes cleaning them up into something they'd be comfortable billing a client for.

"Fixed email" becomes "Investigated reports of delayed email delivery for ACME Corp. Identified a misconfigured SPF record that was causing intermittent rejection by external mail servers. Updated DNS records and confirmed delivery within 5 minutes."

That's the same transformation every time. You have rough notes. You want polished billable language. An LLM is extremely good at this.


The endpoint

POST /time-entry on the TicketScope Lambda:

def _handle_time_entry(body: dict) -> dict:
    ticket_id = body.get("ticket_id")
    creds = _extract_creds(body)
    
    ticket = _cw_get(f"{cw_base_url}/service/tickets/{ticket_id}", headers)
    notes = _cw_get(f".../tickets/{ticket_id}/notes?pageSize=20&orderBy=dateCreated asc", headers)
    time_entries = _cw_get(f".../time/entries?conditions=chargeToId={ticket_id}...", headers)
    
    result = _draft_time_entry(ticket, notes, time_entries)
    return _json(200, result)

It fetches three things from ConnectWise: the ticket (for summary, company, board), the notes thread (for what actually happened), and the existing time entries (so it knows how many hours have already been logged — useful context for estimating what's left).

The prompt is explicit about what "billable notes" means:

Write 2-4 sentences of professional, client-facing billable notes. Write in past 
tense. Start with what was investigated or reported. End with the resolution or 
status. No jargon abbreviations the client wouldn't know. No first-person 'I'.

The result is:

{
  "notes_text": "Investigated delayed email delivery for ACME Corp. ...",
  "hours_suggested": 0.5,
  "work_type_hint": "Remote",
  "confidence": "high"
}

hours_suggested is in 0.25 increments (that's how ConnectWise bills). work_type_hint guesses Remote/On-Site/Internal from the notes. confidence reflects whether the notes were clear enough to be sure.


The stub mode

When ANTHROPIC_API_KEY is empty (dev/test), it falls back to a deterministic stub:

def _stub_time_entry(summary: str, company: str, notes: list) -> dict:
    action = "Reviewed and addressed the reported issue"
    if notes:
        last_note = (notes[-1].get("text") or "").strip()
        if last_note:
            action = last_note[:120]
    return {
        "notes_text": f"Investigated {summary.lower()} for {company}. {action}. Confirmed resolution.",
        "hours_suggested": 0.5,
        "work_type_hint": "Remote",
        "confidence": "low",
    }

Good enough for the shape of the response to be testable without burning API tokens.


The Chrome extension

The sidebar now has a "Draft time entry…" button below the "Draft resolution…" button. Click it → the extension sends a TIME_ENTRY message to background.js → background.js POSTs to /time-entry → result renders in the sidebar with notes text, estimated hours, and work type.

The "Copy time entry notes" button drops the notes_text to clipboard. From there it's one paste into ConnectWise's Notes field on the time entry form.

I considered auto-filling the CW time entry form (TicketScope can already read the DOM), but copy-paste is safer — the tech reviews what they're submitting before it goes into a billed entry. The goal is to eliminate the writing friction, not the review step.


The math

If a technician closes 8 tickets a day and spends 7 minutes per time entry:

A Chrome extension that cuts that to 2 minutes (review and edit vs. write from scratch) saves $600/month per tech. TicketScope at $29/mo is an easy sell.


TicketScope is in closed beta. If you want to try it: chester@cwfrazier.com.