Created
January 15, 2026 00:12
-
-
Save MaxDonchenko/6d5ba4efb8bfa1c47f429562f5386c19 to your computer and use it in GitHub Desktop.
script to export all data from the Kajabi platform (works only for Pro plan introduced in 2026)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import axios, { AxiosInstance } from 'axios'; | |
| import * as fs from 'fs-extra'; | |
| import * as path from 'path'; | |
| const API_BASE_URL = 'https://api.kajabi.com'; | |
| const PAGE_SIZE = 50; | |
| // API credentials - replace with your actual credentials | |
| // API Key from Kajabi settings -> API Key | |
| const CLIENT_ID = 'YOUR_CLIENT_ID_HERE'; | |
| // API Secret from Kajabi settings -> API Secret | |
| const CLIENT_SECRET = 'YOUR_CLIENT_SECRET_HERE'; | |
| // Alternative: If API Key/Secret don't work, try username/password | |
| const USERNAME: string = ''; // Your Kajabi email/username | |
| const PASSWORD: string = ``; // Your Kajabi password | |
| interface TokenResponse { | |
| access_token: string; | |
| refresh_token: string; | |
| token_type: string; | |
| expires_in: number; | |
| } | |
| interface PaginatedResponse<T> { | |
| data: T[]; | |
| links?: { | |
| self?: string; | |
| first?: string; | |
| prev?: string; | |
| next?: string; | |
| last?: string; | |
| }; | |
| meta?: { | |
| total_pages?: number; | |
| total_count?: number; | |
| current_page?: number; | |
| }; | |
| } | |
| let apiClient: AxiosInstance; | |
| let accessToken: string; | |
| async function authenticate(): Promise<void> { | |
| // Try client credentials first (API Key/Secret) | |
| const hasClientCredentials = CLIENT_ID && CLIENT_SECRET; | |
| if (hasClientCredentials) { | |
| try { | |
| const clientId = CLIENT_ID.trim(); | |
| const clientSecret = CLIENT_SECRET.trim(); | |
| // Debug info (without exposing full secret) | |
| console.log(`Attempting authentication...`); | |
| console.log(`API Key (client_id): ${clientId.substring(0, 3)}...${clientId.substring(clientId.length - 3)}`); | |
| console.log(`API Secret length: ${clientSecret.length} characters`); | |
| // Try with form-encoded body (standard OAuth2) | |
| const params = new URLSearchParams(); | |
| params.append('client_id', clientId); | |
| params.append('client_secret', clientSecret); | |
| params.append('grant_type', 'client_credentials'); | |
| const response = await axios.post<TokenResponse>( | |
| `${API_BASE_URL}/v1/oauth/token`, | |
| params, | |
| { | |
| headers: { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| } | |
| } | |
| ); | |
| accessToken = response.data.access_token; | |
| apiClient = axios.create({ | |
| baseURL: API_BASE_URL, | |
| headers: { | |
| Authorization: `Bearer ${accessToken}`, | |
| Accept: 'application/vnd.api+json', | |
| }, | |
| }); | |
| console.log('✅ Authenticated successfully with API Key/Secret'); | |
| return; | |
| } catch (error: any) { | |
| if (error.response?.status === 401) { | |
| const errorMsg = error.response?.data?.error || 'Invalid credentials'; | |
| console.log(`❌ API Key/Secret authentication failed: ${errorMsg}`); | |
| console.log(`\n💡 Troubleshooting:`); | |
| console.log(` 1. Verify your API Key and Secret from: https://app.kajabi.com/admin/settings/security`); | |
| console.log(` 2. Make sure there are no extra spaces or characters`); | |
| console.log(` 3. Check that the API Key has the required permissions`); | |
| console.log(` 4. Try creating a new API Key if this one doesn't work\n`); | |
| if (USERNAME && PASSWORD) { | |
| console.log('Trying username/password authentication as fallback...'); | |
| } else { | |
| throw new Error('Authentication failed. Please check your API Key/Secret or add username/password credentials.'); | |
| } | |
| } else { | |
| throw error; | |
| } | |
| } | |
| } | |
| // Fallback to username/password if client credentials fail | |
| if (USERNAME && PASSWORD) { | |
| const params = new URLSearchParams(); | |
| params.append('username', USERNAME.trim()); | |
| params.append('password', PASSWORD.trim()); | |
| const response = await axios.post<TokenResponse>( | |
| `${API_BASE_URL}/v1/oauth/token`, | |
| params, | |
| { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } | |
| ); | |
| accessToken = response.data.access_token; | |
| apiClient = axios.create({ | |
| baseURL: API_BASE_URL, | |
| headers: { | |
| Authorization: `Bearer ${accessToken}`, | |
| Accept: 'application/vnd.api+json', | |
| }, | |
| }); | |
| console.log('✅ Authenticated successfully with username/password'); | |
| return; | |
| } | |
| throw new Error('Authentication failed. Please check your API Key/Secret or username/password.'); | |
| } | |
| async function fetchAllPages<T>( | |
| endpoint: string, | |
| resourceName: string | |
| ): Promise<T[]> { | |
| const allItems: T[] = []; | |
| let pageNumber = 1; | |
| let hasMore = true; | |
| while (hasMore) { | |
| try { | |
| const response = await apiClient.get<PaginatedResponse<T>>(endpoint, { | |
| params: { | |
| 'page[number]': pageNumber, | |
| 'page[size]': PAGE_SIZE, | |
| }, | |
| }); | |
| const items = response.data.data || []; | |
| allItems.push(...items); | |
| console.log(`Fetched page ${pageNumber}: ${items.length} ${resourceName}`); | |
| const meta = response.data.meta; | |
| if (meta?.total_pages && pageNumber >= meta.total_pages) { | |
| hasMore = false; | |
| } else if (items.length === 0) { | |
| hasMore = false; | |
| } else { | |
| pageNumber++; | |
| } | |
| } catch (error: any) { | |
| if (error.response?.status === 404) { | |
| console.log(`Endpoint ${endpoint} not available, skipping`); | |
| hasMore = false; | |
| } else { | |
| throw error; | |
| } | |
| } | |
| } | |
| return allItems; | |
| } | |
| async function fetchResource<T>(endpoint: string, resourceName: string): Promise<T[]> { | |
| console.log(`Fetching ${resourceName}...`); | |
| try { | |
| const items = await fetchAllPages<T>(endpoint, resourceName); | |
| console.log(`✅ Downloaded ${items.length} ${resourceName}\n`); | |
| return items; | |
| } catch (error: any) { | |
| console.error(`❌ Error fetching ${resourceName}:`, error.message); | |
| return []; | |
| } | |
| } | |
| async function saveToFile(data: any, filename: string, outputDir: string): Promise<void> { | |
| const filePath = path.join(outputDir, filename); | |
| await fs.ensureDir(outputDir); | |
| await fs.writeJSON(filePath, data, { spaces: 2 }); | |
| } | |
| async function downloadBlogPosts(outputDir: string): Promise<void> { | |
| const posts = await fetchResource('/v1/blog_posts', 'blog posts'); | |
| await saveToFile(posts, 'blog_posts.json', outputDir); | |
| } | |
| async function downloadContactNotes(outputDir: string): Promise<void> { | |
| const notes = await fetchResource('/v1/contact_notes', 'contact notes'); | |
| await saveToFile(notes, 'contact_notes.json', outputDir); | |
| } | |
| async function downloadContactTags(outputDir: string): Promise<void> { | |
| const tags = await fetchResource('/v1/contact_tags', 'contact tags'); | |
| await saveToFile(tags, 'contact_tags.json', outputDir); | |
| } | |
| async function downloadContacts(outputDir: string): Promise<void> { | |
| const contacts = await fetchResource('/v1/contacts', 'contacts'); | |
| await saveToFile(contacts, 'contacts.json', outputDir); | |
| } | |
| async function downloadCourses(outputDir: string): Promise<void> { | |
| const courses = await fetchResource('/v1/courses', 'courses'); | |
| await saveToFile(courses, 'courses.json', outputDir); | |
| } | |
| async function downloadCustomFields(outputDir: string): Promise<void> { | |
| const fields = await fetchResource('/v1/custom_fields', 'custom fields'); | |
| await saveToFile(fields, 'custom_fields.json', outputDir); | |
| } | |
| async function downloadCustomers(outputDir: string): Promise<void> { | |
| const customers = await fetchResource('/v1/customers', 'customers'); | |
| await saveToFile(customers, 'customers.json', outputDir); | |
| } | |
| async function downloadFormSubmissions(outputDir: string): Promise<void> { | |
| const submissions = await fetchResource('/v1/form_submissions', 'form submissions'); | |
| await saveToFile(submissions, 'form_submissions.json', outputDir); | |
| } | |
| async function downloadForms(outputDir: string): Promise<void> { | |
| const forms = await fetchResource('/v1/forms', 'forms'); | |
| await saveToFile(forms, 'forms.json', outputDir); | |
| } | |
| async function downloadHooks(outputDir: string): Promise<void> { | |
| const hooks = await fetchResource('/api/v1/hooks', 'hooks'); | |
| await saveToFile(hooks, 'hooks.json', outputDir); | |
| } | |
| async function downloadLandingPages(outputDir: string): Promise<void> { | |
| const pages = await fetchResource('/v1/landing_pages', 'landing pages'); | |
| await saveToFile(pages, 'landing_pages.json', outputDir); | |
| } | |
| async function downloadOffers(outputDir: string): Promise<void> { | |
| const offers = await fetchResource('/v1/offers', 'offers'); | |
| await saveToFile(offers, 'offers.json', outputDir); | |
| } | |
| async function downloadOrderItems(outputDir: string): Promise<void> { | |
| const items = await fetchResource('/v1/order_items', 'order items'); | |
| await saveToFile(items, 'order_items.json', outputDir); | |
| } | |
| async function downloadOrders(outputDir: string): Promise<void> { | |
| const orders = await fetchResource('/v1/orders', 'orders'); | |
| await saveToFile(orders, 'orders.json', outputDir); | |
| } | |
| async function downloadProducts(outputDir: string): Promise<void> { | |
| const products = await fetchResource('/v1/products', 'products'); | |
| await saveToFile(products, 'products.json', outputDir); | |
| } | |
| async function downloadPurchases(outputDir: string): Promise<void> { | |
| const purchases = await fetchResource('/v1/purchases', 'purchases'); | |
| await saveToFile(purchases, 'purchases.json', outputDir); | |
| } | |
| async function downloadSites(outputDir: string): Promise<void> { | |
| const sites = await fetchResource('/v1/sites', 'sites'); | |
| await saveToFile(sites, 'sites.json', outputDir); | |
| } | |
| async function downloadTransactions(outputDir: string): Promise<void> { | |
| const transactions = await fetchResource('/v1/transactions', 'transactions'); | |
| await saveToFile(transactions, 'transactions.json', outputDir); | |
| } | |
| async function downloadWebsitePages(outputDir: string): Promise<void> { | |
| const pages = await fetchResource('/v1/website_pages', 'website pages'); | |
| await saveToFile(pages, 'website_pages.json', outputDir); | |
| } | |
| async function downloadMe(outputDir: string): Promise<void> { | |
| try { | |
| console.log('Fetching user info...'); | |
| const response = await apiClient.get('/v1/me'); | |
| await saveToFile(response.data, 'me.json', outputDir); | |
| console.log('✅ Downloaded user info\n'); | |
| } catch (error: any) { | |
| console.error('❌ Error fetching user info:', error.message); | |
| } | |
| } | |
| async function main(): Promise<void> { | |
| const outputDir = path.join(process.cwd(), 'backup', new Date().toISOString().split('T')[0]); | |
| console.log('Starting Kajabi backup...\n'); | |
| console.log(`Output directory: ${outputDir}\n`); | |
| try { | |
| await authenticate(); | |
| await downloadMe(outputDir); | |
| await downloadSites(outputDir); | |
| await downloadBlogPosts(outputDir); | |
| await downloadContactNotes(outputDir); | |
| await downloadContactTags(outputDir); | |
| await downloadContacts(outputDir); | |
| await downloadCourses(outputDir); | |
| await downloadCustomFields(outputDir); | |
| await downloadCustomers(outputDir); | |
| await downloadFormSubmissions(outputDir); | |
| await downloadForms(outputDir); | |
| await downloadHooks(outputDir); | |
| await downloadLandingPages(outputDir); | |
| await downloadOffers(outputDir); | |
| await downloadOrderItems(outputDir); | |
| await downloadOrders(outputDir); | |
| await downloadProducts(outputDir); | |
| await downloadPurchases(outputDir); | |
| await downloadTransactions(outputDir); | |
| await downloadWebsitePages(outputDir); | |
| console.log('✅ Backup completed successfully!'); | |
| console.log(`✅ Data saved to: ${outputDir}`); | |
| } catch (error: any) { | |
| console.error('❌ Backup failed:', error.message); | |
| if (error.response) { | |
| console.error('Response:', error.response.data); | |
| } | |
| process.exit(1); | |
| } | |
| } | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment