
Implementation Guide: Auto-generate time entries from calendar and document activity logs
Step-by-step implementation guide for deploying AI to auto-generate time entries from calendar and document activity logs for Legal Services clients.
Hardware Procurement
Dell Latitude 5450 Business Laptop
$1,150 per unit (MSP cost via Dell Premier) / $1,400 suggested resale
Standard attorney workstation capable of running the desktop time-capture agent (Billables AI or Memtime) alongside Microsoft 365 apps, Clio web client, and legal research tools concurrently. 16GB RAM ensures the background agent (~200MB footprint) does not impact performance. Only needed if client requires workstation refresh; existing machines meeting minimum specs (i5/8GB/256GB SSD/Win10+) are sufficient.
Lenovo ThinkPad T14s Gen 6 Business Laptop
$1,200 per unit (MSP cost via Lenovo Partner Direct) / $1,500 suggested resale
Alternative to Dell Latitude for firms preferring Lenovo. Same role as above — primary attorney workstation. ThinkPad keyboard quality is preferred by many attorneys for extended document drafting. Only procure if workstation refresh is needed.
Apple MacBook Air M3
$1,199 per unit (MSP cost via Apple Business) / $1,399 suggested resale
Alternative workstation for Mac-preferred firms. Memtime supports macOS natively; Billables AI browser extension works cross-platform. Only procure for firms with established Mac workflows.
Dell UltraSharp U2422H Monitor
$230 per unit (MSP cost) / $310 suggested resale
Secondary display for attorney timesheet review workflow. Dual monitors allow attorneys to review AI-generated draft time entries on one screen while referencing the original calendar/document on the other, significantly improving review efficiency.
Software Procurement
Clio Manage Advanced
$119/user/month (annual billing) or $149/user/month (monthly billing). MSP earns 20% lifetime recurring commission through Clio Partner Program = ~$23.80/user/month ongoing revenue.
Core practice management and billing platform. Serves as the destination for all AI-generated time entries. Provides client/matter database, billing rate tables, pre-bill review workflow, trust/IOLTA accounting, invoice generation, and online payments. Advanced tier required for API access, custom fields, and advanced reporting.
Billables AI (Full-Featured Plan)
$59–$99/user/month depending on seat count and feature tier. Negotiate MSP volume pricing for 10+ seats; estimated 15% resale margin.
Primary AI passive time-capture agent. Syncs with Clio Manage clients and matters, monitors Microsoft 365 activity (calendar, email, documents), captures billable work in the background, generates complete time entries with AI-created narratives and automatic client-matter matching. Desktop and browser-based agent.
Memtime Connect
$21/user/month (Connect tier required for Clio integration). Volume discounts available for 10+ seats. Estimated 20% MSP margin on volume purchases.
Alternative AI time-capture agent with privacy-first architecture. All activity data stored locally on the attorney's machine (offline-first), not in vendor cloud. Supports Windows, macOS, and Linux. Integrates with Clio via Connect tier. Recommended for firms with heightened confidentiality concerns or when Billables AI pricing is prohibitive.
Microsoft 365 Business Premium
$22/user/month via CSP. MSP earns 15–20% margin = ~$3.30–$4.40/user/month. Increasing to $24.75/user/month effective April 2025 (new pricing announced).
Primary productivity suite providing calendar events, email metadata, document activity signals, and Teams call logs that feed the time-capture agent. Business Premium tier recommended over Standard for Intune MDM (desktop agent deployment), Azure AD P1 (SSO/conditional access), and advanced security (Defender for Office 365) required for legal compliance.
Tempello
$0.39 per matched email. No subscription, no minimums, no long-term contracts. Estimated $195–$780/attorney/month.
Optional add-on for email-specific time capture. Scans Gmail or Outlook email, matches messages to Clio matters, and generates time entries for email correspondence. Useful as a supplement to Billables AI for firms with heavy email billing, or as standalone low-risk entry point for cautious firms.
Azure Active Directory (Entra ID) P1
Included in M365 Business Premium license — no additional cost
Identity provider for SSO to Clio, conditional access policies for time-capture agents, and OAuth 2.0 consent management for Microsoft Graph API permissions. Required for centralized authentication governance.
Microsoft Intune
Included in M365 Business Premium license — no additional cost
Mobile Device Management / endpoint management platform used to deploy and manage the desktop time-capture agent (Billables AI or Memtime) across all attorney workstations via Intune Win32 app deployment or configuration profiles.
Prerequisites
- Active Microsoft 365 Business Premium (or Standard minimum) tenant with all attorney and paralegal accounts provisioned and licensed
- Azure AD (Entra ID) configured with MFA enabled for all users — required for ABA Rule 1.6 compliance and Clio SSO
- Clio Manage account provisioned (Essentials tier minimum, Advanced tier recommended) with all clients, matters, billing rates, and activity/task codes configured
- Client/matter structure in Clio must be current and accurate — AI matching depends on clean matter names and client records
- All attorney workstations running Windows 10 22H2+ or macOS 12+ with minimum 8GB RAM (16GB recommended), 256GB SSD, and current browser (Chrome 120+ or Edge 120+)
- Stable internet connectivity: minimum 25 Mbps symmetric for 10 concurrent users, 50+ Mbps recommended
- Firewall/proxy configured to allow outbound HTTPS (port 443) to: *.clio.com, *.billables.ai (or *.memtime.com), login.microsoftonline.com, graph.microsoft.com, and *.office365.com
- Global Admin credentials for Microsoft 365 tenant (for Graph API consent and Intune configuration)
- Clio account with Admin role (for API key generation, webhook configuration, and user management)
- RMM agent (e.g., ConnectWise Automate, Datto RMM, NinjaRMM) installed on all workstations for remote deployment and monitoring
- Designated pilot group of 2–3 attorneys who bill actively and are willing to provide feedback during tuning phase
- Written authorization from firm managing partner acknowledging AI-assisted time entry generation and human review requirement per ABA Model Rule 1.5 and Formal Opinion 512
- Vendor due diligence completed: SOC 2 Type II reports obtained from Clio and chosen time-capture vendor, data processing agreements signed
Installation Steps
Step 1: Clio Environment Audit and Configuration
Verify the firm's Clio Manage instance is properly configured to receive automated time entries. This includes confirming all active clients and matters are present, billing rates are current, activity codes (UTBMS/LEDES if applicable) are configured, and the API is accessible. Create a dedicated API user account for the time-capture integration with appropriate scopes.
# expected response is a JSON array of open matters with client
# associations. Verify count matches firm's expected open matter count.
curl -X GET 'https://app.clio.com/api/v4/matters.json?status=open&fields=id,display_number,description,client' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json'curl -X GET 'https://app.clio.com/api/v4/activity_descriptions.json?fields=id,name,type,rate' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'If the firm is new to Clio, allow 1–2 additional weeks for full data migration from their existing system (PracticePanther, MyCase, Smokeball, etc.). Clio offers a free migration service for firms switching from competitor platforms. Ensure all matter statuses are accurate — closed/archived matters should not sync to the time-capture agent.
Step 2: Microsoft 365 Tenant Configuration for Graph API Access
Configure the Microsoft 365 tenant to allow the time-capture agent to read calendar events, email metadata, and document activity via Microsoft Graph API. This involves registering an Azure AD enterprise application, granting the required API permissions, and configuring OAuth 2.0 consent. For agents that use desktop-level monitoring instead of Graph API (like Memtime), this step focuses on Azure AD SSO configuration instead.
Connect-MgGraph -Scopes 'Application.ReadWrite.All'
$app = Get-MgApplication -Filter "displayName eq 'Legal Time Capture Agent'"
# Grant admin consent via Graph API or Azure Portal UIIf using Billables AI, the vendor provides a pre-configured OAuth flow — you will enter the M365 tenant ID in their admin portal rather than manually registering an app. The commands above are for custom/self-hosted integrations or agents requiring manual Graph API setup. For Memtime (which uses desktop-level window monitoring, not Graph API), skip the app registration and focus on ensuring Azure AD SSO is configured for the Memtime web portal.
Step 3: Configure Azure AD SSO for Clio
Set up Single Sign-On between Azure AD (Entra ID) and Clio Manage so attorneys use their Microsoft credentials to log into Clio. This simplifies user management, enables conditional access policies, and satisfies ABA Rule 1.6 requirements for access control.
Identifier (Entity ID): https://app.clio.com
Reply URL (ACS URL): https://app.clio.com/session/saml
Sign on URL: https://app.clio.com/session/newClio supports SAML 2.0 SSO on all paid plans. After enabling SSO, you can optionally enforce SSO-only login (disabling password-based Clio login) for tighter security. Wait to enforce SSO until all users have successfully tested the SSO flow. Document the SAML configuration in the client's IT runbook.
Step 4: Deploy Desktop Time-Capture Agent via Intune
Package and deploy the Billables AI (or Memtime) desktop agent to all attorney workstations using Microsoft Intune. This ensures consistent deployment, automatic updates, and centralized monitoring. The agent runs as a lightweight background service (~100-200MB RAM, <1% CPU) that monitors active window titles, calendar events, and document activity.
IntuneWinAppUtil.exe -c C:\Packages\BillablesAI -s BillablesAI_Setup.exe -o C:\Packages\Output# Install command
BillablesAI_Setup.exe /S /TENANT=YOUR_TENANT_ID
# Uninstall command
C:\Program Files\BillablesAI\uninstall.exe /SFor Memtime specifically, the installer is available as MSI (Windows) or DMG (macOS). Memtime stores all activity data locally on the workstation — no cloud sync of raw activity data occurs. This is a key selling point for privacy-conscious firms. For firms without Intune, deploy via your RMM tool (ConnectWise Automate, Datto RMM, NinjaRMM) using similar silent install parameters. Verify the agent starts automatically at user login and runs in the system tray.
Step 5: Connect Time-Capture Agent to Clio API
Configure the bidirectional integration between the time-capture agent and Clio Manage. This sync pulls the client/matter list from Clio into the agent (so it can match activities to matters) and pushes generated time entries back to Clio as draft entries pending attorney review.
- Matter sync frequency: Every 15 minutes (or real-time via webhook)
- Time entry push mode: 'Draft' (entries created as non-billed drafts)
- Billing increment: 0.1 hours (6-minute increments — standard legal billing)
- Minimum entry threshold: 0.1 hours (entries below 6 minutes aggregated)
- Narrative style: 'Detailed' (include document names, email subjects, meeting titles)
- Auto-assign activity codes: Enabled
Test the integration: Create a test time entry in Billables AI → verify it appears in Clio using the following API call:
curl -X GET 'https://app.clio.com/api/v4/activities.json?type=TimeEntry&status=draft&created_since=2025-06-01' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'Set time entry push mode to 'Draft' — never 'Billed'. Attorneys must review and approve all AI-generated entries before they appear on client invoices. This is required by ABA Model Rule 1.5 (reasonable fees) and Formal Opinion 512 (supervision of AI outputs). If using Memtime instead of Billables AI, the integration flow is similar: Memtime Connect tier → Settings → Integrations → Clio → OAuth authorize → configure sync preferences.
Step 6: Configure Microsoft 365 Calendar Integration
Ensure the time-capture agent can read calendar events from Outlook/Exchange Online to automatically generate time entries for meetings, client calls, court appearances, and depositions. Calendar events are the highest-confidence source for time entries because they have explicit start/end times and often contain client/matter references in the subject or body.
# Configure narrative generation for calendar events:
# Template: '[Event Type] re: [Subject] - [Duration] - [Attendees if external]'
# Example output: 'Conference call re: Smith v. Jones Discovery Dispute - 0.5 hrs - with opposing counsel J. Williams'Calendar integration is the single highest-value component of this solution. Most firms report that 40–60% of recovered billable time comes from calendar events that attorneys forgot to manually enter. Advise attorneys to include client names or matter numbers in calendar event subjects for best auto-matching accuracy. The agent can also detect recurring events and apply matter matching from previous instances.
Step 7: Configure Email Activity Time Capture
Set up email-based time capture to generate time entries for legal correspondence. The agent analyzes email metadata (sender, recipient, subject, timestamp, thread length) to identify billable correspondence and map it to the correct client matter. Email body content is typically analyzed only for matter-matching keywords — full content is not stored by the time-capture vendor.
Configure Email Capture Rules
Domain-Based Client Matching
Map client email domains to Clio clients:
smithcorp.com → Smith Corporation (Matter: 2024-00045)
janedoe@gmail.com → Doe, Jane (Matter: 2024-00089)Narrative Generation for Emails
Template: 'Email correspondence with [Recipient/Sender] re: [Subject] ([sent/received] [count] messages)'
Example: 'Email correspondence with J. Smith (SmithCorp) re: Contract Review - NDA Revisions (sent 3 messages)'Billing Increments for Email
- Single email: 0.1 hr (6 min)
- Email thread (3+ messages same subject within 1 hour): aggregate into single entry
- Maximum per-thread entry: 0.5 hr (attorney can adjust upward during review)
Optional: Tempello as Email-Specific Supplement
Email time capture is the most sensitive area from a confidentiality perspective. Verify with the vendor that email body content is processed in-memory only and not stored persistently in their cloud. Memtime does not capture email content at all — it only records that Outlook was the active window and the visible subject line. For maximum privacy, pair Memtime (desktop monitoring) with Tempello (email-specific matching) rather than granting full Mail.Read Graph API access to any single vendor.
Step 8: Configure Document Activity Time Capture
Set up passive monitoring of document-related work activity. The desktop agent observes when attorneys are actively working in Word, Excel, PowerPoint, Adobe Acrobat, or web-based document editors, captures the document title and active duration, and generates time entries for drafting, reviewing, and editing work.
# File-to-matter matching strategy:
# 1. Filename contains matter number → exact match (e.g., '2024-00045 - Smith NDA Draft.docx')
# 2. Filename contains client name → fuzzy match to Clio client
# 3. File located in matter-specific folder → folder name match
# 4. No match found → queue for manual assignment by attorney
# Idle detection:
# Idle threshold: 3 minutes (no keyboard/mouse activity → pause timer)
# Resume threshold: Any keyboard/mouse input → resume timer
# Maximum continuous entry: 2.0 hours (auto-split into 2hr blocks for review)
# Narrative generation for document work:
# Template: '[Action] [Document Type] - [Document Title]'
# Actions auto-detected: 'Drafted', 'Reviewed', 'Revised', 'Analyzed'
# Example: 'Drafted Motion for Summary Judgment - Smith v. Jones (Case 2024-00045)'
# Legal research detection:
# When Westlaw/Lexis browser tabs are active:
# Template: 'Legal research re: [search terms if captured] - [Duration]'
# Example: 'Legal research re: statute of limitations tort claims California - 0.4 hrs'Document title capture is a key privacy consideration. The agent reads window titles (which typically contain the document filename) but does NOT read document content. Communicate this clearly to attorneys during onboarding. For firms using NetDocuments or iManage as their DMS, the agent can often extract matter metadata from the DMS filing structure, improving auto-matching accuracy significantly. Encourage the firm to adopt a consistent file naming convention that includes matter numbers.
Step 9: Pilot Deployment with Test Attorney Group
Deploy the complete solution to a pilot group of 2–3 attorneys for a 2–4 week evaluation period. The pilot validates accuracy of matter matching, quality of narrative generation, and identifies edge cases before firm-wide rollout. Select attorneys who bill actively across multiple matters and are open to providing candid feedback.
Get-Process -Name 'BillablesAI' -ErrorAction SilentlyContinue | Select-Object ProcessName, CPU, WorkingSet64
# Expected: Process running with <200MB working setcurl -X GET 'https://app.clio.com/api/v4/activities.json?type=TimeEntry&status=draft&user_id=PILOT_USER_ID&created_since=2025-06-15' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' | python -m json.toolThe pilot phase is critical for attorney buy-in. If the first experience is poor (wrong matter assignments, low-quality narratives), adoption will fail. Set expectations with pilot attorneys: the first week will have lower accuracy as the AI learns patterns, and accuracy should improve to 80%+ by week 2–3. Document all feedback systematically — this data drives the tuning in the next step.
Step 10: Tune AI Matching Rules and Narrative Templates Based on Pilot Feedback
Analyze pilot data to improve matter-matching accuracy and narrative quality. Create custom rules for the firm's specific naming conventions, common matters, and billing preferences. This is the most impactful step for long-term adoption and accuracy.
Analyze pilot accuracy data by exporting from the tracking sheet or Billables AI analytics dashboard. Target metrics:
- Matter match accuracy: >85% (entries assigned to correct matter)
- Narrative acceptance rate: >70% (attorney uses AI narrative as-is or with minor edits)
- Capture completeness: >90% (AI detected the billable activity)
1. Add Custom Matter-Matching Keywords
'Smith' + 'NDA' → Matter 2024-00045
'Jones divorce' → Matter 2024-00112
'ABC Corp' OR 'abc-corp.com' → Client: ABC Corporation2. Adjust Narrative Templates Per Practice Area
Litigation template: '[Action] [Document Type] in [Case Caption]'
Transactional template: '[Action] [Document Type] for [Transaction Name]'
Example: 'Reviewed opposing party discovery responses in Smith v. Jones, Case No. 2024-CV-1234'3. Configure Activity Code Auto-Assignment
Calendar meetings → Activity Code: 'Conference/Meeting' (Clio code)
Email correspondence → Activity Code: 'Correspondence'
Document drafting → Activity Code: 'Document Preparation'
Legal research → Activity Code: 'Legal Research'
Court appearances → Activity Code: 'Court Appearance'4. Set Per-Attorney Narrative Preferences
5. Create Exclusion Rules
Exclude personal calendar entries: keywords 'lunch', 'gym', 'doctor', 'personal'
Exclude admin tasks: 'staff meeting', 'CLE', 'training' (unless billable CLE)
Exclude specific applications: Spotify, Netflix, social media browsersMatter-matching accuracy is highly dependent on the firm's organizational habits. Firms that use consistent matter numbers in document names and calendar subjects will see 90%+ accuracy quickly. Firms with inconsistent naming may need 20+ custom keyword rules. Consider recommending the firm adopt a standard naming convention as part of this project — it improves both AI accuracy and general organizational efficiency.
Step 11: Firm-Wide Rollout and Attorney Training
After successful pilot completion (85%+ match accuracy, positive pilot feedback), deploy to all remaining attorneys and paralegals. Conduct structured training sessions covering how to review AI-generated entries, how to provide corrections that improve future accuracy, and the ethical obligation to review all entries before billing.
Get-Process -Name 'BillablesAI' -ErrorAction SilentlyContinue
# Or for Memtime:
Get-Process -Name 'Memtime' -ErrorAction SilentlyContinueSchedule two training sessions to accommodate attorney schedules — one in-person and one recorded/virtual. Create a one-page quick reference card (PDF) that attorneys can tape to their monitor showing: (1) How to find draft entries in Clio, (2) How to approve/edit/reject entries, (3) Who to contact for support. The biggest adoption risk is attorneys ignoring draft entries — the daily reminder automation is essential.
Step 12: Establish Pre-Bill Review Workflow
Configure the end-to-end billing workflow that ensures AI-generated time entries go through proper human review before appearing on client invoices. This addresses ABA Model Rule 1.5 (reasonable fees) and Rule 5.1/5.3 (supervisory responsibilities). The workflow is: AI generates draft → Attorney reviews and approves → Billing coordinator aggregates → Managing partner reviews pre-bill → Invoice sent.
Automation: Flag high-value AI entries for extra review using a Clio API script run weekly via scheduled task. See custom AI component: 'Pre-Bill AI Entry Validator' below.
python3 flag_high_value_entries.pyMonthly accuracy report: Export from Billables AI: Entries created vs. entries billed (no modifications). Target: 85%+ of AI entries billed as-is or with minor narrative edits only.
The pre-bill review workflow is non-negotiable from an ethics perspective. Even if AI accuracy reaches 95%+, every entry must have attorney review before billing. Document this workflow in the firm's billing policy and have the managing partner sign off. This documentation serves as evidence of ABA Rule 5.1/5.3 compliance if the firm is ever audited by the state bar.
Custom AI Components
Calendar Event to Time Entry Mapper
Type: workflow
A Power Automate flow that supplements the primary time-capture agent by providing a secondary calendar-to-time-entry pipeline. This catches calendar events that the desktop agent misses (e.g., when the attorney joins a call from their phone rather than their workstation). The flow triggers when a calendar event ends, extracts event metadata, performs client/matter matching against Clio, and creates a draft time entry if one doesn't already exist from the desktop agent.
Implementation
Power Automate Flow: Calendar Event → Clio Time Entry Trigger: When a calendar event ends (Microsoft 365 Outlook connector) License: Included with M365 Business Premium (standard connector)
Trigger
- Connector: Office 365 Outlook
- Trigger: When an event ends (V3)
- Calendar: Default calendar
- Filter: Category != 'Personal' AND Category != 'Internal'
Step 1: Check for Existing Entry
- Action: HTTP (Premium connector)
- Method: GET
- Purpose: Avoid duplicate entries if desktop agent already captured this event
GET https://app.clio.com/api/v4/activities.json?type=TimeEntry&status=draft&date=@{formatDateTime(triggerOutputs()?['body/start'],'yyyy-MM-dd')}&user_id=@{variables('ClioUserId')}
Authorization: Bearer @{variables('ClioAccessToken')}Step 2: Parse Response
- Action: Parse JSON
- Schema: { type: object, properties: { data: { type: array } } }
- Condition: If length(body('Parse_JSON')?['data']) == 0 → continue (no existing entry found)
- If any existing entry narrative contains triggerOutputs()?['body/subject'] → skip (already captured)
Step 3: Match Client/Matter
- Action: HTTP
- Method: GET
- Purpose: Search Clio matters using calendar event subject as query
GET https://app.clio.com/api/v4/matters.json?query=@{encodeURIComponent(triggerOutputs()?['body/subject'])}&status=open&fields=id,display_number,description,client
Authorization: Bearer @{variables('ClioAccessToken')}Step 4: Calculate Duration
- Action: Compose
- Purpose: Calculate duration in hours (tenths), rounding up to nearest 0.1
# converts ticks to tenths of an hour, rounds up
div(div(sub(ticks(triggerOutputs()?['body/end']),ticks(triggerOutputs()?['body/start'])),600000000),10.0)
Round up: if(equals(mod(result,0.1),0),result,add(sub(result,mod(result,0.1)),0.1))Step 5: Generate Narrative
- Action: Compose
concat(
if(contains(toLower(triggerOutputs()?['body/subject']),'call'),'Telephone conference','Conference'),
' re: ',
triggerOutputs()?['body/subject'],
if(greater(length(triggerOutputs()?['body/requiredAttendees']),0),
concat(' with ',join(triggerOutputs()?['body/requiredAttendees'],' ,')),''),
' (',formatNumber(outputs('Calculate_Duration'),'0.0'),' hrs)'
)Step 6: Create Draft Time Entry in Clio
- Action: HTTP
- Method: POST
POST https://app.clio.com/api/v4/activities.json
Authorization: Bearer @{variables('ClioAccessToken')}
Content-Type: application/json
{
"data": {
"type": "TimeEntry",
"date": "@{formatDateTime(triggerOutputs()?['body/start'],'yyyy-MM-dd')}",
"quantity": @{outputs('Calculate_Duration')},
"note": "@{outputs('Generate_Narrative')}",
"matter": { "id": @{first(body('Match_Client_Matter')?['data'])?['id']} },
"user": { "id": @{variables('ClioUserId')} },
"activity_description": { "id": @{variables('ConferenceActivityCodeId')} },
"non_billable": false
}
}Step 7: Error Handling
- If matter match fails (no results): Create entry with matter=null, add note prefix '[UNMATCHED - ASSIGN MATTER] '
- If Clio API returns 4xx: Log to SharePoint list 'Time Entry Errors' with full request/response
- If duration < 0.1 hrs: Skip entry creation (below minimum billing increment)
Variables (configured per user)
- ClioUserId: Mapped from Azure AD UPN → Clio user ID (lookup table in SharePoint)
- ClioAccessToken: Stored in Azure Key Vault, refreshed via OAuth refresh token flow
- ConferenceActivityCodeId: Clio activity code ID for 'Conference/Meeting'
Deployment
Pre-Bill AI Entry Validator
Type: integration A Python script that runs as a weekly scheduled task (via Azure Function or on-premises Task Scheduler) to analyze AI-generated time entries in Clio before pre-bills are generated. It flags entries that may be inaccurate, duplicated, or ethically problematic — such as unusually high hours, entries without matter assignments, duplicate entries from overlapping capture sources, and entries that exceed the calendar event duration by a suspicious margin.
Implementation
# weekly scheduled script
#!/usr/bin/env python3
"""
Pre-Bill AI Entry Validator for Clio
Runs weekly to flag potentially problematic AI-generated time entries
before pre-bill review. Outputs a report to SharePoint/email.
Requirements: pip install requests python-dateutil
Schedule: Weekly, Monday 7:00 AM (before billing coordinator review)
"""
import requests
import json
from datetime import datetime, timedelta
from dateutil import parser as dateparser
from collections import defaultdict
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# Configuration
CLIO_API_BASE = "https://app.clio.com/api/v4"
CLIO_ACCESS_TOKEN = "YOUR_ACCESS_TOKEN" # Use environment variable in production
REVIEW_PERIOD_DAYS = 7
ALERT_EMAIL = "billingcoordinator@lawfirm.com"
SMTP_SERVER = "smtp.office365.com"
SMTP_PORT = 587
SMTP_USER = "alerts@lawfirm.com"
SMTP_PASS = "USE_APP_PASSWORD" # Use Azure Key Vault in production
# Thresholds
MAX_DAILY_HOURS = 12.0 # Flag if attorney has >12 billable hours in a day
MAX_SINGLE_ENTRY = 4.0 # Flag single entries > 4 hours
DUPLICATE_WINDOW_MINUTES = 15 # Entries within 15 min for same matter = potential duplicate
MIN_NARRATIVE_LENGTH = 20 # Flag entries with very short narratives
headers = {
"Authorization": f"Bearer {CLIO_ACCESS_TOKEN}",
"Content-Type": "application/json"
}
def get_draft_entries(since_date):
"""Fetch all draft time entries from the review period."""
entries = []
url = f"{CLIO_API_BASE}/activities.json"
params = {
"type": "TimeEntry",
"status": "draft",
"created_since": since_date.isoformat(),
"fields": "id,date,quantity,note,matter{id,display_number,description},user{id,name},activity_description{id,name},created_at",
"limit": 200
}
while url:
resp = requests.get(url, headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
entries.extend(data.get("data", []))
url = data.get("meta", {}).get("paging", {}).get("next", None)
params = {} # Next URL already includes params
return entries
def validate_entries(entries):
"""Run validation rules and return list of flagged entries."""
flags = []
# Group entries by user and date for daily total check
daily_totals = defaultdict(lambda: defaultdict(float))
# Group by user, date, matter for duplicate detection
entry_groups = defaultdict(list)
for entry in entries:
user_name = entry.get("user", {}).get("name", "Unknown")
user_id = entry.get("user", {}).get("id", 0)
entry_date = entry.get("date", "")
quantity = entry.get("quantity", 0)
note = entry.get("note", "")
matter = entry.get("matter", {})
matter_id = matter.get("id") if matter else None
matter_desc = matter.get("display_number", "UNASSIGNED") if matter else "UNASSIGNED"
entry_id = entry.get("id")
# Rule 1: No matter assigned
if not matter_id:
flags.append({
"entry_id": entry_id,
"user": user_name,
"date": entry_date,
"issue": "NO_MATTER_ASSIGNED",
"detail": f"Entry '{note[:50]}...' has no matter assigned",
"severity": "HIGH"
})
# Rule 2: Single entry exceeds threshold
if quantity > MAX_SINGLE_ENTRY:
flags.append({
"entry_id": entry_id,
"user": user_name,
"date": entry_date,
"issue": "EXCESSIVE_SINGLE_ENTRY",
"detail": f"{quantity} hrs on {matter_desc}: '{note[:50]}...'",
"severity": "MEDIUM"
})
# Rule 3: Very short or empty narrative
if len(note.strip()) < MIN_NARRATIVE_LENGTH:
flags.append({
"entry_id": entry_id,
"user": user_name,
"date": entry_date,
"issue": "SHORT_NARRATIVE",
"detail": f"Narrative too short ({len(note)} chars): '{note}'",
"severity": "LOW"
})
# Rule 4: Narrative contains placeholder text
placeholder_indicators = ["[UNMATCHED", "TODO", "PLACEHOLDER", "TBD"]
if any(p in note.upper() for p in placeholder_indicators):
flags.append({
"entry_id": entry_id,
"user": user_name,
"date": entry_date,
"issue": "PLACEHOLDER_NARRATIVE",
"detail": f"Contains placeholder text: '{note[:80]}'",
"severity": "HIGH"
})
# Accumulate for cross-entry checks
daily_totals[user_id][entry_date] += quantity
entry_groups[(user_id, entry_date, matter_id)].append(entry)
# Rule 5: Daily total exceeds threshold
for user_id, dates in daily_totals.items():
for date, total in dates.items():
if total > MAX_DAILY_HOURS:
user_name = next((e["user"]["name"] for e in entries if e["user"]["id"] == user_id), "Unknown")
flags.append({
"entry_id": None,
"user": user_name,
"date": date,
"issue": "EXCESSIVE_DAILY_TOTAL",
"detail": f"Total draft hours: {total:.1f} (threshold: {MAX_DAILY_HOURS})",
"severity": "HIGH"
})
# Rule 6: Potential duplicates (multiple entries same user/date/matter)
for (user_id, date, matter_id), group in entry_groups.items():
if len(group) > 3 and matter_id: # More than 3 entries same matter same day
user_name = group[0].get("user", {}).get("name", "Unknown")
matter_desc = group[0].get("matter", {}).get("display_number", "Unknown")
total = sum(e.get("quantity", 0) for e in group)
flags.append({
"entry_id": None,
"user": user_name,
"date": date,
"issue": "POTENTIAL_DUPLICATES",
"detail": f"{len(group)} entries for matter {matter_desc} totaling {total:.1f} hrs",
"severity": "MEDIUM"
})
return flags
def generate_report(flags):
"""Generate HTML report of flagged entries."""
if not flags:
return "<h2>No issues found</h2><p>All AI-generated draft entries passed validation.</p>"
high = [f for f in flags if f["severity"] == "HIGH"]
medium = [f for f in flags if f["severity"] == "MEDIUM"]
low = [f for f in flags if f["severity"] == "LOW"]
html = f"""<h2>Pre-Bill AI Entry Validation Report</h2>
<p>Review period: Last {REVIEW_PERIOD_DAYS} days | Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
<p><strong style='color:red'>{len(high)} HIGH</strong> |
<strong style='color:orange'>{len(medium)} MEDIUM</strong> |
<strong style='color:gray'>{len(low)} LOW</strong> severity issues</p>
<table border='1' cellpadding='5' style='border-collapse:collapse'>
<tr><th>Severity</th><th>User</th><th>Date</th><th>Issue</th><th>Detail</th></tr>"""
for f in sorted(flags, key=lambda x: {"HIGH": 0, "MEDIUM": 1, "LOW": 2}[x["severity"]]):
color = {"HIGH": "red", "MEDIUM": "orange", "LOW": "gray"}[f["severity"]]
html += f"<tr><td style='color:{color}'><strong>{f['severity']}</strong></td><td>{f['user']}</td><td>{f['date']}</td><td>{f['issue']}</td><td>{f['detail']}</td></tr>"
html += "</table>"
return html
def send_report(html_report):
"""Send report via email to billing coordinator."""
msg = MIMEMultipart("alternative")
msg["Subject"] = f"Weekly AI Time Entry Validation Report - {datetime.now().strftime('%Y-%m-%d')}"
msg["From"] = SMTP_USER
msg["To"] = ALERT_EMAIL
msg.attach(MIMEText(html_report, "html"))
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.starttls()
server.login(SMTP_USER, SMTP_PASS)
server.sendmail(SMTP_USER, ALERT_EMAIL, msg.as_string())
def main():
since_date = datetime.now() - timedelta(days=REVIEW_PERIOD_DAYS)
print(f"Fetching draft entries since {since_date.date()}...")
entries = get_draft_entries(since_date)
print(f"Retrieved {len(entries)} draft entries")
flags = validate_entries(entries)
print(f"Found {len(flags)} issues")
report = generate_report(flags)
send_report(report)
print("Report sent to billing coordinator")
if __name__ == "__main__":
main()Deployment
schtasks /create /tn "ClioEntryValidator" /tr "python3 C:\Scripts\validate_entries.py" /sc weekly /d MON /st 07:00Clio-M365 User Mapping Sync
Type: integration A lightweight integration that maintains a mapping table between Azure AD user accounts and Clio user IDs, enabling all other automations to correctly route time entries to the right Clio user. This runs daily to catch new hires and departures.
Implementation:
#!/usr/bin/env python3
"""
Sync Azure AD users to Clio user mapping table.
Maintains a JSON mapping file used by other automation components.
Runs daily via scheduled task.
Requirements: pip install requests msal
"""
import requests
import json
import os
from msal import ConfidentialClientApplication
# Azure AD Configuration
AZURE_TENANT_ID = "YOUR_TENANT_ID"
AZURE_CLIENT_ID = "YOUR_APP_CLIENT_ID"
AZURE_CLIENT_SECRET = "YOUR_CLIENT_SECRET" # Use Key Vault in production
AZURE_AUTHORITY = f"https://login.microsoftonline.com/{AZURE_TENANT_ID}"
GRAPH_SCOPE = ["https://graph.microsoft.com/.default"]
# Clio Configuration
CLIO_API_BASE = "https://app.clio.com/api/v4"
CLIO_ACCESS_TOKEN = "YOUR_CLIO_TOKEN" # Use Key Vault in production
# Output
MAPPING_FILE = "/opt/legal-automation/user_mapping.json"
# Windows: C:\\LegalAutomation\\user_mapping.json
def get_azure_ad_users():
"""Get all licensed users from Azure AD."""
app = ConfidentialClientApplication(
AZURE_CLIENT_ID,
authority=AZURE_AUTHORITY,
client_credential=AZURE_CLIENT_SECRET
)
token = app.acquire_token_for_client(scopes=GRAPH_SCOPE)
headers = {"Authorization": f"Bearer {token['access_token']}"}
url = "https://graph.microsoft.com/v1.0/users?$select=id,displayName,mail,userPrincipalName,jobTitle&$filter=accountEnabled eq true"
users = []
while url:
resp = requests.get(url, headers=headers)
resp.raise_for_status()
data = resp.json()
users.extend(data.get("value", []))
url = data.get("@odata.nextLink")
return users
def get_clio_users():
"""Get all active users from Clio."""
headers = {
"Authorization": f"Bearer {CLIO_ACCESS_TOKEN}",
"Content-Type": "application/json"
}
url = f"{CLIO_API_BASE}/users.json?fields=id,name,email,enabled&enabled=true"
users = []
while url:
resp = requests.get(url, headers=headers)
resp.raise_for_status()
data = resp.json()
users.extend(data.get("data", []))
url = data.get("meta", {}).get("paging", {}).get("next"))
return users
def build_mapping(azure_users, clio_users):
"""Match Azure AD users to Clio users by email address."""
clio_by_email = {u["email"].lower(): u for u in clio_users if u.get("email")}
mapping = []
unmatched = []
for az_user in azure_users:
email = (az_user.get("mail") or az_user.get("userPrincipalName", "")).lower()
if email in clio_by_email:
clio_user = clio_by_email[email]
mapping.append({
"azure_ad_id": az_user["id"],
"azure_upn": az_user.get("userPrincipalName"),
"display_name": az_user.get("displayName"),
"email": email,
"clio_user_id": clio_user["id"],
"clio_name": clio_user["name"],
"job_title": az_user.get("jobTitle", "")
})
else:
unmatched.append({
"azure_upn": az_user.get("userPrincipalName"),
"display_name": az_user.get("displayName"),
"reason": "No matching Clio account found"
})
return mapping, unmatched
def main():
print("Fetching Azure AD users...")
azure_users = get_azure_ad_users()
print(f"Found {len(azure_users)} active Azure AD users")
print("Fetching Clio users...")
clio_users = get_clio_users()
print(f"Found {len(clio_users)} active Clio users")
mapping, unmatched = build_mapping(azure_users, clio_users)
print(f"Matched {len(mapping)} users, {len(unmatched)} unmatched")
output = {
"generated_at": datetime.now().isoformat(),
"matched_users": mapping,
"unmatched_users": unmatched
}
os.makedirs(os.path.dirname(MAPPING_FILE), exist_ok=True)
with open(MAPPING_FILE, "w") as f:
json.dump(output, f, indent=2)
print(f"Mapping saved to {MAPPING_FILE}")
if unmatched:
print("WARNING: Unmatched users:")
for u in unmatched:
print(f" - {u['display_name']} ({u['azure_upn']}): {u['reason']}")
if __name__ == "__main__":
from datetime import datetime
main()Deployment
schtasks /create /tn "ClioUserSync" /tr "python3 C:\Scripts\sync_users.py" /sc daily /st 06:00Daily Time Entry Review Reminder Bot
Type: workflow A Power Automate flow that sends personalized daily reminders to each attorney via Microsoft Teams and email, summarizing their pending AI-generated draft time entries and prompting review before end of day. Includes a direct deep-link to the filtered Clio view showing only their draft entries.
Implementation
Power Automate Cloud Flow: Daily Time Entry Review Reminder. Trigger: Recurrence - Daily at 4:30 PM local time (Mon-Fri). License: Requires Power Automate Premium for HTTP connector (or use standard M365 connector with SharePoint intermediary).
Step 1: Trigger — Recurrence
- Frequency: Day
- Interval: 1
- Time: 16:30
- Time zone: (firm's local timezone, e.g., Eastern Standard Time)
- On these days: Monday, Tuesday, Wednesday, Thursday, Friday
Step 2: Get User Mapping
- Action: Get file content (SharePoint)
- Site: Firm's SharePoint site
- File: /Shared Documents/LegalAutomation/user_mapping.json
- Parse JSON output to get list of {email, clio_user_id, display_name}
Step 3: Apply to Each (loop over mapped users)
Step 3a: Get Draft Entry Count from Clio
- Action: HTTP
- Method: GET
GET https://app.clio.com/api/v4/activities.json?type=TimeEntry&status=draft&user_id=@{items('Apply_to_each')?['clio_user_id']}&date=@{formatDateTime(utcNow(),'yyyy-MM-dd')}&fields=id,quantity,note,matter
Authorization: Bearer @{body('Get_Clio_Token')?['access_token']}Step 3b: Condition — If draft entries exist (length > 0)
Step 3c: Compose Summary (If Yes)
- Count: @{length(body('Parse_Entries')?['data'])}
- Total hours: @{sum of quantity fields}
- Preview: First 3 entry narratives truncated to 60 chars each
Step 3d: Send Teams Message (If Yes)
- Action: Post message in a chat or channel (Microsoft Teams)
- Post as: Flow bot
- Post in: Chat with Flow bot
- Recipient: @{items('Apply_to_each')?['email']}
{
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "⏰ You have @{variables('EntryCount')} draft time entries to review",
"weight": "Bolder",
"size": "Medium"
},
{
"type": "TextBlock",
"text": "Total: @{variables('TotalHours')} hours pending approval",
"spacing": "Small"
},
{
"type": "TextBlock",
"text": "Preview of entries:",
"spacing": "Medium",
"weight": "Bolder"
},
{
"type": "TextBlock",
"text": "@{variables('EntryPreview')}",
"wrap": true,
"spacing": "Small"
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Review in Clio →",
"url": "https://app.clio.com/nc/#/activities?type=time_entry&status=draft"
}
]
}Step 3e: Send Email — Backup for attorneys who don't check Teams (If Yes)
- Action: Send an email (V2) - Office 365 Outlook
- To: @{items('Apply_to_each')?['email']}
- Subject: "Action Required: @{variables('EntryCount')} draft time entries pending review"
- Body: HTML version of the same content with Clio deep link
If No (no draft entries)
Do nothing — attorney has already reviewed or no AI entries were generated today.
Error Handling
- Configure 'Run after' on HTTP actions to handle failures gracefully
- On Clio API failure: Send alert to MSP monitoring email
- On Teams failure: Fall back to email-only notification
Deployment
Monthly Billing Recovery Report Generator
Type: integration A reporting script that runs monthly to quantify the ROI of the automated time capture system. It compares AI-generated entries versus manual entries, calculates additional revenue captured, and produces a client-facing PDF report the MSP can present during quarterly business reviews (QBRs) to demonstrate value and justify ongoing managed services fees.
Implementation:
# manual capture, and outputs an HTML ROI report
#!/usr/bin/env python3
"""
Monthly Billing Recovery Report Generator
Compares AI-captured time entries vs historical baseline to quantify ROI.
Generates HTML report (convertible to PDF via wkhtmltopdf or browser print).
Requirements: pip install requests jinja2
Schedule: Monthly, 1st business day
"""
import requests
import json
from datetime import datetime, timedelta
from collections import defaultdict
CLIO_API_BASE = "https://app.clio.com/api/v4"
CLIO_ACCESS_TOKEN = "YOUR_TOKEN"
FIRM_NAME = "Smith & Associates LLP"
AVERAGE_BILLING_RATE = 400 # $/hour - adjust per firm
SOFTWARE_COST_PER_USER_MONTH = 200 # Total stack cost per user per month
headers = {
"Authorization": f"Bearer {CLIO_ACCESS_TOKEN}",
"Content-Type": "application/json"
}
def get_monthly_entries(year, month):
"""Fetch all time entries for a given month."""
start = f"{year}-{month:02d}-01"
if month == 12:
end = f"{year+1}-01-01"
else:
end = f"{year}-{month+1:02d}-01"
url = f"{CLIO_API_BASE}/activities.json"
params = {
"type": "TimeEntry",
"date_from": start,
"date_to": end,
"fields": "id,date,quantity,note,user{id,name},matter{id},created_at,updated_at",
"limit": 200
}
entries = []
while url:
resp = requests.get(url, headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
entries.extend(data.get("data", []))
url = data.get("meta", {}).get("paging", {}).get("next"))
params = {}
return entries
def analyze_entries(entries):
"""Analyze entries to separate AI-generated from manual."""
by_user = defaultdict(lambda: {"ai_hours": 0, "manual_hours": 0, "ai_count": 0, "manual_count": 0})
for entry in entries:
user_name = entry.get("user", {}).get("name", "Unknown")
quantity = entry.get("quantity", 0)
note = entry.get("note", "")
# Heuristic: AI-generated entries typically have consistent formatting
# and may contain markers. Adjust detection logic based on vendor.
# Better approach: use Clio custom field 'AI Generated' if configured
is_ai = (
"[AI]" in note or
"Billables AI" in note or # Some agents watermark entries
len(note) > 100 # AI narratives tend to be more detailed
)
if is_ai:
by_user[user_name]["ai_hours"] += quantity
by_user[user_name]["ai_count"] += 1
else:
by_user[user_name]["manual_hours"] += quantity
by_user[user_name]["manual_count"] += 1
return dict(by_user)
def generate_report(analysis, year, month, num_users):
"""Generate HTML report."""
total_ai_hours = sum(u["ai_hours"] for u in analysis.values())
total_manual_hours = sum(u["manual_hours"] for u in analysis.values())
total_hours = total_ai_hours + total_manual_hours
estimated_revenue_recovered = total_ai_hours * AVERAGE_BILLING_RATE
software_cost = num_users * SOFTWARE_COST_PER_USER_MONTH
net_roi = estimated_revenue_recovered - software_cost
roi_percentage = (net_roi / software_cost * 100) if software_cost > 0 else 0
user_rows = ""
for name, data in sorted(analysis.items(), key=lambda x: x[1]["ai_hours"], reverse=True):
user_rows += f"""
<tr>
<td>{name}</td>
<td>{data['ai_hours']:.1f}</td>
<td>{data['ai_count']}</td>
<td>{data['manual_hours']:.1f}</td>
<td>{data['manual_count']}</td>
<td>{data['ai_hours'] + data['manual_hours']:.1f}</td>
<td>${data['ai_hours'] * AVERAGE_BILLING_RATE:,.0f}</td>
</tr>"""
html = f"""
<!DOCTYPE html>
<html>
<head><title>Monthly Billing Recovery Report</title>
<style>
body {{ font-family: 'Segoe UI', Arial, sans-serif; margin: 40px; color: #333; }}
h1 {{ color: #1a365d; border-bottom: 3px solid #2b6cb0; padding-bottom: 10px; }}
.metric {{ display: inline-block; margin: 10px 20px; padding: 20px; background: #f7fafc; border-radius: 8px; text-align: center; min-width: 180px; }}
.metric .value {{ font-size: 2em; font-weight: bold; color: #2b6cb0; }}
.metric .label {{ font-size: 0.9em; color: #718096; }}
table {{ border-collapse: collapse; width: 100%; margin-top: 20px; }}
th, td {{ border: 1px solid #e2e8f0; padding: 10px; text-align: left; }}
th {{ background: #2b6cb0; color: white; }}
tr:nth-child(even) {{ background: #f7fafc; }}
.roi {{ background: #c6f6d5; padding: 15px; border-radius: 8px; margin: 20px 0; }}
</style>
</head>
<body>
<h1>📊 Monthly Billing Recovery Report</h1>
<p><strong>{FIRM_NAME}</strong> | {datetime(year, month, 1).strftime('%B %Y')} | Generated {datetime.now().strftime('%B %d, %Y')}</p>
<div class='metric'><div class='value'>{total_ai_hours:.1f}</div><div class='label'>AI-Captured Hours</div></div>
<div class='metric'><div class='value'>{total_hours:.1f}</div><div class='label'>Total Billable Hours</div></div>
<div class='metric'><div class='value'>${estimated_revenue_recovered:,.0f}</div><div class='label'>Estimated Revenue Recovered</div></div>
<div class='metric'><div class='value'>{roi_percentage:.0f}%</div><div class='label'>Monthly ROI</div></div>
<div class='roi'>
<strong>💰 Net ROI This Month:</strong> ${net_roi:,.0f}
(Revenue recovered ${estimated_revenue_recovered:,.0f} - Software cost ${software_cost:,.0f})
</div>
<h2>By Attorney</h2>
<table>
<tr><th>Attorney</th><th>AI Hours</th><th>AI Entries</th><th>Manual Hours</th><th>Manual Entries</th><th>Total Hours</th><th>AI Revenue Value</th></tr>
{user_rows}
</table>
<h2>Methodology</h2>
<p>AI-captured hours represent time entries generated by the automated time capture system (Billables AI/Memtime)
that were subsequently reviewed and approved by the attorney. These hours may include time that would have been
captured manually regardless; however, industry benchmarks suggest that 60-80% of AI-captured entries represent
genuinely recovered billing that would otherwise have been lost to end-of-day recall failure.</p>
<p style='color:#718096; font-size:0.85em; margin-top:40px;'>Report generated by [MSP Name] Legal Technology Practice.
For questions, contact your account manager.</p>
</body></html>
"""
report_path = f"/opt/legal-automation/reports/billing_recovery_{year}_{month:02d}.html"
os.makedirs(os.path.dirname(report_path), exist_ok=True)
with open(report_path, "w") as f:
f.write(html)
print(f"Report saved to {report_path}")
return report_path
def main():
import os
now = datetime.now()
# Report on previous month
if now.month == 1:
report_year, report_month = now.year - 1, 12
else:
report_year, report_month = now.year, now.month - 1
entries = get_monthly_entries(report_year, report_month)
analysis = analyze_entries(entries)
num_users = len(analysis)
report_path = generate_report(analysis, report_year, report_month, num_users)
print(f"Report generated for {num_users} users covering {len(entries)} entries")
if __name__ == "__main__":
main()Deployment
wkhtmltopdf report.html report.pdfTesting & Validation
Agent Installation Test
Verify the time-capture agent (Billables AI or Memtime) is running on each workstation by checking Task Manager (Windows) or Activity Monitor (macOS) for the agent process. Confirm memory usage is <200MB and CPU <1%.
Get-Process -Name 'BillablesAI' | Select ProcessName, WorkingSet64, CPUClio API Connectivity Test
Execute a test API call to Clio from the agent server or a workstation. Expected response: 200 OK with authenticated user details. If 401, re-authenticate OAuth flow.
curl -X GET 'https://app.clio.com/api/v4/users/who_am_i.json' -H 'Authorization: Bearer TOKEN'Client/Matter Sync Test
Calendar Event Capture Test
Email Capture Test
Send a test email to an external address (or from an external address) with subject line containing a known client/matter reference. Verify the agent generates a draft time entry mapped to the correct matter within the configured sync interval (typically 15–60 minutes).
Document Activity Capture Test
Duplicate Prevention Test
SSO Authentication Test
Billing Increment Test
Verify all AI-generated entries use the firm's billing increment (typically 0.1 hours / 6 minutes). Create activities of various durations and confirm correct rounding behavior.
- 3 min → 0.1 hr
- 8 min → 0.2 hr
- 15 min → 0.3 hr
- 45 min → 0.8 hr
Pre-Bill Validator Test
Run the Pre-Bill AI Entry Validator script manually. Verify it correctly identifies the following issue types, and verify the HTML report is emailed to the billing coordinator address.
- Entries without matter assignments (HIGH)
- Entries exceeding 4 hours (MEDIUM)
- Entries with very short narratives (LOW)
End-to-End Billing Workflow Test
With a pilot attorney, complete the full billing cycle. Verify no AI-generated entries appear on invoice without attorney approval.
Performance Impact Test
On a pilot workstation, measure system performance with agent running vs. baseline. Open Task Manager → Performance tab, note CPU and memory usage while running typical workflow (Outlook + Word + Chrome + Clio). Agent should add <1% CPU and <200MB RAM. If impact exceeds thresholds, contact vendor support for optimization.
Privacy Verification Test
Confirm the time-capture agent is NOT recording sensitive content. Review the agent's captured data in its admin portal and document this verification for compliance records.
No document content, email body text, or keystrokes should be stored by the time-capture agent. Failure to verify this could expose the firm to confidentiality and compliance violations.
Client Handoff
Client Handoff Checklist
Training Sessions to Deliver
Documentation to Deliver
- Quick Reference Card (1-page PDF): Laminated card for each attorney covering: where to find draft entries in Clio, how to approve/edit/reject, daily workflow checklist, support contact info
- System Architecture Diagram: Visual showing data flow from M365 → Agent → Clio, including which data is captured and where it is stored
- Vendor Compliance Binder: SOC 2 reports, data processing agreements, and privacy policies for Clio, Billables AI/Memtime, and Microsoft 365. Includes firm's signed acknowledgment of AI-assisted billing per ABA requirements
- Troubleshooting Runbook: Common issues and resolutions (agent not syncing, calendar events not captured, incorrect matter matching), escalation path to MSP and vendor support
- Password/Credential Safe Access: Ensure firm admin has documented access to Clio admin account, Azure AD global admin, time-capture agent admin portal, and all API credentials (stored in firm's password manager)
Success Criteria to Review Together
Maintenance
Ongoing Maintenance Responsibilities
Weekly Tasks (MSP)
- Monitor agent health: Verify time-capture agent is running on all workstations via RMM dashboard. Check for crash reports, high memory usage, or process termination. Auto-remediate by restarting agent service if down >1 hour.
- Review Pre-Bill Validator report: Check the weekly automated email for HIGH-severity flags. Investigate and resolve any systematic issues (e.g., new client matters not syncing, recurring duplicate entries).
- Check integration sync status: Log into time-capture agent admin portal and verify Clio sync is active (last sync <15 min ago), matter count matches Clio, and no webhook delivery failures.
Monthly Tasks (MSP)
- Generate and review Monthly Billing Recovery Report: Run the ROI report script, review results, and send to firm managing partner and MSP account manager.
- Review and tune matching rules: Analyze entries that required manual matter assignment (unmatched entries). Add new keyword rules for any new matters or clients that the AI is not automatically matching.
- Software updates: Check for time-capture agent updates and deploy via Intune. Review Clio release notes for API changes that might affect integrations. Update Microsoft 365 and browser versions.
- User provisioning review: Ensure all new attorneys have been onboarded with the agent and all departed attorneys have been deprovisioned.
Quarterly Tasks (MSP)
- Quarterly Business Review (QBR): Present cumulative ROI data to firm leadership. Review accuracy trends. Identify optimization opportunities (new practice areas, additional capture sources like phone call logging). Discuss any compliance changes.
- Compliance review: Verify vendor SOC 2 certifications are current. Check for any new state bar ethics opinions regarding AI in billing. Update compliance documentation if needed.
- Clio API credential rotation: Regenerate OAuth tokens if approaching expiration. Update in all automation scripts and agent configurations.
Annual Tasks (MSP)
- Full system audit: Review entire integration architecture, test disaster recovery (agent reinstallation from scratch, Clio data export/import), verify all documentation is current.
- License renewal management: Coordinate Clio, Billables AI/Memtime, and M365 license renewals. Negotiate volume discounts if firm has grown.
- Vendor reassessment: Evaluate if current time-capture vendor remains the best option. Compare against new market entrants. Present findings in QBR.
Escalation Paths
- Tier 1 (firm IT liaison / office manager): Agent not running → restart from system tray; entries not appearing → check internet connectivity and agent login status
- Tier 2 (MSP L2/L3 technician): Sync failures, API errors, Intune deployment issues, SSO problems → 4-hour response SLA during business hours
- Tier 3 (vendor support): AI matching engine issues, feature requests, platform outages → escalate to Billables AI/Memtime support with ticket reference; Clio support for billing platform issues
- Emergency: Data breach or suspected unauthorized access to client data → immediately disable agent and Clio API connections; notify firm managing partner; follow incident response plan per firm's cybersecurity policy
Alternatives
Smokeball Built-In Automatic Time Tracking
Instead of using a separate time-capture agent (Billables AI/Memtime) connected to Clio, use Smokeball as the practice management platform, which includes built-in automatic time tracking as a core feature. Smokeball passively captures all attorney activity and automatically generates time entries without requiring a third-party agent or API integration.
Memtime (Privacy-First) Instead of Billables AI
Use Memtime as the time-capture agent instead of Billables AI. Memtime stores all activity data locally on the attorney's workstation rather than in the vendor's cloud. The attorney manually reviews captured activity timelines and creates time entries from them, which then sync to Clio.
Tempello Email-Only as Entry Point
Start with only email-based time capture using Tempello ($0.39/matched email, no subscription) connected to Clio and Gmail/Outlook. This captures only email correspondence billing, not calendar events or document work. Expand to full time capture later after demonstrating ROI.
Microsoft 365 Copilot + Power Automate Custom Build
Instead of using a dedicated legal time-capture vendor, build a custom solution using Microsoft 365 Copilot (for AI narrative generation) and Power Automate (for workflow automation) that reads from Microsoft Graph API (calendar, email, document activity) and writes to Clio via its REST API. No third-party time-capture agent required.
BigHand SmartTime for Enterprise Firms
For firms with 50+ attorneys or those using enterprise billing platforms (Thomson Reuters Elite 3E, Aderant Expert), replace the entire Clio + Billables AI stack with BigHand SmartTime, an enterprise-grade AI time capture solution designed for large law firms.
Want early access to the full toolkit?