# How to Integrate Lovable with SEMrush

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

## TL;DR

To integrate SEMrush with Lovable, create a Supabase Edge Function that proxies requests to the SEMrush API using your API key stored in Cloud Secrets. The Edge Function fetches domain analytics, keyword overview data, and organic research results. SEMrush uses a credit-based API model, so the Edge Function should validate parameters and cache responses to minimize credit consumption.

## Build a Keyword Research and Competitive Analysis Dashboard with SEMrush and Lovable

SEMrush is the most widely used SEO platform for keyword research and competitive analysis. Its database covers 25+ billion keywords across 142 countries, tracking not just organic rankings but also paid search (PPC) data, making it uniquely useful for teams running both SEO and Google Ads campaigns simultaneously. Building a custom Lovable dashboard powered by SEMrush data lets you surface competitive intelligence in exactly the format your team needs — without navigating SEMrush's own interface or relying on limited CSV exports.

The SEMrush API uses a credit-based billing model. Each API call to different endpoint types costs a different number of credits per row returned. Domain analytics calls (like domain overview) tend to be lower cost, while comprehensive organic research reports cost more. Understanding the credit costs of each endpoint before building your dashboard is essential for staying within your plan's allocation. The free SEMrush plan includes 10 API units per day — enough for testing, but production dashboards require a paid plan with API access.

The integration pattern for Lovable is the standard edge function proxy: your React frontend calls a Supabase Edge Function, which adds the API key from Cloud Secrets and forwards the request to SEMrush's API. This architecture is required because the SEMrush API key must never appear in browser requests — any user could inspect network traffic and steal it. Lovable's security infrastructure blocks approximately 1,200 hardcoded API keys per day, and properly storing credentials in Cloud Secrets is the correct path.

## Before you start

- A Lovable project with Lovable Cloud enabled
- A SEMrush account with API access (available on Pro plan and above; limited free trial available)
- Your SEMrush API key from semrush.com/api-analytics
- Understanding of SEMrush's credit-based API pricing before building production dashboards
- A target domain or set of keywords to test the integration

## Step-by-step guide

### 1. Get your SEMrush API key

Log in to your SEMrush account at semrush.com and navigate to your account profile settings. Look for the API section or go directly to semrush.com/api-analytics. Your API key is displayed on this page as a long alphanumeric string. If you are on the free plan, you will have access to 10 API units per day, which is enough for testing. For a production dashboard, you will need a Pro plan ($129.95/month) or higher, which includes a larger API unit allocation. Copy your API key and do not paste it into Lovable's chat interface — instead you will store it in Cloud Secrets in the next step. SEMrush also displays your current API unit balance on this page, which you should monitor when building your dashboard to avoid hitting limits unexpectedly. The API key is a 32-character hex string unique to your account.

**Expected result:** You have your SEMrush API key copied and ready to store securely.

### 2. Store your API key in Cloud Secrets

In your Lovable project, click the plus (+) icon next to the preview window to open the side panels. Select the Cloud tab. Scroll down to the Secrets section and click Add Secret. In the Name field, type SEMRUSH_API_KEY exactly — this is case-sensitive and must match what your Edge Function will use. Paste your SEMrush API key into the Value field. Click Save. The key is now encrypted and stored in the server-side environment. It will only be accessible from Edge Functions via Deno.env.get('SEMRUSH_API_KEY') — it will never appear in browser network requests, JavaScript bundles, or any client-side code. This is Lovable's secure secrets architecture, which also prevents the key from being committed to your GitHub repository if you have GitHub sync enabled.

**Expected result:** SEMRUSH_API_KEY appears in the Cloud Secrets panel with its value masked.

### 3. Create the SEMrush proxy Edge Function

Prompt Lovable to create an Edge Function that accepts requests from your frontend, reads the SEMRUSH_API_KEY from secrets, and proxies calls to the SEMrush API. The SEMrush API is a query-string-based REST API. Most endpoints accept parameters like type (the report type), domain or phrase, database (the regional database, e.g. 'us' for United States), export_columns (which data columns to return), and display_limit (max rows). The function constructs the SEMrush API URL at https://api.semrush.com/ with all parameters including the key, makes the fetch request, and returns the CSV or JSON response (SEMrush returns CSV by default; you can request JSON in some endpoints). The Edge Function should parse the CSV response into JSON for easier consumption by the frontend. Include CORS headers for all responses.

```
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 csvToJson(csv: string): Record<string, string>[] {
  const lines = csv.trim().split('\n')
  if (lines.length < 2) return []
  const headers = lines[0].split(';')
  return lines.slice(1).map(line => {
    const values = line.split(';')
    return Object.fromEntries(headers.map((h, i) => [h.trim(), values[i]?.trim() ?? '']))
  })
}

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

  try {
    const apiKey = Deno.env.get('SEMRUSH_API_KEY')
    if (!apiKey) {
      return new Response(
        JSON.stringify({ error: 'SEMRUSH_API_KEY not configured' }),
        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    const url = new URL(req.url)
    const type = url.searchParams.get('type')
    const target = url.searchParams.get('target')
    const database = url.searchParams.get('database') || 'us'
    const displayLimit = url.searchParams.get('display_limit') || '10'
    const exportColumns = url.searchParams.get('export_columns') || ''

    if (!type || !target) {
      return new Response(
        JSON.stringify({ error: 'type and target parameters are required' }),
        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    const semrushUrl = new URL('https://api.semrush.com/')
    semrushUrl.searchParams.set('type', type)
    semrushUrl.searchParams.set('key', apiKey)
    semrushUrl.searchParams.set('database', database)
    semrushUrl.searchParams.set('display_limit', displayLimit)
    // domain vs phrase endpoint uses different param name
    if (type.startsWith('phrase_')) {
      semrushUrl.searchParams.set('phrase', target)
    } else {
      semrushUrl.searchParams.set('domain', target)
    }
    if (exportColumns) semrushUrl.searchParams.set('export_columns', exportColumns)

    const response = await fetch(semrushUrl.toString())
    const text = await response.text()

    // Check for SEMrush error responses
    if (text.startsWith('ERROR') || text.includes('ERROR 50')) {
      return new Response(
        JSON.stringify({ error: 'SEMrush API error', details: text }),
        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    const jsonData = csvToJson(text)

    return new Response(
      JSON.stringify({ rows: jsonData, total: jsonData.length }),
      { 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 semrush-proxy Edge Function is deployed and can be tested at /functions/v1/semrush-proxy?type=domain_organic&target=example.com&display_limit=5, returning a JSON array of organic keyword rows.

### 4. Build the keyword research dashboard

With the Edge Function working, build the keyword research dashboard in Lovable. The most valuable report for a first dashboard is the keyword overview (type=phrase_this) combined with a domain organic report (type=domain_organic). Ask Lovable to create a page with two sections: a keyword search section where you enter a phrase and get back volume, difficulty, CPC, and competitive density; and a domain section where you enter a domain and see its top organic keywords with their positions, traffic estimates, and keyword difficulty. Use shadcn/ui Table, Card, and Badge components for the layout. The difficulty score (0–100) should be visualized as a color-coded badge: green (0–39 Easy), yellow (40–69 Medium), red (70–100 Hard). Add a database selector dropdown so users can switch between US, UK, and other SEMrush regional databases.

**Expected result:** A working keyword research dashboard showing phrase overview data and domain organic rankings, with color-coded difficulty badges and regional database selection.

### 5. Implement API credit management with caching

SEMrush API calls consume credits that do not regenerate immediately. To prevent your dashboard from exhausting your daily credit allocation, implement response caching in Supabase. Create a Supabase table called semrush_cache with columns: cache_key (text, primary key), data (jsonb), and created_at (timestamptz). The cache key is a hash of the type + target + database parameters. Before calling the SEMrush API, the Edge Function checks if a non-expired cache entry exists for this cache key. Use a cache TTL of 12 hours — SEMrush updates its data roughly once per day for most metrics. On a cache miss, call SEMrush, store the result, and return it. On a cache hit, return the cached result with a note in the response indicating it is cached. This approach can reduce SEMrush API credit consumption by over 90% for dashboards that display the same domains and keywords throughout the day.

**Expected result:** Repeated requests for the same domain or keyword return cached results instantly without consuming SEMrush API credits. The Edge Function logs show cache hits and misses.

## Best practices

- Always store the SEMrush API key in Cloud → Secrets as SEMRUSH_API_KEY — never include it in frontend code or paste it into Lovable's chat
- Implement 12-hour response caching in Supabase to prevent credit exhaustion; SEMrush data updates at most once daily
- Always include a display_limit parameter — SEMrush defaults to 10 rows, and some endpoints allow up to 10,000, so be intentional about how many rows you fetch
- Add the display_offset parameter to implement server-side pagination for large datasets rather than fetching all rows at once
- Use export_columns to request only the specific columns your UI needs, which reduces response size and in some cases reduces credit cost
- Monitor your API credit balance via the semrush.com/api-analytics dashboard and implement logging in your Edge Function so you can see which queries are most expensive
- Test new dashboard features using SEMrush's low-cost domain_overview endpoint before building more expensive organic research reports
- The 'us' database is the most comprehensive for testing; add a database selector to your UI so users can query their target market's regional data

## Use cases

### Domain overview comparison tool

Enter two or more domains and compare their organic traffic estimates, keyword counts, backlink counts, and authority scores side by side. This gives sales teams a quick competitive context for prospects and helps content teams benchmark their site against competitors.

Prompt example:

```
Create a domain comparison tool where I can enter up to three domains and see them side by side. Call our /functions/v1/semrush-proxy Edge Function with the domain overview endpoint for each. Show a comparison table with organic keywords count, organic traffic estimate, paid keywords, and domain authority score for each domain.
```

### Keyword difficulty and volume research tool

Enter a seed keyword and retrieve volume, keyword difficulty, CPC, and competitive density. Display related keywords with their metrics so content writers can identify which terms to target in new content. Include a difficulty filter to surface only achievable keywords.

Prompt example:

```
Build a keyword research page where I enter a keyword and our SEMrush Edge Function returns the keyword overview data including search volume, keyword difficulty (0-100), CPC, and top 5 competing domains. Show the data in a card with a difficulty indicator badge (Easy/Medium/Hard based on KD score) and a related keywords table.
```

### Organic keyword gap analysis

Compare your site's organic keyword rankings against a competitor to find keywords the competitor ranks for in the top 10 that you do not rank for at all. These gap keywords represent content opportunities with proven search demand.

Prompt example:

```
Create a keyword gap analysis page that calls our SEMrush Edge Function with two domains. Use the phrase_organic endpoint to get keywords for each domain, then display keywords the competitor ranks for in positions 1-10 that our domain doesn't rank for. Sort by monthly search volume descending.
```

## Troubleshooting

### Edge Function returns 'ERROR 50 :: NOTHING FOUND' from SEMrush

Cause: SEMrush has no data for the queried domain or keyword in the selected regional database. This is common for small or new sites, or for regional databases that do not track the target domain.

Solution: Try querying the 'us' database first — it has the most comprehensive coverage. For domains that primarily operate in non-English markets, try the appropriate regional database. Newly registered domains with no organic rankings will return no data in any database.

### SEMrush API returns 'ERROR 10 :: WRONG KEY'

Cause: The SEMRUSH_API_KEY secret is incorrect, has expired, or was pasted with extra whitespace characters.

Solution: Go to Cloud → Secrets in your Lovable project, delete the SEMRUSH_API_KEY secret, and re-add it by copying the key fresh from semrush.com/api-analytics. Make sure there are no leading or trailing spaces in the value field.

### API calls succeed but return fewer rows than expected

Cause: The display_limit parameter defaults to 10 if not specified. For reports with many rows, you need to explicitly set a higher limit.

Solution: Add the display_limit parameter to your frontend request when you need more than 10 rows. The maximum varies by endpoint type but is typically 1,000–10,000. Note that higher row counts consume more API credits proportionally.

```
// Pass display_limit explicitly from the frontend
const response = await fetch(
  `/functions/v1/semrush-proxy?type=domain_organic&target=${domain}&display_limit=100`
)
```

### The CSV parsing returns empty objects or misaligned columns

Cause: Some SEMrush report types use different column separators or return data in different formats depending on the endpoint.

Solution: Log the raw response text from SEMrush before parsing to inspect the actual format. Most SEMrush reports use semicolons (;) as separators, but some newer endpoints may use commas. Adjust the csvToJson function's split character accordingly.

```
// Log raw response for debugging
console.log('SEMrush raw response:', text.substring(0, 200))
// Try comma separator if semicolon gives empty results
const separator = text.includes(';') ? ';' : ','
```

## Frequently asked questions

### How many API credits does a typical SEMrush dashboard query consume?

Credit costs vary by endpoint. The domain_organic report costs 10 units per row returned — so fetching 100 keywords for one domain costs 1,000 units. The phrase_this (keyword overview) endpoint costs 1 unit per row. Free accounts get 10 units per day, which limits you to 1 domain_organic row or 10 phrase_this rows. Pro plans include 3,000 units per day.

### Can SEMrush data be called directly from the React frontend without an Edge Function?

No. The SEMrush API requires your API key as a query parameter, which means any direct frontend call would expose the key in browser network requests. Anyone could view this in DevTools and use your key to drain your credits. Always proxy SEMrush calls through an Edge Function with the key stored in Cloud Secrets.

### Which SEMrush API endpoints are available without a paid plan?

The SEMrush API is not available on the free plan for most report types. You need at minimum the Pro plan ($129.95/month) to access domain_organic, phrase_this, and domain_ranks reports. Some limited API access is available via a free API key but with a 10 unit/day cap, which is only sufficient for testing single queries.

### How accurate is SEMrush's organic traffic data?

SEMrush estimates organic traffic based on keyword rankings and average click-through rates for each position. It is an estimate, not exact data. Actual traffic can vary significantly from SEMrush estimates depending on brand searches, navigational queries, and other factors. For your own site, Google Search Console provides exact data; SEMrush estimates are most useful for competitor analysis where you have no access to actual data.

### Can I use the SEMrush API to track keyword rankings over time?

The SEMrush API's domain_organic endpoint returns current rankings, not historical trends. To track rankings over time in your Lovable app, you need to schedule regular API calls (for example, daily via a scheduled Edge Function) and store the results in Supabase with timestamps. Your dashboard then queries Supabase for the historical data rather than hitting the SEMrush API for every view.

---

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