DEV Community

Cover image for Building an AI Sales Agent with OpenAI and the Outscraper Google Maps API
Michael Angelo Chavez
Michael Angelo Chavez

Posted on

Building an AI Sales Agent with OpenAI and the Outscraper Google Maps API

An AI sales agent with OpenAI becomes useful when it has real Google Maps business data to work with.

A model can help score leads, explain sales signals, and draft outreach, but it should not guess which local businesses are active, contactable, reviewed, or missing a website.

That is where the Outscraper Google Maps API fits.

In this tutorial, we will build a developer workflow that starts with GPT for prototyping prompts and local search logic, then moves to Outscraper for production Google Maps business data.

The workflow is:

GPT prototype → Outscraper Google Maps API → OpenAI lead scoring → human-reviewed outreach

What We Are Building

We are building a simple AI sales agent that can take a local search query, collect business records, score the strongest lead signals, and prepare outreach drafts for human review.

The agent will not send messages automatically.

The goal is to help developers build a safer sales research workflow where the AI agent prepares the work, but a person reviews the final outreach.

The architecture looks like this:

Local search query
   ↓
Outscraper Google Maps API
   ↓
Structured business records
   ↓
OpenAI lead scoring
   ↓
Outreach angle
   ↓
Human review
Enter fullscreen mode Exit fullscreen mode

The agent behavior comes from the loop: collect business data, score each record, explain the strongest signal, draft the outreach angle, and wait for human approval before sending anything.

Example input:

Find dental clinics in Austin that may need appointment booking or reputation management help.
Enter fullscreen mode Exit fullscreen mode

Example output:

Business: Example Dental Clinic
Lead score: 8/10
Strongest signal: High review count, rating below 4.0, and appointment-related complaints
Outreach angle: Booking friction and patient experience
Human-reviewed opener: I noticed your clinic has strong review volume, but several reviews mention appointment scheduling issues.
Enter fullscreen mode Exit fullscreen mode

Why an AI Sales Agent Needs Google Maps Business Data

An AI sales agent needs more than a company name.

For local lead generation, the agent should reason from fields that show whether a business is active, reachable, and relevant to the offer.

Useful Google Maps business data includes:

  • business name
  • category
  • address
  • city
  • website
  • phone number
  • rating
  • review count
  • business status
  • opening hours
  • local demand signals
  • missing website signal
  • weak contact path
  • customer review patterns

This matters because two businesses in the same category can need different outreach.

A dental clinic with 300 reviews, a 3.7 rating, and repeated booking complaints is not the same as a clinic with 12 reviews, a complete website, and no visible friction.

The first business may be a better fit for appointment workflow software or reputation management.

The second business may not need the same message.

OpenAI helps with reasoning.

The Outscraper Google Maps API provides the local business data layer.

Together, they can create an AI lead generation agent that scores real businesses using real fields.

Prototype the Sales Logic in GPT First

Do not start by building the full backend.

Start by testing the sales-agent logic inside GPT.

This is the prototype layer.

Take 10 local business records and paste them into GPT.

Then test prompts like:

I will paste local business records from Google Maps.

Score each business as a possible lead for this offer:
"We help dental clinics reduce missed calls and improve appointment booking workflows."

For each record, check:
1. Category fit
2. Website presence
3. Phone availability
4. Rating
5. Review count
6. Business status
7. Visible demand signal
8. Missing or weak contact path

Return:
Business name | Lead score | Strongest signal | Why it matters | Outreach angle

Use only the data provided. Do not invent missing facts.
Enter fullscreen mode Exit fullscreen mode

This small prototype answers important questions before you write production code:

  • Does the prompt identify useful sales signals?
  • Does the scoring logic make sense?
  • Does the model avoid inventing details?
  • Does the outreach angle connect to the business data?
  • Does the output help a salesperson decide what to review next?

GPT is useful here because you can test logic fast.

But GPT is not the production data layer.

It helps you design the reasoning.

It does not replace repeatable Google Maps data collection.

Move From GPT Prototype to Outscraper Production Workloads

Once the prototype works on a small sample, move the workflow into production.

That is where Outscraper becomes important.

Use the Google Maps Scraper when you want to test bulk extraction, export CSV files, or explore Google Maps data without building the backend first.

Use the Google Maps API when you want to connect Google Maps business data directly into your app, CRM, backend job, or AI sales agent.

The bridge is simple:

GPT = prototype prompts and search logic
Outscraper Google Maps API = production local business data
OpenAI = lead scoring and outreach drafts
Human review = final approval
Enter fullscreen mode Exit fullscreen mode

This keeps the system practical.

GPT tests the agent logic.

Outscraper collects structured business records.

OpenAI scores and drafts from the records.

A person approves the final message.

Set Up the Node.js Project

Create a new project:

mkdir ai-sales-agent-outscraper
cd ai-sales-agent-outscraper
npm init -y
npm install openai dotenv
Enter fullscreen mode Exit fullscreen mode

In package.json, add module support:

{
  "type": "module"
}
Enter fullscreen mode Exit fullscreen mode

Create a .env file:

OPENAI_API_KEY=your_openai_api_key
OUTSCRAPER_API_KEY=your_outscraper_api_key
OPENAI_MODEL=your_preferred_openai_model
Enter fullscreen mode Exit fullscreen mode

Do not hardcode API keys in your source code.

Fetch Google Maps Business Data With Outscraper

Create a file called agent.js.

import "dotenv/config";
import OpenAI from "openai";

const OUTSCRAPER_API_KEY = process.env.OUTSCRAPER_API_KEY;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_MODEL = process.env.OPENAI_MODEL;

if (!OUTSCRAPER_API_KEY) {
  throw new Error("Missing OUTSCRAPER_API_KEY in .env");
}

if (!OPENAI_API_KEY) {
  throw new Error("Missing OPENAI_API_KEY in .env");
}

if (!OPENAI_MODEL) {
  throw new Error("Missing OPENAI_MODEL in .env");
}

const openai = new OpenAI({
  apiKey: OPENAI_API_KEY
});

async function searchGoogleMapsBusinessData(query, limit = 10) {
  const url = new URL("https://api.outscraper.com/google-maps-search");

  url.searchParams.set("query", query);
  url.searchParams.set("limit", String(limit));
  url.searchParams.set("async", "false");
  url.searchParams.set(
    "fields",
    [
      "query",
      "name",
      "category",
      "full_address",
      "city",
      "site",
      "phone",
      "rating",
      "reviews",
      "business_status",
      "working_hours"
    ].join(",")
  );

  const response = await fetch(url, {
    method: "GET",
    headers: {
      "X-API-KEY": OUTSCRAPER_API_KEY
    }
  });

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`Outscraper request failed: ${response.status} ${body}`);
  }

  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

The search query should include both category and location.

Good examples:

dental clinics, Austin, TX, USA
restaurants, Miami, FL, USA
roofing companies, Phoenix, AZ, USA
law firms, Chicago, IL, USA
Enter fullscreen mode Exit fullscreen mode

For production workloads, avoid vague searches like:

businesses near me
Enter fullscreen mode Exit fullscreen mode

The agent needs specific local search logic.

Use category, city, state, and country.

Normalize the Business Records

API responses can include nested arrays depending on query settings.

Create a helper function that turns the response into a clean list.

function flattenOutscraperResults(rawResponse) {
  const data = rawResponse?.data;

  if (!Array.isArray(data)) {
    return [];
  }

  const flatRecords = data.flatMap((item) => {
    if (Array.isArray(item)) return item;
    return [item];
  });

  return flatRecords
    .filter(Boolean)
    .map((place) => ({
      name: place.name || "",
      category: place.category || "",
      address: place.full_address || "",
      city: place.city || "",
      website: place.site || "",
      phone: place.phone || "",
      rating: place.rating ?? null,
      reviews: place.reviews ?? 0,
      businessStatus: place.business_status || "",
      query: place.query || ""
    }));
}
Enter fullscreen mode Exit fullscreen mode

Keep the payload focused before sending it to OpenAI.

The model does not need every field.

It needs the fields that support lead scoring:

  • category fit
  • contactability
  • website presence
  • rating
  • review count
  • business status
  • local demand signal

Score Leads With OpenAI

Now create a function that sends normalized records to OpenAI.

async function scoreLeadsWithOpenAI({ businesses, offer }) {
  const prompt = `
You are an AI sales research assistant.

Your job is to score local businesses as possible leads using only the data provided.

Offer:
${offer}

Business records:
${JSON.stringify(businesses, null, 2)}

Return one section per business with:
- Business name
- Lead score from 1 to 10
- Strongest signal
- Weakest signal
- Why this business may or may not fit the offer
- Outreach angle
- One short opening line for human review

Scoring rules:
+2 if the business has a website
+2 if the business has a phone number
+2 if the category matches the offer
+1 if reviews are above 50
+1 if rating is below 4.0 and reviews are above 50
-3 if the business appears closed
-2 if website and phone are both missing

Safety rules:
- Do not invent missing data.
- Do not claim you audited the business deeply.
- Do not write a full email unless there is a clear signal.
- Keep outreach specific, helpful, and non-pushy.
- Mark every draft as "needs human review."
`;

  const response = await openai.responses.create({
    model: OPENAI_MODEL,
    input: prompt
  });

  return response.output_text;
}
Enter fullscreen mode Exit fullscreen mode

This creates a lead scoring layer.

The agent is not just ranking businesses.

It is explaining why the business may be worth reviewing.

That explanation matters because a salesperson needs to understand the signal before sending a message.

Run the First AI Sales Agent

Add the main function.

async function runAgent() {
  const query = "dental clinics, Austin, TX, USA";
  const offer =
    "We help dental clinics reduce missed calls and improve appointment booking workflows.";

  console.log("Searching Google Maps business data...");
  const rawResponse = await searchGoogleMapsBusinessData(query, 10);

  const businesses = flattenOutscraperResults(rawResponse);

  if (businesses.length === 0) {
    console.log("No businesses found. Try a more specific category and location.");
    return;
  }

  console.log(`Found ${businesses.length} business records.`);
  console.log("Scoring leads with OpenAI...");

  const leadAnalysis = await scoreLeadsWithOpenAI({
    businesses,
    offer
  });

  console.log("\nAI Sales Agent Output:\n");
  console.log(leadAnalysis);
}

runAgent().catch((error) => {
  console.error(error);
  process.exit(1);
});
Enter fullscreen mode Exit fullscreen mode

Run the file:

node agent.js
Enter fullscreen mode Exit fullscreen mode

Expected output pattern:

Business name: Example Dental Care
Lead score: 8/10
Strongest signal: Phone number, website, high review count, and rating below 4.0
Weakest signal: No review text available in this record
Why this fits: The review count suggests active local demand, while the rating may indicate patient experience friction.
Outreach angle: Appointment booking and missed-call follow-up
Opening line: I noticed your clinic has strong review volume, and patient experience may be worth reviewing based on the rating pattern.
Status: needs human review
Enter fullscreen mode Exit fullscreen mode

Draft Outreach With Human Review

After scoring leads, you can generate a short outreach draft.

Do not let the agent send it automatically.

Create a separate function.

async function draftHumanReviewedOutreach({ leadSummary, offer }) {
  const prompt = `
Write a short outreach draft for human review.

Offer:
${offer}

Lead summary:
${leadSummary}

Rules:
- Maximum 90 words.
- Mention only signals from the lead summary.
- Do not invent facts.
- Do not use pressure language.
- Do not claim deep research.
- End with a soft question.
- Add the label: "Needs human review before sending."
`;

  const response = await openai.responses.create({
    model: OPENAI_MODEL,
    input: prompt
  });

  return response.output_text;
}
Enter fullscreen mode Exit fullscreen mode

A bad outreach draft sounds like this:

Hi, we help businesses grow. Are you interested?
Enter fullscreen mode Exit fullscreen mode

A stronger human-reviewed draft sounds like this:

Hi, I noticed your clinic has strong review volume, and the rating pattern may be worth reviewing. We help dental clinics improve missed-call follow-up and appointment booking workflows. Would it be useful if I shared a few ideas?
Enter fullscreen mode Exit fullscreen mode

The better draft works because it connects to a visible business signal.

It does not pretend to know more than the data shows.

Add Better Local Search Logic

The first version uses one query.

For production workloads, use repeatable search logic.

For example:

const searches = [
  "dental clinics, Austin, TX, USA",
  "dental clinics, Dallas, TX, USA",
  "dental clinics, Houston, TX, USA"
];
Enter fullscreen mode Exit fullscreen mode

You can loop through each query and collect records.

async function collectFromMultipleLocalSearches(searches, limitPerSearch = 10) {
  const allBusinesses = [];

  for (const search of searches) {
    const rawResponse = await searchGoogleMapsBusinessData(search, limitPerSearch);
    const businesses = flattenOutscraperResults(rawResponse);

    allBusinesses.push(...businesses);
  }

  const uniqueByNameAndPhone = new Map();

  for (const business of allBusinesses) {
    const key = `${business.name}-${business.phone}-${business.city}`;
    uniqueByNameAndPhone.set(key, business);
  }

  return [...uniqueByNameAndPhone.values()];
}
Enter fullscreen mode Exit fullscreen mode

This lets the agent support a real local prospecting workflow.

Examples:

  • same niche across many cities
  • many categories in one city
  • one state split into multiple local markets
  • repeated searches for monthly lead refreshes

This is where the workflow becomes transactional.

The reader is not only learning what an AI sales agent is.

They are building the first version of the data pipeline.

Production Checklist

Before using this in a real workflow, add production controls.

Use this checklist:

API keys stored in environment variables
Specific category + city search queries
Field limits to reduce payload size
Error handling for API requests
Retry logic for temporary failures
Deduplication before scoring
Lead scoring rules documented
Human review before outreach
No automatic sending
No invented data in prompts
CRM export only after validation
Logging for each search run
Cost checks for API and model usage
Enter fullscreen mode Exit fullscreen mode

The most important rule:

The AI sales agent can prepare outreach.
A human should approve outreach.
Enter fullscreen mode Exit fullscreen mode

That keeps the workflow useful without turning it into an automatic spam system.

Where Outscraper Fits in the Production Workflow

Outscraper is the production data layer for this AI sales agent.

The Google Maps Scraper is useful when you want bulk extraction, CSV export, no-code testing, or a fast way to inspect Google Maps business data before writing code.

The Outscraper Google Maps API is useful when you want to connect Google Maps business data directly into your backend, CRM, sales dashboard, or agent workflow.

Use the scraper page when you want to test the data.

Use the API when you want repeatable production workloads.

The production path looks like this:

Local search logic
   ↓
Outscraper Google Maps API
   ↓
Structured business records
   ↓
OpenAI lead scoring
   ↓
Outreach draft
   ↓
Human review
   ↓
CRM or sales workflow
Enter fullscreen mode Exit fullscreen mode

The agent becomes stronger when the data fields are specific.

Instead of asking OpenAI to guess, send fields like:

business name
category
website
phone number
rating
review count
business status
address
city
Enter fullscreen mode Exit fullscreen mode

That is enough to create a useful first lead scoring pass.

Final Thoughts

Building an AI sales agent with OpenAI is not just about writing prompts.

The agent needs a data layer.

For local business prospecting, the Outscraper Google Maps API can provide structured Google Maps business data that OpenAI can score, summarize, and turn into human-reviewed outreach drafts.

Start with GPT to prototype the prompt logic.

Use a small sample to test lead scoring.

Check whether the outreach angle connects to real fields.

Then move to Outscraper for production workloads.

Use the Google Maps Scraper for bulk extraction and data testing.

Use the Google Maps API for app, backend, CRM, or agent integration.

The final workflow is:

GPT prototype
   ↓
Outscraper Google Maps API
   ↓
OpenAI lead scoring
   ↓
Human-reviewed outreach
Enter fullscreen mode Exit fullscreen mode

When you are ready to move from GPT prompt testing to a production AI sales agent, start with structured Google Maps business data from Outscraper.

Use the Google Maps Scraper for bulk extraction, or connect the Google Maps API to your app, agent, CRM, or backend workflow.

Then send the structured records into OpenAI for lead scoring, research summaries, and human-reviewed outreach drafts.

Try Outscraper Free

Top comments (0)