DEV Community

Cover image for Your First SerpApi Integration in JavaScript — From Hello World to Production
Jordan Sterchele
Jordan Sterchele

Posted on

Your First SerpApi Integration in JavaScript — From Hello World to Production

The gaps the docs don’t fill: authentication, JSON response structure, credit efficiency, and the most common errors with their actual fixes.


SerpApi turns messy search engine result pages into clean, structured JSON. One API call instead of a scraper, a proxy pool, and a parser you maintain forever. If you’re building an AI agent, an SEO tool, a price tracker, or a research product that needs search data — SerpApi is the practical choice.

Getting your first call working takes under ten minutes. Getting it production-ready takes longer, and the documentation gaps are specific. This post covers both.


Authentication — The Right Way

SerpApi uses a single API key passed as a query parameter. No OAuth, no Bearer tokens, no SDK initialization ceremony.

const params = new URLSearchParams({
  engine: 'google',
  q: 'javascript developer advocate',
  api_key: process.env.SERPAPI_API_KEY // Never hardcode this
});

const response = await fetch(`https://serpapi.com/search?${params}`);
const data = await response.json();
Enter fullscreen mode Exit fullscreen mode

Your API key lives in an environment variable. Never in source code. Never in a public repository.

Getting your key: Sign up at serpapi.com → Dashboard → Your API Key. You get 100 free searches per month on the free plan. The key works immediately.


Your First Search Call

async function search(query, options = {}) {
  const params = new URLSearchParams({
    engine: 'google',
    q: query,
    api_key: process.env.SERPAPI_API_KEY,
    num: options.num || 10,           // Results per page (max 100)
    hl: options.language || 'en',     // Language
    gl: options.country || 'us',      // Country
    ...options
  });

  const res = await fetch(`https://serpapi.com/search?${params}`);

  if (!res.ok) {
    const error = await res.json();
    throw new Error(`SerpApi error: ${error.error}`);
  }

  return res.json();
}

// Usage
const results = await search('web scraping api javascript');
console.log(results.organic_results[0]); // First organic result
Enter fullscreen mode Exit fullscreen mode

Understanding the JSON Response

The response structure is the thing nobody explains clearly. Here’s the map:

{
  "search_metadata": {
    "id": "...",              // Unique search ID for this call
    "status": "Success",
    "total_time_taken": 1.23  // Seconds
  },
  "search_parameters": {
    "engine": "google",
    "q": "your query",
    "google_domain": "google.com"
  },
  "organic_results": [        // ← This is what you usually want
    {
      "position": 1,
      "title": "Result title",
      "link": "https://...",
      "snippet": "Description text...",
      "displayed_link": "example.com › path"
    }
  ],
  "related_searches": [...],  // Related query suggestions
  "pagination": {             // For fetching next pages
    "next": "https://serpapi.com/search?...",
    "next_page_token": "..."
  },
  // Sometimes present, depending on the query:
  "answer_box": {},           // Featured snippet
  "knowledge_graph": {},      // Knowledge panel
  "ai_overview": {},          // Google's AI Overview
  "ads": [],                  // Paid results
  "shopping_results": [],     // Shopping carousel
  "images_results": []        // Image results
}
Enter fullscreen mode Exit fullscreen mode

The most important thing: Not all fields are always present. Always check for existence before accessing:

// Wrong — crashes if answer_box is absent
const answer = results.answer_box.answer;

// Right — defensive access
const answer = results.answer_box?.answer ?? null;
const organicResults = results.organic_results ?? [];
Enter fullscreen mode Exit fullscreen mode

The Location Error Everyone Hits

The most common bug filed on SerpApi’s public roadmap: your search returns only images, FAQs, or AI snippets instead of organic results.

The cause: Google is returning a different SERP layout for your query/location combination. The fix is usually one of three things:

Fix 1 — Set explicit location:

const params = new URLSearchParams({
  engine: 'google',
  q: 'your query',
  location: 'Austin, Texas, United States', // Explicit location
  google_domain: 'google.com',
  gl: 'us',
  hl: 'en',
  api_key: process.env.SERPAPI_API_KEY
});
Enter fullscreen mode Exit fullscreen mode

Fix 2 — Use the Locations API to find the right location string:

// Find valid location strings
const locationRes = await fetch(
  `https://serpapi.com/locations.json?q=Austin&limit=5`
);
const locations = await locationRes.json();
console.log(locations[0].name); // "Austin, Texas, United States"
Enter fullscreen mode Exit fullscreen mode

Fix 3 — Check what’s actually in the response:

const results = await search('your query');

// Debug: see what fields are present
const fields = Object.keys(results);
console.log('Available fields:', fields);

// If no organic_results, something is wrong with query/location
if (!results.organic_results?.length) {
  console.log('No organic results. Present fields:', fields);
  console.log('Search params used:', results.search_parameters);
}
Enter fullscreen mode Exit fullscreen mode

Managing Credits Efficiently

SerpApi charges per search. At scale, credit efficiency is a real concern. Here’s how to manage it:

Check your remaining credits before high-volume operations:

async function getAccountInfo() {
  const res = await fetch(
    `https://serpapi.com/account?api_key=${process.env.SERPAPI_API_KEY}`
  );
  const account = await res.json();
  return {
    searchesUsed: account.searches_this_month,
    creditsRemaining: account.plan_searches_left,
    planMonthlyLimit: account.plan_monthly_searches
  };
}

const { creditsRemaining } = await getAccountInfo();
if (creditsRemaining < 100) {
  console.warn('Low credits — pausing batch operation');
}
Enter fullscreen mode Exit fullscreen mode

Cache results to avoid duplicate calls:

const cache = new Map();

async function cachedSearch(query, ttlMs = 3600000) { // 1 hour TTL
  const cacheKey = query.toLowerCase().trim();
  const cached = cache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < ttlMs) {
    return cached.data; // Return cached result — no credit used
  }

  const data = await search(query);
  cache.set(cacheKey, { data, timestamp: Date.now() });
  return data;
}
Enter fullscreen mode Exit fullscreen mode

Use pagination instead of multiple searches:

async function* paginateResults(query, maxPages = 5) {
  let nextToken = null;
  let page = 0;

  while (page < maxPages) {
    const params = {
      engine: 'google',
      q: query,
      api_key: process.env.SERPAPI_API_KEY,
      num: 100, // Max per page — most credits-efficient
    };

    if (nextToken) params.next_page_token = nextToken;

    const results = await search(query, params);
    yield results.organic_results ?? [];

    nextToken = results.pagination?.next_page_token;
    if (!nextToken) break;
    page++;
  }
}

// Use it
for await (const page of paginateResults('javascript tutorials', 3)) {
  console.log(`Got ${page.length} results`);
}
Enter fullscreen mode Exit fullscreen mode

Using SerpApi in an AI Agent

SerpApi added llms.txt in April 2026 — they’re intentionally building for the AI agent use case. Here’s how to wire it into a LangChain workflow:

import { SerpAPI } from "@langchain/community/tools/serpapi";
import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
import { pull } from "langchain/hub";

// SerpApi as a LangChain tool
const searchTool = new SerpAPI(process.env.SERPAPI_API_KEY, {
  location: "United States",
  hl: "en",
  gl: "us",
});

const llm = new ChatOpenAI({ model: "gpt-4", temperature: 0 });
const prompt = await pull("hwchase17/openai-functions-agent");

const agent = await createOpenAIFunctionsAgent({
  llm,
  tools: [searchTool],
  prompt,
});

const executor = new AgentExecutor({ agent, tools: [searchTool] });

const result = await executor.invoke({
  input: "What are the latest developments in JavaScript developer tooling?",
});

console.log(result.output);
Enter fullscreen mode Exit fullscreen mode

For RAG pipelines, use SerpApi to retrieve fresh search results before passing to your vector store:

async function searchAndStore(query, vectorStore) {
  const results = await search(query);
  const documents = (results.organic_results ?? []).map(r => ({
    pageContent: `${r.title}\n${r.snippet}`,
    metadata: { url: r.link, position: r.position }
  }));

  await vectorStore.addDocuments(documents);
  return documents.length;
}
Enter fullscreen mode Exit fullscreen mode

Error Handling Reference

async function safeSearch(query, options = {}) {
  try {
    return await search(query, options);
  } catch (err) {
    // Rate limited — back off and retry
    if (err.message.includes('429')) {
      await new Promise(r => setTimeout(r, 60000));
      return search(query, options);
    }

    // Invalid API key
    if (err.message.includes('Invalid API key')) {
      throw new Error('Check SERPAPI_API_KEY in your environment');
    }

    // Out of credits
    if (err.message.includes('Your account has run out')) {
      throw new Error('SerpApi credits exhausted — upgrade plan or wait for reset');
    }

    throw err;
  }
}
Enter fullscreen mode Exit fullscreen mode

What to Read Next

If you’re building on SerpApi and hitting a wall — location errors, credit management, AI integration patterns — drop a comment. I’ll answer.


Disclosure: This post was produced by AXIOM, an agentic developer advocacy workflow powered by Anthropic’s Claude, operated by Jordan Sterchele. Human-reviewed before publication.

Top comments (0)