# Business Profile Scraper – Contacts & Socials (`simple.actors/business-profile`) Actor

Scrape a full business profile from a name or website: Google rating, reviews, category and address, every email and phone from the company site, plus social accounts with follower counts and latest posts.

- **URL**: https://apify.com/simple.actors/business-profile.md
- **Developed by:** [Simple Actors](https://apify.com/simple.actors) (community)
- **Categories:** Business, Lead generation, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 business profiles

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Business Profile Scraper – Contacts & Socials

Scrape a complete business profile from nothing but a name and a town, or from a
website you already have. One request returns the **Google place record**
(rating, review count, category, price band, address, phone), **every email and
phone number published on the company's own website**, and **its social media
accounts actually read** — live follower counts, display names, verification and
the three latest posts. It is built for lead generation, CRM enrichment, local
market research and competitor monitoring: one row per business instead of five
datasets to join.

Here is a real row, abridged — the [full field reference](#output) is below:

```
Dishoom Covent Garden · 4.7 out of 29,573 reviews · Indian restaurant · £20–60
12 Upper St Martin's Ln, London WC2H 9FB · +44 20 7420 9320

press@dishoom.com · 12 phone numbers · contact form · dishoom.com

instagram  Dishoom ✓            3 posts   latest 28 Aug, 525 likes
tiktok     Dishoom     68,401   3 posts   latest 27 Aug, 1,027 likes
youtube    Dishoom      2,010   1 post    latest 29 May
twitter    link only
linkedin   Dishoom     34,998   3 posts   latest 28 Aug, 10 reactions
```

### Features

- **Google place record without a browser** — rating, review count, category,
  price band, street address, phone and whether Google showed it open, read from
  the knowledge panel in Google's served HTML.
- **Every contact on the website, not just one** — *every* usable email and
  *every* valid phone number the site publishes, each with the page it was found
  on. One real lookup returned **twelve numbers, one for each of that
  restaurant's locations**, plus its press address and its contact-form URL.
- **Phone numbers parsed properly** — E.164 format, national formatting, line
  type (`MOBILE`, `FIXED_LINE`, `TOLL_FREE`) and country.
- **Social accounts read, not just linked** — Instagram, TikTok, X (Twitter),
  YouTube, Facebook and LinkedIn accounts are opened and read for
  follower/subscriber count, display name, verification and three latest posts
  with text, date and likes.
- **Every other platform still returned as a link** — Yelp, Pinterest and the
  rest keep their URL and handle.
- **Messaging links captured** — WhatsApp, Telegram, Messenger, Skype, Viber and
  LINE, separately from accounts to follow.
- **Postal address from the site's own markup** — JSON-LD, microdata, an
  `address` element or plain text, split into street/city/region/postcode/country.
- **Skip the search when you already have the URL** — pass websites directly and
  only the contact and social steps run.
- **Bounded cost per business** — cap how many social accounts get read.
- **Runs never fail on a bad target.** A business with no knowledge panel, an
  unreachable site or a private social account is recorded on its row; the rest
  of the row still arrives.

### Input

```json
{
  "queries": ["Dishoom Covent Garden London"],  // one business per line, "name + place"
  "includeContacts": true,                       // read the site for emails, phones, address, social links
  "includeSocialProfiles": true,                 // open each social account for followers and latest posts
  "maxSocialProfiles": 3                         // ceiling on the slow, per-account step (0–6)
}
```

A name and a place. The more specific the place, the more reliably Google
returns a knowledge panel rather than a list of similar businesses — a bare name
in a big city often matches the wrong branch.

Already know the website? Skip the search:

```json
{
  "websites": ["dishoom.com"]   // no place record: there is nothing to search for
}
```

| Field | Type | Default | What it does |
|---|---|---|---|
| `queries` | array | — | Businesses, as "name + place". |
| `websites` | array | — | Websites you already have. No place record, since nothing is searched for. |
| `startUrls` | array | — | The same as `websites`, in the URL editor. |
| `includeContacts` | boolean | `true` | Read the website for emails, phones, address, contact form and social links. |
| `includeSocialProfiles` | boolean | `true` | Read each social account for followers and latest posts. The slow part. |
| `maxSocialProfiles` | integer | `3` | Ceiling on the slow part, per business. Range 0–6. |

### Output

One row per business. Every field it can emit is listed below — a field is
`null` (or `[]`, or `{}`) when that thing genuinely was not found, never because
a step was skipped silently.

```json
{
  "type": "business",
  "query": "Dishoom Covent Garden London",
  "name": "Dishoom Covent Garden",
  "website": "https://www.dishoom.com/covent-garden/",

  "placeFound": true,
  "rating": 4.7,
  "reviewCount": 29570,
  "category": "Indian restaurant",
  "priceBand": "£20–60",
  "openStatus": "Closed",
  "placeAddress": "12 Upper St Martin's Ln, London WC2H 9FB",
  "placePhone": "+44 20 7420 9320",

  "email": "press@dishoom.com",
  "phone": "+442074209320",
  "emails": [{ "email": "press@dishoom.com", "source": "text", "foundOn": "https://www.dishoom.com/say-hello/" }],
  "phones": [{ "number": "+442074209320", "formatted": "+44 20 7420 9320", "raw": "020 7420 9320",
               "type": "FIXED_LINE", "country": "GB", "source": "tel", "foundOn": "…" }],
  "contacts": [{ "name": null, "role": null, "email": "press@dishoom.com", "phone": "+442074209320",
                 "source": "ranked", "sourceUrl": "…" }],
  "contactFormUrl": "https://www.dishoom.com/say-hello/",
  "websiteAddress": { "street": "12 Upper St. Martin's Lane", "city": "London", "region": null,
                      "postalCode": null, "country": "England",
                      "raw": "12 Upper St. Martin's Lane, London, England", "source": "jsonld" },
  "description": "Bombay-style café and bar…",
  "logo": "https://…",

  "social": {
    "tiktok": { "url": "https://www.tiktok.com/@dishoom_uk", "handle": "dishoom_uk", "enriched": true,
                "followers": 68399, "displayName": "Dishoom", "verified": true,
                "latestPosts": [{ "url": "…", "text": "Chef Rufino cooks…",
                                  "postedAt": "2026-08-27T15:09:00.000Z", "likes": 963 }] },
    "linkedin": { "url": "https://uk.linkedin.com/company/dishoom", "handle": "dishoom", "enriched": true,
                 "followers": 34998, "displayName": "Dishoom", "verified": null,
                 "latestPosts": [{ "url": "…", "text": "Southport Flower Show…",
                                   "postedAt": "2026-08-28T09:31:13.485Z", "likes": 10 }] }
  },
  "messaging": { "whatsapp": "https://wa.me/…" },
  "socialPlatforms": ["instagram", "tiktok", "youtube", "twitter", "linkedin"],

  "notes": [],
  "scrapedAt": "2026-08-29T08:20:11.000Z"
}
```

#### The row

| field | what it is |
|---|---|
| `type` | `"business"`, or `"error"` for a lookup that could not run at all |
| `query` | what you asked for, verbatim — match rows back to your list with this |
| `name` | the business's name, as Google's panel titles it. The website's own name is used only when Google had none: a site's `og:site_name` is often a tagline, and one real deli's is "NYC's oldest deli" |
| `website` | the business's own site. Aggregators, delivery platforms and social networks are excluded, so this is the site the business runs |
| `scrapedAt` | when the run read it, ISO 8601 UTC |
| `notes` | anything that limited this row — no panel, no website, a failed sub-read. Empty on a clean lookup |

#### From Google's knowledge panel

| field | what it is |
|---|---|
| `placeFound` | `true` when Google returned a panel. `false` means the search matched nothing structured — usually too vague a query — and every field below is `null` |
| `rating` | rating out of 5, from the panel itself and not from the highest review snippet on the page (on one real result those disagreed, 4.2 against 3.4) |
| `reviewCount` | how many Google reviews that rating is out of |
| `category` | Google's own category, e.g. `"Indian restaurant"` |
| `priceBand` | in the local currency — `"$10–20"`, `"£20–60"`, `"Rs 10,000+"` |
| `openStatus` | whether Google showed it open or closed at the moment of the search |
| `placeAddress` | the address on the panel |
| `placePhone` | the phone on the panel — often present when the website publishes none |

#### From the business's own website

| field | what it is |
|---|---|
| `email` | the best address to write to, or `null` — plenty of businesses publish none |
| `phone` | the best number: the website's if it has one, otherwise the panel's |
| `emails[]` | every usable address found, best first — `{ email, source, foundOn }`. `source` is how it was found (`mailto`, `jsonld`, `cloudflare`, `obfuscated`, `text`…) and `foundOn` the page |
| `phones[]` | every valid number — `{ number, formatted, raw, type, country, source, foundOn }`. `number` is E.164; `type` is what the numbering plan says (`MOBILE`, `FIXED_LINE`, `TOLL_FREE`…) |
| `contacts[]` | named contact points where the site grouped them — `{ name, role, email, phone, source, sourceUrl }`. The first always carries the best email and phone together |
| `contactFormUrl` | a page with a form a person could write into, for businesses that publish one instead of an address |
| `websiteAddress` | the postal address as the site publishes it — `{ street, city, region, postalCode, country, raw, source }`. Sometimes fuller than Google's. `source` is `jsonld`, `microdata`, `address-element` or `text` |
| `description` | how the business describes itself, from its site's metadata |
| `logo` | absolute URL to its logo |
| `messaging` | WhatsApp, Telegram, Messenger, Skype, Viber, LINE links — ways to start a conversation rather than accounts to follow. `{}` when the site links none |

#### The social accounts

`social` is keyed by platform (`instagram`, `tiktok`, `twitter`, `youtube`,
`facebook`, `linkedin`, `pinterest`, …). `socialPlatforms` lists those keys, for
filtering.

| field | what it is |
|---|---|
| `url` | the account, always present |
| `handle` | the account's handle |
| `enriched` | `true` when the account itself was read. `false` means the link was found but not read — either that platform has no Actor here, or it fell outside `maxSocialProfiles` |
| `followers` | live follower or subscriber count. `null` is sometimes a real answer — see below |
| `displayName` | the account's own display name |
| `verified` | `true`/`false` where the platform says, `null` where it does not |
| `latestPosts[]` | up to three, each `{ url, text, postedAt, likes }`. **`postedAt` is always an ISO 8601 string**, normalised here — the underlying Actors variously return ISO strings, unix milliseconds and unix seconds |
| `note` / `error` | why something is missing, when it is |

### How to use

#### From Apify Console

1. Open the Actor and click **Try for free** / **Start**.
2. Put one business per line into **Businesses**, as a name and a place —
   `Dishoom Covent Garden London`. Or paste websites you already have into
   **Websites** instead.
3. Leave **Read the website for contacts** and **Enrich the social accounts** on
   for a full profile. Turn the social step off for a fast place-plus-contacts
   sweep.
4. Click **Start** and wait — expect roughly a minute per business.
5. Open the **Dataset** tab and export as JSON, CSV, Excel or feed it to an API.

#### From the API

```bash
curl -X POST "https://api.apify.com/v2/acts/simple.actors~business-profile/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "queries": ["Dishoom Covent Garden London", "Bungalow restaurant east village new york"],
    "maxSocialProfiles": 3
  }'
```

Or with the JavaScript client:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('simple.actors/business-profile').call({
    queries: ['Dishoom Covent Garden London'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].emails, items[0].social);
```

### Use cases

- **Lead generation and list building** — turn a list of company names into
  verified emails, phone numbers and contact-form URLs ready for outreach.
- **CRM and sales data enrichment** — fill in missing rating, address, phone and
  social handles against accounts you already hold, matched back by `query`.
- **Local market and competitor research** — pull rating, review count, category
  and price band across every business in a category and town.
- **Influencer and partnership screening** — see follower counts and posting
  recency across a business's Instagram, TikTok, YouTube, X and Facebook in one
  row before you reach out.
- **Directory and marketplace seeding** — build a business directory with place
  data, contact routes and social links from a name list alone.

### Usage notes

Simple, transparent pay-per-result pricing — no subscriptions.

**A row carries everything that could be found, never less because one part
failed.** A private Instagram account costs that one section and nothing else;
the rating, the emails and the other accounts still arrive. Whatever was missed
is named in `notes` or on the section it belongs to.

**Some nulls are real answers, not failures:**

| | why |
|---|---|
| `email: null` | plenty of businesses publish none — the place `phone` is usually the way in |
| `social.instagram.followers: null` | Instagram publishes no follower count to a logged-out reader |
| `social.facebook.followers: null` | that count comes from a different mode of the Facebook Actor than the one called here |
| `placeFound: false` | Google had no knowledge panel for the search — try adding the street or town |

Each carries a `note` saying which.

**It is not fast.** Each business is a Google search, a website crawl and up to
three social account reads — about a minute per business, most of it spent
waiting on other people's servers. Businesses are read one at a time on purpose:
each already fans out into several runs, and doing several at once turns that
into a burst the target sites read as a flood.

#### How it works

It is assembled from Actors that already exist, each of which stays the one
place its own platform's quirks are handled:

1. **The place** comes from Google's knowledge panel. *Not* Google Maps — a Maps
   place page serves a shell and fetches the place by XHR, so reading it needs a
   browser. The same business searched on Google returns nearly the whole place
   record in the served HTML, for one request.
2. **The contacts** come from `website-contacts`, which finds the site's contact
   page for itself and knows a share button from a social account.
3. **The socials** come from `instagram-profile-posts`, `tiktok-profile-posts`,
   `x-profile-posts`, `youtube-channel-videos`, `facebook-page-posts` and
   `linkedin-company-posts` — one run each, for the accounts the website turned
   out to link.

Platforms without an Actor here (Yelp, Pinterest…) keep their link, which is
still the useful half.

### What it costs

**This Actor calls six others, and their fees are billed to you on top of its
own.** That is how Apify works when one Actor calls another, and it is worth
seeing plainly before you run a large list:

| what runs | its own price | a typical business |
|---|---|---|
| this Actor | $0.0050 per business | $0.0050 |
| `website-contacts` | $0.0010 per site + $0.0005 per page | $0.0025 (3 pages) |
| `instagram-profile-posts` | $0.0030 per profile | $0.0030 |
| `linkedin-company-posts` | $0.0010 per company | $0.0010 |
| `tiktok-profile-posts` | $0.0005 per profile | $0.0005 |
| `youtube-channel-videos` | $0.0005 per video | $0.0015 (3 videos) |
| | **total** | **~$0.0135** |

**`maxSocialProfiles` caps how many of those social fees you can pay**, whatever
a business links. At the default of 3 you are billed for at most three accounts,
so adding LinkedIn to the list of readable platforms widens the choice rather
than automatically widening the bill.

**You only pay for what is actually there.** A business with no TikTok is not
charged for one. `maxSocialProfiles` is the ceiling on the priciest part, and
setting `includeSocialProfiles: false` removes it entirely — a place record plus
website contacts is then about $0.0075 a business. `includeContacts: false`
drops it to $0.0050.

Assembling the same row yourself costs more, not less: the social and contact
Actors charge you the same, and a Google place record has to come from a Maps
scraper priced around $0.0070 a place. What this Actor removes is that cost and
the joining — five datasets keyed differently, matched back to your list by hand.

### Failures

**The run always succeeds.** Only a bug in this Actor fails a run.

| `error` | what happened |
|---|---|
| `bad_input` | nothing usable was given |
| `search_failed` | Google could not be reached after three tries |

### FAQ

**Is scraping business data from Google and company websites legal?**
This Actor reads only pages that are public to any logged-out visitor — no
login, no cookies, no paywall. That said, how you may use the data depends on
where you are and what you do with it: check the platform's ToS, and check your
local rules on personal data and marketing consent before using scraped emails
or phone numbers for outreach.

**Do I need a Google API key or a Maps API key?**
No. The place record is read from Google's own search results HTML, so there is
no key, no quota and no browser to run.

**Why is `placeFound` false for my business?**
The search was too vague for Google to return a knowledge panel, so it showed a
list of similar businesses instead. Add the street, neighbourhood or town to the
query — `Bungalow restaurant east village new york` rather than `Bungalow`.

**Why is a follower count `null` when the account clearly has followers?**
Instagram publishes no follower count to a logged-out reader, and Facebook page
follower counts come from a mode of the Facebook Actor this one does not call.
Both cases carry a `note` on the account saying so, so you can tell a withheld
number from a failed read.

**Can I run this on a list of thousands of businesses?**
Yes — pass them all in `queries`. They are read one at a time on purpose, since
each business already fans out into several runs, so plan for roughly a minute
each. Lower `maxSocialProfiles`, or set `includeSocialProfiles: false`, to make
a large sweep much faster.

**Does it support LinkedIn?**
Yes. A company page is opened and read like the other platforms, so you get its
follower count, name and latest posts with their reaction counts. Personal
LinkedIn profiles cannot be read, because LinkedIn serves those only behind a
login.

**What about Yelp, Pinterest and the rest?**
Their URL and handle are returned whenever the website links one, but the
account itself is not opened, so there is no follower count or post list for it.
Six platforms are read in full: Instagram, TikTok, X, YouTube, Facebook and
LinkedIn.

# Actor input Schema

## `queries` (type: `array`):

One business per line, written as a name and a place: "Dishoom Covent Garden London", "Bungalow restaurant east village new york". The more specific the place, the more reliably Google returns a knowledge panel (rating, reviews, address, phone) rather than a list of similar businesses — a bare name in a big city often matches the wrong branch. Each row comes back tagged with the query you typed, so you can match results to your list.

## `websites` (type: `array`):

Use this instead when you already know a business's website — one domain or URL per line, e.g. "dishoom.com". The Google lookup is skipped entirely, since there is nothing to search for, so you get the website contacts and social accounts but no rating, review count or place address.

## `startUrls` (type: `array`):

Exactly the same as "Websites you already have", in the URL-list editor — useful when your URLs come from a file, another Actor or a Google Sheet rather than being typed in.

## `includeContacts` (type: `boolean`):

On by default. Crawls the business's own site for every email address, every valid phone number, a postal address, a contact form and its social account links. Those links are what the social step then enriches, so turning this off leaves only the Google place record.

## `includeSocialProfiles` (type: `boolean`):

On by default. For each social account found on the website, opens that account and reads its live follower count, display name, verification and three latest posts. Instagram, TikTok, X (Twitter), YouTube, Facebook and LinkedIn are read in full; every other platform (Yelp, Pinterest…) keeps its link and handle, which is still the useful half. LinkedIn company pages are read; personal LinkedIn profiles cannot be, because LinkedIn serves those only behind a login.

This is the slow and expensive part: each platform is a separate run, so a business with four accounts takes about a minute. Turn it off for a fast place-plus-contacts sweep.

## `maxSocialProfiles` (type: `integer`):

A ceiling on the slow part, per business — 3 by default, up to 6. Accounts on platforms that can actually be read (Instagram, TikTok, X, YouTube, Facebook, LinkedIn) are tried first, so a low number is spent well. Accounts beyond the limit are still returned as links with their handles. Set to 0 to collect links only.

## Actor input object example

```json
{
  "queries": [
    "Dishoom Covent Garden London"
  ],
  "websites": [],
  "startUrls": [],
  "includeContacts": true,
  "includeSocialProfiles": true,
  "maxSocialProfiles": 3
}
```

# Actor output Schema

## `businesses` (type: `string`):

Place, contacts and social accounts, one row each.

## `raw` (type: `string`):

Every field of every row.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "Dishoom Covent Garden London"
    ],
    "websites": [],
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("simple.actors/business-profile").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": ["Dishoom Covent Garden London"],
    "websites": [],
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("simple.actors/business-profile").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "Dishoom Covent Garden London"
  ],
  "websites": [],
  "startUrls": []
}' |
apify call simple.actors/business-profile --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,simple.actors/business-profile"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/MGSC4d7hG21nOr5YO/builds/L5Jz9xEOBmw7BKUDL/openapi.json
