# Api overview
Source: https://developers.fathom.ai/api-overview
This documentation describes Fathom's REST API.
## Authentication
All REST API resources are authenticated with API keys generated via the Fathom website's settings area.
## Rate limiting
We use several rate limiting strategies to ensure the availability of our APIs. Rate-limited calls to our APIs return a 429 status code. Calls to our APIs include headers indicating the current rate limit status.
### Global rate limits
Authenticated requests are subject to a global limit. This is the maximum number of calls that your account can make to the API per minute. Endpoints subject to rate limits may return the headers below.
| Header | Description |
| --------------------- | ------------------------------------------------------------------------- |
| `RateLimit-Limit` | The maximum number of requests allowed in a time window |
| `RateLimit-Remaining` | The number of requests remaining in the current time window |
| `RateLimit-Reset` | The time remaining in the current time window |
| `Retry-After` | The number of seconds to wait before retrying. Only sent on 429 responses |
Currently, you are able to make a maximum of 60 calls to the API in a 60 second window.
The official SDKs automatically retry rate-limited requests, waiting for the duration indicated by the `Retry-After` header. If you are calling the API directly, wait at least `Retry-After` seconds before retrying a 429 response.
### Heavy Requests rate limits
For heavy requests, the maximum allowed number of calls is 30 in a 60 second window.
During periods of elevated activity this limit may be adjusted down to 5 every 60 seconds.
Heavy requests are:
* Requests to the /recordings summary and transcript endpoints
* Requests to /meetings that set include\_summary or include\_transcript to true
### Recording download rate limits
Requesting a recording download has its own limit, separate from heavy requests: 30 calls in a 60 second window.
During periods of elevated activity this limit may be adjusted.
Polling a download's status counts against the global limit.
### OAuth Rate Limits
60 requests per 60 seconds per OAuth app for the `https://api.fathom.ai/external/v1/oauth2/token` endpoint
# List meeting types
Source: https://developers.fathom.ai/api-reference/meetings/list-meeting-types
/api-reference/openapi.yaml get /meeting_types
List your org's meeting types, including both `active` and `inactive`. Use the returned `name` values as the meeting_type filter on GET /meetings.
# List meetings
Source: https://developers.fathom.ai/api-reference/meetings/list-meetings
/api-reference/openapi.yaml get /meetings
# Get download status
Source: https://developers.fathom.ai/api-reference/recordings/get-download-status
/api-reference/openapi.yaml get /recordings/{recording_id}/downloads/{download_id}
Returns the status of a download created with the request-a-download endpoint. When the
download is completed, the payload carries a short-lived signed URL for the generated
file. Only the API client that created the download can read it.
# Get summary
Source: https://developers.fathom.ai/api-reference/recordings/get-summary
/api-reference/openapi.yaml get /recordings/{recording_id}/summary
This endpoint has two behaviors depending on your request payload:
- If you send `destination_url`, the endpoint will behave in an asynchronous manner.
- If you do not send `destination_url`, the endpoint will return the data directly.
# Get transcript
Source: https://developers.fathom.ai/api-reference/recordings/get-transcript
/api-reference/openapi.yaml get /recordings/{recording_id}/transcript
This endpoint has two behaviors depending on your request payload:
- If you send `destination_url`, the endpoint will behave in an asynchronous manner.
- If you do not send `destination_url`, the endpoint will return the data directly.
# Request a download
Source: https://developers.fathom.ai/api-reference/recordings/request-a-download
/api-reference/openapi.yaml post /recordings/{recording_id}/download
Starts async generation of a downloadable file and returns a `download_id` to poll. Video is generated in the background; audio-only recordings complete immediately, so the response may already return `status: completed` with the file payload.
Downloads are private to the API client that created them, and URLs expire ~24 hours after generation—request a new one when it expires.
Only the owner, teammates who can view the recording, and people it was shared with at standard or admin level can download it. Limited-access shares get `403 Forbidden`.
# List team members
Source: https://developers.fathom.ai/api-reference/team-members/list-team-members
/api-reference/openapi.yaml get /team_members
# List teams
Source: https://developers.fathom.ai/api-reference/teams/list-teams
/api-reference/openapi.yaml get /teams
# List users and their permissions
Source: https://developers.fathom.ai/api-reference/users/list-users-and-their-permissions
/api-reference/openapi.yaml get /users
List all users in the account with their permissions.
Admin only. Returns 403 Forbidden unless the API key belongs to a user with `settings_access` of `account_admin`.
Results include active, deactivated, then pending members; page through with `next_cursor`, or filter with `status`. Invited users have no permissions object yet, and their `created_at` is the invite date.
# New meeting content ready
Source: https://developers.fathom.ai/api-reference/webhook-payloads/new-meeting-content-ready
/api-reference/openapi.yaml webhook newMeeting
Webhook sent to the URL you register in Fathom settings.
# Create a webhook
Source: https://developers.fathom.ai/api-reference/webhooks/create-a-webhook
/api-reference/openapi.yaml post /webhooks
Create a webhook to receive new meeting content.
At least one of `include_transcript`, `include_crm_matches`, `include_summary`, or `include_action_items` must be true.
# Delete a webhook
Source: https://developers.fathom.ai/api-reference/webhooks/delete-a-webhook
/api-reference/openapi.yaml delete /webhooks/{id}
Delete a webhook.
# What's new?
Source: https://developers.fathom.ai/changelog
Updates and announcements for developers.
Added download recording endpoint. [docs](https://developers.fathom.ai/api-reference/recordings/request-a-download)
Added users and permissions endpoint. [docs](https://developers.fathom.ai/api-reference/users/list-users-and-their-permissions)
Added `meeting_url` to API response and webhooks. [docs](https://developers.fathom.ai/api-reference/meetings/list-meetings#response-items-items-meeting-url-one-of-0:~:text=The%20underlying%20meeting%20join%20URL%20\(Zoom%2C%20Google%20Meet%2C%20Microsoft%20Teams%2C%20or%20Slack%20huddle\)%20from%20the%20calendar%20event.%20null%20when%20there%20is%20no%20associated%20calendar%20meeting.)
Added `meeting_type` to API response and webhooks. [docs](https://developers.fathom.ai/api-reference/meetings/list-meetings#parameter-meeting-type)
Added `include_highlights` to API response and webhooks. [docs](https://developers.fathom.ai/api-reference/meetings/list-meetings#parameter-include-highlights)
Added `shared_with` to API response and webhooks. [docs](https://developers.fathom.ai/api-reference/meetings/list-meetings#response-items-items-shared-with)
Webhooks include `id` [docs](https://developers.fathom.ai/api-reference/webhooks/create-a-webhook#response-id)
[MCP added.](https://developers.fathom.ai/mcp-docs)
API + SDKs and webhooks added.
# FAQ
Source: https://developers.fathom.ai/faq
Frequently asked questions about Fathom's API
## Basics
### I’m new to APIs. How do I get started?
See the [Quickstart](/quickstart).
### Should I use an API key or build an OAuth app?
Use an **API key** if you only need access to your own (or your team’s) meetings—for internal tools or personal automation. Use **OAuth** if you’re building an app that other Fathom users will install so they can connect their own accounts. Note: OAuth apps can’t use `include_summary`/`include_transcript` on `/meetings`—fetch those via the `/recordings` endpoints instead.
***
## Access & Permissions
### Does my plan include API access?
Yes. API access—keys and webhooks— are included on all plans. Generate a key in User Settings → API Access.
### What meetings can my API key access?
Meetings you recorded and meetings shared with you or your team. Not other users’ private, unshared meetings. Whatever a user can view in Fathom, their API key can access.
### Can I access all calls across my org? Do you offer org-level API keys?
API keys are per user, not per org, and there are no org-level keys. For org-wide access, grant one or more **Admins view access to all shared calls**; that Admin’s key can then read everything shared across teams. Private calls remain visible only to their host, so only the host’s key can access them.
### Do admin API keys grant access to other users’ private meetings?
No. Admin or not, your API key only accesses meetings you recorded or that are shared with you or your team (or, for Admins, shared calls you’ve been granted view access to). Private calls stay host-only.
### If I generated my key while on a team and later leave or downgrade, does it still work?
Yes. The key keeps working and doesn’t need to be regenerated—only your permissions (which meetings it can see) change.
### What happens to a user’s API key when they are deactivated?
The key stops working and will return **4xx** response.
### Can an admin disable another user’s API key?
No. Admins can’t directly revoke another user’s API key. A user’s key only stops working when that user is deactivated or removed from the team (see above).
### Why am I getting a 401 Unauthorized error?
Your auth is missing or invalid: wrong or revoked API key, expired OAuth token, or malformed header. Check that the key is valid in User Settings, the user is still active, and you’re sending `X-Api-Key` (or Bearer for OAuth) correctly.
***
## Querying
### Does the API support pagination? Is there a bulk endpoint?
Yes to pagination—responses include a `next_cursor`; pass it as the `cursor` query parameter for the next page (the SDKs handle this for you). There is no bulk endpoint; pagination is the way to retrieve large sets.
### How many results come back per request?
The default page size is 10 meetings. There is no parameter to raise it—use `next_cursor` to page through more.
### Can I filter meetings by meeting type?
Yes. Pass `meeting_type` on `GET /meetings`, and use `GET /meeting_types` to discover valid names.
### Can I query meetings by attendee?
Not yet. Today you can filter by the attendee’s company domain via `calendar_invitees_domains[]`, and by `recorded_by[]` or `teams[]`.
### Can I look up a recording using a Fathom call URL?
No. The ID in a call URL (e.g. `/calls/610313346`) is the `url`, not the `recording_id` used by the API. They are different identifiers and aren’t interchangeable. To find a recording, query the [list meetings endpoint](/api-reference/meetings/list-meetings) with the available filters (e.g. `created_after`/`created_before`, `recorded_by[]`, `teams[]`, `calendar_invitees_domains[]`, `meeting_type`); each returned meeting includes its `recording_id`.
***
## Summaries
### What summary template is returned by the API?
Your account’s **default** summary template. The response has `template_name` (e.g. `"general"`) and `markdown_formatted` with the summary text.
### Can I change which summary type is returned?
No. The API returns your default template; changing a call’s template in the UI won’t change what the API returns.
### Can I request multiple summaries for a call?
No. One summary per recording.
### Can I get a plaintext (non-Markdown) summary?
No. Summaries from both the API and webhooks are Markdown-formatted only.
***
## Recordings & Downloads
### Can I download video or audio recordings via the API?
Yes. Call `POST /recordings/{recording_id}/download` to start generating a downloadable file, then poll [Get download status](/api-reference/recordings/get-download-status) (or pass a `destination_url` to have Fathom POST the result to you). The response returns a short-lived signed URL that expires \~24 hours after generation—request a new download when it expires. Downloading needs more than view access: limited-access shares receive **403 Forbidden**.
***
## Webhooks
### How do I set up and test a webhook?
Create one in User Settings → API Access → Manage → Add Webhook (or via the API). To test, use **Send test payload** in the webhook settings to fire a sample event at your endpoint. Note that this will still show "Test payload has been sent" if your endpoint responds with an error.
### Why isn’t my webhook firing?
Most often: **no post-call summary means no webhook.** Webhooks are tied to summary-email delivery, so short test calls without enough audio cues won’t fire. Also check: (1) the call generated a summary, (2) the call matches your trigger scope (private calls only fire `my_recordings`), (3) visibility was correct *at the moment the call finalized*—changing it afterward does not re-fire, (4) your endpoint returns 2xx quickly, and (5) no firewall/SSL issues.
### When are webhooks triggered, and how soon?
When new meeting content is ready—when a recording finalizes (the same event that triggers the summary email). One event per new meeting. Processing time varies, so we don’t publish a guaranteed delivery window.
### Do webhooks fire for impromptu calls?
Yes, as long as the call finalizes and generates a summary, and is owned by you or shared to a team.
### Are webhooks retried on non-2xx responses or timeouts?
Yes. Non-2xx responses or timeouts may trigger retries (the same event can be sent again). We don’t publish the retry schedule or attempt count.
### Can the same webhook event be delivered more than once?
Yes—but only via automatic retries; there’s no way to manually re-send a webhook after the fact. When an event is retried, the `webhook-id` header stays the same, so use it to deduplicate.
### Do you send additional webhooks if transcripts or summaries are updated later, or if visibility changes?
No. Webhooks fire once when content is first ready and won’t re-fire if content changes or the call’s visibility changes afterward. Poll the API if you need later updates.
### Can I add custom headers to outgoing webhook requests?
No. You can only set the destination URL, trigger scopes, and which data to include—there’s no option to add custom headers. Fathom sends its standard headers (including `webhook-id`, `webhook-timestamp`, and `webhook-signature` for verification).
### How do I verify a webhook actually came from Fathom?
Each request includes `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers. Verify the HMAC-SHA256 signature using your webhook secret (the SDK’s `verify_webhook` helper does this for you). See [Webhooks](/webhooks) for the full method.
### Do webhook payloads include sharing or team-membership info?
The `shared_with` field indicates the team-level sharing scope - `no_teams`, `single_team`, `multiple_teams`, `all_teams`.
### Can I retrieve a webhook’s ID later to delete it?
No. A webhook’s ID is returned only when you create it and can’t be retrieved afterward via the API—and it isn’t included in delivered payloads. Since deleting a webhook via the API requires that ID, save it at creation; if you didn’t, delete the webhook from the UI (User Settings → API Access → Manage) instead. (This creation ID is separate from the per-delivery `webhook-id` header used for deduplication.)
### How can I identify which user account triggered a webhook?
The payload doesn’t identify which user’s API key the webhook is tied to. If you’re receiving webhooks for multiple users, route each user’s webhooks to a separate, user-specific destination URL rather than a shared endpoint.
***
## OAuth
### Can a single OAuth app register multiple redirect URIs?
Only multiple development redirect URIs are supported. For multiple production URIs, use separate OAuth apps.
### Is HTTPS required for redirect URIs?
Yes.
### How do refresh tokens work?
Refresh tokens are one-time-use and rotate on each refresh—using one returns a new access token and a new refresh token, and invalidates the old refresh token. If two workers refresh the same connection at once, one succeeds and the other gets an **HTTP 400**, so wrap refreshes in a lock if you run them concurrently.
### Can OAuth apps use `include_summary` / `include_transcript` on `/meetings`?
No. OAuth-connected apps must fetch summaries and transcripts via the `/recordings` endpoints instead.
***
## Rate Limits
### What are the API rate limits?
See [Rate Limiting](https://developers.fathom.ai/api-overview#rate-limiting)
### What happens if I exceed the rate limit?
You get **429** and should back off until the window resets.
### Are rate limits applied per API key or per organization?
Per account (per API key or OAuth token).
### Can you increase my rate limit?
We don’t offer standard or seat-based increases—the limits exist for stability. If they’re blocking a real workflow, share your use case and we can raise it with the Product team.
***
## Misc
### Is real-time or live transcript available via the API?
No. Transcripts are available only after post-call processing completes.
### I have a suggestion or feature request.
Reach out to **[help@fathom.video](mailto:help@fathom.video)** for integration and API feedback.
# Introduction
Source: https://developers.fathom.ai/index
Welcome to Fathom's API! 🚀
Building a **public OAuth app** (for other Fathom users to install)? [Start here](/oauth) instead.
## Getting Started
Generate an API key and make your first call in minutes.
See all available endpoints and methods.
Set up a webhook for your meetings.
Use our Typescript or Python SDK.
## Build an app with Fathom
Build a Fathom integration that other users can install.
## Need inspiration?
See what others have built with Fathom's API.
# 💡 Get Inspired
Source: https://developers.fathom.ai/inspiration/index
Peek at what others have built—and spark your own next project.
Want to showcase your own Fathom integration? [Tell us about it!](mailto:api@fathom.video)
# Pylon
Source: https://developers.fathom.ai/inspiration/pylon
How Pylon uses Fathom to power AI-native customer support.
[Pylon](https://www.usepylon.com/?utm_source=fathom\&utm_medium=referral\&utm_campaign=integration-comarketing) is the AI-native support platform built for B2B. They help modern businesses handle support tickets efficiently and understand what's going on with their customers.
They built [a Fathom integration](https://docs.usepylon.com/pylon-docs/integrations/call-recording/fathom) to bring meeting insights directly into their customer support and success workflows.
> "With Fathom connected, our customers get enriched account notebooks that combine call data with data from Slack, email, etc. They also get AI-powered tasks auto-created from them and tracked in Pylon based on what was discussed on their call. Finally, their support team can now also harness all the knowledge across their collective Fathom calls to answer customer questions with Pylon's AI copilot."
> — Advith Chelikani, CTO, Pylon
***
## How it works
1. **Customer connects via OAuth** — Pylon uses Fathom's OAuth to let each customer securely link their account.
2. **Webhook fires** — when a meeting is completed, Fathom sends Pylon the meeting metadata.
3. **Data processed** — Pylon requests transcript and meeting metadata from Fathom.
4. **Data enriched** — Pylon uses Fathom data to enrich customer records and power AI workflows inside the platform.
***
## Example setup
Below is a simplified version of how Pylon wired things up:
### OAuth Token Exchange
```go go theme={null}
func (c *Controller) HandleFathomOAuth(w http.ResponseWriter, req *http.Request) {
code := req.FormValue("code")
data := url.Values{}
data.Set("grant_type", "authorization_code")
data.Set("code", code)
data.Add("client_id", c.config.FathomClientID)
data.Add("client_secret", c.config.FathomClientSecret)
data.Add("redirect_uri", c.config.CallbackUrl()+"/fathom/oauth-callback")
tokenReq, _ := http.NewRequest(http.MethodPost,
"https://api.fathom.ai/external/v1/oauth2/token",
strings.NewReader(data.Encode()))
tokenReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, _ := c.httpClient.Do(tokenReq)
var tokenResp FathomTokenResponse
json.NewDecoder(resp.Body).Decode(&tokenResp)
// Store encrypted tokens
org.AppSettings.FathomTokenEncrypted = c.tokenCrypter.EncryptTokenString(tokenResp.AccessToken)
org.AppSettings.FathomRefreshTokenEncrypted = c.tokenCrypter.EncryptTokenString(tokenResp.RefreshToken)
}
```
### Webhook Setup
```go go theme={null}
// Create webhook for receiving events
func (c *Impl) CreateWebhook(ctx context.Context, input *fathomtypes.CreateWebhookInput) (*fathomtypes.CreateWebhookResponse, error) {
url := "https://api.fathom.ai/external/v1/webhooks"
jsonBody, _ := json.Marshal(input)
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.apiKey)
resp, _ := c.httpClient.Do(req)
defer resp.Body.Close()
var webhookResp fathomtypes.CreateWebhookResponse
json.NewDecoder(resp.Body).Decode(&webhookResp)
return &webhookResp, nil
}
```
### Fetching Meeting Data
```go theme={null}
// Get meetings with transcripts from Fathom API
func (c *Impl) GetTranscripts(ctx context.Context, input *GetTranscriptsInput) ([]*fathomtypes.Meeting, string, error) {
baseURL := "https://api.fathom.ai/external/v1/meetings"
parsedURL, _ := url.Parse(baseURL)
query := url.Values{}
if input.IncludeTranscript {
query.Add("include_transcript", "true")
}
if input.Cursor != "" {
query.Add("cursor", input.Cursor)
}
query.Add("limit", strconv.Itoa(input.Limit))
parsedURL.RawQuery = query.Encode()
req, _ := http.NewRequest(http.MethodGet, parsedURL.String(), nil)
req.Header.Add("Authorization", "Bearer "+c.apiKey)
resp, _ := c.httpClient.Do(req)
defer resp.Body.Close()
var response fathomtypes.GetMeetingsResponse
json.NewDecoder(resp.Body).Decode(&response)
meetings := make([]*fathomtypes.Meeting, len(response.Items))
for i, meeting := range response.Items {
meetings[i] = &meeting
}
return meetings, *response.NextCursor, nil
}
```
## Why it matters
With the Pylon + Fathom integration, support and success teams can:
* Build account notebooks that combine calls, Slack, email, and more
* Auto-generate AI-powered tasks directly from customer conversations
* Use tools like Ask AI and Issue Copilot with call recordings as a knowledge source
👉 [Learn more about Pylon](https://www.usepylon.com/?utm_source=fathom\&utm_medium=referral\&utm_campaign=integration-comarketing).
# Twine
Source: https://developers.fathom.ai/inspiration/twine
How Twine bring meeting insights into revenue workflows with the help of Fathom
[Twine](https://twine.com) is *"the easiest way to turn customer conversations into intelligence that drives growth."* By connecting directly to Fathom, Twine analyzes every sales, customer success, and support call to uncover product gaps, deal blockers, churn risks, competitor mentions, and customer love—all automatically.
The two tools work in tandem. As Dee Kulkarni, CTO of The Martec, describes it:
> “The entry point is **Twine**, and then I end up going into **Fathom** to dive deeper on our most useful calls."
This workflow gives product teams the high-level patterns, revenue context, and the nuanced details they need to make informed decisions.
***
## How it works
1. **Customer connects via OAuth** — Twine uses Fathom's OAuth to let each customer securely link their Fathom account.
2. **Periodic sync** — Twine regularly fetches new meetings from Fathom's API based on the user's filters and settings.
3. **Data processed** — Twine requests transcripts and meeting metadata for each new meeting.
4. **Data enriched** — Twine uses Fathom data to power AI-driven customer intelligence.
***
## Example setup
Below is a simplified version of how Twine build their integration:
### OAuth Token Exchange
```js TypeScript theme={null}
// Exchange OAuth authorization code for access token
const api = ky.extend({
prefixUrl: "https://api.fathom.ai/external/v1",
});
export const exchangeOAuthCodeForToken = async (
code: string,
redirectUri: string
) => {
const res = await api
.post("oauth2/token", {
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri,
client_id: config.FATHOM_CLIENT_ID,
client_secret: config.FATHOM_CLIENT_SECRET,
}),
})
.json();
return zOAuthTokenResponse.parse(res);
};
```
Twine then stores the response in their secure encrypted store for each organization and handles token refresh during periodic syncs.
### Fetching Meetings
```js TypeScript theme={null}
// Fetch meetings with filters and pagination support
export const listMeetings = async (
auth: EncryptedAuth,
params: {
cursor?: string,
createdAfter: Date,
createdBefore: Date,
}
) => {
const res = await api
.get("meetings", {
headers: authHeaders(auth),
searchParams: {
created_after: params.createdAfter.toISOString(),
created_before: params.createdBefore.toISOString(),
...(params.cursor !== undefined
? { cursor: params.cursor }
: undefined),
},
})
.json();
return z
.object({
limit: z.number().nullish(),
items: z.array(
z.object({
title: z.string(),
url: z.string(),
recording_id: z.number(),
recording_start_time: z.string().datetime(),
recording_end_time: z.string().datetime(),
calendar_invitees: z.array(
z.object({
name: z.string(),
email: z.string(),
is_external: z.boolean(),
})
),
})
),
next_cursor: z.string().nullish(),
})
.parse(res);
};
```
Twine uses the calendar invitees to apply various filters to decide which call transcripts to import.
## Why it matters
With a Fathom and Twine integration, GTM and product teams can automatically get:
1. **Insights from GTM conversations shared with product teams**
Twine transforms every Fathom-recorded call into decision-ready insights, so Product teams can finally tap into Sales and CS conversations without sifting through transcripts.
2. **Curated intelligence where you work**
Instead of dashboards or noise, Twine delivers role-specific signals straight from Fathom calls into Slack or email — while there’s still time to act.
3. **Always-on, revenue-tied clarity**
Twine’s purpose-built AI connects the dots across customer calls, competitors, and accounts, surfacing insights tied directly to revenue — not just generic meeting notes.
👉 [Learn more about Twine](https://twine.com).
# ChatGPT
Source: https://developers.fathom.ai/mcp-docs/chatgpt
Connect Fathom to ChatGPT using the official Fathom app
### Steps:
1. Go to the **[Fathom app for ChatGPT](https://chatgpt.com/apps/fathom/asdk_app_69d88b99c5c481918e8da9225737e1e9)**.
2. Click **Connect** and complete the authorization prompts
That's it! Just type **@Fathom** to query your meetings.
# Claude
Source: https://developers.fathom.ai/mcp-docs/claude
Connect Fathom to Claude with the official connector, or via Claude Code
Claude (web & desktop)
Fathom is available as an **official connector** in Claude:
### Steps:
1. Go to the **[Fathom connector for Claude](https://claude.ai/directory/connectors/fathom)**.
If you're using Claude in a team or organization, an owner must first enable the connector before it will appear for members.
2. Click **Connect** and complete the authorization prompts.
That's it! You can now ask Claude about your meetings.
Claude Code
Claude Code supports adding MCP servers directly from the command line.
Run the following command in your terminal:
```bash theme={null}
claude mcp add fathom -- npx mcp-remote@latest https://api.fathom.ai/mcp
```
The Fathom MCP server will now be available in your Claude Code sessions.
# Overview
Source: https://developers.fathom.ai/mcp-docs/index
Connect your meeting data to LLMs using the Model Context Protocol
## How to Connect the Fathom MCP Server
Fathom now offers an official MCP (Model Context Protocol) server, allowing you to connect your Fathom meeting data to AI assistants like ChatGPT, Claude, and more. This guide walks you through setup on each supported platform.
## Choose your assistant
## Use with other tools
Connect Fathom to any MCP-compatible tool using the server URL below, then authenticate to access your meeting data.
**MCP server URL:** `https://api.fathom.ai/mcp`
# Troubleshooting
Source: https://developers.fathom.ai/mcp-docs/troubleshooting
### Claude Code not connecting?
Verify that Node.js is installed on your machine, as the `mcp-remote` bridge package requires it. You can check by running `node --version` in your terminal.
# Building with OAuth
Source: https://developers.fathom.ai/oauth
Launching an integration for mutual customers with Fathom? Let's get started!
## Integrating your app with Fathom
If you're developing a public app that you wish to integrate with Fathom, you should build it using OAuth.
OAuth apps are eligible for promotion by Fathom and can unlock visibility in our App Marketplace, as well as co-marketing opportunities. They are subject to review to ensure the best experience for all users.
## How to launch your app:
### 👀 Step 1: Check out Fathom’s partner enablement resources
Learn more about our product and scope out your integration opportunities:
* [Fathom Partner Demo Walkthrough](https://drive.google.com/file/d/1WKr6TWp01pxijE9OFjYsqVEseS3-ZcCJ/view)
* [Fathom’s Upcoming Webinars and Recordings](https://watch.getcontrast.io/fathom-ai)
### ✍️ Step 2: Register your app
Configure your redirects and receive your OAuth credentials. (2 mins)
### 🔑 Step 3: Configure OAuth Authentication
Once you have your OAuth credentials, [set up authentication](/sdks/oauth) using our SDK.
### 🔨 Step 4: Build it
See [what others have built](/inspiration) for proven integration patterns.
### 🚀 Step 5: Share your app with users!
Before sharing, review our [About Us page](https://www.fathom.ai/about-us) for boilerplate language, logos, and brand guidelines to ensure your app aligns with Fathom standards.
Reach out to [help@fathom.video](mailto:help@fathom.video) if you have any questions or need support on launch materials (ex: information for an integration listing on your site, amplification of a social post, etc.)
### 🌟 Step 6: Get featured
Each quarter, the Fathom team will review OAuth app integration usage. Once you have 20+ users, we will reach out to have you added to our own [Integration Listing Page](https://www.fathom.ai/integrations), connect via Crossbeam, and unlock co-marketing opportunities.
We can’t wait to partner with you!
# Quickstart
Source: https://developers.fathom.ai/quickstart
Generate an API key and make your first call
## Generate an API Key
Head to the API Access section of your User Settings and generate an API key.
API keys are scoped to the user who creates them. Your key can only access meetings you've recorded or that have been shared with you or your Team.
Admins can access all users' shared meetings by [configuring View permissions](https://help.fathom.video/en/articles/10783489#understanding_permissions). API keys *never* grant access to other users' private meetings.
## List recent meetings
List the 10 most recent meetings recorded by you or shared to your team.
```cURL cURL theme={null}
curl https://api.fathom.ai/external/v1/meetings \
-H "X-Api-Key: YOUR_API_KEY"
```
```Python Python theme={null}
import requests
# List meetings (GET /meetings)
response = requests.get(
"https://api.fathom.ai/external/v1/meetings",
headers={
"X-Api-Key": "YOUR_API_KEY"
},
)
print(response.json())
```
```Typescript TypeScript theme={null}
// List meetings (GET /meetings)
const response = await fetch("https://api.fathom.ai/external/v1/meetings", {
method: "GET",
headers: {
"X-Api-Key": "YOUR_API_KEY"
},
});
const body = await response.json();
console.log(body);
```
Replace `YOUR_API_KEY` with the API key you generated above.
```json Example response theme={null}
{
"items": [
{
"title": "Quarterly Business Review",
"meeting_title": "QBR 2025 Q1",
"meeting_type": "Quarterly Business Review",
"url": "https://fathom.video/xyz123",
"meeting_url": "https://us02web.zoom.us/j/123456789",
"share_url": "https://fathom.video/share/xyz123",
"created_at": "2025-03-01T17:01:30Z",
"scheduled_start_time": "2025-03-01T16:00:00Z",
"scheduled_end_time": "2025-03-01T17:00:00Z",
"recording_start_time": "2025-03-01T16:01:12Z",
"recording_end_time": "2025-03-01T17:00:55Z",
"transcript_language": "en",
"calendar_invitees": [
{
"is_external": false,
"name": "Alice Johnson",
"email": "alice.johnson@acme.com"
}
],
"recorded_by": {
"name": "Alice Johnson",
"email": "alice.johnson@acme.com",
"team": "Marketing"
},
"transcript": [
{
"speaker": {
"display_name": "Alice Johnson",
"matched_calendar_invitee_email": "alice.johnson@acme.com"
},
"text": "Let's revisit the budget allocations.",
"timestamp": "00:05:32"
}
],
"default_summary": {
"template_name": "general",
"markdown_formatted": "## Summary\nWe reviewed Q1 OKRs, identified budget risks, and agreed to revisit projections next month.\n"
},
"action_items": [
{
"description": "Email revised proposal to client",
"user_generated": false,
"completed": false,
"recording_timestamp": "00:10:45",
"recording_playback_url": "https://fathom.video/calls/xyz123?timestamp=645",
"assignee": {
"name": "Alice Johnson",
"email": "alice.johnson@acme.com",
"team": "Marketing"
}
}
],
"crm_matches": {
"contacts": [
{
"name": "Jane Smith",
"email": "jane.smith@client.com",
"record_url": "https://app.hubspot.com/contacts/123"
}
],
"companies": [
{
"name": "Acme Corp",
"record_url": "https://app.hubspot.com/companies/456"
}
],
"deals": [
{
"name": "Q1 Renewal",
"amount": 50000,
"record_url": "https://app.hubspot.com/deals/789"
}
],
"error": "no CRM connected"
}
}
],
"limit": 1,
"next_cursor": "eyJwYWdlX251bSI6Mn0="
}
```
## Get next 10 meetings
Use the `next_cursor` from the previous response to get the next page of meetings.
```cURL bash theme={null}
curl https://api.fathom.ai/external/v1/meetings \
-H "X-Api-Key: YOUR_API_KEY" \
-d cursor=CURSOR_FROM_PREVIOUS_RESPONSE
```
```Python python theme={null}
import requests
cursor = "CURSOR_FROM_PREVIOUS_RESPONSE"
response = requests.get(
f"https://api.fathom.ai/external/v1/meetings?cursor={cursor}",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
```
```Typescript TypeScript theme={null}
const cursor = "CURSOR_FROM_PREVIOUS_RESPONSE";
const response = await fetch(`https://api.fathom.ai/external/v1/meetings?cursor=${cursor}`, {
headers: {"X-Api-Key": "YOUR_API_KEY"}
});
```
If you're using our [TypeScript or Python SDKs](/sdks), pagination is handled automatically - no need to manage cursors manually. See [SDK Pagination](/sdks/pagination) for examples.
## Find specific meetings and get their transcripts
Let's say you met with `john.doe@client.com` a couple times during August and want to pull those transcripts. Use filters to return just those meetings.
```cURL cURL theme={null}
curl https://api.fathom.ai/external/v1/meetings \
-H "X-Api-Key: YOUR_API_KEY" \
-d include_transcript=true \
-d recorded_by[]=me@mydomain.com \
-d created_after=2024-08-01T00:00:00Z \
-d created_before=2024-09-01T00:00:00Z
# include_transcript=true: get transcripts in the response
# recorded_by[]=me@mydomain.com: meetings you recorded
# created_after/before: August date range
```
```Python Python theme={null}
import requests
response = requests.get(
"https://api.fathom.ai/external/v1/meetings",
headers={"X-Api-Key": "YOUR_API_KEY"},
params={
"include_transcript": "true", # get transcripts in the response
"recorded_by[]": "me@mydomain.com", # meetings you recorded
"created_after": "2024-08-01T00:00:00Z", # August 1st onward
"created_before": "2024-09-01T00:00:00Z" # before September 1st
}
)
meetings = response.json()["items"]
transcript = meetings[0]["transcript"] # Get first meeting's transcript
```
```Typescript TypeScript theme={null}
const params = new URLSearchParams({
include_transcript: "true", // get transcripts in the response
"recorded_by[]": "me@mydomain.com", // meetings you recorded
created_after: "2024-08-01T00:00:00Z", // August 1st onward
created_before: "2024-09-01T00:00:00Z" // before September 1st
});
const response = await fetch(`https://api.fathom.ai/external/v1/meetings?${params}`, {
headers: {"X-Api-Key": "YOUR_API_KEY"}
});
const meetings = await response.json();
const transcript = meetings.items[0].transcript; // Get first meeting's transcript
```
You can also fetch transcripts separately using the [/recordings/\{recording\_id}/transcript](/api-reference/recordings/get-transcript) endpoint. **OAuth apps** must use this approach since they can't use `include_transcript` or `include_summary`.
## Next steps
Now that you you've made your first API calls, time to go deeper:
See all available endpoints and methods
Set up a webhook
Use our Typescript or Python SDK
Build a Fathom integration with OAuth
# Advanced Configuration
Source: https://developers.fathom.ai/sdks/advanced-configuration
Advanced options and configurations for the Fathom SDKs
## Advanced Configuration
Configure your SDK for production use with custom settings, debugging, and optimization.
### Server Selection
Override the default server URL when needed:
```typescript TypeScript theme={null}
import { Fathom } from "fathom";
const fathom = new Fathom({
serverUrl: "https://api.fathom.ai/external/v1",
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
```
```python Python theme={null}
from fathom_python import Fathom, models
import os
with Fathom(
server_url="https://api.fathom.ai/external/v1",
security=models.Security(
api_key_auth=os.getenv("FATHOM_API_KEY_AUTH", ""),
),
) as fathom:
res = fathom.list_meetings()
while res is not None:
res = res.next()
```
### Custom HTTP Client
Customize headers and other HTTP client settings:
```typescript TypeScript theme={null}
import { Fathom } from "fathom";
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
```
```python Python theme={null}
from fathom_python import Fathom, models
import httpx
http_client = httpx.Client(headers={"x-custom-header": "someValue"})
fathom = Fathom(client=http_client, security=models.Security(api_key_auth="YOUR_API_KEY"))
```
### Standalone Functions (TypeScript Only)
Use standalone functions for bundle size optimization:
```typescript theme={null}
import { listMeetings } from "fathom";
const result = await listMeetings({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
for await (const page of result) {
console.log(page);
}
```
#### Available Standalone Functions
* `createWebhook` - Create a webhook
* `deleteWebhook` - Delete a webhook
* `listMeetings` - List meetings
* `listTeamMembers` - List team members
* `listTeams` - List teams
### Retries (Python Only)
Configure retry strategies for production reliability:
```python theme={null}
from fathom_python import Fathom, models
from fathom_python.utils import BackoffStrategy, RetryConfig
import os
with Fathom(
retry_config=RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False),
security=models.Security(
api_key_auth=os.getenv("FATHOM_API_KEY_AUTH", ""),
),
) as fathom:
res = fathom.list_meetings()
while res is not None:
res = res.next()
```
### Resource Management (Python Only)
Use context managers for proper resource cleanup:
```python theme={null}
from fathom_python import Fathom, models
import os
def main():
with Fathom(
security=models.Security(
api_key_auth=os.getenv("FATHOM_API_KEY_AUTH", ""),
),
) as fathom:
# Rest of application here...
```
### Debugging
Enable debug logging during development:
```typescript TypeScript theme={null}
import { Fathom } from "fathom";
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
// Manual debug logging
console.log('Making API request...');
const result = await fathom.listMeetings({});
console.log('Response received:', result);
```
```python Python theme={null}
from fathom_python import Fathom, models
import logging
import os
# Setup debug logging
logging.basicConfig(level=logging.DEBUG)
fathom = Fathom(debug_logger=logging.getLogger("fathom_python"))
# Or enable via environment variable
# export FATHOM_DEBUG=true
with Fathom(
security=models.Security(
api_key_auth=os.getenv("FATHOM_API_KEY_AUTH", ""),
),
) as fathom:
res = fathom.list_meetings()
while res is not None:
res = res.next()
```
### Configuration Best Practices
1. **Use environment variables** for sensitive configuration
2. **Implement proper error handling** with try-catch blocks
3. **Use context managers (Python)** for resource cleanup
4. **Configure timeouts** appropriate for your use case
5. **Enable debugging** during development
6. **Use standalone functions (TypeScript)** for bundle optimization
7. **Configure retries** for production reliability
# Authentication
Source: https://developers.fathom.ai/sdks/authentication
Authenticate with the Fathom SDKs using API keys
## API Key Authentication
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: process.env.FATHOM_API_KEY_AUTH || ""
}
});
```
```python Python theme={null}
from fathom_python import models, Fathom
import os
fathom = Fathom(security=models.Security(
api_key_auth=os.getenv("FATHOM_API_KEY_AUTH", "")
))
```
## Bearer Token Authentication
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
bearerAuth: "YOUR_BEARER_TOKEN"
}
});
```
```python Python theme={null}
from fathom_python import models, Fathom
fathom = Fathom(security=models.Security(
bearer_auth="YOUR_BEARER_TOKEN"
))
```
## Security Schemes
| Name | Type | Environment Variable |
| ------------ | ------ | --------------------- |
| `apiKeyAuth` | apiKey | `FATHOM_API_KEY_AUTH` |
| `bearerAuth` | http | `FATHOM_BEARER_AUTH` |
## Security Best Practices
* Use environment variables for credentials
* Use OAuth for multi-user integrations
* Rotate API keys regularly
* Monitor API usage
# Available Methods
Source: https://developers.fathom.ai/sdks/available-methods
All available methods in the Fathom SDKs
## Available Methods
Both TypeScript and Python SDKs support all Fathom API operations.
### Core Methods
| TypeScript | Python | Description |
| -------------------- | ---------------------- | ------------------------------------------- |
| `listMeetings()` | `list_meetings()` | List meetings with filtering and pagination |
| `listMeetingTypes()` | `list_meeting_types()` | List your team's published meeting types |
| `listTeams()` | `list_teams()` | List teams accessible to the user |
| `listTeamMembers()` | `list_team_members()` | List members of a specific team |
| `createWebhook()` | `create_webhook()` | Create webhook for real-time notifications |
| `deleteWebhook()` | `delete_webhook()` | Delete an existing webhook |
### Quick Examples
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: { apiKeyAuth: "YOUR_API_KEY" }
});
// List meetings with filtering
const meetings = await fathom.listMeetings({
calendarInviteesDomains: [
"acme.com",
"client.com",
],
recordedBy: [
"ceo@acme.com",
"pm@acme.com",
],
teams: [
"Sales",
"Engineering",
],
meetingType: "Quarterly Business Review",
});
// List meeting types
const meetingTypes = await fathom.listMeetingTypes({});
// List teams
const teams = await fathom.listTeams({});
// Create webhook
const webhook = await fathom.createWebhook({
destinationUrl: "https://your-app.com/webhook",
includeTranscript: true,
includeCrmMatches: true
});
```
```python Python theme={null}
from fathom_python import models, Fathom
with Fathom(security=models.Security(
api_key_auth="YOUR_API_KEY"
)) as fathom:
# List meetings with filtering
meetings = fathom.list_meetings(
calendar_invitees_domains=[
"acme.com",
"client.com",
],
recorded_by=[
"ceo@acme.com",
"pm@acme.com",
],
teams=[
"Sales",
"Engineering",
],
meeting_type="Quarterly Business Review",
include_crm_matches=False,
include_transcript=False
)
# List meeting types
meeting_types = fathom.list_meeting_types()
# List teams
teams = fathom.list_teams()
# Create webhook
webhook = fathom.create_webhook(
destination_url="https://your-app.com/webhook",
include_transcript=True,
include_crm_matches=True
)
```
For complete parameter documentation including types, examples, and detailed descriptions, see [API Reference](/api-reference/meetings/list-meetings).
# Python SDK Changes
Source: https://developers.fathom.ai/sdks/breaking-changes/python-changes
Breaking changes in Python SDK version 0.0.30
## Python SDK Breaking Changes
Version 0.0.30 introduces breaking changes for the Python SDK. Older versions continue to work but don't support OAuth.
### API Key Authentication Syntax
The API key authentication syntax has changed to support the new security model.
#### Old Syntax
```python theme={null}
from fathom_python import Fathom
fathom = Fathom("your_api_key")
```
#### New Syntax
```python theme={null}
from fathom_python import models, Fathom
fathom = Fathom(security=models.Security(api_key_auth="your_api_key"))
```
### OAuth Support
Version 0.0.30 adds OAuth support, which requires the new client structure:
```python theme={null}
from fathom_python import Fathom
# OAuth authorization URL generation
url = Fathom.get_authorization_url(
"YOUR_CLIENT_ID", # client ID
"your_redirect_url",
"public_api", # required scope
"randomState123"
)
# OAuth client initialization
token_store = Fathom.new_token_store()
fathom = Fathom(security=Fathom.with_authorization(
"YOUR_CLIENT_ID", # client_id
"YOUR_CLIENT_SECRET", # client_secret
"AUTHORIZATION_CODE_FROM_CALLBACK", # code
'your_redirect_uri',
token_store
))
```
### Migration Guide
To migrate from an older version to 0.0.30:
1. **Update imports**:
```python theme={null}
# Old
from fathom_python import Fathom
# New
from fathom_python import models, Fathom
```
2. **Update client initialization**:
```python theme={null}
# Old
fathom = Fathom("your_api_key")
# New
fathom = Fathom(security=models.Security(api_key_auth="your_api_key"))
```
3. **Update method calls** (if any method signatures changed):
```python theme={null}
# Method calls remain the same
result = fathom.list_meetings()
```
### Context Manager Usage
The new version encourages the use of context managers for proper resource management:
#### Old Usage
```python theme={null}
from fathom_python import Fathom
fathom = Fathom("your_api_key")
result = fathom.list_meetings()
print(result)
```
#### New Usage (Recommended)
```python theme={null}
from fathom_python import models, Fathom
with Fathom(
security=models.Security(
api_key_auth="your_api_key",
),
) as fathom:
result = fathom.list_meetings()
print(result)
```
### Asynchronous Support
Version 0.0.30 includes improved asynchronous support:
```python theme={null}
import asyncio
from fathom_python import models, Fathom
async def main():
async with Fathom(
security=models.Security(
api_key_auth="your_api_key",
),
) as fathom:
result = await fathom.list_meetings_async()
print(result)
asyncio.run(main())
```
### Backward Compatibility
Older versions of the SDK continue to work with API key authentication, but they don't support OAuth features.
If you need to maintain compatibility with older code while adding OAuth support, you can:
1. **Pin to the old version** for existing applications
2. **Create a new integration** using version 0.0.30 for OAuth features
3. **Gradually migrate** existing code to the new syntax
### Version Pinning
We recommend pinning to a specific version to avoid unexpected breaking changes:
#### pip
```bash theme={null}
pip install fathom-python==0.0.30
```
#### requirements.txt
```
fathom-python==0.0.30
```
#### pyproject.toml (Poetry)
```toml theme={null}
[tool.poetry.dependencies]
fathom-python = "0.0.30"
```
### What's New in 0.0.30
* ✅ OAuth 2.0 support
* ✅ Improved security model
* ✅ Better Pydantic integration
* ✅ Enhanced error handling
* ✅ Context manager support
* ✅ Asynchronous operations
* ✅ Resource management improvements
* ✅ Better type hints
### Environment Variables
The new version supports environment variables for configuration:
```python theme={null}
import os
from fathom_python import models, Fathom
with Fathom(
security=models.Security(
api_key_auth=os.getenv("FATHOM_API_KEY_AUTH", ""),
),
) as fathom:
result = fathom.list_meetings()
print(result)
```
### Error Handling Improvements
The new version includes better error handling with specific error types:
```python theme={null}
from fathom_python import Fathom, errors, models
with Fathom(
security=models.Security(
api_key_auth="your_api_key",
),
) as fathom:
try:
result = fathom.list_meetings()
print(result)
except errors.FathomError as e:
print(f"API Error: {e.message}")
except errors.ResponseValidationError as e:
print(f"Validation Error: {e.message}")
```
# SDK Maturity
Source: https://developers.fathom.ai/sdks/breaking-changes/sdk-maturity
Information about SDK maturity and version management
## SDK Maturity
Both TypeScript and Python SDKs are currently in beta, and there may be breaking changes between versions without a major version update.
### Beta Status
Our SDKs are currently in beta, which means:
* **Active development**: We're actively improving and adding features
* **Breaking changes**: There may be breaking changes between versions
* **Feedback welcome**: We encourage feedback and bug reports
* **Production ready**: The SDKs are stable enough for production use
### Version Management
We recommend pinning usage to a specific package version to avoid breaking changes unless you are intentionally looking for the latest version.
#### TypeScript SDK
```json theme={null}
{
"dependencies": {
"fathom-typescript": "0.0.30"
}
}
```
#### Python SDK
```bash theme={null}
pip install fathom-python==0.0.30
```
Or in `requirements.txt`:
```
fathom-python==0.0.30
```
# TypeScript SDK Changes
Source: https://developers.fathom.ai/sdks/breaking-changes/typescript-changes
Breaking changes in TypeScript SDK version 0.0.30
## TypeScript SDK Breaking Changes
Version 0.0.30 introduces breaking changes for the TypeScript SDK. Older versions continue to work but don't support OAuth.
### Client Class Name Change
The client is now called `Fathom` instead of `FathomApi`.
#### Old Syntax
```typescript theme={null}
import { FathomApi } from 'fathom-typescript';
const fathom = new FathomApi("apikey");
```
#### New Syntax
```typescript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({security: {apiKeyAuth: "apikey"}});
```
### API Key Authentication Syntax
The API key authentication syntax has changed to support the new security model.
#### Old Syntax
```typescript theme={null}
import { FathomApi } from 'fathom-typescript';
const fathom = new FathomApi("your_api_key");
```
#### New Syntax
```typescript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "your_api_key"
}
});
```
### OAuth Support
Version 0.0.30 adds OAuth support, which requires the new client structure:
```typescript theme={null}
import { Fathom } from 'fathom-typescript';
// OAuth authorization URL generation
const url = Fathom.getAuthorizationUrl({
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'https://your_redirect_url',
scope: 'public_api',
state: 'randomState123',
});
// OAuth client initialization
const tokenStore = Fathom.newTokenStore();
const fathom = new Fathom({
security: Fathom.withAuthorization({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
code: "AUTHORIZATION_CODE_FROM_CALLBACK",
redirectUri: "https://your_redirect_url",
tokenStore: tokenStore
}),
});
```
### Migration Guide
To migrate from an older version to 0.0.30:
1. **Update imports**:
```typescript theme={null}
// Old
import { FathomApi } from 'fathom-typescript';
// New
import { Fathom } from 'fathom-typescript';
```
2. **Update client initialization**:
```typescript theme={null}
// Old
const fathom = new FathomApi("your_api_key");
// New
const fathom = new Fathom({
security: {
apiKeyAuth: "your_api_key"
}
});
```
3. **Update method calls** (if any method signatures changed):
```typescript theme={null}
// Method calls remain the same
const result = await fathom.listMeetings({});
```
### Backward Compatibility
Older versions of the SDK continue to work with API key authentication, but they don't support OAuth features.
If you need to maintain compatibility with older code while adding OAuth support, you can:
1. **Pin to the old version** for existing applications
2. **Create a new integration** using version 0.0.30 for OAuth features
3. **Gradually migrate** existing code to the new syntax
### Version Pinning
We recommend pinning to a specific version to avoid unexpected breaking changes:
```json theme={null}
{
"dependencies": {
"fathom-typescript": "0.0.30"
}
}
```
### What's New in 0.0.30
* ✅ OAuth 2.0 support
* ✅ Improved security model
* ✅ Better TypeScript types
* ✅ Enhanced error handling
* ✅ Standalone functions for bundle optimization
* ✅ Async iteration for pagination
# Error Handling
Source: https://developers.fathom.ai/sdks/error-handling
Handle errors with the Fathom SDKs
## Error Handling
Both TypeScript and Python SDKs provide comprehensive error handling capabilities. The base error class is `FathomError` for both SDKs.
### Basic Error Handling
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
import * as errors from 'fathom-typescript/models/errors';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
try {
const result = await fathom.listMeetings({});
for await (const page of result) {
console.log(page);
}
} catch (error) {
if (error instanceof errors.FathomError) {
console.log(error.message);
console.log(error.statusCode);
console.log(error.body);
console.log(error.headers);
}
}
```
```python Python theme={null}
from fathom_python import Fathom, errors, models
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
try:
res = fathom.list_meetings()
while res is not None:
print(res)
res = res.next()
except errors.FathomError as e:
print(f"Error: {e.message}")
print(f"Status Code: {e.status_code}")
print(f"Body: {e.body}")
print(f"Headers: {e.headers}")
```
### Handle Specific Status Codes
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
import * as errors from 'fathom-typescript/models/errors';
async function handleSpecificErrors() {
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
try {
const result = await fathom.listMeetings({});
return result;
} catch (error) {
if (error instanceof errors.FathomError) {
switch (error.statusCode) {
case 401:
console.log("Authentication failed. Check your API key.");
break;
case 403:
console.log("Access forbidden. Check your permissions.");
break;
case 404:
console.log("Resource not found.");
break;
case 429:
console.log("Rate limit exceeded. Try again later.");
break;
default:
if (error.statusCode >= 500) {
console.log("Server error. Try again later.");
} else {
console.log(`Unexpected error: ${error.message}`);
}
}
}
return null;
}
}
```
```python Python theme={null}
from fathom_python import Fathom, errors, models
def handle_specific_errors():
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
try:
res = fathom.list_meetings()
return res
except errors.FathomError as e:
if e.status_code == 401:
print("Authentication failed. Check your API key.")
elif e.status_code == 403:
print("Access forbidden. Check your permissions.")
elif e.status_code == 404:
print("Resource not found.")
elif e.status_code == 429:
print("Rate limit exceeded. Try again later.")
elif e.status_code >= 500:
print("Server error. Try again later.")
else:
print(f"Unexpected error: {e.message}")
return None
```
### Error Classes
**Primary error:**
* `FathomError`: The base class for HTTP error responses.
**Network errors (TypeScript):**
* `ConnectionError`: HTTP client was unable to make a request to a server.
* `RequestTimeoutError`: HTTP request timed out due to an AbortSignal signal.
* `RequestAbortedError`: HTTP request was aborted by the client.
* `InvalidRequestError`: Any input used to create a request is invalid.
* `UnexpectedClientError`: Unrecognised or unexpected error.
**Network errors (Python):**
* `httpx.RequestError`: Base class for request errors.
* `httpx.ConnectError`: HTTP client was unable to make a request to a server.
* `httpx.TimeoutException`: HTTP request timed out.
**Inherit from `FathomError`:**
* `ResponseValidationError`: Type mismatch between the response data and the expected model structure.
# Filtering
Source: https://developers.fathom.ai/sdks/filtering
Filter data with the Fathom SDKs
## Filtering Data
Both TypeScript and Python SDKs provide powerful filtering capabilities for retrieving specific data from the Fathom API.
### Basic Filtering
Start with simple single filters:
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
// Filter by team only
const result = await fathom.listMeetings({
teams: ["Sales"]
});
for await (const page of result) {
console.log(page);
}
```
```python Python theme={null}
from fathom_python import models, Fathom
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
# Filter by team only
res = fathom.list_meetings(
teams=["Sales"]
)
while res is not None:
print(res)
res = res.next()
```
### Filter by Meeting Type
Pass the name of one of your team's meeting types. Use `listMeetingTypes()` / `list_meeting_types()` to discover the valid names — an unknown name returns an empty list.
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
const result = await fathom.listMeetings({
meetingType: "Quarterly Business Review"
});
for await (const page of result) {
console.log(page);
}
```
```python Python theme={null}
from fathom_python import models, Fathom
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
res = fathom.list_meetings(
meeting_type="Quarterly Business Review"
)
while res is not None:
print(res)
res = res.next()
```
### Include Transcript Data
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
async function getMeetingsWithTranscripts() {
const result = await fathom.listMeetings({
includeTranscript: true
});
for await (const page of result) {
for (const meeting of page.items || []) {
console.log(`Meeting: ${meeting.title}`);
if (meeting.transcript) {
console.log(`Transcript: ${meeting.transcript}`);
}
}
}
}
```
```python Python theme={null}
from fathom_python import Fathom, models
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
# Get meetings with transcript data
meetings_with_transcripts = fathom.list_meetings(
include_transcript=True
)
while meetings_with_transcripts is not None:
for meeting in meetings_with_transcripts.items:
if meeting.transcript:
print(f"Meeting: {meeting.title}")
print(f"Transcript: {meeting.transcript}")
meetings_with_transcripts = meetings_with_transcripts.next()
```
### Combining Multiple Filters
Combine multiple filters for precise queries:
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
async function getFilteredMeetings() {
const result = await fathom.listMeetings({
calendarInviteesDomains: [
"acme.com",
"client.com",
],
recordedBy: [
"ceo@acme.com",
"pm@acme.com",
],
teams: [
"Sales",
"Engineering",
],
meetingType: "Quarterly Business Review",
includeTranscript: true,
includeCrmMatches: true
});
for await (const page of result) {
for (const meeting of page.items || []) {
console.log(`Meeting: ${meeting.title}`);
console.log(`Recorded by: ${meeting.recordedBy?.name}`);
if (meeting.crmMatches) {
console.log(`CRM matches: ${meeting.crmMatches}`);
}
}
}
}
```
```python Python theme={null}
from fathom_python import Fathom, models
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
# Complex filtering with multiple criteria
filtered_meetings = fathom.list_meetings(
calendar_invitees_domains=[
"acme.com",
"client.com",
],
recorded_by=[
"ceo@acme.com",
"pm@acme.com",
],
teams=[
"Sales",
"Engineering",
],
meeting_type="Quarterly Business Review",
include_transcript=True,
include_crm_matches=True
)
while filtered_meetings is not None:
for meeting in filtered_meetings.items:
print(f"Meeting: {meeting.title}")
print(f"Recorded by: {meeting.recorded_by.name}")
if meeting.crm_matches:
print(f"CRM matches: {meeting.crm_matches}")
filtered_meetings = filtered_meetings.next()
```
### TypeScript Type Safety
The TypeScript SDK provides full type safety for filter parameters:
```typescript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
// TypeScript will provide autocomplete and type checking
async function typedFiltering() {
const result = await fathom.listMeetings({
includeTranscript: true,
includeCrmMatches: false
});
for await (const page of result) {
// TypeScript knows the structure of the response
console.log(`Page has ${page.items?.length || 0} meetings`);
}
}
```
For complete parameter documentation including types, examples, and detailed descriptions, see the [API Reference](/api-reference/meetings/list-meetings).
# SDK Introduction
Source: https://developers.fathom.ai/sdks/index
Get started with Fathom using our official TypeScript and Python SDKs
## Choose Your SDK
Get started in minutes with our official SDKs. They handle authentication, pagination, error handling, and all the complex stuff so you can focus on building amazing integrations.
Full type safety, async/await support, and tree-shaking for optimal bundle sizes.
Built with Pydantic for validation and supports both sync and async operations.
# OAuth
Source: https://developers.fathom.ai/sdks/oauth
Use OAuth authentication with the Fathom SDKs
## OAuth Authentication
OAuth users need to register an app with us before using this feature. Visit our [OAuth Setup Guide](/oauth) to get your client credentials and configure your redirect URL.
Both TypeScript and Python SDKs support OAuth 2.0 authentication for building integrations that can be installed by multiple Fathom accounts.
***
### Step 1: Get Authorization URL
Using the `Client ID` and `Client Secret` you received when registering your app, generate an authorization URL that users will visit to grant your app access:
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const url = Fathom.getAuthorizationUrl({
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'https://your_redirect_url',
scope: 'public_api',
state: 'randomState123',
});
// Redirect user to this URL
console.log(url);
```
```python Python theme={null}
from fathom_python import Fathom
url = Fathom.get_authorization_url(
"YOUR_CLIENT_ID", # client ID
"your_redirect_url",
"public_api", # required scope
"randomState123"
)
print(url)
```
### Step 2: Handle OAuth Callback
After the user authorizes your app, they'll be redirected back to your redirect URI with an authorization code. Use this code to exchange it for access tokens:
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
// User gets redirected here with code
const tokenStore = Fathom.newTokenStore(); // demo only — use persistent store in production
const fathom = new Fathom({
security: Fathom.withAuthorization({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
code: "AUTHORIZATION_CODE_FROM_CALLBACK",
redirectUri: "https://your_redirect_url",
tokenStore
}),
});
// Now you can make requests and the SDK will refresh tokens as needed
const result = await fathom.listMeetings({});
```
```python Python theme={null}
from fathom_python import Fathom
# User gets redirected back to your redirect URI with a code
token_store = Fathom.new_token_store() # demo only — use persistent store in production
fathom = Fathom(security=Fathom.with_authorization(
"YOUR_CLIENT_ID", # client_id
"YOUR_CLIENT_SECRET", # client_secret
"AUTHORIZATION_CODE_FROM_CALLBACK", # code
"your_redirect_uri",
token_store
))
result = fathom.list_meetings()
print(result)
```
newTokenStore() is an in-memory store — great for demos and quick starts. For production, you'll want a persistent TokenStore so users only need to install once.
### Step 3: Token Management & Persistence
For production, you'll want to implement your own `TokenStore` that persists tokens to a database, cache, or file.
The SDK will automatically call your `set()` when new tokens are issued, and `get()` when it needs to reuse or refresh them.
#### Example: Python persistent store (SQLite)
This SQLite example is meant as a simple demo. In production, you’ll want to plug in whatever storage makes sense for your stack (e.g. Postgres, Redis, cloud secret store).
```python theme={null}
import sqlite3
from fathom_python import Fathom
class SQLiteTokenStore(Fathom.TokenStore):
def __init__(self, db_path="tokens.db"):
self.db_path = db_path
self._init_db()
def _init_db(self):
conn = sqlite3.connect(self.db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS fathom_tokens (
id INTEGER PRIMARY KEY,
token TEXT,
refresh_token TEXT,
expires INTEGER
)
""")
conn.commit()
conn.close()
def get(self):
conn = sqlite3.connect(self.db_path)
row = conn.execute("SELECT token, refresh_token, expires FROM fathom_tokens WHERE id = 1").fetchone()
conn.close()
return {"token": row[0], "refresh_token": row[1], "expires": row[2]} if row else None
def set(self, token, refresh_token, expires):
conn = sqlite3.connect(self.db_path)
conn.execute("""
INSERT INTO fathom_tokens (id, token, refresh_token, expires)
VALUES (1, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET token=excluded.token,
refresh_token=excluded.refresh_token,
expires=excluded.expires
""", (token, refresh_token, expires))
conn.commit()
conn.close()
```
**Usage:**
```python theme={null}
token_store = SQLiteTokenStore()
fathom = Fathom(security=Fathom.with_authorization(
client_id,
client_secret,
authorization_code,
redirect_uri,
token_store
))
```
#### Further examples
For a real-world production example, see [how Pylon implemented OAuth and token storage](/inspiration/pylon) as part of their Fathom integration.
### Common Mistakes
* **Calling `tokenStore.get()` too early**
Nothing will be there yet — tokens are only written after the SDK exchanges the authorization code.
* **Assuming tokens never expire**
Access tokens are short-lived. Always persist the refresh token and let the SDK refresh automatically.
* **Using `newTokenStore()` in production**
It’s an in-memory store for demos only. Use a persistent store (database, Redis, file, etc.) so tokens survive restarts.
### Manual Token Exchange (Optional)
If you prefer to handle the token exchange yourself (or to debug), you can call the OAuth token endpoint directly.
The SDK handles token exchange and refresh automatically. You only need this section if you’re debugging or implementing your own flow.
**Exchange authorization code for tokens:**
```bash theme={null}
curl -X POST https://api.fathom.ai/external/v1/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=YOUR_REDIRECT_URI"
```
This returns a JSON object with both an `access_token` and a `refresh_token`.
**Refresh an expired access token:**
```bash theme={null}
curl -X POST https://api.fathom.ai/external/v1/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=YOUR_REFRESH_TOKEN" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
```
This also returns a JSON object with both an `access_token` and a `refresh_token`. Use the new `access_token` in API requests. Store and use the new `refresh_token` the next time your access token expires. A `refresh_token` can only be used once. If it is unused, it stays valid until the user revokes access.
### OAuth Handler Examples
Complete OAuth flow implementations for web frameworks:
```typescript Express.js theme={null}
import express from 'express';
import { Fathom } from 'fathom-typescript';
const app = express();
// OAuth initiation endpoint
app.get('/auth/fathom', (req, res) => {
const authUrl = Fathom.getAuthorizationUrl({
clientId: process.env.FATHOM_CLIENT_ID!,
clientSecret: process.env.FATHOM_CLIENT_SECRET!,
redirectUri: 'https://your-app.com/auth/fathom/callback',
scope: 'public_api',
state: 'random_state_string',
});
res.redirect(authUrl);
});
// OAuth callback endpoint
app.get('/auth/fathom/callback', async (req, res) => {
const { code, state } = req.query;
if (!code || typeof code !== 'string') {
return res.status(400).send('Authorization code required');
}
try {
const tokenStore = Fathom.newTokenStore();
const fathom = new Fathom({
security: Fathom.withAuthorization({
clientId: process.env.FATHOM_CLIENT_ID!,
clientSecret: process.env.FATHOM_CLIENT_SECRET!,
code,
redirectUri: 'https://your-app.com/auth/fathom/callback',
tokenStore
}),
});
// Test the connection
const meetings = await fathom.listMeetings({});
res.json({ success: true, meetingsCount: meetings.items?.length || 0 });
} catch (error) {
console.error('OAuth error:', error);
res.status(500).send('OAuth authentication failed');
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
```
```python Flask theme={null}
from flask import Flask, request, redirect, jsonify
from fathom_python import Fathom
import os
app = Flask(__name__)
# OAuth initiation endpoint
@app.route('/auth/fathom')
def auth_fathom():
auth_url = Fathom.get_authorization_url(
os.getenv('FATHOM_CLIENT_ID'),
'https://your-app.com/auth/fathom/callback',
'public_api',
'random_state_string'
)
return redirect(auth_url)
# OAuth callback endpoint
@app.route('/auth/fathom/callback')
def auth_fathom_callback():
code = request.args.get('code')
state = request.args.get('state')
if not code:
return 'Authorization code required', 400
try:
token_store = Fathom.new_token_store()
fathom = Fathom(security=Fathom.with_authorization(
os.getenv('FATHOM_CLIENT_ID'),
os.getenv('FATHOM_CLIENT_SECRET'),
code,
'https://your-app.com/auth/fathom/callback',
token_store
))
# Test the connection
meetings = fathom.list_meetings()
return jsonify({
'success': True,
'meetingsCount': len(meetings.items) if meetings.items else 0
})
except Exception as e:
print(f'OAuth error: {e}')
return 'OAuth authentication failed', 500
if __name__ == '__main__':
app.run(port=3000)
```
### OAuth Scopes
Currently, the only available scope is:
* `public_api` — Access to the Fathom API
### OAuth Rate limits
60 requests per 60 seconds per OAuth app for the `https://api.fathom.ai/external/v1/oauth2/token` endpoint
# Pagination
Source: https://developers.fathom.ai/sdks/pagination
Handle paginated responses with the Fathom SDKs
## Pagination
Both TypeScript and Python SDKs handle pagination automatically. The SDKs return paginated responses that you can iterate through to access all data.
### Basic Pagination
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
const result = await fathom.listMeetings({});
for await (const page of result) {
console.log(page);
}
```
```python Python theme={null}
from fathom_python import Fathom, models
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
res = fathom.list_meetings()
while res is not None:
print(res)
res = res.next()
```
### Processing All Data
Here's how to collect all data from paginated responses:
```typescript TypeScript theme={null}
import { Fathom } from 'fathom-typescript';
async function getAllMeetings() {
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
const result = await fathom.listMeetings({});
const allMeetings: any[] = [];
for await (const page of result) {
if (page.items) {
allMeetings.push(...page.items);
}
}
console.log(`Total meetings: ${allMeetings.length}`);
return allMeetings;
}
```
```python Python theme={null}
from fathom_python import Fathom, models
def get_all_meetings():
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
res = fathom.list_meetings()
all_meetings = []
while res is not None:
all_meetings.extend(res.result.items)
res = res.next()
print(f"Total meetings: {len(all_meetings)}")
return all_meetings
```
### Pagination Best Practices
1. **Use async iteration (TypeScript)**: The `for await...of` syntax is the recommended way to handle pagination
2. **Use while loops (Python)**: The `while res is not None` pattern is the standard approach
3. **Process incrementally**: For large datasets, consider processing each page as it comes rather than collecting all data in memory
4. **Handle errors**: Wrap pagination in try-catch blocks for robust error handling (see [Error Handling](/sdks/error-handling))
5. **Respect rate limits**: The SDK handles [rate limiting](/api-overview#rate-limiting) automatically, but be mindful of API usage
# Python Installation
Source: https://developers.fathom.ai/sdks/python-installation
Install the Fathom Python SDK
## How to Install
```bash pip theme={null}
pip install fathom-python
```
```bash poetry theme={null}
poetry add fathom-python
```
```bash uv theme={null}
uvx --from fathom-python python
```
## Requirements
The Python SDK supports Python 3.9+ and works with most modern Python environments.
## Module Systems
The Python SDK works with standard Python import syntax:
```python theme={null}
from fathom_python import models, Fathom
```
## IDE Support
For PyCharm users, install the [PyCharm Pydantic Plugin](https://docs.pydantic.dev/latest/integrations/pycharm/) for better integration with Pydantic models.
## Alternative Installation Methods
### Shell and Script Usage with `uv`
You can use this SDK in a Python shell with [uv](https://docs.astral.sh/uv/) and the `uvx` command:
```shell Shell usage theme={null}
uvx --from fathom-python python
```
```python Standalone script theme={null}
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.9"
# dependencies = [
# "fathom-python",
# ]
# ///
from fathom_python import Fathom
sdk = Fathom(
# SDK arguments
)
# Rest of script here...
```
Once that is saved to a file, you can run it with `uv run script.py` where `script.py` can be replaced with the actual file name.
## Quick Start
After installation, you can quickly test your setup:
```python theme={null}
from fathom_python import models, Fathom
with Fathom(
security=models.Security(
api_key_auth="YOUR_API_KEY",
),
) as fathom:
result = fathom.list_meetings()
print(result)
```
## Version Management
This SDK is in beta, and there may be breaking changes between versions without a major version update. Therefore, we recommend pinning usage to a specific package version:
```bash theme={null}
pip install fathom-python==0.0.30
```
Or with poetry:
```bash theme={null}
poetry add fathom-python@0.0.30
```
# TypeScript Installation
Source: https://developers.fathom.ai/sdks/typescript-installation
Install the Fathom TypeScript SDK
## How to Install
```bash npm theme={null}
npm install fathom-typescript
```
```bash yarn theme={null}
yarn add fathom-typescript
```
```bash pnpm theme={null}
pnpm add fathom-typescript
```
This package is published with CommonJS and ES Modules (ESM) support.
## Requirements
For supported JavaScript runtimes, please consult the [RUNTIMES.md](https://github.com/fathom/fathom-typescript/blob/main/RUNTIMES.md) file in the SDK repository.
## Module Systems
The SDK supports both CommonJS and ES Modules:
```typescript ES Modules (Recommended) theme={null}
import { Fathom } from 'fathom-typescript';
```
```javascript CommonJS theme={null}
const { Fathom } = require('fathom-typescript');
```
## Quick Start
After installation, you can quickly test your setup:
```typescript theme={null}
import { Fathom } from 'fathom-typescript';
const fathom = new Fathom({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
const result = await fathom.listMeetings({});
console.log(result);
```
## Standalone Functions
All SDK methods are available as standalone functions, ideal for applications where bundle size is a concern:
```typescript theme={null}
import { listMeetings } from 'fathom-typescript';
const result = await listMeetings({
security: {
apiKeyAuth: "YOUR_API_KEY"
}
});
for await (const page of result) {
console.log(page);
}
```
### Available Standalone Functions
* `listMeetings` - List meetings
* `listTeams` - List teams
* `listTeamMembers` - List team members
* `createWebhook` - Create a webhook
* `deleteWebhook` - Delete a webhook
### Tree Shaking
When using a bundler, unused functionality will be excluded from the final bundle:
```typescript theme={null}
// Only listMeetings will be included in the bundle
import { listMeetings } from 'fathom-typescript';
// This won't be included if not used
// import { createWebhook } from 'fathom-typescript';
```
## Version Management
This SDK is in beta, and there may be breaking changes between versions without a major version update. Therefore, we recommend pinning usage to a specific package version:
```json theme={null}
{
"dependencies": {
"fathom-typescript": "0.0.30"
}
}
```
# Webhooks
Source: https://developers.fathom.ai/webhooks
Automatically trigger webhook events after your meetings
## About Webhooks in Fathom
Webhooks will send your meeting data (optionally including the summary, transcript, and action items) to a URL of your choice.
Webhooks can be set to fire after your own meetings and/or meetings that have been shared with you. Configure these triggers in your [Settings](https://fathom.video/customize#api-access-header), or when generating a webhook [via API](/api-reference/webhooks/create-a-webhook#body-triggered-for).
## Create a webhook
There are two ways to create a webhook:
#### Option 1 - in Settings
Webhooks can be configured in the [**API Access**](https://fathom.video/customize#api-access-header) section of your **User Settings**.
* Generate an API key, then go to **Manage** > **Add Webhook**
* Enter a Destination URL
* Select which new recordings should trigger webhooks
* Select what data to include in the payload
#### Option 2 - via API
You also have the option of creating and deleting webhooks with an API call.
[API docs: Create a webhook](/api-reference/webhooks/create-a-webhook).
Be sure the check the response body to confirm the webhook was created as expected. Webhooks created via API will also appear in your Settings.
## Test Your Webhook
To ensure your webhook is working as expected, you can record a brief, 2-minute meeting. Shortly after the meeting ends, your Destination URL should receive a webhook event.
For details on the webhook's payload, see our [API docs](/api-reference/webhook-payloads/new-meeting-content-ready).
**Coming soon:** send a test payload from your Settings page
## Verifying Webhooks
Webhook verification helps ensure that incoming requests to your endpoint are from Fathom and haven’t been altered.
Each webhook request sent from Fathom includes a signature in the request headers, which you can use to confirm the authenticity of the payload.
To test webhooks locally or during development, you can skip verification—but don’t forget to add it back in before going live.
### How to verify a webhook
#### Method 1 - SDK
If you're [using our SDK](/sdk), you can use the `verify_webhook` helper. Simply call:
```javascript Typescript theme={null}
Fathom.verifyWebhook(webhook_secret, request.headers, request.body)
```
```python Python theme={null}
Fathom.verify_webhook(webhook_secret, request.headers, request.body)
```
`webhook_secret` – Provided when you create the webhook (either in Settings or via the API).
`request.headers` – The HTTP headers from the incoming request, which include the signature Fathom sends.
`request.body` – The raw string body of the POST request.
#### Method 2 - Without the SDK
You can also verify incoming webhooks yourself using basic tools available in most programming languages.
Every webhook payload from Fathom includes three headers used for verification:
* `webhook-id` – The unique message identifier for the webhook message
* `webhook-timestamp` – Timestamp in seconds since epoch
* `webhook-signature` – The Base64 encoded list of signatures (space delimited), each prefixed with a version identifier
To verify the request:
1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` from the request headers
2. Construct the signed content by concatenating the id, timestamp, and raw body, separated by periods: `${id}.${timestamp}.${body}` (be sure to use the **raw** body, before any JSON parsing)
3. Base64 decode the portion of your `webhook_secret` after the `whsec_` prefix (e.g., if your secret is `whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH`, use `5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH`)
4. Use the decoded secret to HMAC the signed content with SHA-256, then Base64-encode the result
5. Extract all signatures from the `webhook-signature` header (remove version prefixes like `v1,` before comparing)
6. Compare your calculated signature to each provided signature using a constant-time comparison method
7. Verify the timestamp is within your acceptable tolerance (typically 5 minutes) to prevent replay attacks
8. If any signature matches and the timestamp is valid, the webhook is authentic
Example:
```javascript TypeScript theme={null}
const crypto = require('crypto')
function verifyWebhook(secret, headers, rawBody) {
const webhookId = headers['webhook-id']
const webhookTimestamp = headers['webhook-timestamp']
const webhookSignature = headers['webhook-signature']
// Verify timestamp (within 5 minutes)
const timestamp = parseInt(webhookTimestamp, 10)
const currentTimestamp = Math.floor(Date.now() / 1000)
if (Math.abs(currentTimestamp - timestamp) > 300) {
return false
}
// Construct signed content
const signedContent = `${webhookId}.${webhookTimestamp}.${rawBody}`
// Base64 decode the secret (part after whsec_)
const secretBytes = Buffer.from(secret.split('_')[1], 'base64')
// Calculate expected signature
const expectedSignature = crypto
.createHmac('sha256', secretBytes)
.update(signedContent)
.digest('base64')
// Extract signatures from header (remove version prefixes)
const signatures = webhookSignature.split(' ').map(sig => {
const parts = sig.split(',')
return parts.length > 1 ? parts[1] : parts[0]
})
// Constant-time comparison
return signatures.some(sig =>
crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(sig)
)
)
}
```
```python Python theme={null}
import hmac
import hashlib
import base64
import time
def verify_webhook(secret, headers, raw_body):
webhook_id = headers.get('webhook-id')
webhook_timestamp = headers.get('webhook-timestamp')
webhook_signature = headers.get('webhook-signature')
# Verify timestamp (within 5 minutes)
timestamp = int(webhook_timestamp)
current_timestamp = int(time.time())
if abs(current_timestamp - timestamp) > 300:
return False
# Construct signed content
signed_content = f"{webhook_id}.{webhook_timestamp}.{raw_body}"
# Base64 decode the secret (part after whsec_)
secret_bytes = base64.b64decode(secret.split('_')[1])
# Calculate expected signature
expected_signature = base64.b64encode(
hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest()
).decode()
# Extract signatures from header (remove version prefixes)
signatures = []
for sig in webhook_signature.split(' '):
parts = sig.split(',')
signatures.append(parts[1] if len(parts) > 1 else parts[0])
# Constant-time comparison
return any(
hmac.compare_digest(expected_signature, sig) for sig in signatures
)
```