Claude Code skill pack for Fathom (18 skills)
Installation
Open Claude Code and run this command:
/plugin install fathom-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
Skills (18)
Test Fathom integrations in CI/CD pipelines.
Fathom CI Integration
Overview
Set up CI/CD for Fathom meeting intelligence integrations: run unit tests with mocked transcript and action-item responses on every PR, validate live API connectivity against the Fathom meetings endpoint on merge to main. Fathom provides AI-generated meeting summaries, transcripts, and action items, so CI pipelines focus on verifying data parsing logic and webhook handling for real-time meeting events.
GitHub Actions Workflow
# .github/workflows/fathom-ci.yml
name: Fathom CI
on:
pull_request:
paths: ['src/fathom/**', 'tests/**']
push:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm test -- --reporter=verbose
integration-tests:
if: github.ref == 'refs/heads/main'
needs: unit-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm run test:integration
env:
FATHOM_API_KEY: ${{ secrets.FATHOM_API_KEY }}
Mock-Based Unit Tests
// tests/fathom-service.test.ts
import { describe, it, expect, vi } from 'vitest';
import { extractActionItems } from '../src/fathom-service';
const mockMeeting = {
id: 'mtg_abc123',
title: 'Sprint Planning',
date: '2026-04-01T10:00:00Z',
transcript: 'We need to fix the login bug by Friday...',
action_items: [
{ assignee: 'Alice', task: 'Fix login bug', due: '2026-04-05' },
{ assignee: 'Bob', task: 'Update API docs', due: '2026-04-07' },
],
};
vi.mock('../src/fathom-client', () => ({
FathomClient: vi.fn().mockImplementation(() => ({
getMeeting: vi.fn().mockResolvedValue(mockMeeting),
listMeetings: vi.fn().mockResolvedValue({ meetings: [mockMeeting], total: 1 }),
})),
}));
describe('Fathom Service', () => {
it('extracts action items from meeting transcript', async () => {
const items = await extractActionItems('mtg_abc123');
expect(items).toHaveLength(2);
expect(items[0].assignee).toBe('Alice');
});
});
Integration Tests
// tests/integration/fathom.integration.test.ts
import { describe, it, expect } from 'vitest';
const hasKey = !!process.env.FATHOM_API_KEY;
describe.skipIf(!hasKey)('Fathom Live API', () => {
it('lists recent meetings', async () => {
const res = await fetch('https://api.fathom.video/v1/meetings?limit=1', {
headers: { Authorization: `Bearer ${process.env.FATHOM_API_KEY}` },
});
Diagnose and fix Fathom API errors including auth failures and missing data.
Fathom Common Errors
Error Reference
1. 401 Unauthorized
Fix: Regenerate API key at Settings > Integrations > API Access.
2. 429 Rate Limited
Limit: 60 calls per minute across all API keys.
Fix: Implement exponential backoff. Batch requests.
3. Empty Transcript
Causes: Meeting still processing, recording too short, or audio quality issues.
Fix: Wait 5-10 minutes after recording. Check recording in Fathom UI.
4. Missing Summary
Cause: AI processing not complete.
Fix: Poll the recording endpoint until summary is available.
5. Webhook Not Firing
Fix: Verify webhook URL in Settings > Integrations > Webhooks. Test with:
curl -X POST https://your-url.com/webhooks/fathom \
-H "Content-Type: application/json" \
-d '{"type": "test"}'
6. OAuth Token Expired
Fix: Refresh the access token using your refresh token.
Quick Diagnostics
# Test API key
curl -s -o /dev/null -w "%{http_code}" -H "X-Api-Key: ${FATHOM_API_KEY}" \
https://api.fathom.ai/external/v1/meetings?limit=1
Resources
Next Steps
For diagnostics, see fathom-debug-bundle.
Build a meeting analytics pipeline with Fathom transcripts and summaries.
Fathom Core Workflow: Meeting Analytics
Overview
Build automated meeting analytics: extract action items, sync to project management tools, analyze meeting patterns, and create follow-up workflows.
Instructions
Step 1: Batch Meeting Export
from fathom_client import FathomClient
from datetime import datetime, timedelta
client = FathomClient()
# Get all meetings from last 7 days
week_ago = (datetime.utcnow() - timedelta(days=7)).isoformat() + "Z"
meetings = client.list_meetings(
limit=50,
created_after=week_ago,
include_summary="true",
)
for meeting in meetings:
print(f"Meeting: {meeting['title']}")
print(f" Date: {meeting['created_at']}")
print(f" Summary: {meeting.get('summary', 'N/A')[:100]}...")
for item in meeting.get("action_items", []):
print(f" Action: {item['text']} -> {item.get('assignee', 'unassigned')}")
print()
Step 2: Action Item Extraction Pipeline
def extract_action_items(meetings: list[dict]) -> list[dict]:
items = []
for meeting in meetings:
for action in meeting.get("action_items", []):
items.append({
"meeting_title": meeting["title"],
"meeting_date": meeting["created_at"],
"action_text": action["text"],
"assignee": action.get("assignee", "unassigned"),
"meeting_id": meeting["id"],
})
return items
# Sync to task tracker
def sync_to_linear(items: list[dict], api_key: str):
for item in items:
# Create Linear issue from action item
pass
Step 3: Meeting Pattern Analysis
def analyze_meeting_patterns(meetings: list[dict]) -> dict:
total = len(meetings)
total_duration = sum(m.get("duration_seconds", 0) for m in meetings)
total_actions = sum(len(m.get("action_items", [])) for m in meetings)
return {
"total_meetings": total,
"total_hours": round(total_duration / 3600, 1),
"avg_duration_min": round(total_duration / total / 60, 1) if total else 0,
"total_action_items": total_actions,
"avg_actions_per_meeting": round(total_actions / total, 1) if total else 0,
}
Resources
Next Steps
For CRM sync and webhook automation, see fathom-core-workflow-b.
Sync Fathom meeting data to CRM and build automated follow-up workflows.
Fathom Core Workflow: CRM Sync & Follow-Up
Overview
Automate post-meeting workflows: sync meeting notes to CRM opportunities, send follow-up emails with action items, and maintain a meeting history database.
Instructions
Meeting-to-CRM Sync
def sync_meeting_to_crm(meeting: dict, crm_client):
summary = meeting.get("summary", "")
action_items = meeting.get("action_items", [])
participants = meeting.get("participants", [])
# Find matching CRM contact/opportunity by participant email
for email in participants:
contact = crm_client.find_contact(email=email)
if contact:
crm_client.log_activity(
contact_id=contact["id"],
type="meeting",
subject=meeting["title"],
body=f"Summary: {summary}\n\nAction Items:\n" +
"\n".join(f"- {a['text']}" for a in action_items),
date=meeting["created_at"],
)
Automated Follow-Up Email
def generate_followup_email(meeting: dict) -> str:
actions = meeting.get("action_items", [])
action_list = "\n".join(f"- {a['text']}" for a in actions)
return f"""Hi team,
Thanks for the meeting: {meeting['title']}
Summary:
{meeting.get('summary', 'No summary available')}
Action Items:
{action_list if action_list else '- None recorded'}
Best regards"""
Meeting History Database
CREATE TABLE fathom_meetings (
id VARCHAR PRIMARY KEY,
title VARCHAR NOT NULL,
created_at TIMESTAMP NOT NULL,
duration_seconds INTEGER,
summary TEXT,
participant_count INTEGER,
action_item_count INTEGER,
synced_to_crm BOOLEAN DEFAULT FALSE,
synced_at TIMESTAMP
);
Resources
Next Steps
For error troubleshooting, see fathom-common-errors.
Optimize Fathom API usage and plan selection.
Fathom Cost Tuning
Overview
Fathom pricing scales with per-seat licensing for team features, with primary cost drivers being transcript storage volume and recording hours consumed. Every meeting generates a transcript and AI summary that persist in storage. For organizations running dozens of meetings daily, unchecked transcript accumulation and redundant API polling for meeting data create unnecessary spend. Optimizing retrieval patterns and storage lifecycle directly reduces both API costs and plan overhead.
Cost Breakdown
| Component | Cost Driver | Optimization |
|---|---|---|
| Seat licenses | Per-user/month for Team plan | Audit active seats quarterly; remove inactive users |
| Transcript storage | Accumulated meeting transcripts | Archive transcripts older than 90 days to local storage |
| Recording hours | Meeting duration across all users | Disable recording for standup/informal meetings |
| API polling | Repeated list/get calls for meeting data | Use webhooks for push notifications instead of polling |
| CRM sync events | Per-meeting sync to Salesforce/HubSpot | Batch CRM writes; skip internal-only meetings |
API Call Reduction
class FathomTranscriptCache {
private cache = new Map<string, { transcript: string; summary: string }>();
async getTranscript(meetingId: string, apiFn: () => Promise<any>): Promise<any> {
// Transcripts are immutable after generation — cache permanently
if (this.cache.has(meetingId)) return this.cache.get(meetingId);
const result = await apiFn();
this.cache.set(meetingId, result);
return result;
}
async listMeetings(params: { include_summary: boolean }): Promise<any[]> {
// Always use include_summary=true to avoid N+1 calls
// Fetches summaries inline with the list response
const response = await fetch('/api/meetings?include_summary=true');
return response.json();
}
}
Usage Monitoring
class FathomUsageTracker {
private apiCalls = 0;
private readonly rateLimit = 60; // 60 req/min
private windowStart = Date.now();
async throttledCall<T>(fn: () => Promise<T>): Promise<T> {
if (Date.now() - this.windowStart > 60_000) {
this.apiCalls = 0;
this.windowStart = Date.now();
}
if (this.apiCalls >= this.rateLimit) {
const waitMs = 60_000 - (Date.now() - this.windowStart);
await new Promise(r => setTimeout(r, waitMs));
this.apiCalls = 0;
this.windowStart = Date.now();
}
this.apiCalls++;
return fn();
}
getUsageReport(): { callsThisMinute: number; remainingCapacity: number } {
return { callsThisMinuCollect Fathom API diagnostics for support cases.
Fathom Debug Bundle
Overview
Collect Fathom API connectivity status, meeting recording metadata, transcript availability, and authentication state into a single diagnostic archive. This bundle helps troubleshoot missing transcripts, failed meeting syncs, webhook delivery issues, and API authentication problems. Attach the output to Fathom support tickets so engineers can diagnose integration failures without back-and-forth.
Debug Collection Script
#!/bin/bash
set -euo pipefail
BUNDLE="debug-fathom-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE"
# Environment check
echo "=== Fathom Debug Bundle ===" | tee "$BUNDLE/summary.txt"
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$BUNDLE/summary.txt"
echo "FATHOM_API_KEY: ${FATHOM_API_KEY:+[SET]}" >> "$BUNDLE/summary.txt"
# API connectivity
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
-H "X-Api-Key: ${FATHOM_API_KEY}" \
https://api.fathom.ai/external/v1/meetings?limit=1 2>/dev/null || echo "000")
echo "API Status: HTTP $HTTP" >> "$BUNDLE/summary.txt"
# Recent meetings (last 5)
curl -s -H "X-Api-Key: ${FATHOM_API_KEY}" \
"https://api.fathom.ai/external/v1/meetings?limit=5" \
> "$BUNDLE/recent-meetings.json" 2>&1 || true
# Check transcript availability for latest meeting
MEETING_ID=$(curl -s -H "X-Api-Key: ${FATHOM_API_KEY}" \
"https://api.fathom.ai/external/v1/meetings?limit=1" 2>/dev/null \
| jq -r '.meetings[0].id // empty' 2>/dev/null || true)
if [ -n "$MEETING_ID" ]; then
curl -s -H "X-Api-Key: ${FATHOM_API_KEY}" \
"https://api.fathom.ai/external/v1/meetings/$MEETING_ID/transcript" \
> "$BUNDLE/latest-transcript-status.json" 2>&1 || true
fi
# Rate limit headers
curl -s -D "$BUNDLE/rate-headers.txt" -o /dev/null \
-H "X-Api-Key: ${FATHOM_API_KEY}" \
https://api.fathom.ai/external/v1/meetings?limit=1 2>/dev/null || true
tar -czf "$BUNDLE.tar.gz" "$BUNDLE" && rm -rf "$BUNDLE"
echo "Bundle: $BUNDLE.tar.gz"
Analyzing the Bundle
tar -xzf debug-fathom-*.tar.gz
cat debug-fathom-*/summary.txt # API auth + connectivity
jq '.meetings[] | {id, title, created_at}' debug-fathom-*/recent-meetings.json
grep -i "ratelimit\|retry" debug-fathom-*/rate-headers.txt
Common Issues
| Symptom | Check in Bundle | Fix | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| API returns 401 | summary.txt shows HTTP 401 |
Regenerate API key in Fathom Settings > Integrations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Meetings list empty | recent-meetings.
|