> Enrich.chat gives AI agents verified work emails and mobiles. To use it from an agent, read https://enrich.chat/agents.md . This is the Markdown version of https://enrich.chat/integrations/google-sheets/

# Lead enrichment in Google Sheets with Apps Script, Zapier, Make or n8n

Add a work email, a mobile and a status to every row of a sheet. Run a script you paste in, a Zapier, Make or n8n workflow, or an AI agent. Credits are used only when a lookup finds something.

By Enrich.chat Editorial. Updated October 15, 2026.

**Short answer:** To enrich leads in Google Sheets, call a contact data API from Apps Script with [UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app), or from a Zapier, Make or n8n workflow that watches for new rows. Enrich.chat's `GET /v1/person` takes a LinkedIn URL, an email, or a name and company, and returns a work email and a mobile, each with a status. A work email found uses 10 credits, a mobile 250, and a miss 0.

## Key takeaways

- Google's IMPORTDATA reads CSV and TSV, and IMPORTXML reads XML, HTML, CSV and TSV. Enrich.chat replies in JSON, so a script, a workflow tool or an agent has to make the call.
- Apps Script stops a run after 6 minutes. The script on this page saves each row as it returns and skips finished rows, so you click again to continue.
- Zapier needs a paid plan for Webhooks, from $19.99 a month on yearly billing. Make's Free plan has 1,000 credits a month, and n8n counts a whole run as one execution.
- A work email found uses 10 credits, a mobile 250, and a miss 0. A sheet of 1,000 rows with both fits the Scale plan.

## Lead enrichment that stays in your spreadsheet

- **Results in columns.** Five columns hold the work email, its status, the mobile, its line type and the outcome of each row.
- **One script, no add-on.** Paste it into Extensions, Apps Script. There is no marketplace install and no seat to buy.
- **Safe to run twice.** Rows with a result are skipped, so a second run continues where the first one stopped.
- **Credits only on matches.** 10 credits per work email, 250 per mobile. A row with no match uses 0.
- **Zapier, Make and n8n.** Each one can watch for a new row, send the same GET request and update the row.
- **Or ask your agent.** Tell an agent to use www.enrich.chat on your rows. It reads enrich.chat/agents.md and starts.

## From a column of LinkedIn URLs to checked contacts

1. **Add the columns.** Row 1 holds headers such as `linkedin_url`, `email`, `name` and `company`. One identifier per row is enough.
2. **Run the lookup.** The script calls `GET /v1/person` for each row without a result and saves every reply as it comes back.
3. **Filter on the status.** Keep verified emails, treat catch_all as risky, and skip rows marked not_found.

## How do you enrich leads in Google Sheets?

You call a contact data API once for each row and write the reply into new columns. Google's own import functions will not do it. [IMPORTDATA](https://support.google.com/docs/answer/3093335) reads CSV and TSV, and [IMPORTXML](https://support.google.com/docs/answer/3093342) reads XML, HTML, CSV, TSV and feeds, while every Enrich.chat endpoint replies in JSON. You need a script, a workflow tool or an AI agent.

All four routes send the same request to `GET /v1/person`. The [person enrichment API](https://enrich.chat/api/person-enrichment/) takes a LinkedIn URL, an email, or a name and company website. It returns a work email and a mobile, each with a status.

**Figure 1: Sheet columns in, five result columns out.** A row with a LinkedIn URL, an email, or a name and company goes to one GET request to Enrich.chat. The script writes five result columns: work_email, email_status (verified, catch_all or unknown), mobile, mobile_line (mobile or unknown) and enrich_result (found, not_found, no_identifier or error_400). A miss uses 0 credits.

*What goes into the sheet and what the script writes back. Column names match the script below. Source: [Enrich.chat agent docs](https://enrich.chat/agents.md).*

Put one identifier in each row. A LinkedIn URL is the strongest key, and it is only a lookup key: Enrich.chat never logs in to LinkedIn. Personal addresses such as Gmail are not looked up, and company names are not supported yet, so give a company's website, such as acme.com. See the [LinkedIn URL to contact API](https://enrich.chat/api/linkedin-to-contact/).

| Route | Runs when | Best for |
| --- | --- | --- |
| Apps Script menu | You click a menu item | A list you enrich once, or in batches |
| Apps Script with a timer | On a schedule you set | A growing sheet, with no other tool |
| Zapier, Make or n8n | A new row appears | Teams that already run workflows |
| An AI agent | You ask in chat | Quick jobs and one-off lists |

## How do you enrich a Google Sheet with Apps Script?

Paste one script into the sheet and add a menu. For each row, the script calls `GET /v1/person`, saves five result columns and stops cleanly at a limit. We did not run it in Google Sheets. We checked its logic in Node against stand-ins for the Apps Script services, using methods from Google's [UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app) and [Spreadsheet service](https://developers.google.com/apps-script/reference/spreadsheet) references.

1. Name the tab **Leads**. In row 1, add the headers you have: `linkedin_url`, `email`, `name`, and `company` (a website such as acme.com).
2. Choose **Extensions > Apps Script** to open a [script bound to the sheet](https://developers.google.com/apps-script/guides/bound). Paste the script below and save.
3. Optional: with a plan key, add a script property named `ENRICH_KEY` under **Project Settings**. [Google's Properties guide](https://developers.google.com/apps-script/guides/properties) lists the steps. The free tier needs no key.
4. Reload the sheet and choose **Enrich.chat > Enrich rows without a result**, the [custom menu](https://developers.google.com/apps-script/guides/menus) the script adds. Authorize the script when Google asks. The `@OnlyCurrentDoc` comment limits the request to this file, per [Google's authorization guide](https://developers.google.com/apps-script/guides/services/authorization), and Google may show an unverified app warning. Read the permissions first.

```
/** @OnlyCurrentDoc */
const SHEET_NAME = 'Leads';
const ENDPOINT = 'https://enrich.chat/v1/person';
const FIELDS = 'email,mobile'; // use 'email' alone to skip mobiles
const PAUSE_MS = 250;          // limits are listed at enrich.chat/docs
const MAX_MS = 5 * 60 * 1000;  // Apps Script stops a run at 6 minutes
const OUT = [
  'work_email', 'email_status', 'mobile', 'mobile_line', 'enrich_result',
];
const WHY = {
  401: 'the key is not valid',
  402: 'no free lookups or credits left',
  429: 'requests came too fast',
  503: 'the service is busy',
  time: 'time limit reached',
};

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Enrich.chat')
    .addItem('Enrich rows without a result', 'enrichSheet')
    .addToUi();
}

function enrichSheet() {
  const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
  if (!sheet) return say('Name the tab ' + SHEET_NAME);
  const props = PropertiesService.getScriptProperties();
  const key = props.getProperty('ENRICH_KEY');
  const headers = key ? { Authorization: 'Bearer ' + key } : {};
  const started = Date.now();

  // Row 1 holds the headers. Add the result columns if they are missing.
  const width = sheet.getLastColumn();
  let head = sheet.getRange(1, 1, 1, width).getValues()[0].map(String);
  let first = head.indexOf(OUT[0]) + 1; // 1-based column of work_email
  if (!first) {
    first = head.length + 1;
    head = head.concat(OUT);
    sheet.getRange(1, first, 1, OUT.length).setValues([OUT]);
  } else if (OUT.some((name, i) => head[first - 1 + i] !== name)) {
    return say('Put these headers side by side: ' + OUT.join(', '));
  }
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;
  const count = lastRow - 1;
  const rows = sheet.getRange(2, 1, count, head.length).getValues();
  // Keep the + in mobile numbers by formatting that column as plain text.
  sheet.getRange(2, first + 2, count, 1).setNumberFormat('@');
  const at = (name) => head.indexOf(name);
  const col = {
    li: at('linkedin_url'), em: at('email'), nm: at('name'), co: at('company'),
  };
  const done = first - 2 + OUT.length; // index of enrich_result

  let found = 0, missed = 0, stop = '';
  for (let i = 0; i < rows.length && !stop; i++) {
    const row = rows[i];
    if (row[done]) continue; // already has a result
    if (row.slice(0, first - 1).every((v) => v === '')) continue; // empty row
    if (Date.now() - started > MAX_MS) { stop = 'time'; break; }

    const params = identify(row, col);
    let out = ['', '', '', '', 'no_identifier'];
    if (params) {
      params.fields = FIELDS;
      const r = lookup(params, headers);
      if (r.code === 200 && r.body.found) {
        const w = r.body.work_email || {};
        const m = r.body.mobile || {};
        const vals = [w.value, w.status, m.value, m.line];
        out = vals.map((v) => v || '').concat('found');
        found++;
      } else if (r.code === 200) {
        out = ['', '', '', '', 'not_found']; // a miss uses 0 credits
        missed++;
      } else if (WHY[r.code]) {
        stop = String(r.code); // leave this row blank, resume later
        break;
      } else {
        // 400: fix the row, then clear this cell to retry
        out = ['', '', '', '', 'error_' + r.code];
      }
      Utilities.sleep(PAUSE_MS);
    }
    // Save each row as it returns, so a timeout loses nothing.
    sheet.getRange(i + 2, first, 1, OUT.length).setValues([out]);
  }
  let msg = 'Found ' + found + ', not found ' + missed + '.';
  if (stop) msg += ' Stopped: ' + WHY[stop] + '. Run again to continue.';
  say(msg);
}

// LinkedIn URL first, then email, then name plus company website.
function identify(row, c) {
  const get = (i) => (i >= 0 && row[i] ? String(row[i]).trim() : '');
  if (get(c.li)) return { linkedin_url: get(c.li) };
  if (get(c.em)) return { email: get(c.em) };
  if (get(c.nm) && get(c.co)) return { name: get(c.nm), company: get(c.co) };
  return null;
}

function lookup(params, headers) {
  const query = Object.keys(params)
    .map((k) => k + '=' + encodeURIComponent(params[k]))
    .join('&');
  const options = { headers: headers, muteHttpExceptions: true };
  let res, body, code;
  for (let attempt = 0; attempt < 2; attempt++) {
    res = UrlFetchApp.fetch(ENDPOINT + '?' + query, options);
    code = res.getResponseCode();
    try { body = JSON.parse(res.getContentText()); } catch (e) { body = {}; }
    if ((code === 429 || code === 503) && attempt === 0) {
      // wait for retry_after seconds, then try once more
      Utilities.sleep(Math.min(body.retry_after || 5, 20) * 1000);
    } else {
      break;
    }
  }
  return { code: code, body: body };
}

function say(msg) {
  console.log(msg);
  try { SpreadsheetApp.getActive().toast(msg, 'Enrich.chat'); } catch (e) {}
}
```

The script adds any missing result headers and one run looks like this.

**Figure 2: What one run of the Apps Script does.** The script reads the sheet once, skips rows that already have a result, calls GET /v1/person for each remaining row, saves that row at once, pauses, and moves to the next row. It stops on a 402 or 401, retries a 429 or 503 once and then stops, and quits after 5 minutes because Google caps a run at 6 minutes. Running it again resumes, because finished rows are skipped.

*The loop behind the menu command. The 6 minute cap comes from [Google's Apps Script quotas](https://developers.google.com/apps-script/guides/services/quotas), last updated September 3, 2026. Error codes come from the [Enrich.chat agent docs](https://enrich.chat/agents.md).*

- **It writes values, not formulas.** Google says [every use of a custom function is a separate call to Apps Script](https://developers.google.com/apps-script/guides/sheets/functions), and it recalculates when a referenced cell changes. Plain text means editing the sheet never spends credits.
- **It saves each row as it returns.** Google stops a script after [6 minutes](https://developers.google.com/apps-script/guides/services/quotas), so this one quits at 5 minutes. Rows with an `enrich_result` are skipped, so click again to continue. Clear that cell to retry a row.
- **It stops on a 402, 401, 429 or 503.** A 402 means the free lookups, the plan's credits or the plan itself ran out. A 429 or 503 is retried once after `retry_after` seconds. The failed row stays blank for the next run.
- **It asks only for what you need.** A mobile uses 250 credits and a work email 10, so change `FIELDS` to `'email'` to skip mobiles. The mobile column is set to plain text, so numbers stay as the API sent them.

### How do you run it on new rows automatically?

Add a time-driven trigger: open **Triggers** in the editor, choose **Add Trigger**, then pick `enrichSheet` and a schedule. Per Google's [installable triggers guide](https://developers.google.com/apps-script/guides/triggers/installable), these can call services that need authorization and run as often as every minute. A simple trigger such as `onEdit` cannot, because [simple triggers cannot use services that need authorization](https://developers.google.com/apps-script/guides/triggers). A run with no new rows makes no API calls. Trigger runtime is capped at [90 minutes a day on gmail.com and 6 hours on Workspace](https://developers.google.com/apps-script/guides/services/quotas).

## How do you enrich Google Sheets rows with Zapier, Make or n8n?

Each tool watches the sheet, sends the same GET request and updates the row. Only the module names change.

### How do you set it up in Zapier?

1. Trigger: Google Sheets, **New Spreadsheet Row**, which [fires on rows added at the bottom of the sheet](https://zapier.com/apps/google-sheets/integrations).
2. Action: **Webhooks by Zapier**, event **GET**, URL `https://enrich.chat/v1/person`. Zapier [recommends leaving URL parameters out of the URL field](https://help.zapier.com/hc/en-us/articles/8496326446989-Send-webhooks-in-Zaps), so put `linkedin_url` and `fields` (value `email,mobile`) in **Data**.
3. Optional: a **Filter** step that continues only when `found` is true. [Zapier does not count Filter steps as tasks](https://help.zapier.com/hc/en-us/articles/8496196837261-Tasks-in-Zapier).
4. Action: Google Sheets, **Update Spreadsheet Row**, mapping the reply into the row.

### How do you set it up in Make?

1. Trigger: Google Sheets, **Watch New Rows**, which Make calls [a polling trigger, so it looks for new rows on each scenario run](https://help.make.com/step-5-set-up-the-trigger).
2. HTTP, **Make a request**: method `GET`, the same URL, query parameters, **Authentication type** No authentication and **Parse response** Yes, per [Make's HTTP app docs](https://apps.make.com/http).
3. Google Sheets, **Update a Row**. Map the row number from the trigger, because [Make's community thread on the module shows that Update a Row](https://community.make.com/t/google-sheets-update-a-row-is-not-recognizing-rownumber-and-will-not-update-google-sheet/98764) fails without it.

### How do you set it up in n8n?

Use the Google Sheets node with **Get Row(s)**, an HTTP Request node, then the Google Sheets node with **Update Row**, all in n8n's [sheet operation docs](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googlesheets/sheet-operations/). The built-in [LinkedIn node only creates posts](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.linkedin/), so the HTTP Request node does the lookup. On n8n Cloud, [managed OAuth2 signs in with Google directly](https://docs.n8n.io/integrations/builtin/credentials/google/). Our [n8n lead enrichment guide](https://enrich.chat/blog/n8n-lead-enrichment/) lists every node setting.

Each tool counts work in its own unit, so the same 1,000 rows cost different amounts. Prices were checked on October 15, 2026.

| Tool | Plan the job needs | What it counts | Used by 1,000 rows |
| --- | --- | --- | --- |
| Zapier | Professional: [$49 a month for 2,000 tasks on yearly billing, $73.50 monthly](https://zapier.com/pricing). [Polling is every 2 minutes on Professional and 15 on Free](https://zapier.com/pricing). | [Tasks. Triggers are free, and each successful action counts](https://help.zapier.com/hc/en-us/articles/8496196837261-Tasks-in-Zapier) | 2,000 tasks, the 2K tier |
| Make | Free has [1,000 credits a month and a 15-minute minimum interval](https://www.make.com/en/pricing). Paid plans are priced by credits: [the slider showed $9 a month for 5,000 credits on annual billing](https://www.make.com/en/pricing). | [Credits. Most module actions use 1](https://www.make.com/en/pricing) | About 3,000 credits, 3 modules a row |
| n8n | Cloud Starter is [20€ a month billed annually for 2.5K executions](https://n8n.io/pricing/). | [Executions. One run counts once, however many rows it handles](https://n8n.io/pricing/) | 1 for a scheduled run |
| Apps Script | Your Google account | URL Fetch calls, with quotas of [20,000 a day on gmail.com and 100,000 on Workspace](https://developers.google.com/apps-script/guides/services/quotas) | 1,000 calls |

## What does it cost to enrich a Google Sheet?

Enrich.chat uses credits only when a lookup finds something. A work email found uses 10 credits, a mobile uses 250, and a row with no match uses 0. Each reply carries `credits_used`, so a script or workflow can total a run.

| Sheet | Credits if every row matches | Plan that covers it |
| --- | --- | --- |
| 1,000 rows, work email | 10,000 | Growth, $29 a month |
| 5,000 rows, work email | 50,000 | Growth, $29 a month |
| 1,000 rows, email and mobile | 260,000 | Scale, $90 a month |
| 5,000 rows, email and mobile | 1,300,000 | Pro, $299 a month |

Plans bill monthly with unlimited seats and no annual contract: Growth $29 for 100,000 credits, Scale $90 for 500,000 credits, Pro $299 for 2,500,000 credits. Credits renew with each monthly payment, and unused credits do not roll over. See [Enrich.chat pricing](https://enrich.chat/pricing/) for every field.

You can test first: each IP address gets 10 free lookups a day, with no signup and no key. Apps Script is an awkward place to test, because [Google sends its requests from a pool of IP ranges](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app), and the free count is per IP. Try a few rows, then add a key. Log in at [enrich.chat/account/](https://enrich.chat/account/) with your email, pick a plan, pay at checkout and copy the key from that page.

## Which route should you pick, and when is another tool better?

Pick by how rows arrive: a one-off list suits the menu command or an agent, and a growing sheet suits a trigger.

**Figure 3: Which route fits your sheet.** A decision tree. If new rows do not keep arriving, use the Apps Script menu command once for a list, or ask an AI agent. If new rows keep arriving and you already use Zapier, Make or n8n, use that tool with its Google Sheets trigger. If you do not, use Apps Script with a time-driven trigger. Every route sends the same GET request to the person endpoint.

*Pick by how rows arrive, not by tool. Orange boxes are the routes that use the script on this page. An AI agent works from the sheet or a CSV instead. Source: [Enrich.chat agent docs](https://enrich.chat/agents.md).*

### Can an AI agent edit the sheet for you?

Yes, in some setups. Anthropic says Claude's [Google Docs, Sheets and Slides connectors](https://claude.com/resources/articles/claude-now-works-in-google-docs-sheets-and-slides), still in beta, let it create and edit Google files, with access that matches your sharing permissions. Other agents can work from a CSV: export the sheet, let the agent add the columns, and bring the file back with Google's [import options](https://support.google.com/docs/answer/40608). The [agents page](https://enrich.chat/agents/) lists the clients.

```text
Use www.enrich.chat to fill the work_email, email_status, mobile and mobile_line columns in my Google Sheet, using the linkedin_url column. Do the first 10 rows, show me every status and the total credits_used, and write nothing back until I say go.
```

### When is another tool the better choice?

- **You want a sidebar and no code.** [Hunter's Sheets add-on](https://help.hunter.io/extensions-and-add-ons/google-sheets-add-on) adds Domain Search, Email Finder and Email Verifier and asks for your Hunter API key. [lemlist for Sheets](https://workspace.google.com/marketplace/app/lemlist_for_sheets/1031411472951) is a free add-on that cleans names, enriches leads and pushes rows into lemlist. Enrich.chat has no Sheets add-on. See our [Hunter.io alternatives](https://enrich.chat/alternatives/hunter-io/).
- **Your records live in a CRM.** Write to it directly. Our [HubSpot](https://enrich.chat/integrations/hubspot/) and [Salesforce](https://enrich.chat/integrations/salesforce/) guides show how.
- **You want many data sources in one table.** Read about [waterfall enrichment](https://enrich.chat/blog/waterfall-enrichment/) and [Clay](https://enrich.chat/integrations/clay/).

### What should you watch out for?

- **You paste and maintain the script.** There is no sidebar or visual table builder, and no sequencing or dialer. If Google changes a method, you update the code.
- **Mobiles use the most credits.** Ask for email first, and add the mobile only for rows you will call.

Treat every result as a strong lead, not a guarantee. Keep verified emails and treat catch_all as risky, as the [catch-all checker](https://enrich.chat/tools/catch-all-checker/) explains. Check a number by hand with the [phone number validator](https://enrich.chat/tools/phone-number-validator/).

### What are the privacy and opt-out rules?

Anyone can remove their data at [enrich.chat/opt-out/](https://enrich.chat/opt-out/), and requests are processed within 15 days. You are responsible for the rules that apply to your outreach, such as TCPA, CAN-SPAM and GDPR. Start with our guide to [TCPA rules for B2B calls](https://enrich.chat/blog/tcpa-b2b-cold-calling/). Accounts are covered by our [terms](https://enrich.chat/terms/).

## Frequently asked questions

### How do I enrich leads in Google Sheets for free?

Start with the Enrich.chat free tier: 10 lookups a day per IP address, with no signup and no key. A lookup that finds nothing does not count. From Apps Script, [Google sends its requests from a pool of IP ranges](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app), so the free count may be shared and run out early. Test a few rows, then add a key. [Make's Free plan](https://www.make.com/en/pricing) includes 1,000 credits a month, and Zapier's Free plan has no Webhooks.

### Can IMPORTDATA or IMPORTXML call an API from Google Sheets?

Not for Enrich.chat. [IMPORTDATA](https://support.google.com/docs/answer/3093335) reads CSV or TSV files, and [IMPORTXML](https://support.google.com/docs/answer/3093342) reads XML, HTML, CSV, TSV and feeds, while every Enrich.chat endpoint returns JSON. Use Apps Script instead. A [custom function](https://developers.google.com/apps-script/guides/sheets/functions) can call URL Fetch, but it must finish in 30 seconds and every use makes its own call, so a menu command is safer for a long list.

### How do I find an email from a LinkedIn URL in Google Sheets?

Put the URL in a `linkedin_url` column and run the Apps Script on this page. It sends each URL to `GET /v1/person` and writes the work email, its status, the mobile and its line type into the row. The URL is only a lookup key, and Enrich.chat never logs in to LinkedIn. See the [LinkedIn URL to contact API](https://enrich.chat/api/linkedin-to-contact/), or test one URL in the free [LinkedIn email finder](https://enrich.chat/tools/linkedin-email-finder/).

### Where should I keep the API key in Apps Script?

Use a script property, not a cell. Open Project Settings, add `ENRICH_KEY` and paste the key. [Google's Properties guide](https://developers.google.com/apps-script/guides/properties) lists the steps. Script properties are available to all users of the script, and [a bound script runs only for people who can edit the sheet](https://developers.google.com/apps-script/guides/bound), so give edit access with care. The free tier needs no key. Paid keys start with `pp_` and appear at [enrich.chat/account/](https://enrich.chat/account/) after you pick a plan.

### How many rows can Apps Script enrich in one run?

That depends on how fast each reply returns. Google stops a script after [6 minutes](https://developers.google.com/apps-script/guides/services/quotas), so the script on this page quits at 5 minutes and saves every row as it goes. Click the menu item again to continue, because rows with a result are skipped. Google also caps URL Fetch calls at 20,000 a day on gmail.com accounts and 100,000 on Workspace.

### Does Zapier's free plan work for Google Sheets lead enrichment?

No. [Webhooks by Zapier is available on Professional, Team and Enterprise](https://help.zapier.com/hc/en-us/articles/8496326446989-Send-webhooks-in-Zaps), not Free, and [Zapier's Free plan](https://zapier.com/pricing) allows two-step Zaps and 100 tasks a month. A new row, a GET request and a row update make three steps. Professional lists from $19.99 a month on yearly billing, checked October 15, 2026. Make and n8n are other routes.

## Sources

1. [Class UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app)
2. [Quotas for Google Services](https://developers.google.com/apps-script/guides/services/quotas)
3. [Custom functions in Google Sheets](https://developers.google.com/apps-script/guides/sheets/functions)
4. [Simple triggers](https://developers.google.com/apps-script/guides/triggers)
5. [Installable triggers](https://developers.google.com/apps-script/guides/triggers/installable)
6. [Properties service guide](https://developers.google.com/apps-script/guides/properties)
7. [Container-bound scripts](https://developers.google.com/apps-script/guides/bound)
8. [Authorization for Google Services](https://developers.google.com/apps-script/guides/services/authorization)
9. [Custom menus in Google Workspace](https://developers.google.com/apps-script/guides/menus)
10. [Spreadsheet service](https://developers.google.com/apps-script/reference/spreadsheet)
11. [IMPORTDATA](https://support.google.com/docs/answer/3093335)
12. [IMPORTXML](https://support.google.com/docs/answer/3093342)
13. [Import and convert files to Google Sheets](https://support.google.com/docs/answer/40608)
14. [Send webhooks in Zaps](https://help.zapier.com/hc/en-us/articles/8496326446989-Send-webhooks-in-Zaps)
15. [Tasks in Zapier](https://help.zapier.com/hc/en-us/articles/8496196837261-Tasks-in-Zapier)
16. [Zapier pricing](https://zapier.com/pricing)
17. [Google Sheets integrations](https://zapier.com/apps/google-sheets/integrations)
18. [Make pricing](https://www.make.com/en/pricing)
19. [Google Sheets integrations](https://www.make.com/en/integrations/google-sheets)
20. [HTTP app](https://apps.make.com/http)
21. [Set up the trigger](https://help.make.com/step-5-set-up-the-trigger)
22. [Update a Row is not recognizing rownumber](https://community.make.com/t/google-sheets-update-a-row-is-not-recognizing-rownumber-and-will-not-update-google-sheet/98764)
23. [n8n pricing](https://n8n.io/pricing/)
24. [Google Sheets node: sheet operations](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googlesheets/sheet-operations/)
25. [Google credentials](https://docs.n8n.io/integrations/builtin/credentials/google/)
26. [LinkedIn node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.linkedin/)
27. [Google Sheets add-on](https://help.hunter.io/extensions-and-add-ons/google-sheets-add-on)
28. [lemlist for Sheets](https://workspace.google.com/marketplace/app/lemlist_for_sheets/1031411472951)
29. [Claude now works in Google Docs, Sheets, and Slides](https://claude.com/resources/articles/claude-now-works-in-google-docs-sheets-and-slides)
30. [Enrich.chat agents.md](https://enrich.chat/agents.md)

## Examples

**Apps Script**

```text
// Extensions > Apps Script. The full menu script is below.
// A key is optional: the free tier needs none.
function lookupOne(linkedinUrl) {
  const url = 'https://enrich.chat/v1/person?fields=email,mobile'
    + '&linkedin_url=' + encodeURIComponent(linkedinUrl);
  const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
  const data = JSON.parse(res.getContentText());

  if (res.getResponseCode() !== 200) return data.error; // 402
  if (!data.found) return 'not_found';       // 0 credits
  const w = data.work_email || {};
  return w.value ? w.value + ' (' + w.status + ')' : 'no email';
}
```

**cURL**

```bash
# One row of the sheet, as a request
curl -G "https://enrich.chat/v1/person" \
  -d "linkedin_url=linkedin.com/in/jane-doe-ops" \
  -d "fields=email,mobile"

# The free tier needs no key. A key is optional; paying
# customers get one at enrich.chat/account/ and can add:
#   -H "Authorization: Bearer $ENRICH_KEY"
```

**Response**

```json
{
  "found": true,
  "name": "Jane Doe",
  "title": "VP Operations",
  "company": {"name": "Acme Logistics"},
  "linkedin_url": "https://www.linkedin.com/in/jane-doe-ops",
  "work_email": {"value": "jane.doe@acmelogistics.com", "status": "verified"},
  "mobile": {"value": "+1 415 555 0142", "line": "mobile"},
  "credits_used": 260,
  "free_lookups_remaining": 9
}
```

**Python**

```python
import csv
import os
import time

import requests

# In Sheets: File > Download > CSV. Run this. Then File > Import.
# The key is optional. Set ENRICH_KEY only if you have a plan.
key = os.environ.get("ENRICH_KEY")
headers = {"Authorization": "Bearer " + key} if key else {}

with open("leads.csv", newline="") as f:
    rows = list(csv.DictReader(f))

URL = "https://enrich.chat/v1/person"
new_cols = [
    "work_email", "email_status", "mobile",
    "mobile_line", "enrich_result",
]
for row in rows:
    for c in new_cols:
        row.setdefault(c, "")
    if not row.get("linkedin_url") or row["enrich_result"]:
        continue
    params = {"fields": "email,mobile"}
    params["linkedin_url"] = row["linkedin_url"]
    r = requests.get(URL, params=params, headers=headers, timeout=30)
    data = r.json()
    if r.status_code == 402:
        print("stopped:", data["error"], data["upgrade_url"])
        break
    if not r.ok:
        row["enrich_result"] = "error_%d" % r.status_code
    elif data["found"]:
        email = data.get("work_email") or {}
        mobile = data.get("mobile") or {}
        row["work_email"] = email.get("value", "")
        row["email_status"] = email.get("status", "")
        row["mobile"] = mobile.get("value", "")
        row["mobile_line"] = mobile.get("line", "")
        row["enrich_result"] = "found"
    else:
        row["enrich_result"] = "not_found"  # 0 credits
    time.sleep(0.25)

with open("leads_enriched.csv", "w", newline="") as f:
    w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
    w.writeheader()
    w.writerows(rows)
```

**Agent prompt**

```text
# Paste into Claude (with its Google Sheets connector),
# or into ChatGPT or Cursor with a CSV of the sheet
Use www.enrich.chat to fill the work_email,
email_status, mobile and mobile_line columns
for the first 10 rows of my sheet, using the
linkedin_url column. Show me every status and
write nothing back until I say go.
```

## Prices

| What you get | Credits |
| --- | --- |
| Work email found | 10 |
| Mobile number found | 250 |
| Company tech stack or profile found | 5 |

10 free lookups a day for every IP address, no signup. Nothing is charged when nothing is found. Monthly plans start at $29 for 100,000 credits.

## Try this prompt

Have an agent write the sheet script

```text
Use www.enrich.chat to write an Apps Script menu command for my Google Sheet. For each row with a linkedin_url, look up the work email and mobile, save every reply as it returns, and stop cleanly on a 402.
```

## Related

- [n8n lead enrichment: HTTP Request node, step by step](https://enrich.chat/blog/n8n-lead-enrichment/)
- [LinkedIn URL to Email and Mobile API](https://enrich.chat/api/linkedin-to-contact/)
- [50 ChatGPT and Claude prompts for lead generation](https://enrich.chat/blog/chatgpt-lead-generation-prompts/)
