OAuth Authentication
OAuth users need to register an app with us before using this feature. Visit our OAuth Setup Guide 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:
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);
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:
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({});
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).
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:
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 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:
curl -X POST https://fathom.video/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:
curl -X POST https://fathom.video/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"
Use the new access_token
in API requests. Your refresh_token
stays valid until the user revokes access.
OAuth Handler Examples
Complete OAuth flow implementations for web frameworks:
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');
});
OAuth Scopes
Currently, the only available scope is:
public_api
— Access to the Fathom API