# How to Integrate Lovable with Freshsales

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: March 2026

## TL;DR

To integrate Freshsales with Lovable, generate an API key from your Freshsales profile settings, store it with your subdomain in Cloud → Secrets, then build Edge Functions that proxy calls to the Freshsales API for contacts, deals, accounts, and activities. Freshsales adds Freddy AI lead scoring on top of its pipeline management, making it ideal for founders who want AI-powered sales prioritization built into their Lovable app dashboards.

## Build AI-powered sales dashboards in Lovable with Freshsales and Freddy AI scoring

Freshsales is the sales CRM product from Freshworks, designed for teams that also use Freshdesk (support), Freshservice (IT), or other Freshworks products. Its defining feature is Freddy AI — an AI engine that scores leads and deals based on historical win/loss patterns, engagement data, and demographic signals. For founders building custom sales tools on top of their CRM, surfacing Freddy AI scores in a Lovable dashboard gives sales teams a prioritized view of their pipeline without requiring each rep to log into Freshsales manually.

Freshsales API authentication is simple: each user has a personal API key (similar to Pipedrive's approach), plus your account's subdomain serves as the API base URL. There is no OAuth2 required — the API key is included in an Authorization header as a token, making integration straightforward.

The Freshsales API covers the full CRM data model: contacts (individual people), accounts (companies), deals (opportunities), activities (calls, meetings, tasks), appointments, and notes. The API also exposes Freddy AI data: lead scores, deal health indicators, and propensity-to-buy ratings are available as fields on contact and deal objects.

For teams using the Freshworks suite, Freshsales integrates natively with Freshdesk (support tickets appear in the CRM contact timeline), Freshmarketer (email campaign engagement syncs to contact records), and Freshservice (IT tickets associated with accounts). Building your Lovable app as the unified interface for this data creates significant operational efficiency for Freshworks-heavy organizations.

## Before you start

- A Lovable project with Lovable Cloud enabled (Edge Functions require a deployed app)
- A Freshsales account (free plan available for up to 3 users at freshworks.com/crm/sales/)
- Your Freshsales account subdomain (the 'yourcompany' in yourcompany.freshsales.io)
- Basic familiarity with Freshsales data model: contacts, accounts, deals, and activities

## Step-by-step guide

### 1. Generate a Freshsales API key and find your account subdomain

Freshsales uses API key authentication, similar to Pipedrive. Each user has a personal API key that provides access to the API with that user's permissions.

To get your API key: log in to Freshsales and click your profile avatar in the top-right corner. Select 'Profile Settings' from the dropdown. In the Profile Settings page, scroll to the 'API Settings' section. Your API key is displayed — click the eye icon to show it and copy it.

Your Freshsales subdomain is the company name part of your Freshsales URL. When you log in, the URL looks like yourcompany.freshsales.io. The subdomain is 'yourcompany' (without .freshsales.io). This forms the base URL for all API calls: https://yourcompany.freshsales.io/api/.

Freshsales API base URL format: https://{subdomain}.freshsales.io/api/
Example endpoint: https://yourcompany.freshsales.io/api/contacts

Authentication header: Token token={YOUR_API_KEY}

Note the 'Token token=' prefix — this is Freshsales-specific and different from 'Bearer' or 'Basic' auth formats. Forgetting the 'Token token=' prefix causes 401 errors even with a valid API key.

For production integrations, consider using the API key of a dedicated admin user rather than your personal account, so the integration is not disrupted if your account permissions change.

**Expected result:** A Freshsales API key is copied from Profile Settings. Your Freshsales subdomain is noted (the company name part of your Freshsales URL without the .freshsales.io suffix).

### 2. Store credentials in Cloud Secrets

Add Freshsales credentials to Lovable's Cloud Secrets panel. In Lovable, click '+' → Cloud panel → Secrets tab. Add two secrets:

- FRESHSALES_API_KEY — your personal API key from Freshsales Profile Settings
- FRESHSALES_SUBDOMAIN — your Freshsales subdomain (just 'yourcompany', not the full URL)

The Edge Function constructs the full API base URL as: https://${FRESHSALES_SUBDOMAIN}.freshsales.io/api/

Store the subdomain without protocol or domain suffix. Including 'https://' or '.freshsales.io' creates malformed URLs.

Freshsales API authentication header format is: Token token=YOUR_API_KEY — note the capital T in 'Token' and the lowercase 'token=' prefix before the key value. This double-token format is specific to Freshsales and does not follow the standard Bearer token convention.

The Freshsales API supports JSON responses for all endpoints. All list endpoints return data in a named array matching the resource type: contacts returns { contacts: [...] }, deals returns { deals: [...] }. Single resource endpoints return the object directly: { contact: {...} }. Pagination uses page and per_page query parameters with a default of 25 items per page.

**Expected result:** FRESHSALES_API_KEY and FRESHSALES_SUBDOMAIN appear in Cloud → Secrets with masked values.

### 3. Create the Freshsales API proxy Edge Function

Build the Edge Function that handles common Freshsales operations. The Freshsales API uses standard REST conventions with JSON request/response bodies and query parameters for filtering and pagination.

Key Freshsales API endpoints:
- Contacts: GET /api/contacts (list), GET /api/contacts/{id} (get), POST /api/contacts (create), PUT /api/contacts/{id} (update)
- Deals: GET /api/deals (list), POST /api/deals (create), PUT /api/deals/{id} (update)
- Accounts: GET /api/accounts (list/search), POST /api/accounts (create)
- Activities: GET /api/activities (list), POST /api/activities (create)

Freshsales supports field filtering with the 'include' parameter to reduce response payload size: GET /api/contacts?include=freddy_score,last_activity_date returns only those fields plus the default ID and name fields.

For searching contacts by email (the most common lookup): GET /api/contacts/filter uses POST with a filter body: { filter_rule: [{ attribute: 'email', operator: 'is_in', value: 'user@example.com' }] }. This endpoint requires a POST with the filter body even though it is a search/read operation.

Freddy AI scores for contacts are available as the 'freddy_score' field (0-100). Deal health scores are 'freddy_forecast_score'. These fields are not returned by default — include them explicitly in the 'include' parameter.

```
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

function fsHeaders() {
  const key = Deno.env.get('FRESHSALES_API_KEY')
  if (!key) throw new Error('FRESHSALES_API_KEY not configured')
  return {
    Authorization: `Token token=${key}`,
    'Content-Type': 'application/json',
  }
}

function fsUrl(path: string) {
  const sub = Deno.env.get('FRESHSALES_SUBDOMAIN')!
  return `https://${sub}.freshsales.io/api${path}`
}

serve(async (req) => {
  if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })

  try {
    const { action, params = {} } = await req.json()
    const headers = fsHeaders()
    let resp: Response

    switch (action) {
      case 'create_contact':
        resp = await fetch(fsUrl('/contacts'), {
          method: 'POST',
          headers,
          body: JSON.stringify({
            contact: {
              first_name: params.firstName,
              last_name: params.lastName,
              email: params.email,
              mobile_number: params.phone || '',
              company: { name: params.company || '' },
              lead_source_id: params.leadSourceId,
              tags: params.tags || [],
            },
          }),
        })
        break

      case 'list_contacts':
        resp = await fetch(fsUrl(`/contacts?page=${params.page || 1}&per_page=${params.perPage || 25}&include=freddy_score,last_activity_date,deal_stage`), { headers })
        break

      case 'search_contact_by_email':
        resp = await fetch(fsUrl('/filtered_search/contact'), {
          method: 'POST',
          headers,
          body: JSON.stringify({
            filter_rule: [{ attribute: 'email', operator: 'is_in', value: params.email }],
          }),
        })
        break

      case 'create_deal':
        resp = await fetch(fsUrl('/deals'), {
          method: 'POST',
          headers,
          body: JSON.stringify({
            deal: {
              name: params.name,
              amount: params.amount,
              contacts_added_list: params.contactId ? [params.contactId] : [],
              deal_stage_id: params.dealStageId,
              expected_close: params.expectedClose,
            },
          }),
        })
        break

      case 'list_deals':
        resp = await fetch(fsUrl(`/deals?page=${params.page || 1}&per_page=${params.perPage || 25}&include=freddy_forecast_score,contacts,deal_stage`), { headers })
        break

      case 'log_activity':
        resp = await fetch(fsUrl('/activities'), {
          method: 'POST',
          headers,
          body: JSON.stringify({
            activity: {
              title: params.title,
              type: { name: params.type || 'Task' },
              notes: params.notes || '',
              targetable_type: 'Contact',
              targetable_id: params.contactId,
              due_date: params.dueDate || new Date().toISOString().split('T')[0],
            },
          }),
        })
        break

      default:
        return new Response(JSON.stringify({ error: 'Unknown action' }), {
          status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' },
        })
    }

    const data = await resp.json()
    return new Response(JSON.stringify(data), {
      status: resp.ok ? 200 : resp.status,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  }
})
```

**Expected result:** The freshsales-crm Edge Function deploys in Cloud → Edge Functions. Test with action 'list_contacts' — Freshsales contact records with Freddy AI scores should appear in the response.

### 4. Build the Freshsales dashboard with Freddy AI scoring in Lovable

With the Edge Function deployed, use Lovable's chat to build the sales dashboard that highlights Freddy AI lead scoring. The key differentiator from standard CRM dashboards is the visual prioritization driven by Freddy scores.

For the lead prioritization view, invoke list_contacts with the include parameters for freddy_score and last_activity_date. Sort by freddy_score descending so the highest-scoring leads appear at the top. Apply color coding: scores 75-100 (green/hot), 50-74 (yellow/warm), 25-49 (orange/cool), 0-24 (gray/cold). Include the days since last activity to help reps identify engaged leads with recent interaction.

For deal pipeline health, invoke list_deals with the freddy_forecast_score included. Deals with low forecast scores despite being in late pipeline stages indicate high churn risk — surface these as an 'At Risk' section in your dashboard.

For the contact creation flow (adding leads from your app), call create_contact from form submit handlers. Map your app's plan tiers to Freshsales tags ('free', 'trial', 'pro') for segment-based sales outreach. Use the fire-and-forget pattern (no await) so contact creation latency does not affect the user's registration experience.

For connecting Freshsales with your existing Freshdesk support integration to show support ticket history alongside CRM data in a unified customer 360 view, RapidDev's team can help design the cross-Freshworks API architecture.

**Expected result:** The sales dashboard displays Freshsales contacts sorted by Freddy AI score with color-coded badges. Deal count and total pipeline value show in the summary section. Activity logging creates tasks in Freshsales visible in the contact's timeline.

## Best practices

- Use the 'include' parameter in Freshsales API list calls to request only the fields you need — large contact lists with all fields included can be slow and expensive.
- Always split full names into first_name and last_name before creating Freshsales contacts — the API does not accept a combined name field.
- Surface Freddy AI scores visually with clear color-coded indicators — the scoring system is only valuable if it drives behavioral changes in how reps prioritize outreach.
- Implement fire-and-forget (no await) for Freshsales contact creation in user-facing registration flows — CRM sync should never delay the primary user experience.
- Store deal stage IDs as secrets (FRESHSALES_STAGE_QUALIFICATION_ID) rather than hardcoding — Freshsales stage IDs are account-specific and change if you restructure your pipeline.
- Combine Freddy score with days-since-last-activity for a more nuanced prioritization signal — a high-scoring lead that has been inactive for 30 days needs different outreach than one active yesterday.
- Create activities immediately after contact creation to give Freddy AI initial signals about engagement — even a 'New signup activity' helps the AI model start building a score.

## Use cases

### Sales prioritization dashboard using Freddy AI lead scores

Build a dashboard that fetches contacts and deals from Freshsales with their Freddy AI scores and displays a prioritized list sorted by lead score. Sales reps see which contacts are most likely to convert at the top, with score badges and engagement signals. Clicking a contact shows their activity history and Freddy AI score breakdown. This replaces manual pipeline review with data-driven prioritization.

Prompt example:

```
Create an Edge Function called freshsales-leads that fetches contacts from Freshsales with their freddy_score field using FRESHSALES_API_KEY and FRESHSALES_SUBDOMAIN from secrets. Sort by freddy_score descending. Return name, email, company, freddy_score, last_activity_date, and deal_stage. Build a Lovable dashboard showing a ranked lead list with color-coded score badges (green >75, yellow 40-75, red <40).
```

### Auto-create Freshsales contacts from app registrations

When a new user registers for your Lovable app, automatically create a Freshsales contact with their information, set a lead source tag, and assign to the appropriate sales rep based on territory or plan tier. The contact appears immediately in the Freshsales pipeline for follow-up, with Freddy AI starting to build a score based on engagement data from the CRM.

Prompt example:

```
Create an Edge Function called freshsales-create-contact that accepts { firstName, lastName, email, company, phone, plan, source } and creates a Freshsales contact using FRESHSALES_API_KEY and FRESHSALES_SUBDOMAIN from secrets. Set the lead source to source, add a tag based on plan tier (free/trial/paid), and return the contact ID. Call this from my registration success handler alongside Supabase Auth user creation.
```

### Deal pipeline view with stage-by-stage conversion analytics

Fetch all active deals from Freshsales and display them in a pipeline view grouped by deal stage. For each stage, show total deal value and count. Include a conversion funnel showing the percentage of deals moving between stages. Sales managers use this to identify pipeline bottlenecks without needing to navigate Freshsales reports.

Prompt example:

```
Create an Edge Function called freshsales-pipeline that fetches all open deals from Freshsales grouped by deal_stage using FRESHSALES_API_KEY and FRESHSALES_SUBDOMAIN from secrets. Return deal count, total value, and deal list per stage. Build a pipeline view in Lovable with stage columns showing deal cards (name, amount, contact name, expected close date) and a summary header per stage showing count and total value.
```

## Troubleshooting

### Freshsales API returns 401 Unauthorized on every request

Cause: The Authorization header format is incorrect. Freshsales uses 'Token token=KEY' format, not 'Bearer KEY' or 'Basic' encoding.

Solution: Verify the Authorization header in your Edge Function is exactly: 'Token token=YOUR_API_KEY' — capital T in Token, then a space, then lowercase 'token=' immediately followed by the key with no space. This non-standard format is specific to Freshsales (and other Freshworks APIs).

```
// Correct Freshsales auth header format:
Authorization: `Token token=${apiKey}`  // NOT 'Bearer' or 'Basic'
```

### Contact creation fails with 'Name is not valid' or missing field errors

Cause: Freshsales requires first_name and last_name as separate fields, not a combined 'name' field. Company must be passed as a nested object.

Solution: Split the user's full name into first_name and last_name before sending to the API. If you only have a full name, split on the first space: const [firstName, ...lastParts] = fullName.split(' '). Pass company as: { company: { name: companyName } }. The API returns a 422 with detailed field error messages — check the response body for which specific field is failing.

### The freddy_score field returns null or zero for all contacts

Cause: Freddy AI requires historical deal data to generate scores. New Freshsales accounts or accounts with very few closed deals will have null or zero scores for all contacts.

Solution: This is expected behavior for new accounts. Freddy AI builds its scoring model from historical win/loss patterns over time. As more deals are closed or lost in Freshsales, scores become meaningful. For demonstration purposes, you can display 'Score Pending' for null scores rather than showing zero.

### API returns 404 for all endpoints with 'Account not found' error

Cause: The FRESHSALES_SUBDOMAIN is incorrect or includes the full domain instead of just the subdomain.

Solution: Verify the subdomain value is only the company name portion of your Freshsales URL. For yourcompany.freshsales.io, the secret value should be 'yourcompany' with no protocol, no dots, and no '.freshsales.io' suffix. The Edge Function constructs the full URL from this subdomain.

## Frequently asked questions

### Does Freshsales have a free plan that supports API access?

Yes — Freshsales has a free plan supporting up to 3 users with basic CRM features and full API access. The free plan includes contacts, accounts, deals, and activities but does not include Freddy AI lead scoring (Growth plan at $29/user/month required) or advanced automation (Pro plan at $69/user/month). For building and testing a Lovable integration, the free plan is sufficient. Freddy AI scoring requires a paid plan to generate non-zero scores.

### What is Freddy AI and how accurate are its lead scores?

Freddy AI is Freshworks' machine learning engine that analyzes your historical CRM data to predict lead and deal quality. Lead scores (0-100) are generated based on historical win/loss patterns, deal velocity, contact engagement, demographic signals, and behavioral data. Scores become more accurate over time as Freddy processes more historical data — expect meaningful scores after approximately 3-6 months of deal history. Freddy is included in the Growth plan and above.

### Can I use the Freshsales API for both Freshsales Classic and Freshsales?

Freshworks has two CRM products: Freshsales (the current product, sometimes called Freshsales Suite) and Freshsales Classic (the legacy product). The API endpoints and authentication methods differ between them. This guide covers the current Freshsales API. If your account is on Freshsales Classic (older accounts with a different UI), check the Freshsales Classic API documentation for endpoint differences.

### How do I use Freshsales alongside Freshdesk in the same Lovable app?

Both Freshdesk and Freshsales APIs use the same API key and subdomain-based authentication pattern. You can create Edge Functions for each product separately, using FRESHSALES_API_KEY and FRESHDESK_API_KEY as separate secrets. Both APIs follow the same 'Token token=KEY' authorization format. To build a customer 360 view, call both APIs for a given email address and merge the CRM contact data with the support ticket history in your Lovable UI.

---

Source: https://www.rapidevelopers.com/lovable-integration/freshsales
© RapidDev — https://www.rapidevelopers.com/lovable-integration/freshsales
