Integrate Keap (formerly Infusionsoft) with Bolt.new using their REST API v1 or the legacy XML-RPC API. Use OAuth 2.0 for authentication — get your Client ID and Client Secret from the Keap Developer Portal, implement the authorization code flow, and store the access token in your environment variables for API calls. All Keap API calls must go through a Next.js API route. OAuth callback flows and webhooks require a deployed Netlify URL.
Connect Bolt.new to Keap CRM and Marketing Automation
Keap, rebranded from Infusionsoft in 2019, is a small business-focused CRM and marketing automation platform with a particular strength in service-based businesses — coaches, consultants, agencies, and local service providers. Its tag-based automation system is powerful: you assign tags to contacts, and automation sequences (called 'Campaigns' in Keap's terminology) trigger based on tag assignment. This allows highly targeted email sequences, task creation, and sales pipeline movements driven by contact behavior and status changes.
Keap's authentication model is more complex than most platforms in this tier — it uses OAuth 2.0, which means you need to register a developer application, implement an authorization flow, and manage token refresh. This added complexity is worth understanding before starting: for a personal integration where you are connecting your own Keap account, you can obtain a long-lived access token once and store it. For a multi-tenant product where different clients connect their own Keap accounts, you need to implement the full OAuth flow with per-user token storage.
For Bolt.new developers, the most important constraint is that OAuth callback URLs must be stable, registered URIs — which rules out Bolt's WebContainer preview URL. Deploy to Netlify first, register your Netlify URL as the OAuth redirect URI in the Keap Developer Portal, then implement and test the OAuth flow. Once you have a working access token, contact management and tag assignment via the REST API work seamlessly from Next.js API routes.
Integration method
Keap's REST API v1 uses OAuth 2.0 with an authorization code flow for authentication. You need a Keap Developer account, a registered OAuth app with Client ID and Client Secret, and an access token obtained through the OAuth flow. All API calls go through Next.js API routes using the stored access token in server-side environment variables. The OAuth callback flow requires a deployed URL — configure your Keap OAuth app's redirect URI to use your Netlify domain.
Prerequisites
- A Keap account — API access requires a Keap Pro or Max plan; the free/starter plans do not include API access
- A Keap Developer account at keys.developer.keap.com — register your app to get Client ID and Client Secret
- An OAuth access token — obtained through Keap's OAuth 2.0 flow; for personal use, get a key directly from the Developer Portal
- Your Keap API base URL — typically https://api.infusionsoft.com/crm/rest or https://api.infusionsoft.com (check your Keap account region)
- A Netlify account for deployment — the OAuth callback URL must be a deployed domain registered in your Keap OAuth app settings
Step-by-step guide
Register a Keap Developer app and obtain API credentials
Register a Keap Developer app and obtain API credentials
Keap uses OAuth 2.0 for API authentication, which requires registering an application in the Keap Developer Portal before making any API calls. This is more involved than the API key approach used by many other platforms, but it is a one-time setup. Start by going to the Keap Developer Portal at developer.keap.com and signing in with your Keap account. Go to your Apps and create a new application. Provide an app name, description, and most importantly, the OAuth redirect URI — this is the URL Keap will redirect to after a user authorizes your app. For development testing, use your Netlify URL (https://your-app.netlify.app/api/keap/callback) since the WebContainer preview URL cannot be registered. After creating the app, note the Client ID and Client Secret from the app settings page. These are permanent credentials for your app — keep the Client Secret server-side only. For a personal integration (connecting your own single Keap account), there is a shortcut: in the Developer Portal, you can generate a personal access token directly without going through the full OAuth flow. This token is long-lived and can be stored in KEAP_ACCESS_TOKEN for direct use in API calls. This approach works for integrations where one developer connects their own Keap account. For multi-tenant use (different users connect their own Keap accounts), you must implement the full OAuth flow: redirect users to Keap's authorization URL, receive the authorization code at your callback endpoint, exchange it for an access token via POST to Keap's token endpoint, and store the access token per user. Keap access tokens expire — implement refresh token rotation to maintain access.
Set up Keap API integration. Create .env.local with: KEAP_CLIENT_ID=your-client-id, KEAP_CLIENT_SECRET=your-client-secret, KEAP_ACCESS_TOKEN=your-personal-access-token (for single-account use), KEAP_API_URL=https://api.infusionsoft.com/crm/rest. Create lib/keap.ts with: a base keapRequest function using KEAP_ACCESS_TOKEN as Bearer token, TypeScript interfaces for KeapContact (id, given_name, family_name, email_addresses array with field/email, phone_numbers array with field/number, tag_ids number array), and exported functions: createContact, updateContact, findContactByEmail, applyTag, and createNote.
Paste this in Bolt.new chat
1// lib/keap.ts2const API_URL = process.env.KEAP_API_URL || 'https://api.infusionsoft.com/crm/rest';34export interface KeapEmailAddress {5 field: 'EMAIL1' | 'EMAIL2' | 'EMAIL3';6 email: string;7}89export interface KeapPhoneNumber {10 field: 'PHONE1' | 'PHONE2' | 'PHONE3';11 number: string;12 type?: string;13}1415export interface KeapContact {16 id?: number;17 given_name?: string;18 family_name?: string;19 email_addresses?: KeapEmailAddress[];20 phone_numbers?: KeapPhoneNumber[];21 tag_ids?: number[];22 source_type?: string;23 lead_source_id?: number;24 custom_fields?: Array<{ id: number; content: unknown }>;25}2627async function keapRequest<T>(28 path: string,29 method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',30 body?: unknown31): Promise<T> {32 const token = process.env.KEAP_ACCESS_TOKEN;33 if (!token) throw new Error('KEAP_ACCESS_TOKEN is not set');3435 const response = await fetch(`${API_URL}${path}`, {36 method,37 headers: {38 'Authorization': `Bearer ${token}`,39 'Content-Type': 'application/json',40 },41 ...(body ? { body: JSON.stringify(body) } : {}),42 });4344 if (response.status === 204) return null as T;45 const data = await response.json();46 if (!response.ok) {47 throw new Error(data.message || `Keap API error: ${response.status}`);48 }49 return data as T;50}5152export async function findContactByEmail(email: string): Promise<KeapContact | null> {53 const result = await keapRequest<{ contacts: KeapContact[] }>(54 `/v1/contacts?email=${encodeURIComponent(email)}&fields=id,given_name,family_name,email_addresses,tag_ids`55 );56 return result.contacts?.[0] || null;57}5859export async function createContact(contact: KeapContact): Promise<KeapContact> {60 return keapRequest<KeapContact>('/v1/contacts', 'POST', contact);61}6263export async function updateContact(id: number, data: Partial<KeapContact>): Promise<KeapContact> {64 return keapRequest<KeapContact>(`/v1/contacts/${id}`, 'PATCH', data);65}6667export async function applyTag(contactId: number, tagId: number): Promise<void> {68 await keapRequest(`/v1/contacts/${contactId}/tags`, 'POST', { tagIds: [tagId] });69}7071export async function createNote(72 contactId: number,73 title: string,74 body: string75): Promise<void> {76 await keapRequest('/v1/notes', 'POST', {77 contact_id: contactId,78 title,79 body,80 type: 'Other',81 });82}Pro tip: For a personal Keap integration (single account), use the personal access token from the Keap Developer Portal directly as KEAP_ACCESS_TOKEN. Personal access tokens are long-lived and simplify the setup significantly. Only implement the full OAuth flow if you need multiple users to connect their own Keap accounts.
Expected result: A lib/keap.ts helper with typed functions for contact management, tag assignment, and note creation, with API credentials in .env.local.
Build the contact creation and tagging API route
Build the contact creation and tagging API route
The contact creation route handles the most common Keap integration pattern: capturing a lead from a form submission and creating a corresponding Keap contact, then applying one or more tags to trigger automation sequences. Keap contacts have a specific structure for email addresses and phone numbers — both use arrays of objects with a `field` identifier (`EMAIL1`, `EMAIL2` for emails; `PHONE1`, `PHONE2` for phones) and the actual value. This allows storing multiple emails and phone numbers per contact. When creating contacts, always use `EMAIL1` as the primary email field. The duplicate contact challenge: Keap has built-in deduplication that attempts to merge contacts with the same email, but behavior depends on your Keap deduplication settings. Before creating a new contact, search for an existing one using `findContactByEmail()`. If found, update the existing record rather than creating a new one. This is especially important for scenarios where the same person might submit multiple forms. Tag IDs are numeric integers in Keap. Find tag IDs in Keap under Contacts → Tags, then click on a tag — the ID appears in the URL. Store commonly used tag IDs as environment variables (KEAP_NEW_LEAD_TAG_ID, KEAP_CUSTOMER_TAG_ID, etc.) to avoid hardcoding numeric values in your code. After applying a tag in Keap, any automation sequences that trigger on that tag assignment begin running automatically. This is the core mechanism for starting email sequences from your Bolt app — you do not call Keap's email API directly; you apply a tag and Keap handles the rest.
Create a Keap lead capture API route at app/api/keap/leads/route.ts using lib/keap.ts. Accept POST with: firstName (required), lastName (required), email (required, validate format), phone (optional), source (optional). Check for existing contact by email using findContactByEmail. If found, update with any new data. If new, create contact with email_addresses: [{field: 'EMAIL1', email}], phone_numbers if provided, and given_name/family_name. After create/update, apply tag from KEAP_NEW_LEAD_TAG_ID env var using applyTag. Return 201 for new, 200 for update, with contactId. Handle errors with 500. Create a LeadForm React component with name, email, phone fields and success message.
Paste this in Bolt.new chat
1// app/api/keap/leads/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { findContactByEmail, createContact, updateContact, applyTag } from '@/lib/keap';45const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;67export async function POST(request: NextRequest) {8 const body = await request.json();9 const { firstName, lastName, email, phone, source } = body;1011 if (!firstName?.trim()) return NextResponse.json({ error: 'First name is required' }, { status: 400 });12 if (!lastName?.trim()) return NextResponse.json({ error: 'Last name is required' }, { status: 400 });13 if (!email || !emailRegex.test(email)) {14 return NextResponse.json({ error: 'Valid email is required' }, { status: 400 });15 }1617 const contactData = {18 given_name: firstName.trim(),19 family_name: lastName.trim(),20 email_addresses: [{ field: 'EMAIL1' as const, email: email.toLowerCase().trim() }],21 ...(phone ? { phone_numbers: [{ field: 'PHONE1' as const, number: phone.trim(), type: 'Work' }] } : {}),22 ...(source ? { source_type: source } : {}),23 };2425 try {26 const existing = await findContactByEmail(email);27 let contactId: number;28 let action: 'created' | 'updated';2930 if (existing?.id) {31 await updateContact(existing.id, contactData);32 contactId = existing.id;33 action = 'updated';34 } else {35 const created = await createContact(contactData);36 contactId = created.id!;37 action = 'created';38 }3940 // Apply new lead tag to trigger Keap automation41 const tagId = process.env.KEAP_NEW_LEAD_TAG_ID;42 if (tagId && contactId) {43 try {44 await applyTag(contactId, parseInt(tagId, 10));45 } catch (tagError) {46 console.error('Keap tag application failed (non-critical):', tagError);47 }48 }4950 const statusCode = action === 'created' ? 201 : 200;51 return NextResponse.json({ success: true, action, contactId }, { status: statusCode });52 } catch (error) {53 console.error('Keap lead creation error:', error);54 return NextResponse.json({ error: 'Failed to save contact' }, { status: 500 });55 }56}Pro tip: Keap tag IDs are integers. Find them in your Keap account under Contacts → Tags — the ID is visible in the URL when you click on a tag (e.g., .../tags/1234/details where 1234 is the tag ID). Store frequently used tag IDs as environment variables like KEAP_NEW_LEAD_TAG_ID=1234 to avoid hardcoding numeric IDs in your code.
Expected result: A contact creation API route that finds or creates Keap contacts from form submissions and applies tags to trigger automation sequences.
Implement OAuth flow for multi-tenant Keap connections
Implement OAuth flow for multi-tenant Keap connections
If your Bolt app needs multiple users to connect their own Keap accounts (a SaaS tool that integrates with clients' Keap), you need to implement the OAuth 2.0 authorization code flow. This is different from the personal access token approach — it involves redirecting users to Keap's authorization page, receiving a code at your callback URL, and exchanging that code for access and refresh tokens. The OAuth flow requires two routes: an authorization route that redirects the user to Keap's OAuth page, and a callback route that handles the redirect from Keap with the authorization code. The callback route exchanges the code for tokens and stores them. Keap's OAuth endpoints: authorization URL is `https://accounts.infusionsoft.com/app/oauth/authorize`, token URL is `https://api.infusionsoft.com/token`. Parameters follow standard OAuth 2.0 conventions. The redirect URI in your authorization request must exactly match the URI registered in your Keap Developer app settings. Keap access tokens expire and must be refreshed using the refresh token. When an API call returns 401, refresh the access token using the refresh token endpoint and retry the call. For Supabase-backed apps, store the access token, refresh token, and expiry timestamp per user in a Supabase table. This is the most complex part of a Keap integration. For single-account (personal) integrations, use the personal access token approach from Step 1 instead. This step is only needed for multi-tenant SaaS products.
Create OAuth routes for Keap. Create app/api/keap/auth/route.ts that generates the Keap OAuth authorization URL with KEAP_CLIENT_ID, redirect_uri pointing to /api/keap/callback, response_type=code, and scope=full. Redirect the user to this URL. Create app/api/keap/callback/route.ts that accepts the code param from Keap's redirect, POSTs to https://api.infusionsoft.com/token with grant_type=authorization_code, client_id, client_secret, redirect_uri, and code to exchange for access_token and refresh_token. Store both tokens in a Supabase keap_tokens table with user_id, access_token, refresh_token, expires_at. Redirect to /dashboard on success. Add a ConnectKeap button component that links to /api/keap/auth.
Paste this in Bolt.new chat
1// app/api/keap/callback/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { createClient } from '@supabase/supabase-js';45export async function GET(request: NextRequest) {6 const { searchParams } = new URL(request.url);7 const code = searchParams.get('code');8 const error = searchParams.get('error');910 if (error || !code) {11 return NextResponse.redirect(new URL('/settings?error=keap_auth_failed', request.url));12 }1314 const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/keap/callback`;1516 const tokenResponse = await fetch('https://api.infusionsoft.com/token', {17 method: 'POST',18 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },19 body: new URLSearchParams({20 grant_type: 'authorization_code',21 client_id: process.env.KEAP_CLIENT_ID!,22 client_secret: process.env.KEAP_CLIENT_SECRET!,23 redirect_uri: redirectUri,24 code,25 }),26 });2728 if (!tokenResponse.ok) {29 console.error('Keap token exchange failed:', await tokenResponse.text());30 return NextResponse.redirect(new URL('/settings?error=keap_token_failed', request.url));31 }3233 const tokens = await tokenResponse.json();34 const expiresAt = new Date(Date.now() + tokens.expires_in * 1000).toISOString();3536 const supabase = createClient(37 process.env.NEXT_PUBLIC_SUPABASE_URL!,38 process.env.SUPABASE_SERVICE_ROLE_KEY!39 );4041 // Store tokens — in production, associate with the current user's ID42 await supabase.from('keap_tokens').upsert({43 access_token: tokens.access_token,44 refresh_token: tokens.refresh_token,45 expires_at: expiresAt,46 }, { onConflict: 'user_id' }); // adjust based on your auth model4748 return NextResponse.redirect(new URL('/dashboard?connected=keap', request.url));49}Pro tip: The OAuth callback URL registered in your Keap Developer app must exactly match the redirect_uri you send in OAuth requests. During development in Bolt's WebContainer, the preview URL is not a stable public address and cannot be registered. Deploy to Netlify first, register https://your-app.netlify.app/api/keap/callback as the redirect URI in the Keap Developer Portal, then test the OAuth flow on your deployed site.
Expected result: A working Keap OAuth flow that redirects users to Keap authorization and stores access tokens in Supabase after the callback.
Deploy to Netlify and complete OAuth configuration
Deploy to Netlify and complete OAuth configuration
Deploying to Netlify is essential for Keap integrations — the OAuth callback URL must be a deployed, publicly accessible URL registered in the Keap Developer Portal. The Bolt WebContainer preview URL changes per session and cannot be used as a registered OAuth redirect URI. To deploy: click Deploy in Bolt.new, connect to Netlify via OAuth, and wait for the initial build. After deploying, your app will have a stable `*.netlify.app` URL. In the Keap Developer Portal, update your app's OAuth Redirect URI to include your Netlify URL (e.g., `https://your-app.netlify.app/api/keap/callback`). In the Netlify dashboard → Site Configuration → Environment Variables, add: KEAP_CLIENT_ID, KEAP_CLIENT_SECRET, KEAP_ACCESS_TOKEN (if using personal token), KEAP_API_URL, KEAP_NEW_LEAD_TAG_ID, and any other tag IDs you reference. Also add NEXT_PUBLIC_APP_URL with your Netlify URL — this is used for constructing the OAuth redirect URI in your callback route. Trigger a redeploy. For Keap webhooks: Keap supports webhooks (called 'Hook Types' in their developer documentation) for contact events. Configure them via the Keap API itself: `POST /v1/hooks` with the hookTypeKey, eventKey, objectKey (contact ID), and your callback URL. Keap webhooks are configured programmatically via the API rather than through a UI settings page. Remember: webhook delivery and OAuth callbacks cannot work in Bolt's WebContainer. Both require a deployed, publicly accessible URL. Deploy to Netlify early in your development process to enable these features.
Add netlify.toml with Next.js 14+ build configuration. Also create a Keap connection status API route at app/api/keap/status/route.ts that: calls GET /v1/account/profile using the KEAP_ACCESS_TOKEN, returns isConnected: true with accountName if successful, returns isConnected: false if the token is missing or invalid (401). Create a KeapConnectionStatus React component that calls this route on mount, shows a green 'Connected to Keap: {accountName}' badge if connected, or an amber 'Not connected' badge with a 'Connect Keap' button linking to /api/keap/auth if not connected.
Paste this in Bolt.new chat
1# netlify.toml2[build]3 command = "npm run build"4 publish = ".next"56[[plugins]]7 package = "@netlify/plugin-nextjs"89[build.environment]10 NODE_VERSION = "20"11 NEXT_TELEMETRY_DISABLED = "1"Pro tip: After deploying, test the Keap connection using your app's status endpoint before testing the full OAuth or API flow. If the status endpoint returns isConnected: true, your API credentials work. If it returns 401, the access token has expired or is incorrect — regenerate a personal token or re-authorize the OAuth app.
Expected result: A deployed Netlify app with working Keap API integration, OAuth redirect URI registered in the Keap Developer Portal, and environment variables configured.
Common use cases
Lead Capture Form to Keap CRM
Capture leads from a landing page form into Keap, assign relevant tags that trigger automation sequences, and optionally assign the lead to a pipeline stage. The contact is created immediately in Keap, ensuring no leads are lost and your automated follow-up sequences start right away.
Create a lead capture form that sends contacts to Keap. Build an API route at app/api/keap/contacts/route.ts using KEAP_ACCESS_TOKEN and KEAP_API_URL env vars. Accept POST with firstName, lastName, email, phone, source. Use Keap REST API POST /v1/contacts to create the contact, mapping fields to given_name, family_name, email_addresses (array), phone_numbers (array). After creating, apply a tag using POST /v1/contacts/{id}/tags with KEAP_NEW_LEAD_TAG_ID env var. Return 201 with the contact ID. Create a LeadCaptureForm React component with validation and success message.
Copy this prompt to try it in Bolt.new
Appointment Booking Confirmation to Keap
After a user books an appointment (via Calendly, your own booking system, etc.), update their Keap contact record with the appointment details, apply a 'booked' tag to trigger a preparation email sequence, and log an activity noting the appointment date. Keeps the sales team informed of upcoming meetings directly in Keap.
Create an appointment booking sync. Build an API route at app/api/keap/appointment/route.ts that accepts POST with contactEmail, appointmentDate, appointmentType. First GET /v1/contacts?email={email} to find the existing contact or create one. Apply tag from KEAP_APPOINTMENT_TAG_ID. Log a note using POST /v1/contacts/{id}/notes with body showing appointment type and date. Update the contact's custom field for next_appointment_date using KEAP_APPOINTMENT_FIELD_ID. Return the Keap contact ID.
Copy this prompt to try it in Bolt.new
Purchase-Triggered Keap Automation
After a Stripe payment completes, create or update the customer contact in Keap, apply a buyer tag to trigger a post-purchase automation sequence, and log the order details as a note. The automation sequence handles welcome emails, upsell sequences, and review requests automatically.
Integrate Keap with Stripe checkout. In my Stripe webhook handler at app/api/stripe/webhook/route.ts, after checkout.session.completed, call a syncToKeap(email, name, amount, productName) function. This function should: search for existing contact by email using GET /v1/contacts?email={email}, create if not found using POST /v1/contacts, apply the buyer tag using KEAP_BUYER_TAG_ID, and log a purchase note. Wrap all Keap calls in try-catch so Stripe processing continues if Keap fails.
Copy this prompt to try it in Bolt.new
Troubleshooting
Keap API returns 401 Unauthorized even with the correct access token
Cause: Keap access tokens expire, typically after a few hours for OAuth-issued tokens. Personal access tokens from the Developer Portal are long-lived but can also expire or be revoked. The token may also be stored incorrectly — extra spaces or newlines in the environment variable value cause authentication failures.
Solution: Regenerate a personal access token from the Keap Developer Portal if using personal tokens. For OAuth tokens, implement refresh token rotation: catch 401 errors, use the refresh token to get a new access token from https://api.infusionsoft.com/token, update the stored token, and retry the failed request.
1// Refresh token flow2async function refreshKeapToken(refreshToken: string) {3 const response = await fetch('https://api.infusionsoft.com/token', {4 method: 'POST',5 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },6 body: new URLSearchParams({7 grant_type: 'refresh_token',8 refresh_token: refreshToken,9 client_id: process.env.KEAP_CLIENT_ID!,10 client_secret: process.env.KEAP_CLIENT_SECRET!,11 }),12 });13 return response.json();14}OAuth callback fails with 'redirect_uri mismatch' error
Cause: The redirect_uri in your OAuth request does not exactly match the URI registered in the Keap Developer Portal. Any difference — trailing slash, HTTP vs HTTPS, different domain — causes this error.
Solution: In the Keap Developer Portal, verify the exact redirect URI registered for your app. Ensure NEXT_PUBLIC_APP_URL in your environment variables matches your deployed Netlify domain exactly (no trailing slash). The redirect_uri sent in the authorization request must be character-for-character identical to the registered URI.
Tag application succeeds (no error) but Keap automations are not triggering
Cause: The tag was applied but the automation sequence trigger does not match, or the automation is not published/active. Keap automations must be in 'Published' status to run.
Solution: In Keap, go to Marketing → Campaigns → select the automation. Check that the trigger is 'Tag is applied' and the tag matches exactly. Also verify the campaign is in 'Published' status (not 'Draft'). Check the automation's goal settings to ensure it fires for new tag applications and is not limited to contacts in a specific state.
OAuth flow cannot be tested in Bolt.new's WebContainer preview
Cause: OAuth callback URLs must be stable, registered URIs. Bolt's WebContainer generates a dynamic preview URL that changes per session and cannot be registered as an OAuth redirect URI in the Keap Developer Portal.
Solution: Deploy to Netlify first. Register https://your-app.netlify.app/api/keap/callback as the redirect URI in your Keap OAuth app settings. Test all OAuth functionality on the deployed Netlify site. This is a fundamental WebContainer limitation that affects all OAuth flows requiring registered redirect URIs.
Best practices
- Use a personal access token from the Keap Developer Portal for single-account integrations — it avoids implementing OAuth token refresh and is sufficient for connecting one Keap account
- Store Keap tag IDs as environment variables (KEAP_NEW_LEAD_TAG_ID, KEAP_CUSTOMER_TAG_ID) rather than hardcoding numeric IDs — tag IDs can change if tags are deleted and recreated
- Always implement the create-or-update pattern for contacts — search by email before creating to prevent duplicate records that are difficult to merge in Keap
- Wrap Keap API calls in try-catch with non-throwing error handling when calling from Stripe webhooks or signup flows — a Keap API failure should never break core user flows
- Deploy to Netlify before implementing OAuth flows — the callback URL must be a deployed, registered domain; the Bolt WebContainer preview cannot be used for OAuth redirect URIs
- Implement token refresh logic for OAuth tokens — Keap access tokens expire, and handling 401 errors with automatic refresh avoids users losing their Keap connection
- Test API calls in Bolt.new preview before deploying — contact creation and tag assignment using a personal access token work in the WebContainer since they are outbound HTTP requests
- Verify tag trigger automations are Published in Keap after applying tags — Draft campaigns do not run even when the trigger tag is applied
Alternatives
HubSpot has a larger developer community, clearer API documentation, a generous free CRM tier, and simpler API key authentication — significantly easier to integrate from Bolt.new than Keap's OAuth-required approach.
Creator-focused email marketing with simpler automation workflows and landing pages.
Drip focuses specifically on e-commerce automation with a simpler API and clearer pricing — better for product-based businesses, while Keap's strengths are in service business CRM and appointment workflows.
Mailchimp covers email marketing without the CRM depth of Keap — better for teams that primarily need email campaigns rather than full CRM automation with sales pipelines and appointment management.
Frequently asked questions
Can I use Keap's API without implementing OAuth?
Yes, for single-account integrations. The Keap Developer Portal allows generating a personal access token that can be used directly as a Bearer token in API calls without going through the OAuth authorization code flow. This is the recommended approach for personal integrations or when building tools that connect a single specific Keap account. OAuth is only required for multi-tenant apps where different users connect their own Keap accounts.
Why does Keap's API documentation still reference Infusionsoft?
Keap rebranded from Infusionsoft in 2019, but many API endpoints, documentation pages, and SDK references still use the Infusionsoft name. The API domain is api.infusionsoft.com, the OAuth token URL is api.infusionsoft.com/token, and older documentation refers to the product as Infusionsoft. This is expected — the API has not changed, only the brand name.
Can Keap API calls work in Bolt.new's WebContainer during development?
Outbound API calls (creating contacts, applying tags, fetching data) using a personal access token work in the WebContainer — they are standard HTTPS requests. OAuth callback flows cannot work in the WebContainer because the callback URL must be a stable, registered domain. Deploy to Netlify first before testing OAuth flows.
What Keap plan is required for API access?
Keap API access requires a Keap Pro or Max plan. The basic Keap Grow plan (now called Keap) does not include API access. Check your plan details at keap.com/pricing — API is listed under the Pro plan and above. If your account does not have API access, upgrading to Pro is required before any integration can work.
How do Keap tags work as automation triggers?
In Keap, you create Campaign automation sequences with a 'Tag is applied' goal as the entry trigger. When your API applies that specific tag to a contact, Keap immediately enrolls that contact in the campaign sequence. The sequence can then send emails, create tasks, apply additional tags, update custom fields, or move the contact through a pipeline stage. Tags are the primary mechanism for triggering automation from external integrations.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation