Integrate SEMrush with Bolt.new by proxying all API calls through a Next.js API route — the API key is passed as a query parameter and must never appear in client-side code. SEMrush API uses a units-based credit system on Guru and Business plans. Build keyword research, domain analytics, and competitor intelligence dashboards. Deploy to Netlify or Bolt Cloud before testing any webhook features.
Build an SEO and Competitive Intelligence Dashboard with SEMrush and Bolt.new
SEMrush is the most comprehensive digital marketing intelligence platform available via API. Unlike tools that focus on one dimension of SEO, SEMrush's API covers organic keyword rankings, paid advertising (PPC) data, backlink profiles, domain traffic estimates, content gap analysis, and historical trend data — all accessible through a single API key. This breadth makes it ideal for building multi-faceted marketing dashboards in Bolt.new that give product teams, agency clients, or startup founders a single view of their digital presence.
The integration pattern for SEMrush in Bolt follows the standard Bolt server-side proxy approach. SEMrush's API key is a powerful credential that grants access to every API endpoint under your subscription. Exposing it in client-side JavaScript — which is easy to do accidentally in Bolt's Vite-based default project — would allow anyone to deplete your API units. A Next.js API route running server-side keeps the key in `process.env.SEMRUSH_API_KEY` where it's never bundled into the browser. One quirk of SEMrush's API that requires special handling: responses come back as CSV text by default, not JSON. Your API route must parse this CSV response (using a split + map approach or Papa Parse) before returning clean JSON to the frontend.
SEMrush API access is available on Guru ($229.95/mo) and Business ($449.95/mo) plans. Each API call costs a certain number of units from your monthly allocation (typically 10,000-100,000 units/month depending on plan). Calls like keyword data (10 units/row) and domain overview (10 units/call) are relatively cheap; calls that return hundreds of keyword rows can cost more. Building dashboards with intentional caching and user-initiated fetching (rather than background polling) keeps unit costs manageable.
Integration method
SEMrush API authenticates via an API key passed as a query parameter in every request. Because the key grants full account access, it must be proxied through a Next.js API route — never called from client-side JavaScript. The API returns CSV-formatted responses by default, which your route parses and returns as JSON to the frontend. SEMrush API access requires a Guru or Business plan subscription, and each call deducts API units from your monthly allocation.
Prerequisites
- A SEMrush account on Guru or Business plan with API access enabled
- Your SEMrush API key from semrush.com/api/analytics/limits
- A Bolt.new project using Next.js (required for server-side API key proxying)
- Understanding of SEMrush's unit-based pricing (each API call costs units from your monthly allowance)
- A deployed Netlify or Bolt Cloud URL if you need OAuth-based authentication for the Reporting API
Step-by-step guide
Get Your SEMrush API Key and Understand Unit Costs
Get Your SEMrush API Key and Understand Unit Costs
Log in to your SEMrush account and go to semrush.com/api/analytics/limits or navigate to My Profile → API. Copy your API key from the Analytics API section — this is the key you'll use for keyword data, domain analytics, and backlink data. SEMrush uses a unit-based system: each plan comes with a monthly unit allocation (Guru gets approximately 3,000 units/day; the exact limit is visible on the API limits page). Each API call deducts units based on the number of rows returned. For example, fetching 10 keyword rows costs around 100 units; fetching domain overview costs approximately 10 units per call. Review the unit costs on semrush.com/api/analytics/v1/price before adding new endpoints to avoid unexpected unit depletion. During development, set row limits to 5-10 rows per request to minimize unit consumption while testing. One important note: SEMrush has two API systems — the Analytics API (keyword and domain data, covered here) and the Management API (project management, crawl audits). They use different authentication methods and endpoints. This guide covers the Analytics API, which is the most commonly used for dashboard building.
Set up SEMrush API integration in my Bolt.new app. Create a .env file with SEMRUSH_API_KEY=your_key_here as a placeholder. Create lib/semrush.ts that exports a callSemrushAPI function taking endpoint and params, making an authenticated GET request to https://api.semrush.com/ with the key appended as a query param, and returning the response text for parsing.
Paste this in Bolt.new chat
1// lib/semrush.ts2const SEMRUSH_BASE_URL = 'https://api.semrush.com';34export async function callSemrushAPI(5 params: Record<string, string | number>6): Promise<string> {7 const apiKey = process.env.SEMRUSH_API_KEY;8 if (!apiKey) {9 throw new Error('SEMRUSH_API_KEY environment variable is not set');10 }1112 const url = new URL(SEMRUSH_BASE_URL);13 url.searchParams.set('key', apiKey);14 Object.entries(params).forEach(([k, v]) => {15 url.searchParams.set(k, String(v));16 });1718 const response = await fetch(url.toString());19 const text = await response.text();2021 if (text.startsWith('ERROR')) {22 throw new Error(`SEMrush API error: ${text}`);23 }2425 return text;26}2728// Parse SEMrush CSV response to array of objects29export function parseSemrushCSV(csv: string): Record<string, string>[] {30 const lines = csv.trim().split('\r\n').filter(Boolean);31 if (lines.length < 2) return [];32 const headers = lines[0].split(';');33 return lines.slice(1).map((line) => {34 const values = line.split(';');35 return Object.fromEntries(headers.map((h, i) => [h, values[i] ?? '']));36 });37}Pro tip: SEMrush API responses use semicolon-delimited CSV by default (not comma-delimited). The parseSemrushCSV utility above handles this correctly. If you prefer JSON responses, add export_columns and export_format=json parameters — but not all endpoints support JSON output.
Expected result: The SEMrush API utility is ready. The parseSemrushCSV helper converts semicolon-delimited API responses into JavaScript objects that React components can render directly.
Create a Keyword Analytics API Route
Create a Keyword Analytics API Route
Build the Next.js API route that fetches keyword data from SEMrush. The `phrase_all` endpoint returns comprehensive keyword metrics for any search term: search volume, CPC, competition level, number of results, keyword difficulty, and SERP features (featured snippets, local packs, etc.). This route validates the incoming keyword parameter, calls the SEMrush API server-side (so the API key stays in process.env), parses the CSV response, and returns clean JSON to the browser. Input validation is especially important here for two reasons: first, every API call costs units from your SEMrush quota, so invalid requests that reach the API waste units needlessly; second, the keyword is inserted into the API request URL and should be encoded to prevent any injection issues. The route also handles SEMrush's distinct error format — errors come back as plain text starting with 'ERROR' rather than as HTTP error status codes, so standard response.ok checks are insufficient.
Create a Next.js API route at app/api/semrush/keywords/route.ts. Accept 'keyword' and optional 'database' query params (default database='us'). Use callSemrushAPI from lib/semrush.ts with type='phrase_all', phrase=keyword, database=database, export_columns='Ph,Nq,Cp,Co,Nr,Kd,Fk', display_limit=10. Parse the CSV response with parseSemrushCSV and return a JSON array with mapped field names: keyword, volume, cpc, competition, results, difficulty, features.
Paste this in Bolt.new chat
1// app/api/semrush/keywords/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { callSemrushAPI, parseSemrushCSV } from '@/lib/semrush';45export async function GET(request: NextRequest) {6 const keyword = request.nextUrl.searchParams.get('keyword');7 const database = request.nextUrl.searchParams.get('database') ?? 'us';89 if (!keyword || keyword.trim().length === 0) {10 return NextResponse.json({ error: 'keyword parameter is required' }, { status: 400 });11 }1213 try {14 const csv = await callSemrushAPI({15 type: 'phrase_all',16 phrase: keyword.trim(),17 database,18 export_columns: 'Ph,Nq,Cp,Co,Nr,Kd',19 display_limit: 10,20 });2122 const rows = parseSemrushCSV(csv);23 const keywords = rows.map((row) => ({24 keyword: row['Keyword'] ?? row['Ph'] ?? '',25 volume: parseInt(row['Search Volume'] ?? row['Nq'] ?? '0'),26 cpc: parseFloat(row['CPC'] ?? row['Cp'] ?? '0'),27 competition: parseFloat(row['Competition'] ?? row['Co'] ?? '0'),28 results: parseInt(row['Number of Results'] ?? row['Nr'] ?? '0'),29 difficulty: parseInt(row['Keyword Difficulty'] ?? row['Kd'] ?? '0'),30 }));3132 return NextResponse.json({ keyword, database, keywords });33 } catch (error) {34 const message = error instanceof Error ? error.message : 'Unknown error';35 return NextResponse.json({ error: message }, { status: 500 });36 }37}Pro tip: SEMrush column headers in the CSV response use either the full name ('Search Volume') or the short code ('Nq') depending on the endpoint version. The route above handles both with fallback logic to ensure compatibility.
Expected result: Visiting /api/semrush/keywords?keyword=react+tutorial returns a JSON array with volume, CPC, competition, and difficulty for the keyword and related variations.
Add Domain Overview and Competitor Analysis
Add Domain Overview and Competitor Analysis
Extend the integration with domain-level analytics. The SEMrush `domain_organic` endpoint returns the top organic keywords for any domain with their ranking positions, estimated traffic share, and the specific URLs that rank. The `domain_ranks` endpoint returns aggregate metrics: total organic keywords, estimated organic traffic, traffic cost (what that traffic would cost in PPC), and paid traffic data. Together, these endpoints enable a competitive analysis dashboard where you can compare your domain against competitors by traffic volume, keyword overlap, and content gaps. The implementation follows the same server-side proxy pattern: all calls go through Next.js API routes with the API key in environment variables. For the domain analytics use case, you'll likely want to fetch data for multiple domains simultaneously — use Promise.all to run the SEMrush calls in parallel and reduce total latency. Be aware that parallel calls to the SEMrush API still each consume units independently.
Create two more API routes: app/api/semrush/domain-overview/route.ts (uses domain_ranks type, returns organic keywords count, organic traffic estimate, traffic cost, paid keywords, and paid traffic) and app/api/semrush/domain-keywords/route.ts (uses domain_organic type, returns top 10 organic keywords for the domain with position, volume, traffic %, and URL). Accept 'domain' and 'database' query params.
Paste this in Bolt.new chat
1// app/api/semrush/domain-overview/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { callSemrushAPI, parseSemrushCSV } from '@/lib/semrush';45export async function GET(request: NextRequest) {6 const domain = request.nextUrl.searchParams.get('domain');7 const database = request.nextUrl.searchParams.get('database') ?? 'us';89 if (!domain) {10 return NextResponse.json({ error: 'domain is required' }, { status: 400 });11 }1213 const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/$/, '');1415 try {16 const csv = await callSemrushAPI({17 type: 'domain_ranks',18 domain: cleanDomain,19 database,20 });2122 const rows = parseSemrushCSV(csv);23 const row = rows[0] ?? {};2425 return NextResponse.json({26 domain: cleanDomain,27 organicKeywords: parseInt(row['Organic Keywords'] ?? '0'),28 organicTraffic: parseInt(row['Organic Traffic'] ?? '0'),29 trafficCost: parseFloat(row['Organic Cost'] ?? '0'),30 paidKeywords: parseInt(row['Adwords Keywords'] ?? '0'),31 paidTraffic: parseInt(row['Adwords Traffic'] ?? '0'),32 });33 } catch (error) {34 const message = error instanceof Error ? error.message : 'Unknown error';35 return NextResponse.json({ error: message }, { status: 500 });36 }37}Pro tip: Strip protocol prefixes (https://) and trailing slashes from domain inputs before sending to SEMrush. The API accepts bare domains like 'example.com' — adding https:// causes it to return empty results rather than an error.
Expected result: The domain overview route returns organic keyword count, estimated monthly traffic, and traffic cost for any domain. These metrics update in the dashboard when a user submits a domain search.
Deploy and Configure Environment Variables
Deploy and Configure Environment Variables
SEMrush API calls work in Bolt's WebContainer during development because WebContainers support all outbound HTTPS requests. Test a few keyword searches in the preview to confirm the data is coming back correctly before deploying. When ready to deploy: connect to Netlify via Settings → Applications or click Publish to Bolt Cloud. The critical step after deploying is setting `SEMRUSH_API_KEY` in your hosting platform's environment variables dashboard. For Netlify: go to Site Configuration → Environment Variables and add the key with your actual SEMrush API key value. For Bolt Cloud: use the Secrets panel before clicking Publish. After adding environment variables, trigger a fresh deploy — Netlify does this automatically when env vars change; Bolt Cloud requires re-clicking Publish. One important note about Bolt's WebContainer limitations: since the preview cannot receive incoming connections, you cannot register SEMrush report webhooks to the Bolt preview URL. Any webhook endpoints (for scheduled SEMrush reports or alerts) must use your deployed domain. Register those webhook URLs only after the site is live at a stable URL.
Add a caching layer to my SEMrush API routes using a simple in-memory Map. Cache keyword results for 24 hours using the keyword + database as the cache key. Add a cache status header to responses so I can see if results are coming from cache or a fresh API call.
Paste this in Bolt.new chat
1// lib/semrush-cache.ts2const cache = new Map<string, { data: unknown; expires: number }>();34export function getCached(key: string): unknown | null {5 const entry = cache.get(key);6 if (!entry) return null;7 if (Date.now() > entry.expires) {8 cache.delete(key);9 return null;10 }11 return entry.data;12}1314export function setCached(key: string, data: unknown, ttlMs = 86_400_000): void {15 cache.set(key, { data, expires: Date.now() + ttlMs });16}1718// Usage in API route:19// const cacheKey = `keywords:${keyword}:${database}`;20// const cached = getCached(cacheKey);21// if (cached) return NextResponse.json(cached, { headers: { 'X-Cache': 'HIT' } });22// ... fetch from SEMrush ...23// setCached(cacheKey, result);24// return NextResponse.json(result, { headers: { 'X-Cache': 'MISS' } });Pro tip: The in-memory cache resets when the serverless function cold-starts. For persistent caching across requests in production, use Redis (Upstash works well with Bolt/Vercel) or store results in your database. For development and low-traffic dashboards, the in-memory Map is sufficient.
Expected result: Deployed app returns keyword and domain data. Repeated requests for the same keyword return cached results with X-Cache: HIT header, avoiding unnecessary SEMrush unit consumption.
Common use cases
Keyword Research Dashboard
Build an internal keyword research tool that fetches search volume, keyword difficulty, CPC, and SERP feature data for any keyword or list of keywords. Marketing teams use this to identify which keywords to target in content, PPC campaigns, and link building without switching between tools.
Build a keyword research dashboard using SEMrush API. Create a search form where I can enter a keyword. On submit, call /api/semrush/keywords with the keyword and database='us'. Display the results showing search volume, keyword difficulty (0-100), CPC, competition level, and number of results. Format the difficulty as a color-coded badge: green (0-30 easy), yellow (31-60 medium), red (61-100 hard).
Copy this prompt to try it in Bolt.new
Domain Organic Traffic Overview
Show a domain's estimated organic traffic, number of organic keywords, traffic cost equivalent, and competitive positioning. Used by agencies to benchmark client sites against competitors and track progress over time using SEMrush's traffic estimates.
Create a domain SEO overview page using SEMrush API. Let me enter any domain and fetch its organic traffic estimate, number of organic keywords, traffic cost, and SERP features from the SEMrush domain-overview endpoint. Show results in a grid of metric cards with the domain name, country flag for US database, and the month/year of the data. Proxy all API calls through /api/semrush/domain-overview.
Copy this prompt to try it in Bolt.new
Competitor Keyword Gap Analysis
Identify keywords that competitors rank for but your site does not. Enter your domain and a competitor's domain to see their top organic keywords, estimated traffic per keyword, and ranking positions. This surfaces quick-win content opportunities without requiring manual analysis.
Build a competitor keyword analysis tool using SEMrush API. I enter my domain and a competitor's domain. Fetch the top 20 organic keywords for the competitor domain from SEMrush's domain-organic endpoint. Show each keyword with its position, search volume, traffic percentage, and URL. Also show a summary comparing estimated total organic traffic for both domains side by side.
Copy this prompt to try it in Bolt.new
Troubleshooting
API route returns 'SEMrush API error: ERROR 50 :: NOTHING_FOUND'
Cause: The keyword or domain has no data in SEMrush's database for the specified country database. This is common for very new domains, highly localized terms not indexed in the selected database, or misspelled queries.
Solution: Try a different database parameter (e.g., database='uk' or database='au'). For keywords, try broader variations. Verify the domain exists and has some organic traffic history — newly launched sites have no SEMrush data until SEMrush crawls and indexes them (typically a few weeks).
1// Handle empty results gracefully2if (!csv || csv.trim() === '' || csv.startsWith('ERROR 50')) {3 return NextResponse.json({ keyword, database, keywords: [], note: 'No data found' });4}API route returns 'SEMrush API error: ERROR 120 :: WRONG_KEY'
Cause: The SEMRUSH_API_KEY environment variable is missing, contains extra whitespace, or is the wrong key (SEMrush has separate keys for Analytics API vs Management API).
Solution: Check the .env file for extra spaces around the key value. In production hosting, verify the key is set in the Environment Variables panel and the site has been redeployed after adding it. Confirm you're using the Analytics API key from semrush.com/api/analytics/limits, not any other key.
1// Trim the API key to handle copy-paste whitespace2const apiKey = process.env.SEMRUSH_API_KEY?.trim();3if (!apiKey) throw new Error('SEMRUSH_API_KEY not configured');CSV parsing returns empty objects — all field values are undefined
Cause: SEMrush changed the column header names in the API response, or the exported CSV uses full column names rather than the abbreviated codes the parser expects.
Solution: Log the raw CSV response in the API route using console.log(csv.split('\n')[0]) to see the actual header row. Update the field mapping in your parser to match the actual headers returned.
1// Debug: log actual headers2console.log('SEMrush CSV headers:', csv.split('\r\n')[0]);3// Adjust your field mapping based on what you seeBest practices
- Always proxy SEMrush API calls through a Next.js API route — the API key grants access to your entire SEMrush account and must never appear in client-side code
- Set display_limit=10 during development to minimize unit consumption; switch to higher limits in production once the integration is working
- Cache API responses for 24 hours — SEMrush updates its database weekly, so hourly polling wastes units without providing fresher data
- Strip protocol prefixes and trailing slashes from domain inputs before sending to the API — semrush.com expects bare domains like 'example.com'
- Handle the ERROR 50 (nothing found) response as a valid empty result rather than an error — common for niche keywords or new domains
- Monitor your remaining API units at semrush.com/api/analytics/limits and add a units-remaining display in your admin dashboard to catch depletion before it affects users
- Use Promise.all for parallel domain lookups when comparing multiple competitors, but be aware each parallel call consumes units independently
Alternatives
Ahrefs has deeper backlink data and a stronger organic search focus, while SEMrush covers PPC and content intelligence that Ahrefs doesn't provide.
Moz offers a more accessible API with a free tier and lower pricing, making it the best starting point for teams who can't justify SEMrush's price for API access.
Serpstat provides keyword and backlink data at a significantly lower price point, suitable for smaller budgets that find SEMrush Guru plan pricing prohibitive.
SpyFu specializes in competitor keyword history and PPC analysis with a more affordable pricing model than SEMrush's API plans.
Frequently asked questions
Which SEMrush plan do I need for API access?
SEMrush API access requires the Guru plan ($229.95/mo) or Business plan ($449.95/mo). The Pro plan does not include API access. Each plan includes a monthly unit allocation — Guru includes approximately 3,000 units/day. Check your actual unit allocation at semrush.com/api/analytics/limits after subscribing.
Why does SEMrush return CSV instead of JSON?
SEMrush's original Analytics API was designed in an era where CSV was the standard data transfer format for bulk data. Most endpoints support a semicolon-delimited CSV format by default. Some newer endpoints accept an export_format=json parameter, but the legacy endpoints only return CSV. The parseSemrushCSV utility in this guide handles parsing correctly.
Can I call the SEMrush API directly from my Bolt app's browser code?
No. Your SEMrush API key grants access to your entire account and must never be exposed in client-side code. Always proxy calls through a Next.js API route where the key lives in process.env.SEMRUSH_API_KEY. If you accidentally expose the key, regenerate it immediately in your SEMrush account settings.
Does SEMrush API work in Bolt's WebContainer during development?
Yes. The Next.js API route makes outbound HTTPS calls to api.semrush.com, which is fully supported in Bolt's WebContainer. You can test keyword searches and domain lookups in the Bolt preview before deploying. The only WebContainer limitation that affects SEMrush specifically is that incoming webhook connections don't work in the preview — but SEMrush's Analytics API doesn't use webhooks for standard data retrieval.
How do I deploy my SEMrush dashboard to Netlify?
In Bolt, click Settings → Applications and connect Netlify via OAuth. Click Publish to deploy. After deploying, go to the Netlify dashboard → Site Configuration → Environment Variables and add SEMRUSH_API_KEY with your API key value. Netlify automatically redeploys after adding environment variables. Test by visiting your deployed URL and running a keyword search.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation