To integrate Keap with Lovable, register an application in the Keap Developer portal to get OAuth2 credentials, complete the authorization flow to obtain access and refresh tokens, store them in Cloud → Secrets, then build Edge Functions that call the Keap REST API v2 for contacts, deals, invoices, and automations. Keap's modern API v2 is simpler and more powerful than the legacy Infusionsoft/Keap Max Classic API.
Connect Lovable to Keap's modern API v2 for small business CRM and invoicing automation
Keap is the modern evolution of the Infusionsoft platform — a streamlined CRM and business automation tool for small businesses and entrepreneurs. While Infusionsoft (now Keap Max Classic) is the legacy product with a complex XML-RPC API history, modern Keap (Keap Pro and Keap Max) offers a clean REST API v2 that is significantly easier to work with. For new Lovable integrations, Keap REST API v2 is the right choice over the legacy Infusionsoft API.
Keap's platform uniquely combines what most businesses need three separate tools for: CRM contacts and pipeline, invoicing and payment collection, and marketing automation with email sequences and text message campaigns. For small business founders building internal management tools or client portals in Lovable, Keap integration creates a single data layer across all these functions without requiring separate Stripe (for invoicing), Mailchimp (for email), and HubSpot (for CRM) integrations.
The authentication model is OAuth2 with authorization code flow — the same as Zoho CRM and Infusionsoft, using the Keap Developer portal at developer.infusionsoft.com (which serves both Keap and Infusionsoft integrations). Access tokens expire after 24 hours, and the Edge Function handles refresh automatically using the long-lived refresh token.
Keap REST API v2 base URL: https://api.infusionsoft.com/crm/rest/v2/ — note it still uses the infusionsoft.com domain despite being the modern Keap product. The v2 endpoints have a significantly improved design compared to v1, with better filtering, cleaner response structures, and more consistent naming conventions.
Integration method
Keap has no native Lovable connector. All CRM, invoicing, and automation operations are proxied through Lovable Edge Functions that call the Keap REST API v2. OAuth2 access and refresh tokens are stored in Cloud → Secrets and accessed via Deno.env.get(), with the Edge Function implementing automatic token refresh to keep the integration running without manual intervention.
Prerequisites
- A Lovable project with Lovable Cloud enabled (Edge Functions require a deployed app)
- An active Keap account (keap.com — Keap Pro at $159/month is the minimum paid tier for the modern Keap product; 14-day trial available)
- Access to the Keap Developer portal at developer.infusionsoft.com to register an OAuth2 application
- A tool for making HTTP requests to complete the OAuth2 token exchange (Postman, Insomnia, or Hoppscotch)
- Basic familiarity with Keap concepts: contacts, tags, opportunities (deals), invoices, and automation sequences
Step-by-step guide
Register a Keap Developer application and obtain OAuth2 tokens
Register a Keap Developer application and obtain OAuth2 tokens
Keap uses OAuth2 authorization code flow for API authentication. Both modern Keap and legacy Infusionsoft integrations are registered at developer.infusionsoft.com. Go to developer.infusionsoft.com and sign in with your Keap account. Click 'Register Application'. Fill in: - Application Name: 'Lovable CRM Integration' - Redirect URL: https://yourapp.lovable.app/oauth/callback - Application Type: Server After registration, you receive a Client ID and Client Secret. Construct the OAuth2 authorization URL (note: Keap and Infusionsoft share the same OAuth2 endpoints): https://accounts.infusionsoft.com/app/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=full Open this in your browser and authorize. Keap redirects to your redirect URI with a 'code' parameter. Copy the code — it expires in 10 minutes. Exchange the code for tokens via POST to https://api.infusionsoft.com/token: - grant_type: authorization_code - client_id, client_secret, redirect_uri, code - Send as form URL-encoded (application/x-www-form-urlencoded) The response includes: access_token (valid 24 hours), refresh_token (expires after 90 days of non-use), and token_type: 'bearer'. For Keap REST API v2 specifically: the API base URL is https://api.infusionsoft.com/crm/rest/v2/ (note v2, not v1). The v2 API has improved pagination, filtering, and response structures compared to the v1 API used by Infusionsoft.
Pro tip: Keap API v2 and Infusionsoft API v1 share the same OAuth2 authorization server (accounts.infusionsoft.com) and token endpoint (api.infusionsoft.com/token). If you already have an Infusionsoft application registered, you can use the same client credentials for Keap API v2 — just change the API base URL from /crm/rest/v1/ to /crm/rest/v2/.
Expected result: A Keap/Infusionsoft Developer application is registered. OAuth2 authorization code flow is completed. You have an access_token, refresh_token, Client ID, and Client Secret ready to store in Cloud → Secrets.
Store OAuth2 credentials in Cloud Secrets and note key Keap IDs
Store OAuth2 credentials in Cloud Secrets and note key Keap IDs
Add all Keap OAuth2 credentials to Lovable's Cloud Secrets panel. In Lovable, click '+' → Cloud panel → Secrets. Add: - KEAP_CLIENT_ID — your application Client ID from the developer portal - KEAP_CLIENT_SECRET — your application Client Secret - KEAP_ACCESS_TOKEN — the current access token (24-hour lifetime) - KEAP_REFRESH_TOKEN — the long-lived refresh token (90-day non-use expiry) Keap API v2 uses the same OAuth2 token refresh endpoint as v1: POST https://api.infusionsoft.com/token with grant_type=refresh_token, client_id, client_secret, and refresh_token. For tag-based automation, retrieve your tag IDs. In Keap, go to Settings → Tags or use the API: GET /crm/rest/v2/tags returns all tags with their numeric IDs and names. Store frequently-used tag IDs as additional secrets: - KEAP_TAG_NEW_CONTACT — applied on signup - KEAP_TAG_CUSTOMER — applied on first purchase Keap API v2 uses slightly different field names than v1. Contact creation in v2 uses: given_name (first name), family_name (last name), email_addresses (array with { email, field: 'EMAIL1' }). Verify field names against the Keap API v2 documentation. The Keap API v2 supports more powerful filtering than v1: GET /crm/rest/v2/contacts?filter=email==user@example.com uses equality filter syntax.
Pro tip: Keap API v2's filter syntax uses == for equality (not = or :equals like some other APIs). For example, filter=email==user@example.com. This differs from Keap API v1's search endpoint syntax. Always check which API version you are targeting when constructing filter queries.
Expected result: KEAP_CLIENT_ID, KEAP_CLIENT_SECRET, KEAP_ACCESS_TOKEN, KEAP_REFRESH_TOKEN, and any tag ID secrets appear in Cloud → Secrets.
Create the Keap REST API v2 Edge Function with token refresh
Create the Keap REST API v2 Edge Function with token refresh
Build the Edge Function for Keap REST API v2 operations. The v2 API has a cleaner design than v1, with consistent response shapes and better filtering support. Keap REST API v2 base URL: https://api.infusionsoft.com/crm/rest/v2/ Key v2 endpoints: - GET /contacts — list contacts with filter support - POST /contacts — create contact - PATCH /contacts/{id} — update contact - POST /contacts/{contactId}/tags — apply tags - DELETE /contacts/{contactId}/tags/{tagId} — remove a tag - GET /invoices — list invoices - POST /invoices — create invoice - GET /opportunities — list opportunities (deals) - POST /opportunities — create opportunity The token refresh uses the same endpoint as v1: POST https://api.infusionsoft.com/token. The Edge Function requests a new token when a 401 is returned, just as in the Infusionsoft pattern. Invoice creation in Keap v2 requires: contact_id (numeric), line_items array with { name, quantity, unit_price }, and due_date. The contact_id must be the Keap numeric contact ID, not the email address. For tag operations in v2, apply tags via POST to /contacts/{id}/tags with body { tag_ids: [123, 456] }. Remove individual tags via DELETE /contacts/{id}/tags/{tagId} — tag removal is per-tag-ID, not bulk.
Create an Edge Function called keap-v2 at supabase/functions/keap-v2/index.ts. Accept POST with { action, params }. Support actions: find_or_create_contact, apply_tags, remove_tag, list_contacts, create_invoice, list_invoices, create_opportunity. Use Keap REST API v2 at https://api.infusionsoft.com/crm/rest/v2. Implement token refresh on 401 using KEAP_REFRESH_TOKEN, KEAP_CLIENT_ID, KEAP_CLIENT_SECRET. Include CORS headers.
Paste this in Lovable chat
1import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'23const corsHeaders = {4 'Access-Control-Allow-Origin': '*',5 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',6}78const KEAP_BASE = 'https://api.infusionsoft.com/crm/rest/v2'9const TOKEN_URL = 'https://api.infusionsoft.com/token'1011async function refreshToken(): Promise<string> {12 const resp = await fetch(TOKEN_URL, {13 method: 'POST',14 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },15 body: new URLSearchParams({16 grant_type: 'refresh_token',17 client_id: Deno.env.get('KEAP_CLIENT_ID')!,18 client_secret: Deno.env.get('KEAP_CLIENT_SECRET')!,19 refresh_token: Deno.env.get('KEAP_REFRESH_TOKEN')!,20 }),21 })22 const data = await resp.json()23 if (!data.access_token) throw new Error('Keap token refresh failed: ' + JSON.stringify(data))24 return data.access_token25}2627async function keapFetch(path: string, method = 'GET', body?: unknown, retry = true): Promise<Response> {28 let token = Deno.env.get('KEAP_ACCESS_TOKEN')!29 let resp = await fetch(`${KEAP_BASE}${path}`, {30 method,31 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },32 body: body ? JSON.stringify(body) : undefined,33 })34 if (resp.status === 401 && retry) {35 token = await refreshToken()36 resp = await fetch(`${KEAP_BASE}${path}`, {37 method,38 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },39 body: body ? JSON.stringify(body) : undefined,40 })41 }42 return resp43}4445serve(async (req) => {46 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })4748 try {49 const { action, params = {} } = await req.json()50 let resp: Response5152 switch (action) {53 case 'find_or_create_contact': {54 const search = await keapFetch(`/contacts?filter=email==${encodeURIComponent(params.email)}`)55 const searchData = await search.json()56 if (searchData.contacts?.length > 0) {57 return new Response(JSON.stringify({ contactId: searchData.contacts[0].id, created: false }), {58 headers: { ...corsHeaders, 'Content-Type': 'application/json' },59 })60 }61 resp = await keapFetch('/contacts', 'POST', {62 given_name: params.firstName,63 family_name: params.lastName,64 email_addresses: [{ email: params.email, field: 'EMAIL1' }],65 phone_numbers: params.phone ? [{ number: params.phone, field: 'PHONE1' }] : [],66 tag_ids: params.tagIds || [],67 })68 const created = await resp.json()69 return new Response(JSON.stringify({ contactId: created.id, created: true }), {70 headers: { ...corsHeaders, 'Content-Type': 'application/json' },71 })72 }7374 case 'apply_tags':75 resp = await keapFetch(`/contacts/${params.contactId}/tags`, 'POST', { tag_ids: params.tagIds })76 break7778 case 'remove_tag':79 resp = await keapFetch(`/contacts/${params.contactId}/tags/${params.tagId}`, 'DELETE')80 break8182 case 'list_contacts':83 resp = await keapFetch(`/contacts?page_size=${params.pageSize || 25}&page_token=${params.pageToken || ''}`)84 break8586 case 'create_invoice':87 resp = await keapFetch('/invoices', 'POST', {88 contact_id: params.contactId,89 line_items: params.lineItems,90 due_date: params.dueDate,91 notes: params.notes || '',92 })93 break9495 case 'list_invoices':96 resp = await keapFetch(`/invoices?contact_id=${params.contactId || ''}&page_size=${params.pageSize || 25}`)97 break9899 case 'create_opportunity':100 resp = await keapFetch('/opportunities', 'POST', {101 contact: { id: params.contactId },102 opportunity_title: params.title,103 projected_revenue_low: params.valueLow,104 projected_revenue_high: params.valueHigh,105 stage: { id: params.stageId },106 })107 break108109 default:110 return new Response(JSON.stringify({ error: 'Unknown action' }), {111 status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' },112 })113 }114115 const data = await resp.json()116 return new Response(JSON.stringify(data), {117 status: resp.ok ? 200 : resp.status,118 headers: { ...corsHeaders, 'Content-Type': 'application/json' },119 })120 } catch (error) {121 return new Response(JSON.stringify({ error: error.message }), {122 status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },123 })124 }125})Pro tip: Keap API v2 uses cursor-based pagination instead of page/skip. List responses include a 'next_page_token' field — pass it as 'page_token' in the next request to get the next page. Store the page_token in component state for 'Load more' functionality.
Expected result: The keap-v2 Edge Function deploys in Cloud → Edge Functions. Test with action 'list_contacts' — Keap contacts should appear in the response with numeric IDs and the v2 field structure.
Build the Keap CRM and invoicing dashboard in Lovable
Build the Keap CRM and invoicing dashboard in Lovable
With the Edge Function deployed, use Lovable's chat to build the management portal that combines Keap's CRM and invoicing data. The unique value of Keap integration is having contacts, deals, and invoices in a single data model — your Lovable dashboard should reflect this unified view. For a client detail page, fetch the contact record, their associated opportunities, and their invoice history in parallel (three separate Edge Function calls using Promise.all in your React component). Display as a tabbed layout: Overview (contact info + active tags), Pipeline (opportunities by stage), and Invoices (invoice list with status). For invoice creation, build a form with line items (description, quantity, unit price) that calls the create_invoice action. The form should pre-populate the contact_id from the current client context. After creation, refresh the invoice list to show the new invoice with 'Draft' status. For tag management, build a tag selector that calls list_tags (add this action to your Edge Function using GET /tags), shows current contact tags, and provides add/remove buttons. Applying or removing tags immediately triggers any Keap automation sequences configured for those tags. For automation testing, apply a tag to a test contact and verify the associated Keap automation sequence fires. This confirms the API integration is working correctly end-to-end — not just that the API call returns 200, but that the automation actually runs in Keap. RapidDev's team can help design complex Keap integrations that combine contact management, invoicing workflows, and automation triggering for complete business process automation built on your Lovable app.
Build a client detail page using the keap-v2 Edge Function. Show contact name, email, phone, and tags at the top. Add a Pipeline section showing the contact's opportunities with stage and projected revenue. Add an Invoices section showing all invoices with status badges (Draft=gray, Sent=blue, Paid=green, Overdue=red) and amounts. Add a Create Invoice button that opens a form with line item rows (add/remove), due date, and notes.
Paste this in Lovable chat
Pro tip: Keap API v2 returns contact tags as a 'tag_ids' array of numeric IDs, not tag objects with names. Make a separate list_tags call at app initialization to build a tagId → tagName lookup map, then use it to display tag names throughout your UI.
Expected result: The client detail page renders contact information, opportunities, and invoices from Keap. Invoice creation works and the new invoice appears in Keap. Tag application triggers the configured automation sequences in Keap.
Common use cases
Client management portal showing contacts, pipeline, and invoices
Build an internal Lovable portal where your team manages Keap contacts, views the sales pipeline with deal stages, and creates invoices for won deals — all without switching between Keap and other tools. The portal shows a unified client view: contact details, their deal history, open invoices with payment status, and any active automation tags. Changes sync to Keap in real time.
Create Edge Functions to fetch Keap contacts, their linked opportunities, and open invoices by contact ID. Build a client detail page in Lovable showing contact information, deal history (with stage and value), and invoice list (with status and amount). Add a Create Invoice button that calls a Keap create-invoice Edge Function with line items. Show invoice status: Draft, Sent, Paid, Overdue.
Copy this prompt to try it in Lovable
Automated invoice creation on subscription or project completion
When a client completes a project milestone or a subscription renews in your Lovable app, automatically create and send a Keap invoice for the work. The Edge Function maps the billing event to Keap invoice line items, associates the invoice with the contact's record, and optionally triggers an email notification. This eliminates manual invoice creation for recurring billing scenarios.
Create an Edge Function called keap-create-invoice that accepts { contactId, lineItems: [{ name, quantity, price }], dueDate, notes } and creates a Keap invoice using KEAP_ACCESS_TOKEN from secrets. Set the invoice status to 'draft', associate with the contact, and return the invoice ID and URL. Optionally trigger sending the invoice if params.sendNow is true.
Copy this prompt to try it in Lovable
Contact tagging and automation enrollment from app events
Apply Keap tags to contacts based on behavioral events in your Lovable app — user upgraded their plan, completed onboarding, requested a callback. Tag application triggers Keap's automation sequences: email nurture flows, task creation for the sales team, and text message campaigns. This creates a live feedback loop between your app's behavioral data and Keap's marketing automation engine.
Create an Edge Function called keap-apply-tag that accepts { contactId, tagIds } and applies the given tag IDs to the Keap contact using KEAP_ACCESS_TOKEN from secrets. Return the updated contact. Call this when a user completes onboarding (tagId: KEAP_ONBOARDING_COMPLETE_TAG), upgrades their plan (tagId: KEAP_UPGRADE_TAG), or requests a demo (tagId: KEAP_DEMO_REQUEST_TAG).
Copy this prompt to try it in Lovable
Troubleshooting
Keap API v2 returns 401 Unauthorized after 24 hours
Cause: Keap access tokens expire after 24 hours. Without token refresh logic, all API calls fail after the initial token expires.
Solution: Verify the token refresh logic in your Edge Function is implemented as shown in the code above. On a 401 response, the refreshToken() function is called, which requests a new access_token from the token endpoint using the stored refresh token and client credentials. If the refresh itself fails (e.g., the refresh token has expired after 90 days), you need to re-authorize via the OAuth2 flow.
Contact email search returns empty array even though the contact exists in Keap
Cause: The Keap API v2 filter syntax uses == (double equals) for equality, not = or :equals as seen in other APIs.
Solution: Verify the filter parameter uses the correct syntax: filter=email==user@example.com. Also ensure the email is URL-encoded when passed as a query parameter. Note that Keap may take a few seconds to index newly created contacts for email search.
1// Correct Keap v2 filter syntax:2const url = `/contacts?filter=email==${encodeURIComponent(email)}`Invoice creation returns 400 with contact_id validation error
Cause: The contact_id must be the numeric Keap contact ID, not the contact's email address. Passing email as contact_id returns a validation error.
Solution: Always resolve the contact's Keap numeric ID before creating invoices. Use find_or_create_contact to get the contactId, then pass that numeric ID to create_invoice. Store the Keap contact ID in your Supabase users table to avoid repeated lookups.
Keap refresh token stops working after 90 days
Cause: Keap refresh tokens expire after 90 days of non-use. If your integration is inactive for 90+ days, the refresh token is invalidated and cannot generate new access tokens.
Solution: Re-authorize via the OAuth2 authorization code flow to obtain a new set of tokens. To prevent future expiry, ensure your integration makes at least one API call every 60 days. For production systems, set up a scheduled Supabase Edge Function that runs weekly and makes a simple list_contacts call — this keeps the refresh token active without requiring user action.
Best practices
- Store the Keap contact ID in your Supabase users table after the first find_or_create_contact call — this enables all subsequent tag and invoice operations to use direct contact IDs rather than email searches.
- Implement automatic token refresh in your Edge Function — Keap access tokens expire after 24 hours and the integration must handle this transparently.
- Build a tag ID lookup map at app initialization by calling list_tags once and caching the result — this enables displaying tag names instead of numeric IDs throughout your UI.
- Keep Keap refresh tokens active by ensuring your integration makes at least one API call every 60 days — a weekly health check call prevents the 90-day expiry.
- Use the Keap API v2 over v1 for new integrations — v2 has better filtering, cursor-based pagination, and cleaner response structures that make Edge Function code simpler.
- Implement atomic tag swaps (apply new tag + remove old tag) for state transitions like trial-to-customer — this ensures automation sequences transition correctly without overlapping.
- Test automation sequences end-to-end by applying trigger tags to test contacts and verifying the sequences actually fire in Keap — 200 OK from the API does not confirm that automations are configured correctly.
Alternatives
Choose Infusionsoft (Keap Max Classic) if your existing account is on the legacy product — the APIs share the same OAuth2 credentials but use different base URLs and field naming conventions.
Choose HubSpot if you need a free CRM tier and more extensive marketing automation capabilities — HubSpot's free plan is significantly more generous than Keap's paid-only model.
Choose Pipedrive if you want pure sales pipeline management with a simpler API key authentication and without the invoicing and automation complexity of Keap.
Frequently asked questions
What is the difference between Keap and Infusionsoft by Keap?
Infusionsoft (officially Keap Max Classic) is the legacy product with the original Infusionsoft feature set — complex campaign builder, tag-based automation engine, e-commerce tools. Modern Keap (Keap Pro and Keap Max) is a redesigned, simpler product built for entrepreneurs and small businesses. They share the same company (Keap/Infusionsoft) and the same OAuth2 Developer portal, but have different UIs and different API structures. Keap REST API v2 (this guide) covers the modern Keap product.
Does Keap have a free plan for testing integrations?
Keap does not have a permanent free plan. A 14-day free trial is available at keap.com. The minimum paid tier is Keap Pro at $159/month for 1 user and 500 contacts. For testing Lovable integrations, the 14-day trial provides full API access. You can also use a development Keap account if you have separate production and development environments.
Can I use the same OAuth2 application for both Keap and Infusionsoft?
Yes — one Developer portal application can be authorized against both Keap and Infusionsoft accounts. The same Client ID and Client Secret work for both products since they share the same OAuth2 authorization server (accounts.infusionsoft.com). You will have separate access and refresh tokens for each account. Switch between the Keap API v2 base URL (/crm/rest/v2/) and the Infusionsoft v1 URL (/crm/rest/v1/) based on which account's data you are accessing.
How do I handle Keap tag automation not firing after applying tags via API?
Tag automation in Keap fires based on campaign sequences configured in the Keap Campaign Builder. Applying a tag via the API is equivalent to manually applying the tag in the UI — any campaign sequences configured to start when that tag is applied will fire. If automations are not firing, verify in Keap's Campaign Builder that the campaign is Active (published), the tag trigger matches the tag ID being applied, and the contact meets any other campaign entry criteria.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation