53 min readDeterministic automation

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

Dell TechnologiesLatitude 5450 (i7-1365U, 16GB DDR5, 512GB SSD, 14" FHD)Qty: 10

$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

LenovoThinkPad T14s Gen 6 (i7-1365U, 16GB, 512GB SSD, 14" WUXGA)Qty: 10

$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

AppleMacBook Air M3 (16GB Unified Memory, 512GB SSD, 15")Qty: 10

$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

Dell TechnologiesU2422H (24" USB-C Hub Monitor)Qty: 10

$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

Clio (Themis Solutions Inc.)SaaS per-seat monthly/annualQty: per user

$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)

Billables AI Inc.SaaS per-seat monthlyQty: 10+ seats (MSP volume)

$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

Memtime GmbHConnect tier (SaaS per-seat monthly)Qty: Per seat

$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

Microsoft CorporationSaaS per-seat monthly via CSPQty: per user/month

$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

Tempello Inc.Usage-based (per matched email)Qty: Estimated 500–2,000 emails/attorney/month

$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

Microsoft CorporationP1

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

Microsoft CorporationIncluded with M365 Business Premium

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.

1
Log into Clio as Admin → Settings → API → Developer Applications
2
Create new application: Name: 'Billables AI Integration' (or 'Memtime Integration'), Redirect URI: provided by vendor during setup, Scopes required: activities, bills, calendars, contacts, matters, tasks, time_entries, users
Verify matter structure via Clio API
bash
# 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'
Verify activity codes are configured in Clio.
bash
curl -X GET 'https://app.clio.com/api/v4/activity_descriptions.json?fields=id,name,type,rate' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Note

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.

1
Navigate to Azure Portal → Azure Active Directory → App Registrations → New Registration
2
Set Name to 'Legal Time Capture Agent'
3
Set Supported account types to 'Accounts in this organizational directory only'
4
Set Redirect URI: Web → https://login.billables.ai/oauth/callback (vendor-specific)
5
After registration, note the Application (client) ID and Directory (tenant) ID
1
Configure API Permissions: Microsoft Graph → Delegated permissions: Calendars.Read (read user calendar events), Mail.Read (read email metadata for time capture), Files.Read (read document activity from OneDrive/SharePoint), User.Read (basic profile for user mapping)
2
For admin-consented deployment (all users at once): Azure Portal → Enterprise Applications → [Your App] → Permissions → Grant admin consent
3
Configure Conditional Access Policy (recommended): Azure Portal → Security → Conditional Access → New Policy — Name: 'Legal Time Capture - Require Compliant Device', Assignments: Select time capture app, Conditions: All platforms, Grant: Require device to be marked as compliant (Intune)
Alternatively grant admin consent via PowerShell using Microsoft Graph SDK
powershell
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 UI
Note

If 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.

1
Azure Portal → Enterprise Applications → New Application → Search 'Clio'
2
Select 'Clio' from the gallery → Create
1
Azure Portal → Enterprise Applications → Clio → Single sign-on → SAML
2
Basic SAML Configuration: set Identifier (Entity ID), Reply URL (ACS URL), and Sign on URL (see values below)
3
Download Federation Metadata XML from Azure portal
4
Upload to Clio: Settings → Security → SSO → Upload SAML Metadata
5
Assign users in Azure AD: Enterprise Applications → Clio → Users and groups → Add assignment → Select all attorney/paralegal accounts
Basic SAML Configuration values for Azure AD
text
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/new
1
Open incognito browser → navigate to https://app.clio.com
2
Click 'Sign in with SSO' → should redirect to Microsoft login
3
Verify successful authentication and Clio dashboard loads
Note

Clio 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.

1
Download the Billables AI desktop agent MSI/EXE from vendor portal (Memtime alternative: download from https://www.memtime.com/download)
2
Convert to .intunewin format using Microsoft Win32 Content Prep Tool
3
Navigate to Intune Portal → Apps → Windows → Add → Windows app (Win32) and upload the .intunewin file
4
Configure App information: Name: Billables AI Time Capture Agent | Publisher: Billables AI Inc. | Version: [current version] | Category: Productivity
5
Configure Program settings: Install behavior: User | Restart behavior: No action
6
Configure Requirements: OS architecture: 64-bit | Minimum OS: Windows 10 22H2 | Disk space: 500 MB | RAM: 8192 MB
7
Configure Detection rules: Rule type: File | Path: C:\Program Files\BillablesAI | File: BillablesAI.exe | Detection method: File or folder exists
8
Configure Assignments: Required: 'Legal Staff - Attorneys' Azure AD group | Available: 'Legal Staff - Paralegals' Azure AD group
9
For macOS deployment via Intune: Package as .dmg or .pkg → Intune → Apps → macOS → Add, using similar detection and assignment configuration
Convert installer to .intunewin format using Microsoft Win32 Content Prep Tool
shell
IntuneWinAppUtil.exe -c C:\Packages\BillablesAI -s BillablesAI_Setup.exe -o C:\Packages\Output
Intune install and uninstall commands for Billables AI agent
shell
# Install command
BillablesAI_Setup.exe /S /TENANT=YOUR_TENANT_ID

# Uninstall command
C:\Program Files\BillablesAI\uninstall.exe /S
Note

For 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.

1
In Billables AI Admin Portal (https://app.billables.ai/admin): Navigate to Integrations → Clio
2
Click 'Connect to Clio'
3
OAuth flow redirects to Clio → authorize with Clio Admin account
4
Grant requested scopes: matters, contacts, time_entries, activities, users
5
Verify connection status shows 'Connected' with green indicator
  • 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
1
Map users: Billables AI user emails → Clio user accounts (should auto-match if emails are the same)
2
Configure Clio webhooks for real-time matter sync: Clio Admin → Settings → API → Webhooks → Add
3
Set webhook events: matter.created, matter.updated, matter.closed
4
Set webhook URL: https://api.billables.ai/webhooks/clio/YOUR_TENANT_ID
5
Verify webhook delivery with test event

Test the integration: Create a test time entry in Billables AI → verify it appears in Clio using the following API call:

Verify draft time entries were pushed to Clio successfully
bash
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'
Critical

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.

1
In Billables AI Admin Portal → Integrations → Microsoft 365: Click 'Connect Microsoft 365'
2
OAuth flow → sign in with M365 Global Admin
3
Consent to permissions: Calendars.Read, Mail.Read, User.Read
4
Select 'All users' or specific attorney accounts
1
Rule 1: Calendar events with client name in subject → auto-map to matching Clio matter
2
Rule 2: Calendar events with matter number (e.g., '2024-00123') → exact match to Clio matter
3
Rule 3: Court events (keywords: 'hearing', 'trial', 'deposition', 'mediation') → flag for manual matter assignment
4
Rule 4: Internal meetings (no client reference) → mark as non-billable by default
5
Rule 5: Declined/cancelled events → exclude from time capture
Narrative generation template for calendar-derived time entries
text
# 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'
1
For firms using Google Workspace instead: navigate to Integrations → Google Calendar → OAuth with Google Admin
2
Same mapping rules apply
Note

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.

1
In Billables AI Admin Portal → Integrations → Email
2
Connection method: Microsoft Graph API (already authorized in Step 6)

Configure Email Capture Rules

1
Capture scope: 'Sent items only' (received-only emails may not represent billable work) — Alternative: 'Sent and received from external domains'
2
Exclude internal-only threads: Enabled
3
Exclude newsletters/automated emails: Enabled (filter by sender domain blocklist)
4
Minimum thread engagement: 1 sent reply (avoid capturing spam/marketing)

Domain-Based Client Matching

Map client email domains to Clio clients:

Example domain-to-client mappings
text
smithcorp.com → Smith Corporation (Matter: 2024-00045)
janedoe@gmail.com → Doe, Jane (Matter: 2024-00089)

Narrative Generation for Emails

Narrative template and example output
text
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

1
Tempello portal → Connect Clio → Connect Gmail/Outlook → configure matching
2
Tempello pricing: $0.39 per matched email (usage-based, no subscription)
Note

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.

1
Desktop agent configuration (Billables AI or Memtime): Navigate to Agent Settings → Activity Capture → Document Monitoring
2
Configure monitored applications: Microsoft Word (winword.exe / Microsoft Word), Microsoft Excel (excel.exe / Microsoft Excel), Microsoft PowerPoint (powerpnt.exe / Microsoft PowerPoint), Adobe Acrobat Pro (Acrobat.exe / Adobe Acrobat), Adobe Acrobat Reader (AcroRd32.exe / Adobe Acrobat Reader), Web browsers when URL contains: docs.google.com, sharepoint.com, netdocuments.com, Westlaw (westlaw.com in browser), LexisNexis (lexis.com in browser)
3
Set file-to-matter matching strategy
4
Configure idle detection settings
5
Set up narrative generation templates for document work
6
Configure legal research detection
File-to-matter matching, idle detection, narrative generation, and legal research detection configuration
text
# 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'
Note

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.

1
Identify pilot users in Billables AI admin: Admin → Users → Select 2-3 pilot attorneys → Enable 'Active Capture'. All other users remain in 'Observe Only' mode (agent installed but not generating entries).
2
Configure pilot-specific settings: Set Time entry push mode to 'Draft with Notification' (email alert when new entries created). Enable Accuracy tracking via 'Comparison Mode' — agent generates entries but also asks attorney to manually enter time for same period, enabling accuracy measurement.
3
Create a shared Excel/SharePoint tracking sheet with columns: Date | Matter | AI-Generated Entry | Attorney Adjustment | Match Correct (Y/N) | Narrative Quality (1-5). Share with pilot attorneys and MSP project manager.
4
Daily monitoring during pilot (MSP responsibility): Check Billables AI admin dashboard for sync errors, review Clio for duplicate entries (manual + AI-generated), monitor agent health via RMM (process running, memory usage), and hold a weekly 15-minute check-in call with each pilot attorney.
Verify agent process running on workstations via RMM
powershell
Get-Process -Name 'BillablesAI' -ErrorAction SilentlyContinue | Select-Object ProcessName, CPU, WorkingSet64
# Expected: Process running with <200MB working set
Clio API: Check for draft time entries created by integration
bash
curl -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.tool
Note

The 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

1
Navigate to Admin → Matching Rules → Custom Keywords
Example custom matter-matching keyword rules
text
'Smith' + 'NDA' → Matter 2024-00045
'Jones divorce' → Matter 2024-00112
'ABC Corp' OR 'abc-corp.com' → Client: ABC Corporation

2. Adjust Narrative Templates Per Practice Area

Narrative template formats by practice area
text
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

Activity code mappings by source type
text
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

1
Navigate to Admin → Users → [Attorney] → Narrative Style
2
Set preference to 'Concise', 'Standard', or 'Detailed' based on attorney preference

5. Create Exclusion Rules

Exclusion rule examples for non-billable activity filtering
text
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 browsers
Note

Matter-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.

1
Enable all users in Billables AI admin: Admin → Users → Select All → Enable 'Active Capture'
2
Verify agent deployment status across all workstations: Intune → Apps → Billables AI → Device install status. Expected: 'Installed' for all assigned devices
3
Alternative check via RMM: Run script across all managed devices (see below)
4
Configure Clio dashboard for time entry review: For each attorney: Clio → Time Entries → Filter: Status=Draft, User=[Self]. Save as custom view: 'My AI-Generated Time Entries'. Recommendation: Set as default landing page in Clio
5
Create automated reminder: Power Automate flow: Daily at 4:30 PM → Send Teams message to each attorney: 'You have [X] draft time entries pending review in Clio. Please review before end of day.'
Check agent process status across all managed devices via RMM
powershell
Get-Process -Name 'BillablesAI' -ErrorAction SilentlyContinue
# Or for Memtime:
Get-Process -Name 'Memtime' -ErrorAction SilentlyContinue
1
Overview: How the system works (15 min)
2
Demo: Review and approve draft entries in Clio (15 min)
3
Demo: How to correct matter assignment and improve AI learning (10 min)
4
Ethics: ABA Opinion 512 requirements for AI-assisted billing (10 min)
5
Q&A (10 min)
Note

Schedule 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.

1
Clio workflow configuration: Settings → Billing → Time Entry Settings: Set default status for new entries to 'Draft' (already set in Step 5); Enable 'Require approval before billing' (if available on plan).
2
Create Clio custom fields for AI tracking: Settings → Custom Fields → Activities → Add field named 'AI Generated', Type: Checkbox, Default: Checked (for entries from integration). This allows filtering and reporting on AI vs. manual entries.
3
Billing Coordinator: Clio → Billing → Pre-Bills → Generate for period.
4
Review: Filter for AI-generated entries (custom field = true).
5
Flag any entries that seem incorrect or unusual.
6
Managing Partner: Review flagged entries + random sample (10%).
7
Approve pre-bill → Generate invoice → Send to client.

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.

Run weekly via scheduled task to flag high-value AI-generated entries for extra review
bash
python3 flag_high_value_entries.py

Monthly 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.

Note

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
Check for existing Clio draft time entries for the event date
http
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
Search Clio matters by calendar event subject
http
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
Duration calculation expression
power-automate-expression
# 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
Narrative generation expression using event subject and attendee list
power-automate-expression
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 request to create a draft time entry in Clio
json
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

1
Import flow template into firm's Power Automate environment
2
Configure Clio OAuth connection (shared connection for service account)
3
Create SharePoint list 'Clio User Mapping' with columns: UPN, ClioUserId
4
Populate mapping list with all attorney accounts
5
Test with pilot users before enabling for all

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

Pre-Bill AI Entry Validator for Clio
python
# 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

1
Install Python 3.9+ on a management server or deploy as Azure Function (Timer trigger: weekly)
2
Store Clio API credentials in Azure Key Vault or environment variables
3
Configure SMTP credentials (use M365 app password or OAuth2 SMTP)
4
Schedule via Windows Task Scheduler
5
Test with current draft entries and verify report accuracy
Windows Task Scheduler command to register the weekly validation job
batch
schtasks /create /tn "ClioEntryValidator" /tr "python3 C:\Scripts\validate_entries.py" /sc weekly /d MON /st 07:00

Clio-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:

python
#!/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

1
Deploy to same server as Pre-Bill Validator
2
Schedule daily:
3
Output mapping file is consumed by Calendar Event Mapper and Validator scripts
4
Monitor for unmatched users — indicates onboarding gap
Schedule the user sync script to run daily at 06:00
shell
schtasks /create /tn "ClioUserSync" /tr "python3 C:\Scripts\sync_users.py" /sc daily /st 06:00

Daily 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
Clio API: Get draft time entries for a user
http
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']}
Adaptive Card JSON payload for Teams reminder message
json
{
  "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

1
Import flow into firm's Power Automate environment
2
Create Clio API connection (OAuth service account)
3
Create Teams connection (service account or admin consent)
4
Upload user_mapping.json to SharePoint (generated by User Mapping Sync)
5
Test with pilot users first, then enable for all
6
Monitor flow run history weekly for failures

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:

Monthly Billing Recovery Report Generator — fetches Clio time entries, analyzes AI vs.
python
# 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

1
Schedule monthly: 2nd day of each month at 8:00 AM
2
Output HTML can be converted to PDF using the command below
3
Email report to MSP account manager and firm managing partner
4
Use in QBR presentations to demonstrate ongoing ROI
5
Adjust AVERAGE_BILLING_RATE per firm (get from Clio billing rate tables)
Convert the generated HTML report to PDF
bash
wkhtmltopdf report.html report.pdf

Testing & 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%.

Check agent process resource usage in PowerShell
powershell
Get-Process -Name 'BillablesAI' | Select ProcessName, WorkingSet64, CPU

Clio 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.

Test Clio API connectivity and authentication
bash
curl -X GET 'https://app.clio.com/api/v4/users/who_am_i.json' -H 'Authorization: Bearer TOKEN'

Client/Matter Sync Test

1
In the time-capture agent admin portal, verify the client and matter list matches Clio.
2
Count of open matters in agent should equal count in Clio.
3
Create a new test matter in Clio ('TEST-AutoSync-Delete') and verify it appears in the agent within 15 minutes (or immediately if webhooks configured).

Calendar Event Capture Test

1
Create a test calendar event in Outlook for the current date with subject 'Meeting with Test Client re: TEST-AutoSync-Delete Matter' lasting 30 minutes.
2
After the event time passes, verify the agent generates a draft time entry in Clio with: correct matter assignment, 0.5 hour quantity, narrative containing the meeting subject.
3
Delete test entries after validation.

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

1
Open a Word document with a filename containing a matter number (e.g., '2024-00045 - Test Brief.docx').
2
Work in the document for at least 7 minutes (above the 6-minute minimum increment).
3
Verify a draft time entry appears in Clio with correct matter and narrative referencing the document name.

Duplicate Prevention Test

1
Manually create a time entry in Clio for the same time period and matter as an AI-generated entry.
2
Verify the system does not create a duplicate.
3
If using the Calendar Event Power Automate flow alongside the desktop agent, confirm only one entry is created per event.

SSO Authentication Test

1
Log out of Clio completely.
2
Navigate to https://app.clio.com and click 'Sign in with SSO'.
3
Verify redirect to Microsoft login, successful authentication, and return to Clio dashboard.
4
Test with 3 different user accounts.
5
Test with MFA challenge.

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.

1
AI captures time from a real workday.
2
Attorney reviews draft entries at 5 PM.
3
Attorney approves/edits entries.
4
Billing coordinator generates pre-bill.
5
Managing partner reviews.
6
Invoice is generated.

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.

Critical

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

1
Attorney Training (60 min): How to review and approve AI-generated draft time entries in Clio; how to correct matter assignments; how to edit narratives; how corrections improve future AI accuracy; daily review workflow (check draft entries at 4:30 PM); ethics obligations under ABA Formal Opinion 512
2
Billing Coordinator Training (45 min): How to generate pre-bills with AI entries; how to identify and flag AI-generated entries using custom fields; how to run the weekly validation report; how to escalate issues to MSP
3
Managing Partner Training (30 min): High-level overview of the system; pre-bill review responsibilities per ABA Rules 5.1/5.3; how to read the monthly ROI report; when to contact MSP for adjustments
4
Office Manager/IT Liaison Training (30 min): How to onboard new attorneys (agent installation, Clio provisioning, user mapping); how to offboard departing attorneys; basic troubleshooting (agent not running, sync failures)

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?