# Icypeas API > Icypeas API documentation. Find and verify professional email addresses, scan domains, scrape profiles and companies and search a B2B lead database, with or without code. Key facts for AI agents and developers: - Base URL: `https://app.icypeas.com/api`. Every endpoint is called with HTTPS and a JSON body. - Authentication: header `Authorization: ` (the key alone, no "Bearer" prefix) plus `Content-Type: application/json`. Users get their key at https://app.icypeas.com (API section). - Email search, email verification and domain search are asynchronous. `POST /email-search` (or `/email-verification`, `/domain-search`) returns `item._id`. Then call `POST /bulk-single-searchs/read` with `{"id": "<_id>"}` every few seconds until `items[0].status` is no longer `NONE`, `SCHEDULED` or `IN_PROGRESS`. Webhooks are the recommended alternative to polling. - Found emails are in `items[0].results.emails[]` with a `certainty` (`ultra_sure`, `very_sure`, `probable`, `not_found`, `undeliverable`). - Input mistakes return HTTP 200 with `{"success": false, "validationErrors": [...]}`. Wrong or missing key returns 401, too many calls return 429. - Machine-readable spec: https://api-doc.icypeas.com/openapi.json. Every page below is also available as Markdown by adding `.md` to its URL. All pages in one file: https://api-doc.icypeas.com/llms-full.txt --- # Icypeas API overview > What you can do with the Icypeas API, how much it costs and the fastest way to get started, with or without code. Source: https://api-doc.icypeas.com/overview The Icypeas API lets any tool, script or AI agent find and verify professional email addresses, the same way you do in the Icypeas app. If you can copy and paste, you can use it. ## What you can do - [Find an email](https://api-doc.icypeas.com/reference/email-search): Give a first name, a last name and a company. Get the professional email address back. - [Verify an email](https://api-doc.icypeas.com/reference/email-verification): Check that an address really exists before you send to it, and protect your sender reputation. - [Scan a domain](https://api-doc.icypeas.com/reference/domain-search): List the role-based addresses of a company: contact@, sales@, hello@… - [Run thousands at once](https://api-doc.icypeas.com/reference/bulk-search): Send up to 5,000 searches or verifications in a single call. - [Scrape profiles & companies](https://api-doc.icypeas.com/reference/scrape-profile): Turn a profile or company URL into structured data. - [Search the lead database](https://api-doc.icypeas.com/reference/find-people): Find people and companies with filters: job title, location, headcount… ## Pick your path - [I don't code](https://api-doc.icypeas.com/no-code): Use Google Sheets, Make, Zapier or n8n. Copy, paste, done. - [I can paste a command](https://api-doc.icypeas.com/getting-started): Find your first email from your computer's terminal in 5 minutes. - [I use an AI agent](https://api-doc.icypeas.com/ai-agents): Give your assistant llms.txt or the OpenAPI spec and let it write the integration. ## The 3 things to know 1. **You need an API key.** It's free to get with any Icypeas account: [here's how](https://api-doc.icypeas.com/api-key). 2. **Searches take a few seconds.** You start a search, then you read its result. [See how it works](https://api-doc.icypeas.com/how-it-works). 3. **You only pay for results.** Finding an email costs 1 credit only when an email is found, a verification costs 0.1 credit. [See all costs](https://api-doc.icypeas.com/credits). > **Ready?** > > Start with [Find your first email](https://api-doc.icypeas.com/getting-started). It takes about 5 minutes. --- # Quickstart: find your first email in 5 minutes > Step-by-step guide to find a professional email address with the Icypeas API in 5 minutes, even if you have never used an API before. Source: https://api-doc.icypeas.com/getting-started No experience needed. You'll send one request to Icypeas with a name and a company, and get the person's professional email address back. > **What you need** > > - An Icypeas account ([create one for free](https://app.icypeas.com/create-account)). > - A computer: Mac, Windows or Linux. Nothing to install. > **Prefer no code at all?** > > Skip this page and follow the [Google Sheets](https://api-doc.icypeas.com/no-code/google-sheets), [Make](https://api-doc.icypeas.com/no-code/make), [Zapier](https://api-doc.icypeas.com/no-code/zapier) or [n8n](https://api-doc.icypeas.com/no-code/n8n) guide instead. ## Step 1: copy your API key Your API key is like a password that lets tools use your Icypeas account. 1. Open the [API page of the Icypeas app](https://app.icypeas.com/bo/api). 2. Switch on **Activate API Access**. 3. Copy the value of the **API Key** field. Keep it somewhere safe: anyone with this key can spend your credits. [More details on API keys](https://api-doc.icypeas.com/api-key). ## Step 2: start the search Fill in the person you're looking for. The command below updates as you type, and you can paste your API key so you don't have to edit the command afterwards. ```bash curl -X POST "https://app.icypeas.com/api/email-search" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "firstname": "Pierre", "lastname": "Landoin", "domainOrCompany": "icypeas.com" }' ``` **How to run it:** - **On Mac:** open the **Terminal** app (press ⌘ + Space, type *Terminal*, press Enter). Paste the command, press Enter. - **On Windows:** open **PowerShell** (press the Windows key, type *PowerShell*, press Enter). Pick the *Windows (PowerShell)* tab above, paste the code, press Enter. You get an answer like this right away: ```json { "success": true, "item": { "status": "NONE", "_id": "p_gfS5EB4liCGm90KDFC" } } ``` `"success": true` means Icypeas has accepted your search. Copy the value of **`_id`**: it's the ticket number of your search. > **Seeing an error instead?** > > - `"Unauthorized access."`: the API key is wrong, or API access is not activated. Go back to step 1. > - `"success": false` with `validationErrors`: a field is missing or empty. Check the name and company. > > All errors and their fixes are listed in [Errors and troubleshooting](https://api-doc.icypeas.com/errors). ## Step 3: get the email Wait 5 to 10 seconds, then ask for the result with the `_id` you just copied: ```bash curl -X POST "https://app.icypeas.com/api/bulk-single-searchs/read" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "id": "PASTE_THE_ID_HERE" }' ``` The answer looks like this (shortened): ```json { "success": true, "items": [ { "status": "DEBITED", "results": { "firstname": "Pierre", "lastname": "Landoin", "fullname": "Pierre Landoin", "emails": [ { "email": "pierre.landoin@icypeas.com", "certainty": "ultra_sure", "mxProvider": "google", "mxRecords": ["google.com"] } ] } } ], "total": 1 } ``` **🎉 That's it.** The email is in `emails`, and `certainty` tells you how reliable it is. ## Step 4: read the result Look at `status` first: | `status` | What it means | What to do | | --- | --- | --- | | `NONE`, `SCHEDULED`, `IN_PROGRESS` | Still working on it. | Wait a few seconds and run step 3 again. | | `DEBITED` or `FOUND` | An email was found, 1 credit was used. | Use the email in `emails`. | | `DEBITED_NOT_FOUND` or `NOT_FOUND` | No email could be found. No credit used. | Try another spelling or the company website instead of its name. | | `INSUFFICIENT_FUNDS` | You're out of credits. | [Get more credits](https://app.icypeas.com/bo/billing?o=plans). | Then look at `certainty`: - **`ultra_sure` / `very_sure`**: safe to send, bounce rate below 1%. - **`probable`**: very likely valid, bounce rate below 5%. All statuses are explained in [Result statuses](https://api-doc.icypeas.com/statuses), all certainty levels in [Email certainty](https://api-doc.icypeas.com/certainties). ## What's next? - [Verify an email](https://api-doc.icypeas.com/quickstart/verify-email): Same 3 steps, to check an address you already have. - [Find thousands of emails](https://api-doc.icypeas.com/reference/bulk-search): Send up to 5,000 people in one request. - [Stop waiting: use webhooks](https://api-doc.icypeas.com/reference/webhooks): Have Icypeas send you the result as soon as it's ready. - [Email search reference](https://api-doc.icypeas.com/reference/email-search): Every parameter, response and error, with code in 5 languages. --- # Quickstart: verify an email address > Check whether a professional email address really exists with the Icypeas API, in three copy-paste steps. Costs 0.1 credit per verification. Source: https://api-doc.icypeas.com/quickstart/verify-email Already have an email address and want to know if it's safe to send to? Icypeas checks it with the company's mail server. Each verification costs **0.1 credit**. > **Before you start** > > You need your API key. If you don't have it yet, follow [step 1 of the quickstart](https://api-doc.icypeas.com/getting-started#step-1-copy-your-api-key). ## Step 1: start the verification Type the email you want to check. The command updates as you type. ```bash curl -X POST "https://app.icypeas.com/api/email-verification" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "email": "pierre.landoin@icypeas.com" }' ``` Run it in the **Terminal** (Mac) or **PowerShell** (Windows). You'll get back a search `_id`: ```json { "success": true, "item": { "status": "NONE", "_id": "kMnquYkBTs8kZM9ND26h" } } ``` ## Step 2: get the verdict Wait a few seconds, then read the result with that `_id`: ```bash curl -X POST "https://app.icypeas.com/api/bulk-single-searchs/read" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "id": "PASTE_THE_ID_HERE" }' ``` ## Step 3: read the verdict Look at `items[0].results.emails[0].certainty`: | `certainty` | Verdict | Should you send? | | --- | --- | --- | | `ultra_sure` or `very_sure` | The address exists. Expected bounce rate below 1%. | ✅ Yes | | `probable` | The address very likely exists. Expected bounce rate below 5%. | 👍 Usually yes | | `undeliverable` | The address does not exist. | ❌ No | | `not_found` | The mail server doesn't say (for example it accepts every address). | ⚠️ At your own risk | If `status` is still `NONE`, `SCHEDULED` or `IN_PROGRESS`, wait a few seconds and run step 2 again. ## Verify a whole list To verify many addresses, don't loop over this endpoint: use a [bulk search](https://api-doc.icypeas.com/reference/bulk-search) with the `email-verification` task. It takes up to 5,000 addresses per call. See the [email verification reference](https://api-doc.icypeas.com/reference/email-verification) for every option. --- # Get your API key > Where to find your Icypeas API key and API secret, how to activate API access and how to keep your key safe. Source: https://api-doc.icypeas.com/api-key Your API key identifies your Icypeas account. Every request you send must include it. It's free and takes 30 seconds to get. ### Create an Icypeas account If you don't have one yet, [sign up here](https://app.icypeas.com/create-account). Every account can use the API. ### Open the API page In the app, click **API** at the bottom of the left menu, under *Connect*. Or go straight to [app.icypeas.com/bo/api](https://app.icypeas.com/bo/api). ![The API entry in the left menu of the Icypeas app](https://api-doc.icypeas.com/img/screenshots/api-access-1.png) ### Activate API access Switch on **Activate API Access**. ![The "Activate API Access" toggle](https://api-doc.icypeas.com/img/screenshots/api-access-2.png) ### Copy your key Your key appears in the **API Key** field. Copy it and keep it somewhere safe, like a password manager. ![The API key, API secret and user id fields](https://api-doc.icypeas.com/img/screenshots/api-access-3.png) ## What's on this page | Field | What it's for | | --- | --- | | **API Key** | Goes in the `Authorization` header of every request. This is the only thing you need to get started. | | **API Secret** | Only needed if you choose to [sign your requests](https://api-doc.icypeas.com/authentication#option-2-signed-requests) or to check that webhook calls really come from Icypeas. | | **User id** | Your account id. Rarely needed, also returned by the [subscription endpoint](https://api-doc.icypeas.com/reference/subscription). | | **Notification URLs** | Where Icypeas sends [webhooks](https://api-doc.icypeas.com/reference/webhooks) when a search is done. | ## Keep your key safe > **Treat your API key like a password** > > Anyone who has it can use your credits. > - Never paste it in a public place (GitHub, forums, screenshots, front-end JavaScript). > - If it leaks, click **Regenerate Key** on the API page. The old key stops working immediately. Next step: [find your first email](https://api-doc.icypeas.com/getting-started). --- # How the Icypeas API works > Searches on the Icypeas API are asynchronous. Learn the send, wait, read cycle, how to get results with polling or webhooks, and which tasks are available. Source: https://api-doc.icypeas.com/how-it-works Finding or verifying an email takes a few seconds, because Icypeas really talks to the company's mail server. So the API works like a ticket counter: you drop off a request, you get a ticket number, you come back for the result. 1. **Send the search.** `POST /email-search` answers instantly with a search `_id`. 2. **Wait a few seconds.** Icypeas looks for the email and checks it with the mail server. 3. **Read the result.** `POST /bulk-single-searchs/read` with `{"id": "<_id>"}`, or receive it on a webhook. ## The two ways to get your result ### Option A: ask again until it's ready (polling) The simplest. After starting a search, call [`/bulk-single-searchs/read`](https://api-doc.icypeas.com/reference/get-results) with the search `_id` every few seconds, until `status` is no longer `NONE`, `SCHEDULED` or `IN_PROGRESS`. - ✅ Easy: two requests and a pause. - ⚠️ Limited to 30 reads per minute (see [rate limits](https://api-doc.icypeas.com/rate-limits)). Wait **at least 3 seconds** between two reads. For a bulk search, check its progress with [`/search-files/read`](https://api-doc.icypeas.com/reference/bulk-files) until the file is finished, then fetch the results page by page. ### Option B: let Icypeas call you (webhooks) recommended Give Icypeas the URL of your server, Zap or scenario, and it sends you the result as soon as it's ready. No waiting loop, no rate limit to worry about. - Set one URL for all your searches on the [API page of the app](https://app.icypeas.com/bo/api), **or** - pass a `custom.webhookUrl` with a single search, or `custom.webhookUrlItem` / `custom.webhookUrlBulkDone` with a bulk search. Read [Webhooks](https://api-doc.icypeas.com/reference/webhooks) to set them up. ## Single or bulk? | | Single search | Bulk search | | --- | --- | --- | | **Use it for** | One person at a time, in real time (a form, a CRM button, a Zap). | Lists: a CSV, a CRM export, a database. | | **Items per call** | 1 | Up to 5,000 | | **Speed limit** | 10 calls per second | 1 call per second | | **Endpoints** | [`/email-search`](https://api-doc.icypeas.com/reference/email-search), [`/email-verification`](https://api-doc.icypeas.com/reference/email-verification), [`/domain-search`](https://api-doc.icypeas.com/reference/domain-search) | [`/bulk-search`](https://api-doc.icypeas.com/reference/bulk-search) | > **Don't loop over single searches for big lists** > > Bulk searches are designed to process thousands of rows in parallel. They're faster for you and kinder to the rate limits. ## Tasks A *task* is the kind of work you ask Icypeas to do. Bulk searches and result filters refer to them by name. | Task | What it does | Input | | --- | --- | --- | | [`email-search`](https://api-doc.icypeas.com/reference/email-search) | Finds a person's professional email. | First name and/or last name, plus a company name or domain. | | [`email-verification`](https://api-doc.icypeas.com/reference/email-verification) | Checks that an email address exists. | An email address. | | [`domain-search`](https://api-doc.icypeas.com/reference/domain-search) | Finds the role-based emails of a domain (contact@, sales@…). | A domain or company name. | | [`scrape/profile`](https://api-doc.icypeas.com/reference/scrape-profile) | Gets the data of a person from their profile URL. | A profile URL. | | [`scrape/company`](https://api-doc.icypeas.com/reference/scrape-company) | Gets the data of a company from its company page URL. | A company page URL. | | [`url-search/profile`](https://api-doc.icypeas.com/reference/find-profile-url) | Finds the profile URL of a person. | Name, company, job title… | | [`url-search/company`](https://api-doc.icypeas.com/reference/find-company-url) | Finds the company page URL of a company. | Company name or website. | ## Good to know - **Every request is a `POST` with a JSON body**, except the two single scraping endpoints which are `GET`. - **Input mistakes come back with HTTP 200** and `"success": false`. Always check `success`. See [Errors](https://api-doc.icypeas.com/errors). - **You're only charged for results.** See [Credit costs](https://api-doc.icypeas.com/credits). --- # Use the Icypeas API with AI agents > Give ChatGPT, Claude, Cursor or any AI agent the Icypeas docs in one link. llms.txt, llms-full.txt, Markdown pages and an OpenAPI 3.1 spec are available. Source: https://api-doc.icypeas.com/ai-agents These docs are built to be read by machines as well as humans. Give your AI assistant one link and it can write a working Icypeas integration for you, even if you've never written code. ## The fastest way: one prompt Paste this into ChatGPT, Claude, Cursor, Copilot or any assistant that can browse the web: ```text title="Prompt" Read https://api-doc.icypeas.com/llms-full.txt. Then write a script that reads names and companies from my file people.csv, finds each person's professional email with the Icypeas API and saves the results to results.csv. Ask me for anything you need, like my API key. ``` Change the second part to whatever you need: a Google Sheets script, a Make scenario, a Python function, a CRM integration… > **On any page** > > Use the **Copy page** button at the top of every page to copy it as clean Markdown, or open it straight in ChatGPT or Claude from the menu next to it. ## Machine-readable resources | Resource | URL | Use it for | | --- | --- | --- | | **llms.txt** | [`/llms.txt`](https://api-doc.icypeas.com/llms.txt) | Short index of every page, with a summary of the key rules. Follows the [llmstxt.org](https://llmstxt.org) standard. | | **llms-full.txt** | [`/llms-full.txt`](https://api-doc.icypeas.com/llms-full.txt) | The whole documentation in a single Markdown file. Best for loading everything into the context at once. | | **Markdown pages** | Add `.md` to any URL, for example [`/reference/email-search.md`](https://api-doc.icypeas.com/reference/email-search.md) | One page at a time, without the website around it. | | **OpenAPI 3.1** | [`/openapi.json`](https://api-doc.icypeas.com/openapi.json) | Import into Postman, Insomnia, Bruno, or give it to agent frameworks that generate tools from OpenAPI. | ## Rules your agent must know If your agent only reads one thing, make it this: 1. **Base URL:** `https://app.icypeas.com/api`. 2. **Headers:** `Authorization: ` (no `Bearer`) and `Content-Type: application/json`. 3. **Searches are asynchronous.** `POST /email-search` returns `item._id`. Read the result with `POST /bulk-single-searchs/read` and `{"id": "<_id>"}` until `status` is not `NONE`, `SCHEDULED` or `IN_PROGRESS`. Wait at least 3 seconds between reads. 4. **Check `success`.** Bad input returns HTTP 200 with `"success": false` and a `validationErrors` array. 5. **Respect the [rate limits](https://api-doc.icypeas.com/rate-limits)**: back off and retry on HTTP 429. 6. **Use [bulk search](https://api-doc.icypeas.com/reference/bulk-search)** for more than a handful of items. ## Import into Postman in 30 seconds 1. In Postman, click **Import**. 2. Paste `https://api-doc.icypeas.com/openapi.json` and confirm. 3. In the generated collection, set the `Authorization` header (or the collection's API key auth) to your API key. Every endpoint is now ready to try, with example bodies. ## Keep your key out of prompts > **Warning** > > Don't paste your API key into a chat you share. Ask the assistant to read it from an environment variable (for example `ICYPEAS_API_KEY`) or a settings cell, and set it yourself. --- # Use the Icypeas API without code > Find and verify emails with the Icypeas API from Google Sheets, Make, Zapier or n8n. Step-by-step guides for non-developers. Source: https://api-doc.icypeas.com/no-code You don't need to be a developer to automate Icypeas. Pick the tool you already use and follow the guide: each one takes about 10 minutes. - [Google Sheets](https://api-doc.icypeas.com/no-code/google-sheets): Paste a ready-made script once, then find or verify emails for a whole sheet from a menu. - [Make](https://api-doc.icypeas.com/no-code/make): Add Icypeas to any Make scenario with the HTTP module. - [Zapier](https://api-doc.icypeas.com/no-code/zapier): Find an email every time a new lead lands in your CRM, form or sheet. - [n8n](https://api-doc.icypeas.com/no-code/n8n): Use the HTTP Request and Wait nodes, cloud or self-hosted. ## The recipe is always the same Whatever the tool, an Icypeas automation has three steps: 1. **Send the search.** `POST /email-search` answers instantly with a search `_id`. 2. **Wait a few seconds.** Icypeas looks for the email and checks it with the mail server. 3. **Read the result.** `POST /bulk-single-searchs/read` with `{"id": "<_id>"}`, or receive it on a webhook. So in every tool you will: 1. Add an **HTTP request** step that sends the name and company to `/email-search` (or the email to `/email-verification`). 2. Add a **wait / delay / sleep** step of about 10 seconds. 3. Add a second **HTTP request** step that sends the `_id` from step 1 to `/bulk-single-searchs/read`, and use the email it returns. ## What every HTTP step needs Copy these values into your tool: | Setting | Value | | --- | --- | | **Method** | `POST` | | **URL** | `https://app.icypeas.com/api/email-search` (or `/email-verification`, `/bulk-single-searchs/read`) | | **Header 1** | Name `Authorization`, value: your [API key](https://api-doc.icypeas.com/api-key) | | **Header 2** | Name `Content-Type`, value `application/json` | | **Body type** | JSON (sometimes called *raw* / *application/json*) | > **Not sure which fields to send?** > > Every endpoint page in the [API reference](https://api-doc.icypeas.com/reference/overview) has a **JSON body** tab with a body you can paste as is. --- # Find and verify emails in Google Sheets > Copy-paste a ready-made Apps Script into Google Sheets to find or verify professional emails for every row of your sheet with the Icypeas API. No coding needed. Source: https://api-doc.icypeas.com/no-code/google-sheets Turn a Google Sheet into an email finder. You paste a script once (2 minutes), then an **Icypeas** menu appears in your sheet to find or verify emails for every row. ## Step 1: prepare your sheet Create a sheet with **exactly** these columns, starting in cell `A1`: **To find emails** | | A | B | C | D | E | F | G | | --- | --- | --- | --- | --- | --- | --- | --- | | **1** | First name | Last name | Company or website | Email | Certainty | Status | Search id | | **2** | Pierre | Landoin | icypeas.com | | | | | Fill columns A to C. Leave D to G empty: the script fills them. **To verify emails** | | A | B | C | D | | --- | --- | --- | --- | --- | | **1** | Email | Certainty | Status | Search id | | **2** | pierre.landoin@icypeas.com | | | | Fill column A. Leave B to D empty: the script fills them. ## Step 2: paste the script 1. In your sheet, click **Extensions** › **Apps Script**. A new tab opens. 2. Delete everything in the editor, and paste the script below (use the copy button in its top-right corner). 3. Click the 💾 **Save** icon, then close the tab and reload your sheet. ```javascript title="Icypeas.gs" // Icypeas for Google Sheets: https://api-doc.icypeas.com/no-code/google-sheets const ICYPEAS_API = "https://app.icypeas.com/api"; const ROWS_PER_RUN = 40; // keeps each run under Google's 6-minute limit const FINAL_STATUSES = ["DEBITED", "FOUND", "NOT_FOUND", "DEBITED_NOT_FOUND", "BAD_INPUT", "INSUFFICIENT_FUNDS", "ABORTED"]; function onOpen() { SpreadsheetApp.getUi() .createMenu("Icypeas") .addItem("Find emails (columns A to C)", "findEmails") .addItem("Verify emails (column A)", "verifyEmails") .addSeparator() .addItem("Set my API key", "setApiKey") .addToUi(); } function setApiKey() { const ui = SpreadsheetApp.getUi(); const answer = ui.prompt("Icypeas API key", "Paste your API key (app.icypeas.com > API):", ui.ButtonSet.OK_CANCEL); if (answer.getSelectedButton() === ui.Button.OK) { PropertiesService.getUserProperties().setProperty("ICYPEAS_API_KEY", answer.getResponseText().trim()); ui.alert("Saved. You can now use the Icypeas menu."); } } function findEmails() { run_("email-search"); } function verifyEmails() { run_("email-verification"); } function run_(task) { const isSearch = task === "email-search"; const width = isSearch ? 7 : 4; const resultCol = isSearch ? 3 : 1; // Email (D) or Certainty (B) const statusCol = width - 2; const idCol = width - 1; const sheet = SpreadsheetApp.getActiveSheet(); const lastRow = sheet.getLastRow(); if (lastRow < 2) return toast_("Add some rows below the header first."); const rows = sheet.getRange(2, 1, lastRow - 1, width).getValues(); // 1. Start a search for every row that has no result yet. const pending = []; for (let i = 0; i < rows.length && pending.length < ROWS_PER_RUN; i++) { const r = rows[i]; if (r[resultCol] !== "" || FINAL_STATUSES.indexOf(r[statusCol]) !== -1) continue; let id = r[idCol]; if (!id) { const body = isSearch ? { firstname: r[0], lastname: r[1], domainOrCompany: r[2] } : { email: r[0] }; if (isSearch ? !r[2] || (!r[0] && !r[1]) : !r[0]) continue; const res = call_("/" + task, body); if (!res.success) { const why = (res.validationErrors || []).map((e) => e.field + ": " + e.type).join(", "); sheet.getRange(i + 2, statusCol + 1).setValue("INPUT ERROR " + why); continue; } id = res.item._id; sheet.getRange(i + 2, statusCol + 1, 1, 2).setValues([["IN_PROGRESS", id]]); Utilities.sleep(150); } pending.push({ row: i + 2, id: id }); } if (!pending.length) return toast_("Nothing to do: every row already has a result."); toast_("Searching " + pending.length + " rows, please wait…"); Utilities.sleep(10000); // 2. Read the results (max 30 reads per minute). let done = 0; pending.forEach(function (p) { const res = call_("/bulk-single-searchs/read", { id: p.id }); Utilities.sleep(2100); const item = res.items && res.items[0]; if (!item) return; sheet.getRange(p.row, statusCol + 1).setValue(item.status); const email = item.results && item.results.emails && item.results.emails[0]; if (FINAL_STATUSES.indexOf(item.status) !== -1) done++; if (!email) return; if (isSearch && email.email) sheet.getRange(p.row, 4, 1, 2).setValues([[email.email, email.certainty]]); if (!isSearch) sheet.getRange(p.row, 2).setValue(email.certainty); }); toast_(done + " of " + pending.length + " rows done. Run it again to continue with the rest."); } function call_(path, body, attempt) { const key = PropertiesService.getUserProperties().getProperty("ICYPEAS_API_KEY"); if (!key) throw new Error("Set your API key first: menu Icypeas > Set my API key."); const res = UrlFetchApp.fetch(ICYPEAS_API + path, { method: "post", contentType: "application/json", headers: { Authorization: key }, payload: JSON.stringify(body), muteHttpExceptions: true, }); const code = res.getResponseCode(); if (code === 401) throw new Error("Icypeas refused the API key. Check it with menu Icypeas > Set my API key."); if (code === 429 && (attempt || 0) < 3) { Utilities.sleep(10000); return call_(path, body, (attempt || 0) + 1); } return JSON.parse(res.getContentText()); } function toast_(message) { SpreadsheetApp.getActive().toast(message, "Icypeas", 8); } ``` ## Step 3: add your API key 1. In your sheet, open the new **Icypeas** menu (next to *Help*) › **Set my API key**. 2. The first time, Google asks you to authorize the script: click **Continue**, choose your account, then **Allow**. If you see *"Google hasn't verified this app"*, click **Advanced** › **Go to Untitled project**: it's your own script, it only talks to Icypeas. 3. Paste your [API key](https://api-doc.icypeas.com/api-key) and click **OK**. Your key is stored in your Google account only, not in the sheet: people you share the sheet with won't see it. ## Step 4: run it Click **Icypeas** › **Find emails** (or **Verify emails**). After about a minute, the results appear: - **Email**: the email found. - **Certainty**: how reliable it is (`ultra_sure`, `very_sure`, `probable`, `undeliverable`…). See [Email certainty](https://api-doc.icypeas.com/certainties). - **Status**: where the search is. See [Result statuses](https://api-doc.icypeas.com/statuses). Each run handles 40 rows. **Run it again** to continue: rows that already have a result are skipped, and searches still in progress are picked up without being charged twice. > **Large lists** > > For thousands of rows, ask a developer (or an AI assistant, see [AI agents](https://api-doc.icypeas.com/ai-agents)) to switch the script to the [bulk search endpoint](https://api-doc.icypeas.com/reference/bulk-search), which takes 5,000 rows per call. ## Troubleshooting | You see | Fix | | --- | --- | | No **Icypeas** menu | Reload the sheet. Check the script was saved in **Extensions › Apps Script**. | | *Icypeas refused the API key* | Re-copy your key from the [API page](https://app.icypeas.com/bo/api) and use **Set my API key** again. Check that API access is activated. | | `INPUT ERROR …` in Status | A required cell is empty or invalid on that row. For a search you need a company and at least a first or last name. | | `INSUFFICIENT_FUNDS` | You're out of credits. [Get more](https://app.icypeas.com/bo/billing?o=plans). | | Status stays `IN_PROGRESS` | Run the menu again in a minute. | --- # Find and verify emails with Make > Connect the Icypeas API to Make (formerly Integromat) with the HTTP module to find or verify professional emails in any scenario. Source: https://api-doc.icypeas.com/no-code/make Add an email finder to any Make scenario: a new row in a sheet, a new deal in your CRM, a form submission… You'll use three modules: **HTTP**, **Sleep**, **HTTP**. > **What you need** > > A Make account, an existing scenario with a trigger (for example *Google Sheets › Watch New Rows*), and your [Icypeas API key](https://api-doc.icypeas.com/api-key). ### Start the search Add an **HTTP › Make a request** module after your trigger and fill it in: | Field | Value | | --- | --- | | **URL** | `https://app.icypeas.com/api/email-search` | | **Method** | `POST` | | **Headers** | `Authorization` = your API key
`Content-Type` = `application/json` | | **Body type** | Raw | | **Content type** | JSON (application/json) | | **Request content** | The JSON below, with the values mapped from your trigger | | **Parse response** | Yes | ```json title="Request content" { "firstname": "{{1.firstName}}", "lastname": "{{1.lastName}}", "domainOrCompany": "{{1.company}}" } ``` Replace the `{{1.…}}` placeholders by clicking the matching fields of your trigger in the mapping panel. > **Verifying instead of finding?** > > Use `https://app.icypeas.com/api/email-verification` and the body `{ "email": "{{1.email}}" }`. ### Wait Add a **Tools › Sleep** module with a delay of **10** seconds. ### Read the result Add a second **HTTP › Make a request** module: | Field | Value | | --- | --- | | **URL** | `https://app.icypeas.com/api/bulk-single-searchs/read` | | **Method** | `POST` | | **Headers** | Same two headers as step 1 | | **Body type** / **Content type** | Raw / JSON (application/json) | | **Request content** | `{ "id": "{{2.data.item._id}}" }` (map `data › item › _id` from the first HTTP module) | | **Parse response** | Yes | ### Use the email In the next modules, map from the second HTTP module: - **Email:** `data › items › 1 › results › emails › 1 › email` - **Certainty:** `data › items › 1 › results › emails › 1 › certainty` - **Status:** `data › items › 1 › status` Make counts array items from 1, so `items[1]` is the first result. ## Make it robust - **Add a filter** after step 3: continue only if `status` is `DEBITED` or `FOUND`. See all [statuses](https://api-doc.icypeas.com/statuses). - **Still in progress?** If `status` is `NONE`, `SCHEDULED` or `IN_PROGRESS`, add another *Sleep* + *HTTP* pair, or use a *Repeater*. - **Errors:** if the first module returns `"success": false`, check the mapped fields. See [Errors](https://api-doc.icypeas.com/errors). ## Even better: let Icypeas call Make Instead of waiting, add a **Webhooks › Custom webhook** trigger in a second scenario, copy its URL, and send it with your search: ```json { "firstname": "{{1.firstName}}", "lastname": "{{1.lastName}}", "domainOrCompany": "{{1.company}}", "custom": { "webhookUrl": "https://hook.eu1.make.com/your-webhook-id", "externalId": "{{1.id}}" } } ``` The second scenario receives the full result as soon as it's ready, with your `externalId` to match it to the original record. See the [webhook payload](https://api-doc.icypeas.com/reference/webhook-search-item). --- # Find and verify emails with Zapier > Use Webhooks by Zapier to call the Icypeas API and find or verify a professional email every time a new lead arrives. Source: https://api-doc.icypeas.com/no-code/zapier Find the email of every new lead automatically. You'll build a Zap with four steps: your trigger, a request to Icypeas, a short delay, and a second request to get the result. > **What you need** > > - A Zapier plan that includes **Webhooks by Zapier** (a premium app). > - Your [Icypeas API key](https://api-doc.icypeas.com/api-key). ### Add your trigger For example *Google Sheets › New Spreadsheet Row*, *HubSpot › New Contact* or *Typeform › New Entry*. It must give you a first name, a last name and a company (or an email to verify). ### Start the search Add an action **Webhooks by Zapier**, event **Custom Request**, and fill in: | Field | Value | | --- | --- | | **Method** | `POST` | | **URL** | `https://app.icypeas.com/api/email-search` | | **Data** | The JSON below, inserting your trigger's fields with the **+** button | | **Headers** | `Authorization` › your API key
`Content-Type` › `application/json` | ```json title="Data" { "firstname": "First Name from step 1", "lastname": "Last Name from step 1", "domainOrCompany": "Company from step 1" } ``` Click **Test step**: you should get `Success: true` and an `Item Id`. For a verification, use `https://app.icypeas.com/api/email-verification` and `{ "email": "Email from step 1" }`. ### Wait Add **Delay by Zapier › Delay For** and set **1 minute**. ### Get the result Add another **Webhooks by Zapier › Custom Request**: | Field | Value | | --- | --- | | **Method** | `POST` | | **URL** | `https://app.icypeas.com/api/bulk-single-searchs/read` | | **Data** | `{ "id": "Item Id from step 2" }` | | **Headers** | Same as step 2 | ### Use the email In the next steps, insert from step 4: - **Items Results Emails Email**: the email. - **Items Results Emails Certainty**: how reliable it is ([see levels](https://api-doc.icypeas.com/certainties)). - **Items Status**: `DEBITED` or `FOUND` when an email was found ([see statuses](https://api-doc.icypeas.com/statuses)). > **Only keep found emails** > > Add a **Filter by Zapier** after step 4: *only continue if* `Items Status` *(Text) exactly matches* `DEBITED`. ## Troubleshooting | Problem | Fix | | --- | --- | | `Unauthorized access.` | Check the `Authorization` header: it must contain your API key only, without `Bearer`. | | `Success: false` with validation errors | A field mapped in step 2 is empty. You need a company and at least a first or last name. | | Status is still `IN_PROGRESS` | Increase the delay or add a second Delay + Custom Request. | --- # Find and verify emails with n8n > Call the Icypeas API from n8n with the HTTP Request and Wait nodes to find or verify professional emails in your workflows. Source: https://api-doc.icypeas.com/no-code/n8n In n8n, an Icypeas lookup is three nodes: **HTTP Request** → **Wait** → **HTTP Request**. Works on n8n cloud and self-hosted. ### Save your API key as a credential In **Credentials › Add credential**, choose **Header Auth** and set: - **Name:** `Authorization` - **Value:** your [API key](https://api-doc.icypeas.com/api-key) Call it *Icypeas*. You'll reuse it in both HTTP nodes. ### Start the search Add an **HTTP Request** node, rename it **Start search**, and set: | Setting | Value | | --- | --- | | **Method** | `POST` | | **URL** | `https://app.icypeas.com/api/email-search` | | **Authentication** | Generic Credential Type › Header Auth › *Icypeas* | | **Send Body** | On, **Body Content Type** JSON, **Specify Body** Using JSON | | **JSON** | See below | ```json title="JSON" { "firstname": "{{ $json.firstname }}", "lastname": "{{ $json.lastname }}", "domainOrCompany": "{{ $json.company }}" } ``` Adapt the `$json.…` expressions to the field names coming from your previous node. ### Wait Add a **Wait** node: *Resume* › **After Time Interval**, **10 seconds**. ### Read the result Add a second **HTTP Request** node, **Read result**: | Setting | Value | | --- | --- | | **Method** | `POST` | | **URL** | `https://app.icypeas.com/api/bulk-single-searchs/read` | | **Authentication** | Header Auth › *Icypeas* | | **JSON** | `{ "id": "{{ $('Start search').item.json.item._id }}" }` | ### Use the email In the next nodes: - Email: `{{ $json.items[0].results.emails[0].email }}` - Certainty: `{{ $json.items[0].results.emails[0].certainty }}` - Status: `{{ $json.items[0].status }}` > **Retry while in progress** > > Add an **If** node after *Read result*: if `status` is `NONE`, `SCHEDULED` or `IN_PROGRESS`, loop back to the Wait node. Otherwise continue. > **Many items at once** > > n8n runs the HTTP node once per item. Single searches are limited to 10 per second: in the node's **Options**, enable **Batching** with 5 items every 1,000 ms. For hundreds of rows, use the [bulk search](https://api-doc.icypeas.com/reference/bulk-search) instead. --- # Authentication > Authenticate Icypeas API requests with your API key in the Authorization header, or sign each request with HMAC-SHA1 and your API secret for extra security. Source: https://api-doc.icypeas.com/authentication Every request must say who you are. There are two ways: the API key alone (simple, works everywhere) or a signed request (more secure). Most people only need the first one. ## Option 1: API key only recommended to start Send two headers with every request: ```http Authorization: YOUR_API_KEY Content-Type: application/json ``` > **Common mistake** > > Put **only** the key in `Authorization`. No `Bearer `, no `Token `, no quotes. `Authorization: Bearer abc123` is rejected with a 401 error. Example: ```js const response = await fetch("https://app.icypeas.com/api/email-verification", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "YOUR_API_KEY", }, body: JSON.stringify({ email: "test@icypeas.com" }), }); console.log(await response.json()); ``` This is the method to use with no-code tools (Make, Zapier, n8n, Google Sheets). ## Option 2: signed requests If your key could be intercepted, or your security policy requires it, sign each request with your **API secret** (on the [API page](https://app.icypeas.com/bo/api), *Reveal the secret Key*). A signature is valid for one URL, one method and one moment, so a captured request can't be replayed. Send these headers: | Header | Value | | --- | --- | | `Authorization` | `YOUR_API_KEY:SIGNATURE` | | `X-ROCK-TIMESTAMP` | The current UTC time in ISO 8601, for example `2026-03-12T12:01:02.000Z` | | `Content-Type` | `application/json` | ### How to compute the signature 1. Take the HTTP method, for example `POST`. 2. Take the path of the URL, for example `/api/a/actions/subscription-information` for `https://app.icypeas.com/api/a/actions/subscription-information`. 3. Take the same timestamp you send in `X-ROCK-TIMESTAMP`. 4. Join the three, **in lower case**: `post/api/a/actions/subscription-information2026-03-12t12:01:02.000z`. 5. Compute the HMAC-SHA1 of that string with your API secret, as a hexadecimal string. **JavaScript** ```js function sign(url, method, secret, timestamp = new Date().toISOString()) { const path = new URL(url).pathname; const payload = `${method}${path}${timestamp}`.toLowerCase(); const signature = crypto.createHmac("sha1", secret).update(payload).digest("hex"); return { signature, timestamp }; } const url = "https://app.icypeas.com/api/email-verification"; const { signature, timestamp } = sign(url, "POST", process.env.ICYPEAS_API_SECRET); const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `${process.env.ICYPEAS_API_KEY}:${signature}`, "X-ROCK-TIMESTAMP": timestamp, }, body: JSON.stringify({ email: "test@icypeas.com" }), }); ``` **Python** ```python import hashlib, hmac, os from datetime import datetime, timezone from urllib.parse import urlparse import requests def sign(url, method, secret): timestamp = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z") payload = f"{method}{urlparse(url).path}{timestamp}".lower() signature = hmac.new(secret.encode(), payload.encode(), hashlib.sha1).hexdigest() return signature, timestamp url = "https://app.icypeas.com/api/email-verification" signature, timestamp = sign(url, "POST", os.environ["ICYPEAS_API_SECRET"]) response = requests.post( url, headers={ "Authorization": f"{os.environ['ICYPEAS_API_KEY']}:{signature}", "X-ROCK-TIMESTAMP": timestamp, }, json={"email": "test@icypeas.com"}, ) print(response.json()) ``` **PHP** ```php true, CURLOPT_POSTFIELDS => json_encode(["email" => "test@icypeas.com"]), CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: " . getenv("ICYPEAS_API_KEY") . ":" . $signature, "X-ROCK-TIMESTAMP: " . $timestamp, ], ]); print_r(json_decode(curl_exec($ch), true)); ``` Examples in more languages (Go, Rust…) are available on the [API page of the app](https://app.icypeas.com/bo/api). ## Authentication errors If authentication fails, you get an HTTP **401**: ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` Check, in this order: API access is activated, the key is complete and has no extra space, there's no `Bearer` prefix, and (for signed requests) your clock is on time and the path includes `/api`. See [Errors](https://api-doc.icypeas.com/errors) for everything else. --- # Errors and troubleshooting > Every error the Icypeas API can return, what it means and how to fix it. HTTP 401 and 429 errors, validation errors, unpaid subscription and search statuses. Source: https://api-doc.icypeas.com/errors Something didn't work? Find the message you got in this page: each error comes with what it means and how to fix it. ## Quick diagnosis | What you see | What it means | How to fix it | | --- | --- | --- | | HTTP **401** `Unauthorized access.` | Icypeas doesn't recognise your key. | Copy the key again from the [API page](https://app.icypeas.com/bo/api), make sure API access is **on**, and send it alone in `Authorization` (no `Bearer`). | | HTTP **429** `You have exceeded the max number of requests per minute.` | You're sending requests too fast. | Wait a few seconds, then retry. Slow down, or switch to [bulk search](https://api-doc.icypeas.com/reference/bulk-search) and [webhooks](https://api-doc.icypeas.com/reference/webhooks). See [rate limits](https://api-doc.icypeas.com/rate-limits). | | HTTP **200** with `"success": false` and `validationErrors` | Your request reached Icypeas but a field is missing or wrong. | Read the `field` and `type` of each error and fix your body. See [below](#validation-errors). | | `UnpaidSubscriptionError` | A payment is pending on your subscription. | Update your payment method in the [billing page](https://app.icypeas.com/bo/billing?o=plans). | | Status `INSUFFICIENT_FUNDS` | You don't have enough credits. | [Get more credits](https://app.icypeas.com/bo/billing?o=plans). | | Status `BAD_INPUT` | The search couldn't run with the data given. | Check the name and the company/domain of that row. | | Status stays `IN_PROGRESS` | The search isn't finished. | Wait a few more seconds and read the result again. | | `SearchNotFoundError` | The `id` or `file` you asked for doesn't exist. | Use the exact `_id` / `file` returned when you started the search. | | Your tool says *invalid JSON* or *unexpected token* | The body isn't valid JSON. | Check quotes (use `"` not `'` or `“`), commas and brackets. Paste it in a [JSON validator](https://jsonlint.com). | | Error on `Content-Type` / empty body received | The `Content-Type` header is missing. | Add `Content-Type: application/json`. | > **Always check `success`** > > Validation errors come back with HTTP status **200**, not 400. Don't rely on the HTTP code alone: check that `success` is `true`. ## Validation errors When the body doesn't match what an endpoint expects, the API answers: ```json { "success": false, "validationErrors": [ { "expected": "string", "type": "required", "field": "domainOrCompany", "message": "validation_required" } ] } ``` Each entry tells you exactly what's wrong: | Key | Meaning | Example | | --- | --- | --- | | `field` | The field in error. Nested fields use dots. | `domainOrCompany`, `custom.webhookUrl` | | `type` | What kind of problem. | `required` | | `expected` | What the API wanted. | `string` | | `actual` | What it received (may be absent). | `undefined` | | `message` | A stable code you can test in your code. | `validation_required` | Here, the fix is to add `domainOrCompany` to the body. ## All validation error types | `type` | `message` | What's wrong | How to fix it | | --- | --- | --- | --- | | `required` | `validation_required` | The field is missing, `null` or `undefined`. | Add the field. Check its spelling and case (`firstname`, not `firstName`). | | `string` | `validation_string` | The field must be text. | Wrap the value in quotes: `"123"`. | | `stringEmpty` | `validation_string_empty` | The field is an empty string. | Fill it, or remove it if it's optional. | | `stringEnum` | `validation_string_enum` | The value isn't one of the allowed values. | Use one of the values listed in the endpoint reference, e.g. `email-search`. | | `email` | `validation_email` | Not a valid email address. | Check for typos and spaces. | | `url` | `validation_url` | Not a valid URL. | Use a full URL, starting with `https://`. | | `number` | `validation_number` | The field must be a number. | Remove the quotes: `50`, not `"50"`. | | `numberMin` / `numberMax` | `validation_number_min` / `validation_number_max` | The number is too small or too big. | Respect the limits of the endpoint (e.g. `limit` ≤ 100). | | `boolean` | `validation_boolean` | Must be `true` or `false`. | Remove the quotes: `true`, not `"true"`. | | `array` | `validation_array` | The field must be a list `[ ]`. | Wrap values in brackets. | | `arrayEmpty` | `validation_array_empty` | The list is empty. | Add at least one item. | | `arrayMin` / `arrayMax` | `validation_array_min` / `validation_array_max` | Too few or too many items. | A bulk search accepts up to 5,000 rows, a bulk scrape up to 50. | | `arrayLength` | `validation_array_length` | A row doesn't have the right number of items. | An `email-search` row has 3 items: first name, last name, company. | | `arrayEnum` | `validation_array_enum` | A list contains a value that isn't allowed. | Check the allowed values. | | `FirstLastMandatoryDiscoveryError` | `first_last_mandatory_discovery_error` | Both first name and last name are empty. | Give at least one of them. | | `ExternalIdsArrayShouldMatchDataArrayError` | `external_ids_array_should_match_data_array_error` | `custom.externalIds` and `data` don't have the same length. | Give exactly one id per row. | | `ModeOrIdShouldBeUsedError` | `mode_or_id_should_be_used_error` | Reading results needs either `mode` or `id`. | Send `{"id": "…"}` or `{"mode": "single"}`. | | `SearchNotFoundError` | `search_not_found_error` | The search or file doesn't exist. | Check the id. | | `FilePropertyError` / `InvalidFileError` | `file_property_error` / `invalid_file_error` | The bulk file is invalid. | Check the `file` id you sent. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | A payment is pending. | Update your payment method. | ## HTTP errors **401 Unauthorized** ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` Your API key is missing, wrong, or API access is turned off. See [Authentication](https://api-doc.icypeas.com/authentication). **429 Too many requests** ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` Wait, then retry with a growing delay (for example 2 s, 4 s, 8 s). See [Rate limits](https://api-doc.icypeas.com/rate-limits). ## Still stuck? Email [support@icypeas.com](mailto:support@icypeas.com) with the endpoint, the body you sent (**without your API key**) and the response you got. See [Support](https://api-doc.icypeas.com/support). --- # Result statuses > Every status an Icypeas search can have (NONE, SCHEDULED, IN_PROGRESS, FOUND, DEBITED, NOT_FOUND…), what it means and what to do next. Source: https://api-doc.icypeas.com/statuses Every search has a `status`. It tells you whether the search is finished and whether something was found. ## Still running Read the result again in a few seconds. | Status | Meaning | | --- | --- | | `NONE` | The search is queued, waiting to be scheduled. | | `SCHEDULED` | The search will run in the next batch. | | `IN_PROGRESS` | Icypeas is looking for the email or verifying it right now. | ## Finished | Status | Meaning | Next step | | --- | --- | --- | | `FOUND` / `DEBITED` | A result was found and the credits were debited. | Use the email in `results.emails`. | | `NOT_FOUND` / `DEBITED_NOT_FOUND` | The search ran but found nothing (or the email doesn't exist). | Try another spelling, or the company website instead of its name. | | `BAD_INPUT` | The search couldn't run because some information is missing. | Fix the input and start a new search. | | `INSUFFICIENT_FUNDS` | Not enough credits to run this search. | [Get more credits](https://app.icypeas.com/bo/billing?o=plans), then start it again. | | `ABORTED` | You cancelled the search before this item ran. | Nothing. | > **In code** > > A search is finished when its status is **not** `NONE`, `SCHEDULED` or `IN_PROGRESS`. Test for those three rather than listing all the final ones. Found an email? Its reliability is given by its [certainty](https://api-doc.icypeas.com/certainties). --- # Email certainty levels > What the certainty levels of the Icypeas API mean (ultra_sure, very_sure, probable, undeliverable, not_found) and which emails are safe to send to. Source: https://api-doc.icypeas.com/certainties Every email returned by Icypeas comes with a `certainty`: how sure we are that the address exists and will receive your message. | `certainty` | Applies to | Meaning | Send? | | --- | --- | --- | --- | | `ultra_sure` / `very_sure` | All searches | 99% confidence. Expected bounce rate below 1%. | ✅ Yes | | `probable` | All searches | 95% confidence. Expected bounce rate below 5%. | 👍 Usually | | `undeliverable` | Email verification | The address does not exist. | ❌ No | | `not_found` | Email search, domain search | No email was found for this person or domain. | Nothing to send | | `not_found` | Email verification | We can't tell whether the address exists (for example the server accepts everything). | ⚠️ At your own risk | Where to find it in a result: ```json "results": { "emails": [ { "email": "pierre.landoin@icypeas.com", "certainty": "ultra_sure", "mxProvider": "google", "mxRecords": ["google.com"] } ] } ``` `mxProvider` tells you who hosts the mailbox. See [MX providers](https://api-doc.icypeas.com/mx-providers). --- # Credit costs > How many Icypeas credits each API endpoint uses. Email search costs 1 credit per email found, email verification 0.1 credit, lead database 0.02 credit per result. Source: https://api-doc.icypeas.com/credits The API uses the same credits as the Icypeas app. Most endpoints only charge you when they return a result. | Endpoint | Cost | | --- | --- | | [Email search](https://api-doc.icypeas.com/reference/email-search) `/email-search` | **1 credit** per email found. Free if nothing is found. | | [Email verification](https://api-doc.icypeas.com/reference/email-verification) `/email-verification` | **0.1 credit** per email verified. | | [Domain search](https://api-doc.icypeas.com/reference/domain-search) `/domain-search` | **1 credit** if emails are found for the domain. | | [Bulk search](https://api-doc.icypeas.com/reference/bulk-search) `/bulk-search` | Same as the task, per row. | | [Profile scraper](https://api-doc.icypeas.com/reference/scrape-profile) `/scrape/profile` | **1.5 credits** per profile found. | | [Company scraper](https://api-doc.icypeas.com/reference/scrape-company) `/scrape/company` | **0.5 credit** per company found. | | [Profile URL finder](https://api-doc.icypeas.com/reference/find-profile-url) `/url-search/profile` | **1 credit** per URL found. | | [Company URL finder](https://api-doc.icypeas.com/reference/find-company-url) `/url-search/company` | **1 credit** per URL found. | | [Find people](https://api-doc.icypeas.com/reference/find-people) `/find-people` | **0.02 credit** per result (100 results = 2 credits). | | [Find companies](https://api-doc.icypeas.com/reference/find-companies) `/find-companies` | **0.02 credit** per result (100 results = 2 credits). | | `/find-people/count`, `/find-companies/count` | **Free** | | [Reverse email lookup](https://api-doc.icypeas.com/reference/reverse-email-lookup) `/reverse-email-lookup` | **10 credits** per profile found. | | Every other endpoint (results, files, subscription…) | **Free** | Costs may change. The current prices are always shown at the bottom of the [plans page](https://app.icypeas.com/bo/billing?o=plans). You can check your balance at any time with the [subscription endpoint](https://api-doc.icypeas.com/reference/subscription). --- # Rate limits > How many requests per second or per minute each Icypeas API endpoint accepts, and how to handle HTTP 429 errors. Source: https://api-doc.icypeas.com/rate-limits To keep the service fast for everyone, each endpoint accepts a maximum number of calls. Go over it and you get an HTTP **429** error. | Endpoint | What it does | Limit | | --- | --- | --- | | [`/email-search`](https://api-doc.icypeas.com/reference/email-search) | Find one email | 10 calls / second | | [`/email-verification`](https://api-doc.icypeas.com/reference/email-verification) | Verify one email | 10 calls / second | | [`/domain-search`](https://api-doc.icypeas.com/reference/domain-search) | Scan one domain | 10 calls / second | | [`/bulk-search`](https://api-doc.icypeas.com/reference/bulk-search) | Start a bulk search | 1 call / second, up to 5,000 rows each | | [`/bulk-single-searchs/read`](https://api-doc.icypeas.com/reference/get-results) | Read results | **30 calls / minute** | | [`/search-files/read`](https://api-doc.icypeas.com/reference/bulk-files) | Bulk search progress | **15 calls / minute** | | [`/scrape/profile`](https://api-doc.icypeas.com/reference/scrape-profile), [`/scrape/company`](https://api-doc.icypeas.com/reference/scrape-company) | Scrape one profile or company | 20 calls / second | | `/scrape` | Scrape in bulk | 20 calls / second, up to 50 rows each | | [`/url-search/profile`](https://api-doc.icypeas.com/reference/find-profile-url), [`/url-search/company`](https://api-doc.icypeas.com/reference/find-company-url) | Find one profile or company URL | 20 calls / second | | `/url-search` | Find URLs in bulk | 20 calls / second, up to 50 rows each | | [`/find-people`](https://api-doc.icypeas.com/reference/find-people), [`/find-companies`](https://api-doc.icypeas.com/reference/find-companies) | Search the lead database | 30 calls / second | | `/find-people/count`, `/find-companies/count` | Count leads | 10 calls / second | | [`/reverse-email-lookup`](https://api-doc.icypeas.com/reference/reverse-email-lookup) | One reverse lookup | 20 calls / second | | [`/reverse-email-lookups`](https://api-doc.icypeas.com/reference/reverse-email-lookup) | Reverse lookups in bulk | 15 calls / second, up to 50 rows each | Limits may change. ## Staying under the limits - **Reading results is the tightest limit** (30 per minute). Wait at least 3 seconds between two reads, or better, use [webhooks](https://api-doc.icypeas.com/reference/webhooks): they don't count against any limit. - **Use bulk endpoints for lists.** One bulk call replaces up to 5,000 single calls. - **On a 429, slow down and retry.** Wait 2 seconds, then 4, then 8… ```js title="Retry with backoff" async function callIcypeas(url, body, attempt = 0) { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: process.env.ICYPEAS_API_KEY }, body: JSON.stringify(body), }); if (res.status === 429 && attempt < 5) { await new Promise((r) => setTimeout(r, 2000 * 2 ** attempt)); return callIcypeas(url, body, attempt + 1); } return res.json(); } ``` --- # Pagination > How to page through long lists of results with the Icypeas API, using the sorts array or the pagination token. Source: https://api-doc.icypeas.com/pagination Some endpoints return long lists, so they send them one page at a time. There are two systems, depending on the endpoint. | Endpoints | System | | --- | --- | | [Read results](https://api-doc.icypeas.com/reference/get-results), [Bulk search files](https://api-doc.icypeas.com/reference/bulk-files) | `sorts` array (below) | | [Find people](https://api-doc.icypeas.com/reference/find-people#pagination), [Find companies](https://api-doc.icypeas.com/reference/find-companies#pagination) | `pagination.token`, explained on their pages | ## The `sorts` array ### First page Send the number of items you want with `limit`: ```json { "mode": "bulk", "limit": 30 } ``` The response contains the `total` number of items and a `sorts` array. Keep `sorts`: it's your bookmark. ```json { "success": true, "items": ["…30 items…"], "total": 1000, "sorts": [[1709218821000, "xxxx"], [1709218700000, "yyyy"]] } ``` ### Next pages Send the same request again, adding `next: true` and the `sorts` you received: ```json { "mode": "bulk", "limit": 30, "next": true, "sorts": [[1709218821000, "xxxx"], [1709218700000, "yyyy"]] } ``` Use `next: false` to go back one page instead. Each response gives you a new `sorts`: always use the latest one. Stop when a page comes back empty. --- # MX providers > The list of email hosting providers (Google, Microsoft, OVH, Proofpoint…) the Icypeas API can return in the mxProvider field. Source: https://api-doc.icypeas.com/mx-providers For every email found, Icypeas tells you who hosts the mailbox in `mxProvider`, and gives the raw `mxRecords`. Useful to adapt your sending strategy per provider. | `mxProvider` | Provider | | --- | --- | | `google` | Google (Google Workspace, Gmail) | | `microsoft` | Microsoft (Microsoft 365, Outlook) | | `amazon` | Amazon | | `apple` | Apple | | `barracuda` | Barracuda Networks | | `cisco` | Cisco | | `csc` | Corporation Service Company | | `gandi` | Gandi | | `ionos` | Ionos | | `mimecast` | Mimecast | | `ovh` | OVH | | `proofpoint` | Proofpoint | | `safenames` | Safenames | | `other` | Any other provider | This list may grow over time: treat unknown values as `other`. --- # Frequently asked questions > Answers to the most common questions about the Icypeas API, pricing, speed, limits, webhooks and no-code tools. Source: https://api-doc.icypeas.com/faq ### Do I need to know how to code? No. Follow the [quickstart](https://api-doc.icypeas.com/getting-started) to run your first search by pasting a command, or use [Google Sheets, Make, Zapier or n8n](https://api-doc.icypeas.com/no-code). You can also ask an [AI assistant](https://api-doc.icypeas.com/ai-agents) to write the integration for you. ### Is the API included in my plan? Yes. Every Icypeas account can activate the API on the [API page](https://app.icypeas.com/bo/api). It uses the same credits as the app: see [credit costs](https://api-doc.icypeas.com/credits). ### Why don't I get the email right away? Icypeas checks emails live with the company's mail server, which takes a few seconds. You start the search, then you [read its result](https://api-doc.icypeas.com/reference/get-results) or receive it on a [webhook](https://api-doc.icypeas.com/reference/webhooks). See [How it works](https://api-doc.icypeas.com/how-it-works). ### How long does a search take? Usually a few seconds for a single search. A bulk search of thousands of rows takes a few minutes. ### Am I charged when no email is found? Not for an email search: you pay 1 credit only when an email is found. A verification always costs 0.1 credit. See [credit costs](https://api-doc.icypeas.com/credits). ### How many emails can I find at once? Up to 5,000 per [bulk search](https://api-doc.icypeas.com/reference/bulk-search) call, one call per second. ### What's the difference between `ultra_sure` and `probable`? `ultra_sure` / `very_sure` mean a bounce rate below 1%, `probable` below 5%. See [Email certainty](https://api-doc.icypeas.com/certainties). ### Should I give the company name or its website? The website (domain) when you have it: `icypeas.com` is more precise than `Icypeas`. Both work. ### My key doesn't work, what do I do? Make sure API access is on, and that you send the key alone in the `Authorization` header, without `Bearer`. See [Errors](https://api-doc.icypeas.com/errors). ### Can I use my own ids to match results? Yes. Pass `custom.externalId` with a single search or `custom.externalIds` with a bulk search: they come back with the results and in webhooks. ### How do I check how many credits I have left? Call the [subscription endpoint](https://api-doc.icypeas.com/reference/subscription). It's free. --- # Changelog > All the changes made to the Icypeas API and its documentation, newest first. Source: https://api-doc.icypeas.com/changelog What changed in the Icypeas API, newest first. ## 1.16.0 · November 3, 2026 - New documentation, with step-by-step guides for non-developers. - New [no-code guides](https://api-doc.icypeas.com/no-code): Google Sheets, Make, Zapier and n8n. - Code samples in cURL, JavaScript, Python, PHP and PowerShell on every endpoint. - [AI-ready docs](https://api-doc.icypeas.com/ai-agents): `llms.txt`, `llms-full.txt`, Markdown version of every page and an OpenAPI 3.1 spec. - New [Errors and troubleshooting](https://api-doc.icypeas.com/errors) page. - `quotas.daily` in [subscription information](https://api-doc.icypeas.com/reference/subscription) is deprecated: it's not used anymore and should be ignored. - Clarified [lead database pagination](https://api-doc.icypeas.com/reference/find-people#token-expiration): each token is valid 15 minutes and every page returns a new one. ## 1.15.0 · July 16, 2026 - New filters in [find people](https://api-doc.icypeas.com/reference/find-people). - `location` filters are more reliable when searching for specific cities or regions in a country. ## 1.14.0 · April 3, 2026 - New fields in the [company scraper](https://api-doc.icypeas.com/reference/scrape-company). - New filters in [find companies](https://api-doc.icypeas.com/reference/find-companies). ## 1.13.2 · March 22, 2026 - Updated [credit costs](https://api-doc.icypeas.com/credits). ## 1.13.1 · February 25, 2026 - `companyId` filter in [find companies](https://api-doc.icypeas.com/reference/find-companies). ## 1.13.0 · January 15, 2026 - New [reverse email lookup](https://api-doc.icypeas.com/reference/reverse-email-lookup). ## 1.12.0 · November 25, 2025 - `currentCompanyWebsite`, `pastCompanyWebsite`, `currentCompanyId` and `pastCompanyId` filters in [find people](https://api-doc.icypeas.com/reference/find-people). ## 1.11.6 · August 28, 2025 - `domain` filter in [find companies](https://api-doc.icypeas.com/reference/find-companies). ## 1.11.5 · August 25, 2025 - `urn` field in the [company scraper](https://api-doc.icypeas.com/reference/scrape-company). ## 1.11.4 · July 29, 2025 - `keyword` filter in [find companies](https://api-doc.icypeas.com/reference/find-companies). ## 1.11.3 · July 23, 2025 - Published the [credit costs](https://api-doc.icypeas.com/credits). ## 1.11.2 · July 10, 2025 - Removed the `name` field from [`/search-files/read`](https://api-doc.icypeas.com/reference/bulk-files). ## 1.11.1 · July 9, 2025 - `keyword` filter in [find people](https://api-doc.icypeas.com/reference/find-people). ## 1.11.0 · July 4, 2025 - New [find people](https://api-doc.icypeas.com/reference/find-people) and [find companies](https://api-doc.icypeas.com/reference/find-companies) endpoints. ## 1.10.1 · May 30, 2025 - New screenshots, typo fixes. ## 1.10.0 · May 9, 2025 - More fields in the [company scraper](https://api-doc.icypeas.com/reference/scrape-company). ## 1.9.1 · May 7, 2025 - `currency` in [subscription information](https://api-doc.icypeas.com/reference/subscription). ## 1.9.0 · April 23, 2025 - New URL finder endpoints ([profiles](https://api-doc.icypeas.com/reference/find-profile-url), [companies](https://api-doc.icypeas.com/reference/find-company-url)) and their rate limits. ## 1.8.0 · January 13, 2025 - New scraping endpoints and their rate limits. ## 1.7.0 · September 2, 2024 - Results of a bulk search can be included directly in the end-of-bulk webhook. ## 1.6.1 · August 16, 2024 - Rate limit error documented on every endpoint. ## 1.6.0 · August 13, 2024 - Custom ids and webhooks for single and bulk searches. - Simpler API calls. - Improved getting started section. ## 1.5.2 · July 1, 2024 - [Rate limits](https://api-doc.icypeas.com/rate-limits). ## 1.5.1 · March 6, 2024 - MX providers and MX records in single and bulk searches. ## 1.4.1 · November 15, 2023 - `UnpaidSubscriptionError`. ## 1.4.0 · November 8, 2023 - Clarified insufficient credits. - Requests can be authenticated with the API key only. ## 1.3.0 · November 2, 2023 - Changelog. - `status`, `task` and reserved fields in [`/search-files/read`](https://api-doc.icypeas.com/reference/bulk-files). --- # Get help > How to contact Icypeas support about the API, and what to include in your message to get a fast answer. Source: https://api-doc.icypeas.com/support Stuck? We're happy to help. - **Email:** [support@icypeas.com](mailto:support@icypeas.com) - **Chat:** the *Help* button in the bottom-right corner of the [Icypeas app](https://app.icypeas.com), or the chat bubble on this site. ## Get a fast answer Include in your message: 1. The **endpoint** you called, for example `POST /email-search`. 2. The **body** you sent. 3. The **response** you got, and the time of the request. > **Important** > > Never send your API key or API secret, to anyone, including support. Before writing, the answer might already be in [Errors and troubleshooting](https://api-doc.icypeas.com/errors) or the [FAQ](https://api-doc.icypeas.com/faq). --- # API reference > Base URL, authentication, request and response format of the Icypeas REST API, and the list of every endpoint. Source: https://api-doc.icypeas.com/reference/overview Everything you need to call the Icypeas API: one base URL, one header, JSON in and JSON out. ## Basics | | | | --- | --- | | **Base URL** | | | **Authentication** | `Authorization: YOUR_API_KEY` header. See [Authentication](https://api-doc.icypeas.com/authentication). | | **Format** | Send `Content-Type: application/json` and a JSON body. Responses are JSON. | | **Method** | `POST` for almost everything, `GET` for single scraping. | | **Success** | Check `"success": true` in the body. Input errors return HTTP 200 with `"success": false`. See [Errors](https://api-doc.icypeas.com/errors). | | **Speed** | Each endpoint has a [rate limit](https://api-doc.icypeas.com/rate-limits). | | **OpenAPI** | Download the [OpenAPI 3.1 spec](https://api-doc.icypeas.com/openapi.json) for Postman, Insomnia or code generators. | ## Endpoints ### Emails | Endpoint | What it does | | --- | --- | | [`POST /email-search`](https://api-doc.icypeas.com/reference/email-search) | Find a person's professional email from their name and company. | | [`POST /email-verification`](https://api-doc.icypeas.com/reference/email-verification) | Check that an email address exists. | | [`POST /domain-search`](https://api-doc.icypeas.com/reference/domain-search) | Find the role-based emails of a domain. | | [`POST /bulk-search`](https://api-doc.icypeas.com/reference/bulk-search) | Run up to 5,000 of the above in one call. | ### Results | Endpoint | What it does | | --- | --- | | [`POST /bulk-single-searchs/read`](https://api-doc.icypeas.com/reference/get-results) | Get the result of one search, or list your searches. | | [`POST /search-files/read`](https://api-doc.icypeas.com/reference/bulk-files) | Follow the progress and stats of your bulk searches. | ### Profiles, companies and leads | Endpoint | What it does | | --- | --- | | [`GET /scrape/profile`](https://api-doc.icypeas.com/reference/scrape-profile) · `POST /scrape` | Get a person's data from their profile URL. | | [`GET /scrape/company`](https://api-doc.icypeas.com/reference/scrape-company) · `POST /scrape` | Get a company's data from its page URL. | | [`POST /url-search/profile`](https://api-doc.icypeas.com/reference/find-profile-url) · `POST /url-search` | Find a person's profile URL. | | [`POST /url-search/company`](https://api-doc.icypeas.com/reference/find-company-url) · `POST /url-search` | Find a company's page URL. | | [`POST /reverse-email-lookup`](https://api-doc.icypeas.com/reference/reverse-email-lookup) | Find the profile behind an email. | | [`POST /find-people`](https://api-doc.icypeas.com/reference/find-people) | Search people in the lead database. | | [`POST /find-companies`](https://api-doc.icypeas.com/reference/find-companies) | Search companies in the lead database. | ### Account | Endpoint | What it does | | --- | --- | | [`POST /a/actions/subscription-information`](https://api-doc.icypeas.com/reference/subscription) | Your plan and remaining credits. | ### Webhooks (sent by Icypeas to you) | Webhook | When | | --- | --- | | [Search item processed](https://api-doc.icypeas.com/reference/webhook-search-item) | Each time a single search, or a row of a bulk search, is done. | | [Bulk search done](https://api-doc.icypeas.com/reference/webhook-bulk-done) | When a whole bulk search is done. | --- # Email search: find a professional email > POST /email-search: find the professional email address of a person from their first name, last name and company name or domain. 1 credit per email found. Source: https://api-doc.icypeas.com/reference/email-search Find the professional email of one person from their name and their company. Costs **1 credit** only if an email is found. **How to use it:** 1. Send the person's `firstname`, `lastname` and `domainOrCompany`. You get back a search `_id`. 2. A few seconds later, read the result with [`/bulk-single-searchs/read`](https://api-doc.icypeas.com/reference/get-results), or receive it on a [webhook](https://api-doc.icypeas.com/reference/webhook-search-item). > **Tips for better results** > > - Use the company **website** (`icypeas.com`) rather than its name (`Icypeas`) when you have it. > - You can leave `firstname` **or** `lastname` empty, but not both. > - For more than a few people, use the [bulk search](https://api-doc.icypeas.com/reference/bulk-search): it handles thousands of rows in parallel. **Endpoint:** `POST https://app.icypeas.com/api/email-search` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `firstname` | string | yes | The firstname of the person you want to find. Rule: can be empty if a lastname is set. | | `lastname` | string | yes | The lastname of the person you want to find. Rule: can be empty if a firstname is set. | | `domainOrCompany` | string | yes | The domain or the company name of the person you want to find. | | `custom` | object | no | Optional settings for this search: a webhook URL and/or your own id. | | `custom.webhookUrl` | url | no | A URL Icypeas calls with the result as soon as this search is done. See the 'Search item processed' webhook. | | `custom.externalId` | string | no | Your own id for this search (for example a CRM id). It comes back with the result and in webhooks. Icypeas doesn't check that it's unique. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/email-search" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "firstname": "John", "lastname": "Doe", "domainOrCompany": "icypeas.com" }' ``` #### Responses ##### 200: Response when your input is good. ```json { "success": true, "item": { "_id": "#YOUR_SEARCH_ID#", "status": "NONE" } } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search has been taken into account | | `item` | object | Information about your search | | `item._id` | string | Search id to follow its progression | | `item.status` | string | Status of this search (NONE right after creation). See the Result statuses page. | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "string", "type": "required", "field": "firstname", "message": "validation_required" }, { "expected": "string", "type": "required", "field": "lastname", "message": "validation_required" }, { "expected": "string", "type": "required", "field": "domainOrCompany", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `firstname`, `lastname`, `domainOrCompany` | Error sent when the field is missing | | `string` | `validation_string` | `firstname`, `lastname`, `domainOrCompany`, `custom.externalId` | Error sent when the field is not a string | | `url` | `validation_url` | `custom.webhookUrl` | Error sent when the field is not a valid URL | | `stringEmpty` | `validation_string_empty` | `firstname`, `lastname`, `domainOrCompany` | Error sent when the field is empty | | `FirstLastMandatoryDiscoveryError` | `first_last_mandatory_discovery_error` | `firstname`, `lastname` | Error sent when you need to at least specify a firstname or a lastname and you left both empty | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `user` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Then: read the result ```bash curl -X POST "https://app.icypeas.com/api/bulk-single-searchs/read" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "id": "THE_ID_YOU_RECEIVED" }' ``` The email is in `items[0].results.emails[0].email`, its reliability in `certainty`. See [Read results](https://api-doc.icypeas.com/reference/get-results) and the [step-by-step quickstart](https://api-doc.icypeas.com/getting-started). --- # Email verification: check that an email exists > POST /email-verification: check whether a professional email address exists and is safe to send to. 0.1 credit per verification. Source: https://api-doc.icypeas.com/reference/email-verification Check that an email address exists before you send to it. Costs **0.1 credit** per verification. **How to use it:** 1. Send the `email`. You get back a search `_id`. 2. A few seconds later, read the verdict with [`/bulk-single-searchs/read`](https://api-doc.icypeas.com/reference/get-results), or receive it on a [webhook](https://api-doc.icypeas.com/reference/webhook-search-item). 3. Look at `items[0].results.emails[0].certainty`: `ultra_sure` / `very_sure` / `probable` mean the address exists, `undeliverable` means it doesn't. See [Email certainty](https://api-doc.icypeas.com/certainties). > **Verifying a list?** > > Use the [bulk search](https://api-doc.icypeas.com/reference/bulk-search) with the `email-verification` task: up to 5,000 addresses per call. **Endpoint:** `POST https://app.icypeas.com/api/email-verification` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | The email address you want to verify. | | `custom` | object | no | Optional settings for this search: a webhook URL and/or your own id. | | `custom.webhookUrl` | url | no | A URL Icypeas calls with the result as soon as this search is done. See the 'Search item processed' webhook. | | `custom.externalId` | string | no | Your own id for this search (for example a CRM id). It comes back with the result and in webhooks. Icypeas doesn't check that it's unique. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/email-verification" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "email": "example-email@icypeas.com" }' ``` #### Responses ##### 200: Response when your input is good. ```json { "success": true, "item": { "_id": "#YOUR_SEARCH_ID#", "status": "NONE" } } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search has been taken into account | | `item` | object | Information about your search | | `item._id` | string | Search id to follow its progression | | `item.status` | string | Status of this search (NONE right after creation). See the Result statuses page. | ##### 200 (validation error): Response when there are validation errors ```json { "validationErrors": [ { "expected": "email", "type": "required", "field": "email", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `email` | Error sent when the field is missing | | `stringEmpty` | `validation_string_empty` | `email` | Error sent when the field is empty | | `email` | `validation_email` | `email` | Error sent when the field is not an email address | | `string` | `validation_string` | `custom.externalId` | Error sent when the field is not a string | | `url` | `validation_url` | `custom.webhookUrl` | Error sent when the field is not a valid URL | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `user` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | New to the API? Follow the [verification quickstart](https://api-doc.icypeas.com/quickstart/verify-email). --- # Domain search: find the role-based emails of a company > POST /domain-search: scan a domain or company to find its role-based email addresses such as contact@, sales@ or hello@. 1 credit if emails are found. Source: https://api-doc.icypeas.com/reference/domain-search Scan a company's domain to find its role-based addresses: `contact@`, `sales@`, `admin@`… Costs **1 credit** if emails are found. Send a `domainOrCompany`, get back a search `_id`, then [read the result](https://api-doc.icypeas.com/reference/get-results). The addresses found are in `items[0].results.emails`. > **Tip** > > To scan many domains, use the [bulk search](https://api-doc.icypeas.com/reference/bulk-search) with the `domain-search` task. **Endpoint:** `POST https://app.icypeas.com/api/domain-search` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `domainOrCompany` | string | yes | The domain or the company name you want to scan. | | `custom` | object | no | Optional settings for this search: a webhook URL and/or your own id. | | `custom.webhookUrl` | url | no | A URL Icypeas calls with the result as soon as this search is done. See the 'Search item processed' webhook. | | `custom.externalId` | string | no | Your own id for this search (for example a CRM id). It comes back with the result and in webhooks. Icypeas doesn't check that it's unique. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/domain-search" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "domainOrCompany": "icypeas.com" }' ``` #### Responses ##### 200: Response when your input is good. ```json { "success": true, "item": { "_id": "#YOUR_SEARCH_ID#", "status": "NONE" } } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search has been taken into account | | `item` | object | Information about your search | | `item._id` | string | Search id to follow its progression | | `item.status` | string | Status of this search (NONE right after creation). See the Result statuses page. | ##### 200 (validation error): Response when there are validation errors ```json { "validationErrors": [ { "expected": "string", "type": "required", "field": "domainOrCompany", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `domainOrCompany` | Error sent when the field is missing | | `string` | `validation_string` | `domainOrCompany`, `custom.externalId` | Error sent when the field should be a string but is not | | `url` | `validation_url` | `custom.webhookUrl` | Error sent when the field is not a valid URL | | `stringEmpty` | `validation_string_empty` | `domainOrCompany` | Error sent when the field is an empty string | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `user` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Bulk search: find or verify up to 5,000 emails per call > POST /bulk-search: find emails, verify emails or scan domains for up to 5,000 rows in a single API call, with custom ids and webhooks. Source: https://api-doc.icypeas.com/reference/bulk-search Process a whole list in one call: up to **5,000 rows** per request. Icypeas runs them in parallel and you get the results when they're ready. You send three things: - a `name`, to find your bulk search later (it also appears in the app, under *Bulks*); - a `task`: `email-search`, `email-verification` or `domain-search`; - the `data`: one list per row. You get back a `file` id. Follow the progress with [`/search-files/read`](https://api-doc.icypeas.com/reference/bulk-files), then fetch the rows with [`/bulk-single-searchs/read`](https://api-doc.icypeas.com/reference/get-results). Or skip the waiting with [webhooks](https://api-doc.icypeas.com/reference/webhooks). > **Limits** > > - 5,000 rows maximum per call. Split bigger lists. > - 1 call per second. See [rate limits](https://api-doc.icypeas.com/rate-limits). ## How to fill `data` **Find emails** Each row has **3 items**: first name, last name, company name or domain. First name **or** last name can be empty, not both. ```json { "task": "email-search", "name": "Prospects March", "data": [ ["John", "Doe", "icypeas.com"], ["Charline", "Lestelle", "Google"], ["", "Avocado", "yelp.fr"], ["Jeanine", "", "Comodoro LLC"] ] } ``` **Verify emails** Each row has **1 item**: the email address. ```json { "task": "email-verification", "name": "Newsletter clean-up", "data": [ ["john.doe@icypeas.com"], ["charline.lestelle@google.com"], ["jeanine@comodoro.com"] ] } ``` **Scan domains** Each row has **1 item**: the domain or company name. ```json { "task": "domain-search", "name": "Partner domains", "data": [ ["icypeas.com"], ["google.com"], ["yelp.fr"], ["Comodoro LLC"] ] } ``` ## Match results with your own ids Give each row your own id (a CRM id, a row number…) with `custom.externalIds`. It has to be as long as `data`: one id per row, in the same order. Ids come back in each result as `userData.externalId`. Icypeas doesn't check that they're unique. ```json { "task": "email-search", "name": "CRM sync", "data": [ ["John", "Doe", "icypeas.com"], ["Charline", "Lestelle", "google.com"] ], "custom": { "externalIds": ["crm-1042", "crm-1043"], "webhookUrlItem": "https://example.com/icypeas/row-done", "webhookUrlBulkDone": "https://example.com/icypeas/bulk-done" } } ``` - `webhookUrlItem` is called for each row as soon as it's processed ([payload](https://api-doc.icypeas.com/reference/webhook-search-item)). - `webhookUrlBulkDone` is called once when the whole list is done ([payload](https://api-doc.icypeas.com/reference/webhook-bulk-done)). Add `"includeResultsInWebhook": true` to receive every result in that single call. **Endpoint:** `POST https://app.icypeas.com/api/bulk-search` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. Start a new bulk search from the API. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Name of the bulk search you are creating. | | `task` | string | yes | The type of bulk search you want to do. Rule: has to be either 'domain-search', 'email-search' or 'email-verification'. | | `data` | array | yes | Emails, prospects or domains you want to analyze. | | `data[].row` | array | yes | Each row you want to be analyzed. If you are doing an 'email-search' you should have 3 items in each row.In other modes it's only 1 item. | | `custom` | object | no | Custom object for passing custom webhooks and/or custom ids that will be added to each row | | `custom.webhookUrlItem` | url | no | Custom webhook to be called each time a row is processed. It receives the result of that row. See the 'Search item processed' webhook. | | `custom.webhookUrlBulkDone` | url | no | Custom webhook to be called when the whole bulk search is processed. It receives the statistics of the bulk search (and the results if includeResultsInWebhook is true). See the 'Bulk search done' webhook. | | `custom.externalIds` | array | no | Array of strings. Each id should correspond to each row in your data array. So, if provided, the length of externalIds should match the length of your data array. We do not check for the uniqueness of your IDs, you are responsible of that. | | `custom.includeResultsInWebhook` | boolean | no | If provided, we add the results of your search directly in the webhook called at the end of the bulk. Please if your bulk search has more than 1000 items, make sure you have properly configured your webhook and server to accept large POST requests. If not, do not use this option. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/bulk-search" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "name": "Icypeas Prospects to analyze", "task": "email-search", "data": [ [ "John", "Doe", "icypeas.com" ], [ "John", "Doe", "Icypeas" ] ] }' ``` #### Responses ##### 200: Response when your input is good. ```json { "success": true, "status": "in_progress", "file": "#FILEID#" } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | This field lets you know if your request was successful | | `status` | string | The status of the bulk search. | | `file` | string | The new file's id. | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "string", "type": "required", "field": "user", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `user`, `name`, `task`, `data` | Error sent when the field is missing | | `string` | `validation_string` | `user`, `name`, `task` | Error sent when the field is not a string | | `stringEmpty` | `validation_string_empty` | `user`, `name`, `task` | Error sent when the field is an empty string | | `array` | `validation_array` | `data` | Error sent when the field is not a array | | `arrayEmpty` | `validation_array_empty` | `data` | Error sent when the field is an empty array | | `arrayLength` | `validation_array_length` | `data` | Error sent when the field has more elements than possible. | | `InsufficientCredits` | `insufficient_credits` | `user` | Error sent when you have less credits than the number of items in your data. We take into account ALL the searches currently queued or in progress in your account. If you have many paralel single or bulk searches, it will be taken into account | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `user` | Error sent when you have a pending payment and your subscription is therefore not paid | | `ExternalIdsArrayShouldMatchDataArrayError` | `external_ids_array_should_match_data_array_error` | `externalIds` | Error sent when you have specified an externalIds array but it does not match the size of your data array | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Read search results > POST /bulk-single-searchs/read: get the result of a search by id, or list your single and bulk search results with filters and pagination. Source: https://api-doc.icypeas.com/reference/get-results Every search you run, single or bulk, produces a *search item*: the input, the status, and the emails found. This endpoint gives them back to you. > **Good to know** > > - Up to 100 results per call. > - Limited to **30 calls per minute**: wait at least 3 seconds between calls. [Webhooks](https://api-doc.icypeas.com/reference/webhooks) avoid this limit entirely. > - Long lists are paginated. See [Pagination](https://api-doc.icypeas.com/pagination). ## Get one result The most common use. Send the `_id` you received when you started the search: ```json { "id": "kMnquYkBTs8kZM9ND26h" } ``` This also works for a single row of a bulk search. ## List your single searches ```json { "mode": "single" } ``` Filter by task with `type` (`email-search`, `email-verification`, `domain-search`): ```json { "mode": "single", "type": "email-search" } ``` Newest first. ## List the rows of a bulk search Send the `file` id you got from [`/bulk-search`](https://api-doc.icypeas.com/reference/bulk-search): ```json { "mode": "bulk", "file": "kMnquYkBTs8kZM9ND26h" } ``` Rows come back in the same order as your `data` array. ## List the rows of all your bulk searches ```json { "mode": "bulk" } ``` Sorted by creation date then row order. Rows of bulk searches started at the same time can be mixed: use their `file` field to group them. `type` can't be used with `mode: "bulk"`. ## Where is the email? ```text items[0].status → is it finished? see /statuses items[0].results.emails[0].email → the email items[0].results.emails[0].certainty → how reliable it is, see /certainties items[0].userData.externalId → your own id, if you sent one ``` **Endpoint:** `POST https://app.icypeas.com/api/bulk-single-searchs/read` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. Get results from single or bulk searches #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `mode` | string | no | Type of searches ('bulk' for bulk searches, 'single' for single searches) Rule: single or bulk. | | `id` | string | no | The _id of the item you want to fetch Rule: existing id. | | `file` | string | no | The file id for your bulk. Useful when you want to retrieve only the searches for a given bulk Rule: has to be a valid fileId. | | `limit` | number | no | Number of results to fetch Rule: default value is 10, max value is 100. | | `next` | boolean | no | If you want to paginate, use this field. true = next page \| false = previous page | | `sorts` | array | no | Array used to paginate your files, it is this item you need to pass to your requests to fetch the next page | | `sorts[].sort item` | number | yes | Tuples of (number, string) | #### Request example */api/bulk-single-searchs/read* ```bash curl -X POST "https://app.icypeas.com/api/bulk-single-searchs/read" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "mode": "bulk", "limit": 50, "file": "#FILEID#" }' ``` #### Responses ##### 200: Response when your input is good. ```json { "success": true, "items": [ { "name": "My cool search", "user": "#USERID#", "file": "#FILEID#", "results": { "firstname": "Example", "lastname": "Email", "fullname": "Example Email", "gender": "UNKNOWN", "emails": [ { "email": "example-email@icypeas.com", "certainty": "ultra_sure", "mxProvider": "google", "mxRecords": [ "google.com" ] } ], "phones": [] }, "order": 0, "status": "FOUND", "system": { "createdAt": "2023-01-01T13:49:49.630Z", "modifiedAt": "2023-01-01T13:49:49.630Z" }, "userData": { "externalId": "my-custom-id", "webhookUrl": "https://www.call-me-when-done.com/my-custom-id" }, "_id": "oSmI5YYBMa6Snk9TvjDA" } ], "total": 2534, "sorts": [ [ 1679388635082, "xxxx" ], [ 1679388543443, "yyyy" ] ] } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | This field lets you know if your request was successful | | `items` | array | Array of results | | `items[].name` | string | Name of the search ('__icypeas__individual' if it is a single search). | | `items[].user` | string | User ID of the user linked to this search. | | `items[].file` | string | File id when it is a bulk search | | `items[].results` | object | Object containing the results of your search. | | `items[].results.firstname` | string | First name found during the search, if any. | | `items[].results.lastname` | string | Last name found during the search, if any. | | `items[].results.fullname` | string | Concatenation of the user's first and last names, if any. | | `items[].results.emails` | array | Array of the found email addresses. | | `items[].results.emails[].email` | string | Email address found during the search. | | `items[].results.emails[].certainty` | enum | How sure Icypeas is that this address exists: ultra_sure, very_sure, probable, undeliverable or not_found. See the Email certainty page. | | `items[].results.emails[].mxProvider` | enum | Who hosts the mailbox (google, microsoft, ovh…). See the MX providers page. | | `items[].results.emails[].mxRecords` | array | Array of MX records. Those are strings | | `items[].results.phones` | array | Currently empty. | | `items[].results.saasServices` | array | Currently empty. | | `items[].order` | number | If in bulk search mode, it is the row of the file it was in. | | `items[].status` | string | Status of this search. | | `items[].system` | object | Information concerning the search. | | `items[].system.createdAt` | string | ISO String Date representing the creation date of the search. | | `items[].system.modifiedAt` | string | ISO String Date representing the last modification date of the search. | | `items[].userData` | object | Custom data that were passed when running either the bulk search or the single search | | `items[].userData.webhookUrl` | string | Custom webhook URL that will be called for this search (the field is empty if no URL has been passed when running the single/bulk search) | | `items[].userData.externalId` | string | Custom id provided by the user (the field is empty if not custom id was passed when running the single/bulk search) | | `items[]._id` | string | ID of the search. | | `total` | number | Total number of results | | `sorts` | array | Array used to paginate your files, it is this item you need to pass to your requests to fetch the next page | | `sorts[].sort item` | number | Tuples of (number, string) | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "string", "type": "required", "field": "user", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `string` | `validation_string` | `mode`, `file` | Error sent when the field is not a string | | `stringEmpty` | `validation_string_empty` | `mode` | Error sent when the field is an empty string | | `stringEnum` | `validation_string_enum` | `mode` | Error sent when the field is a string that is not in the enum | | `number` | `validation_number` | `limit` | Error sent when the field is not a number | | `boolean` | `validation_boolean` | `next` | Error sent when the field is not a boolean | | `array` | `validation_array` | `sorts` | Error sent when the field is not a array | | `FilePropertyError` | `file_property_error` | `file` | Error sent when the file property is absent in bulk mode | | `InvalidFileError` | `invalid_file_error` | `file` | Error sent when the file could not be fetch | | `SearchNotFoundError` | `search_not_found_error` | `id` | Error sent when the id does not correspond to a real search | | `ModeOrIdShouldBeUsedError` | `mode_or_id_should_be_used_error` | `id`, `mode` | Error raised when neither mode nor id is used | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Bulk search progress and stats > POST /search-files/read: follow the progress of your bulk searches and get their statistics (rows done, emails found by certainty). Source: https://api-doc.icypeas.com/reference/bulk-files Each bulk search creates a *file*. Read it to know how far the bulk search is and how many emails were found. | You want | Send | | --- | --- | | All your bulk searches | `{}` | | Only the ones running | `{ "status": "in_progress" }` | | Only the finished ones | `{ "status": "done" }` | | One bulk search | `{ "file": "kbn5421aeteb0989" }` | A bulk search is finished when its `finished` flag is `true`. Then fetch its rows with [`/bulk-single-searchs/read`](https://api-doc.icypeas.com/reference/get-results). > **Good to know** > > - Up to 50 files per call, newest first, [paginated](https://api-doc.icypeas.com/pagination). > - Limited to **15 calls per minute**. The [bulk done webhook](https://api-doc.icypeas.com/reference/webhook-bulk-done) tells you when it's finished without polling. **Endpoint:** `POST https://app.icypeas.com/api/search-files/read` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. Get all your bulk searches. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | number | no | Limit of files to fetch. Rule: default value is 10, max value is 50. | | `file` | string | no | If you want to get a specifc bulk, please use this field with your file id | | `status` | string | no | If you want to fetch only bulk searches that are in progress or that are done Rule: has to be either 'in_progress' or 'done'. | | `next` | boolean | no | If you want to paginate, use this field. true = next page \| false = previous page | | `sorts` | array | no | Array used to paginate your files, it is this item you need to pass in your requests if you want to paginate further | | `sorts[].creationDate` | number | yes | I don't know what to write here. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/search-files/read" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "limit": 2 }' ``` #### Responses ##### 200: Response when your input is good. ```json { "success": true, "files": [ { "file": "#FILEID_1#", "system": { "createdAt": "2023-01-01T12:00:00.000Z", "modifiedAt": "2023-01-01T12:00:00.000Z" }, "name": "Last File", "found": 250, "done": 300, "aborted": 0, "credits-missing": 0, "in-progress": 0, "bad-input": 0, "total": 300, "generationInProgress": true, "exported": false, "task": "email-verification", "finished": true }, { "file": "#FILEID_2#", "system": { "createdAt": "2023-01-01T11:00:00.000Z", "modifiedAt": "2023-01-01T11:00:00.000Z" }, "name": "First File", "found": 2500, "done": 2600, "aborted": 0, "bad-input": 50, "in-progress": 350, "credits-missing": 0, "total": 3000, "generationInProgress": false, "exported": false, "task": "email-search", "finished": false } ], "total": 10, "sorts": [ [ 1678195083294 ], [ 1678194871170 ] ] } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | This field lets you know if your request was successful | | `files` | array | Array of your files | | `files[].file` | string | Unique ID of your file | | `files[].system` | object | Information concerning the search. | | `files[].system.createdAt` | string | ISO String Date representing the creation date of the search. | | `files[].system.modifiedAt` | string | ISO String Date representing the last modification date of the search. | | `files[].name` | string | Name of your file | | `files[].found` | number | Number of found items in your bulk search | | `files[].done` | number | Number of items that are already processed | | `files[].aborted` | number | Number of items that you have cancelled | | `files[].credits-missing` | number | Number of items that have been cancelled by the system because you do not have enough credits | | `files[].in-progress` | number | Number of items that are currently in progress | | `files[].bad-input` | number | Number of items that cannot be analyzed because they are invalid (missing data, ...) | | `files[].total` | number | Total number of items in your bulk search | | `files[].finished` | boolean | Set to true when the bulk search has been entirely processed | | `files[].task` | string | Task for the bulk search (email-verification, domain-search, ...) | | `files[].generationInProgress` | boolean | Exposed for internal use only, please do not use | | `files[].exported` | boolean | Exposed for internal use only, please do not use | | `sorts` | array | Array used to paginate your files, it is this item you need to pass in your requests if you want to paginate further | | `sorts[].creationDate` | number | Unix timestamp | | `total` | number | Total number of bulk searches | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "string", "type": "required", "field": "user", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `user`, `limit` | Error sent when the field is missing | | `string` | `validation_string` | `user`, `file` | Error sent when the field is not a string | | `stringEmpty` | `validation_string_empty` | `user` | Error sent when the field is an empty string | | `number` | `validation_number` | `limit` | Error sent when the field 'limit' is not a number | | `boolean` | `validation_boolean` | `next` | Error sent when the field is not a boolean | | `array` | `validation_array` | `sorts` | Error sent when the field is not a array | | `stringEnum` | `validation_string_enum` | `status` | When you gave a wrong status. Status should be either 'done' or 'in_progress' | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Webhooks: get results pushed to you > Have Icypeas call your server, Zapier, Make or n8n as soon as a search or a bulk search is done, instead of polling. Setup and signature verification. Source: https://api-doc.icypeas.com/reference/webhooks Instead of asking "is it ready yet?" again and again, give Icypeas a URL: it sends you the result the moment it's ready. It's the fastest and most scalable way to get results, and it doesn't count against your rate limits. ## Two notifications | Webhook | Sent when | Contains | | --- | --- | --- | | [Search item processed](https://api-doc.icypeas.com/reference/webhook-search-item) | A single search is done, or each row of a bulk search is done. | The full result: emails, certainty, status, your `externalId`. | | [Bulk search done](https://api-doc.icypeas.com/reference/webhook-bulk-done) | A whole bulk search is done. | Statistics (rows, emails found by certainty). Optionally every result. | ## Set them up ### For all your searches: in the app 1. Open the [API page](https://app.icypeas.com/bo/api) and make sure API access is on. 2. Fill in the URLs under *Icypeas can notify routes when a certain action is performed*. 3. Click **Save notifications routes**. ![Notification URL fields on the API page](https://api-doc.icypeas.com/img/screenshots/setup-notifications-3.png) To turn a notification off, empty its field and save. ### For one search: in the request Pass your URL with the search. It's used for that search only, on top of the ones set in the app. | Endpoint | Field | | --- | --- | | [`/email-search`](https://api-doc.icypeas.com/reference/email-search), [`/email-verification`](https://api-doc.icypeas.com/reference/email-verification), [`/domain-search`](https://api-doc.icypeas.com/reference/domain-search) | `custom.webhookUrl` | | [`/bulk-search`](https://api-doc.icypeas.com/reference/bulk-search) | `custom.webhookUrlItem` (each row) and `custom.webhookUrlBulkDone` (end) | ```json { "email": "pierre.landoin@icypeas.com", "custom": { "webhookUrl": "https://example.com/icypeas/result", "externalId": "crm-1042" } } ``` ## Receive them - Icypeas sends a **POST** request with a JSON body. - Answer with an HTTP **200** status. - The URL must be reachable from the internet. Webhook URLs from Zapier (*Catch Hook*), Make (*Custom webhook*) or n8n (*Webhook* node) work out of the box. Every call has this shape: ```json { "signature": "HMAC-SHA1 signature", "timestamp": "2026-03-01T04:40:20Z", "data": { "…": "the result or the statistics" } } ``` ## Check that a call really comes from Icypeas (optional) Anyone who knows your webhook URL could call it. To be sure a call comes from Icypeas, recompute the signature with your **API secret** and compare: 1. Take the path of your webhook URL, for example `/icypeas/result` for `https://example.com/icypeas/result`. 2. Append the `timestamp` from the body. 3. Lower-case the result. 4. Compute its HMAC-SHA1 with your API secret, in hexadecimal. 5. It must equal `signature`. ```js title="Node.js / Express" const app = express(); app.use(express.json({ limit: "50mb" })); app.post("/icypeas/result", (req, res) => { const { signature, timestamp, data } = req.body; const payload = `${req.path}${timestamp}`.toLowerCase(); const expected = crypto .createHmac("sha1", process.env.ICYPEAS_API_SECRET) .update(payload) .digest("hex"); if (expected !== signature) return res.sendStatus(401); console.log(data.results?.emails, data.userData?.externalId); res.sendStatus(200); }); app.listen(3000); ``` > **Large bulk searches** > > If you use `includeResultsInWebhook` on a bulk search of more than 1,000 rows, make sure your server accepts large request bodies (see `limit` above). --- # Webhook: search item processed > Payload of the webhook Icypeas sends each time a single search, or a row of a bulk search, is processed. Contains the emails found, certainty and status. Source: https://api-doc.icypeas.com/reference/webhook-search-item Sent each time a search item is done: a single search, or one row of a bulk search. `data` contains the same search item as [/bulk-single-searchs/read](https://api-doc.icypeas.com/reference/get-results). - **Method:** `POST` to your URL. Answer with HTTP `200`. - **Where to set it:** on the [API page](https://app.icypeas.com/bo/api) (*Notification when a single search or each single row in a bulk search is processed*), or per request with `custom.webhookUrl` / `custom.webhookUrlItem`. See [Set up webhooks](https://api-doc.icypeas.com/reference/webhooks). - **Security:** [verify the signature](https://api-doc.icypeas.com/reference/webhooks#check-that-a-call-really-comes-from-icypeas-optional) if you need to. #### Example payload ```json { "signature": "#EXAMPLE-SIGNATURE#", "timestamp": "2023-03-01T04:40:20Z", "data": { "name": "My cool search", "user": "#USERID#", "file": "#FILEID#", "results": { "firstname": "Example", "lastname": "Email", "fullname": "Example Email", "gender": "UNKNOWN", "emails": [ { "email": "example-email@icypeas.com", "certainty": "ultra_sure", "mxProvider": "google", "mxRecords": [ "google.com" ] } ], "phones": [] }, "order": 0, "status": "FOUND", "system": { "createdAt": "2023-01-01T13:49:49.630Z", "modifiedAt": "2023-01-01T13:49:49.630Z" }, "userData": { "externalId": "my-custom-id", "webhookUrl": "https://www.call-me-when-done.com/my-custom-id" }, "_id": "oSmI5YYBMa6Snk9TvjDA" } } ``` #### Payload fields | Name | Type | Description | | --- | --- | --- | | `signature` | string | The HMAC-SHA1 signature of the request | | `timestamp` | string | The timestamp used to compute the signature | | `data` | object | The file's information | | `data.name` | string | Name of the search ('__icypeas__individual' if it is a single search). | | `data.user` | string | User ID of the user linked to this search. | | `data.file` | string | File id when it is a bulk search | | `data.results` | object | Object containing the results of your search. | | `data.results.firstname` | string | First name found during the search, if any. | | `data.results.lastname` | string | Last name found during the search, if any. | | `data.results.fullname` | string | Concatenation of the user's first and last names, if any. | | `data.results.emails` | array | Array of the found email addresses. | | `data.results.emails[].email` | string | Email address found during the search. | | `data.results.emails[].certainty` | enum | How sure Icypeas is that this address exists: ultra_sure, very_sure, probable, undeliverable or not_found. See the Email certainty page. | | `data.results.emails[].mxProvider` | enum | Who hosts the mailbox (google, microsoft, ovh…). See the MX providers page. | | `data.results.emails[].mxRecords` | array | Array of MX records. Those are strings | | `data.results.phones` | array | Currently empty. | | `data.results.saasServices` | array | Currently empty. | | `data.order` | number | If in bulk search mode, it is the row of the file it was in. | | `data.status` | string | Status of this search. | | `data.system` | object | Information concerning the search. | | `data.system.createdAt` | string | ISO String Date representing the creation date of the search. | | `data.system.modifiedAt` | string | ISO String Date representing the last modification date of the search. | | `data.userData` | object | Custom data that were passed when running either the bulk search or the single search | | `data.userData.webhookUrl` | string | Custom webhook URL that will be called for this search (the field is empty if no URL has been passed when running the single/bulk search) | | `data.userData.externalId` | string | Custom id provided by the user (the field is empty if not custom id was passed when running the single/bulk search) | | `data._id` | string | ID of the search. | --- # Webhook: bulk search done > Payload of the webhook Icypeas sends when a whole bulk search is finished, with statistics and optionally every result. Source: https://api-doc.icypeas.com/reference/webhook-bulk-done Sent once, when every row of a bulk search has been processed. It gives you the statistics of the file: rows processed, emails found per [certainty](https://api-doc.icypeas.com/certainties)… - **Method:** `POST` to your URL. Answer with HTTP `200`. - **Where to set it:** on the [API page](https://app.icypeas.com/bo/api) (*Notification when the bulk search is done*), or per request with `custom.webhookUrlBulkDone`. See [Set up webhooks](https://api-doc.icypeas.com/reference/webhooks). - **Want the results too?** Start the bulk search with `"custom": { "includeResultsInWebhook": true }` and every result is included in this call. For more than 1,000 rows, make sure your server accepts large requests. #### Example payload ```json { "signature": "#EXAMPLE-SIGNATURE#", "timestamp": "2023-03-01T04:40:20Z", "data": { "file": "#FILEID#", "total": 2500, "done": 2500, "found": 2200, "aborted": 0, "insufficientCredits": 0, "certainties": { "verySure": 800, "sure": 1000, "probable": 400, "undeliverable": 0 } } } ``` #### Payload fields | Name | Type | Description | | --- | --- | --- | | `signature` | string | The HMAC-SHA1 signature of the request | | `timestamp` | string | The timestamp used to compute the signature | | `data` | object | The file's information | | `data.file` | string | The unique ID of the file that was being processed. | | `data.total` | number | Total number of valid rows in your file | | `data.done` | number | Number of rows that were already searched in your file | | `data.found` | number | Number of found emails in your file | | `data.aborted` | number | Number of aborted rows in your file | | `data.insufficientCredits` | number | Number of aborted rows caused by a lack of credits in your file | | `data.certainties` | object | Certainties' statistics for your bulk search | | `data.certainties.verySure` | number | Count of currently processed rows classified as 'very_sure' | | `data.certainties.sure` | number | Count of currently processed rows classified as 'sure' | | `data.certainties.probable` | number | Count of currently processed rows classified as 'probable' | | `data.certainties.undeliverable` | number | Count of currently processed rows classified as 'undeliverable' (only used for email verification) | | `data.results` | array | Results of the bulk if you have specified the 'includeResultsInWebhook' option in the custom object when launching your bulk | | `data.results[].name` | string | Name of the search ('__icypeas__individual' if it is a single search). | | `data.results[].user` | string | User ID of the user linked to this search. | | `data.results[].file` | string | File id when it is a bulk search | | `data.results[].results` | object | Object containing the results of your search. | | `data.results[].results.firstname` | string | First name found during the search, if any. | | `data.results[].results.lastname` | string | Last name found during the search, if any. | | `data.results[].results.fullname` | string | Concatenation of the user's first and last names, if any. | | `data.results[].results.emails` | array | Array of the found email addresses. | | `data.results[].results.emails[].email` | string | Email address found during the search. | | `data.results[].results.emails[].certainty` | enum | How sure Icypeas is that this address exists: ultra_sure, very_sure, probable, undeliverable or not_found. See the Email certainty page. | | `data.results[].results.emails[].mxProvider` | enum | Who hosts the mailbox (google, microsoft, ovh…). See the MX providers page. | | `data.results[].results.emails[].mxRecords` | array | Array of MX records. Those are strings | | `data.results[].results.phones` | array | Currently empty. | | `data.results[].results.saasServices` | array | Currently empty. | | `data.results[].order` | number | If in bulk search mode, it is the row of the file it was in. | | `data.results[].status` | string | Status of this search. | | `data.results[].system` | object | Information concerning the search. | | `data.results[].system.createdAt` | string | ISO String Date representing the creation date of the search. | | `data.results[].system.modifiedAt` | string | ISO String Date representing the last modification date of the search. | | `data.results[].userData` | object | Custom data that were passed when running either the bulk search or the single search | | `data.results[].userData.webhookUrl` | string | Custom webhook URL that will be called for this search (the field is empty if no URL has been passed when running the single/bulk search) | | `data.results[].userData.externalId` | string | Custom id provided by the user (the field is empty if not custom id was passed when running the single/bulk search) | | `data.results[]._id` | string | ID of the search. | --- # Profile scraper: get a person's data from a profile URL > GET /scrape/profile: turn a profile URL into structured data (name, headline, current job, experience, education, skills). 1.5 credits per profile found. Bulk version for 50 profiles per call. Source: https://api-doc.icypeas.com/reference/scrape-profile Turn a person's profile URL into structured data: name, headline, location, current and past jobs, education, skills… Costs **1.5 credits** per profile found. Unlike email searches, the single endpoint answers **directly** with the result. The bulk endpoint takes up to **50 items** per call. See the [rate limits](https://api-doc.icypeas.com/rate-limits). ## Scrape one profile **Endpoint:** `GET https://app.icypeas.com/api/scrape/profile?url=PROFILE_URL` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | The profile URL Rule: should be a correctly formatted user profile (e.g. https://www.linkedin.com/in/my-linkedin-id). | #### Request example ```bash curl -X GET "https://app.icypeas.com/api/scrape/profile?url=PROFILE_URL" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" ``` #### Responses ##### 200: Response when the profile is found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "FOUND", "result": { "firstname": "Corentin", "lastname": "Ribeyre", "numOfConnections": 786, "headline": "CTO at Icypeas", "url": "https://www.linkedin.com/in/corentin-ribeyre-30085a3b", "skills": [], "languages": [ { "value": "Français", "norm": "FR" }, { "value": "Anglais", "norm": "EN" } ], "lid": "corentin-ribeyre-30085a3b", "description": "", "address": { "streetAddress": "", "addressLocality": "Paris et périphérie", "addressRegion": "", "postalCode": "", "addressCountry": "France", "addressCountryCode": "FR" }, "educations": [ { "fieldsOfStudy": [ { "value": "Linguistique Informatique" } ], "urn": "urn:li:fs_salesSchool:19143575", "name": "Université Denis Diderot (Paris VII)", "degree": "Doctorat", "startDate": "2012-01-01T00:00:00.000Z", "endDate": "2015-01-01T00:00:00.000Z" }, { "fieldsOfStudy": [ { "value": "Linguistique Informatique" } ], "urn": "urn:li:fs_salesSchool:19143575", "name": "Université Denis Diderot (Paris VII)", "degree": "Master", "startDate": "2010-01-01T00:00:00.000Z", "endDate": "2012-01-01T00:00:00.000Z" }, { "fieldsOfStudy": [ { "value": "Lettres Classiques" } ], "urn": "", "name": "Lycée Fustel de Coulanges", "degree": "Licence", "startDate": "2007-01-01T00:00:00.000Z", "endDate": "2010-01-01T00:00:00.000Z" } ], "worksFor": [ { "name": "Icypeas", "location": "Paris, Île-de-France, France", "lid": "89667826", "urn": "urn:li:fs_salesCompany:89667826", "startDate": "2022-10-01T00:00:00.000Z", "endDate": "0000-01-01T00:00:00.000Z", "description": "", "jobTitle": "Chief Technology Officer / Founder", "company": { "name": "Icypeas", "lid": "icypeas", "urn": "urn:li:fs_salesCompany:89667826", "url": "https://www.linkedin.com/company/icypeas/", "address": { "streetAddress": "", "addressLocality": "Paris", "addressRegion": "Île-de-France", "postalCode": "", "addressCountry": "France", "addressCountryCode": "FR" }, "industry": "Développement de logiciels", "website": "https://www.icypeas.com", "specialties": [], "_id": "b5VJUJMBbTK9Wqxt2B_V" } } ], "alumniOf": [ { "name": "Superpitch Agence Créative", "location": "Paris Area, France", "lid": "10668200", "urn": "urn:li:fs_salesCompany:10668200", "startDate": "2018-01-01T00:00:00.000Z", "endDate": "2022-09-01T00:00:00.000Z", "description": "", "jobTitle": "Chief Technology Officer" }, { "name": "ETERMIND", "location": "Région de Lyon, France", "lid": "", "urn": "", "startDate": "2017-02-01T00:00:00.000Z", "endDate": "2021-01-01T00:00:00.000Z", "description": "", "jobTitle": "NLP specialist, web developer" }, { "name": "Université de Genève", "location": "Région de Genève, Suisse", "lid": "166637", "urn": "urn:li:fs_salesCompany:166637", "startDate": "2016-02-01T00:00:00.000Z", "endDate": "2017-01-01T00:00:00.000Z", "description": "- Study the ability to properly induce the alignment between AMR graphs and the corresponding sentences using semi-supervised methods.\\n- Work on universal dependency parsing.\\n- Reviewer for the main NLP conferences (ACL, EACL, COLING, CONLL, EMNLP, LREC, AAAI, ...)\\n- Presenter on various topics for the weekly seminar of the CLCL.", "jobTitle": "Chercheur postdoctoral" }, { "name": "Université Sorbonne Paris Cité", "location": "Région de Paris, France", "lid": "10202215", "urn": "urn:li:fs_salesCompany:10202215", "startDate": "2012-11-01T00:00:00.000Z", "endDate": "2015-12-01T00:00:00.000Z", "description": "PhD title :\\n\\nData-driven methods for syntax-semantic interface : From graph rewriting to transition-\\nbased parsing.\\n\\nSummary :\\n\\nIn this thesis, we offer resources, models and methods to allow: (i) the automatic annotation of deep syntactic corpora to extract argument structure that links (verbal) predicates to their arguments (ii) the use of these resources with the help of efficient algorithms.\\n\\nWe developed a graph rewriting system and a set of manually-designed rewriting rules to automatically annotate deep syntax in French. We also extended two transition-based parsers to deal with graph structures. We incorporated a set of rich linguistic features extracted from various syntactic trees into an arc-factored second-order parsing model. With this set of features, we reached the first state-of-the-art results on French and outperforms the one established on the DM and PAS corpora for English.\\n\\nFinally, we briefly explored a method to automatically induce the transformation between a tree and a graph.", "jobTitle": "PhD Student" }, { "name": "Dublin City University", "location": "Dublin, Ireland", "lid": "12144", "urn": "urn:li:fs_salesCompany:12144", "startDate": "2012-07-01T00:00:00.000Z", "endDate": "2012-11-01T00:00:00.000Z", "description": "- Implement highly efficient multihreading into LORG, a PCFG-LA parser, based on the same principles as the Berkeley parser (Petrov et al., 2006)\\n- Domain adaptation experiments to improve LORG' performances (F1-score) on noisy web data.\\n- Design of a web interface to expose LORG as a service.", "jobTitle": "Intern in Natural Language Processing at the NCLT" }, { "name": "INRIA", "location": "Région de Paris, France", "lid": "164385", "urn": "urn:li:fs_salesCompany:164385", "startDate": "2012-02-01T00:00:00.000Z", "endDate": "2012-07-01T00:00:00.000Z", "description": "Developing a graph rewriting system to transform syntactic trees (dependency parses from a syntactic parser such as FrMG or Malt) into semantic structures (such as DMRS) with a hybrid approach based on a simplified form of a Single Push Out approach and a constraints propagation system.", "jobTitle": "Intern in NLP" }, { "name": "Alpage -- INRIA", "location": "Région de Paris, France", "lid": "164385", "urn": "urn:li:fs_salesCompany:164385", "startDate": "2011-11-01T00:00:00.000Z", "endDate": "2012-01-01T00:00:00.000Z", "description": "I had to implement a program capable of retrieving information from a TAG parser (FrMG parser) in order to reinject deep syntax information in the French Treebank, such as deep extractions.", "jobTitle": "Creation of a prototype for semi-automatic annotation in deep syntax based on a TAG parser" }, { "name": "VirtuOz", "location": "Région de Paris, France", "lid": "53594", "urn": "urn:li:fs_salesCompany:53594", "startDate": "2011-04-01T00:00:00.000Z", "endDate": "2011-08-01T00:00:00.000Z", "description": "Internship for training a probabilistic tagger for English, following by an evaluation process of the new tagger with the tools developed by the company.", "jobTitle": "Intern in NLP for training a new probabilistic tagger for English" } ], "volunteeringExperiences": [], "deleted": false } } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | FOUND because the profile has been found | | `result` | object | The profile | | `result.id` | string | Icypeas unique identifier. | | `result.firstname` | string | First name. | | `result.lastname` | string | Last name. | | `result.numOfConnections` | number | Number of connections. | | `result.headline` | string | Profile headline. | | `result.url` | string | Profile URL. | | `result.ContactInfo` | object | Contact information. | | `result.ContactInfo.phoneNumber` | string | Phone number. | | `result.ContactInfo.linkedinEmail` | string | Email address. | | `result.skills` | array | Skills. | | `result.skills[].value` | string | Data value. | | `result.languages` | array | Languages. | | `result.languages[].value` | string | Data value. | | `result.languages[].norm` | string | Data normalized (country code, language code, etc.). | | `result.lid` | string | LinkedIn ID (the `my-linkedin-id` in https://linkedin.com/in/my-linkedin-id). | | `result.linkedinEmail` | string | Email address. | | `result.description` | string | Profile description. | | `result.address` | object | Address information. | | `result.address.streetAddress` | string | Street address. | | `result.address.addressLocality` | string | Address locality. | | `result.address.addressRegion` | string | Address region. | | `result.address.postalCode` | string | Postal code. | | `result.address.addressCountry` | string | Address country. | | `result.address.addressCountryCode` | string | Address country code (ISO Alpha 2 or UNK). | | `result.educations` | array | Education history. | | `result.educations[].fieldsOfStudy` | array | Fields of study. | | `result.educations[].fieldsOfStudy[].value` | string | Data value. | | `result.educations[].urn` | string | URN of the school | | `result.educations[].name` | string | School name. | | `result.educations[].degree` | string | Degree earned. | | `result.educations[].startDate` | string | Start date (ISO 8601 format). | | `result.educations[].endDate` | string | End date (ISO 8601 format). | | `result.educations[].schoolInformation` | string | Additional school information. (if the education is still in progress this will be 0) | | `result.worksFor` | array | Work experience. | | `result.worksFor[].name` | string | Organization name. | | `result.worksFor[].location` | string | Organization location. | | `result.worksFor[].lid` | string | LinkedIn ID of the company. | | `result.worksFor[].urn` | string | URN of the organization entry. | | `result.worksFor[].startDate` | string | Start date (ISO 8601 format). | | `result.worksFor[].endDate` | string | End date (ISO 8601 format). If the | | `result.worksFor[].description` | string | Job description. | | `result.worksFor[].jobTitle` | string | Job title. | | `result.alumniOf` | array | Alumni of organizations. | | `result.alumniOf[].name` | string | Organization name. | | `result.alumniOf[].location` | string | Organization location. | | `result.alumniOf[].lid` | string | LinkedIn ID of the company. | | `result.alumniOf[].urn` | string | URN of the organization entry. | | `result.alumniOf[].startDate` | string | Start date (ISO 8601 format). | | `result.alumniOf[].endDate` | string | End date (ISO 8601 format). If the | | `result.alumniOf[].description` | string | Job description. | | `result.alumniOf[].jobTitle` | string | Job title. | | `result.volunteeringExperiences` | array | Volunteering experiences. | | `result.volunteeringExperiences[].name` | string | Organization name. | | `result.volunteeringExperiences[].location` | string | Organization location. | | `result.volunteeringExperiences[].lid` | string | LinkedIn ID of the company. | | `result.volunteeringExperiences[].urn` | string | URN of the organization entry. | | `result.volunteeringExperiences[].startDate` | string | Start date (ISO 8601 format). | | `result.volunteeringExperiences[].endDate` | string | End date (ISO 8601 format). If the | | `result.volunteeringExperiences[].description` | string | Job description. | | `result.volunteeringExperiences[].jobTitle` | string | Job title. | | `result.openProfile` | boolean | Indicates if the profile is open to connections. | | `result.deleted` | boolean | Indicates if the profile has been deleted. | ##### 200: Response when the profile is not found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "NOT_FOUND", "result": null } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | NOT_FOUND because the profile was not found | | `result` | object | The field is null | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "url", "message": "wrong_linkedin_url", "humanReadableMessage": "The URL should be a user profile URL", "type": "WrongLinkedinUrl", "expected": null, "actual": "https://linkedin.com/xxx/martin-alejandro-espino-huaman-003a39206" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `stringEmpty` | `validation_string_empty` | `url` | Error sent when the parameter is missing | | `url` | `validation_url` | `url` | Error sent when the parameter is not a valid URL | | `WrongLinkedinUrl` | `wrong_linkedin_url` | `url` | The URL should be a user profile URL. | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Scrape up to 50 profiles **Endpoint:** `POST https://app.icypeas.com/api/scrape` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | The type of data Rule: should always be 'profile'. | | `data` | array | yes | An array containing the profile URLs to scrape. Max number of profiles per request is 50 Rule: should be valid profile URLs. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/scrape" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "type": "profile", "data": [ "https://www.linkedin.com/in/corentin-ribeyre-30085a3b", "https://www.linkedin.com/in/pierre-baptiste-landoin-icypes/" ] }' ``` #### Responses ##### 200: Response when your input does not contain any validation errors ```json { "success": true, "data": [ { "searchId": "#YOUR_SEARCH_ID_1#", "status": "FOUND", "result": { "firstname": "Corentin", "lastname": "Ribeyre", "numOfConnections": 786, "headline": "CTO at Icypeas", "url": "https://www.linkedin.com/in/corentin-ribeyre-30085a3b", "skills": [], "languages": [ { "value": "Français", "norm": "FR" }, { "value": "Anglais", "norm": "EN" } ], "lid": "corentin-ribeyre-30085a3b", "description": "", "address": { "streetAddress": "", "addressLocality": "Paris et périphérie", "addressRegion": "", "postalCode": "", "addressCountry": "France", "addressCountryCode": "FR" }, "educations": [ { "fieldsOfStudy": [ { "value": "Linguistique Informatique" } ], "urn": "urn:li:fs_salesSchool:19143575", "name": "Université Denis Diderot (Paris VII)", "degree": "Doctorat", "startDate": "2012-01-01T00:00:00.000Z", "endDate": "2015-01-01T00:00:00.000Z" }, { "fieldsOfStudy": [ { "value": "Linguistique Informatique" } ], "urn": "urn:li:fs_salesSchool:19143575", "name": "Université Denis Diderot (Paris VII)", "degree": "Master", "startDate": "2010-01-01T00:00:00.000Z", "endDate": "2012-01-01T00:00:00.000Z" }, { "fieldsOfStudy": [ { "value": "Lettres Classiques" } ], "urn": "", "name": "Lycée Fustel de Coulanges", "degree": "Licence", "startDate": "2007-01-01T00:00:00.000Z", "endDate": "2010-01-01T00:00:00.000Z" } ], "worksFor": [ { "name": "Icypeas", "location": "Paris, Île-de-France, France", "lid": "89667826", "urn": "urn:li:fs_salesCompany:89667826", "startDate": "2022-10-01T00:00:00.000Z", "endDate": "0000-01-01T00:00:00.000Z", "description": "", "jobTitle": "Chief Technology Officer / Founder", "company": { "name": "Icypeas", "lid": "icypeas", "urn": "urn:li:fs_salesCompany:89667826", "url": "https://www.linkedin.com/company/icypeas/", "address": { "streetAddress": "", "addressLocality": "Paris", "addressRegion": "Île-de-France", "postalCode": "", "addressCountry": "France", "addressCountryCode": "FR" }, "industry": "Développement de logiciels", "website": "https://www.icypeas.com", "specialties": [], "_id": "b5VJUJMBbTK9Wqxt2B_V" } } ], "alumniOf": [ { "name": "Superpitch Agence Créative", "location": "Paris Area, France", "lid": "10668200", "urn": "urn:li:fs_salesCompany:10668200", "startDate": "2018-01-01T00:00:00.000Z", "endDate": "2022-09-01T00:00:00.000Z", "description": "", "jobTitle": "Chief Technology Officer" }, { "name": "ETERMIND", "location": "Région de Lyon, France", "lid": "", "urn": "", "startDate": "2017-02-01T00:00:00.000Z", "endDate": "2021-01-01T00:00:00.000Z", "description": "", "jobTitle": "NLP specialist, web developer" }, { "name": "Université de Genève", "location": "Région de Genève, Suisse", "lid": "166637", "urn": "urn:li:fs_salesCompany:166637", "startDate": "2016-02-01T00:00:00.000Z", "endDate": "2017-01-01T00:00:00.000Z", "description": "- Study the ability to properly induce the alignment between AMR graphs and the corresponding sentences using semi-supervised methods.\\n- Work on universal dependency parsing.\\n- Reviewer for the main NLP conferences (ACL, EACL, COLING, CONLL, EMNLP, LREC, AAAI, ...)\\n- Presenter on various topics for the weekly seminar of the CLCL.", "jobTitle": "Chercheur postdoctoral" }, { "name": "Université Sorbonne Paris Cité", "location": "Région de Paris, France", "lid": "10202215", "urn": "urn:li:fs_salesCompany:10202215", "startDate": "2012-11-01T00:00:00.000Z", "endDate": "2015-12-01T00:00:00.000Z", "description": "PhD title :\\n\\nData-driven methods for syntax-semantic interface : From graph rewriting to transition-\\nbased parsing.\\n\\nSummary :\\n\\nIn this thesis, we offer resources, models and methods to allow: (i) the automatic annotation of deep syntactic corpora to extract argument structure that links (verbal) predicates to their arguments (ii) the use of these resources with the help of efficient algorithms.\\n\\nWe developed a graph rewriting system and a set of manually-designed rewriting rules to automatically annotate deep syntax in French. We also extended two transition-based parsers to deal with graph structures. We incorporated a set of rich linguistic features extracted from various syntactic trees into an arc-factored second-order parsing model. With this set of features, we reached the first state-of-the-art results on French and outperforms the one established on the DM and PAS corpora for English.\\n\\nFinally, we briefly explored a method to automatically induce the transformation between a tree and a graph.", "jobTitle": "PhD Student" }, { "name": "Dublin City University", "location": "Dublin, Ireland", "lid": "12144", "urn": "urn:li:fs_salesCompany:12144", "startDate": "2012-07-01T00:00:00.000Z", "endDate": "2012-11-01T00:00:00.000Z", "description": "- Implement highly efficient multihreading into LORG, a PCFG-LA parser, based on the same principles as the Berkeley parser (Petrov et al., 2006)\\n- Domain adaptation experiments to improve LORG' performances (F1-score) on noisy web data.\\n- Design of a web interface to expose LORG as a service.", "jobTitle": "Intern in Natural Language Processing at the NCLT" }, { "name": "INRIA", "location": "Région de Paris, France", "lid": "164385", "urn": "urn:li:fs_salesCompany:164385", "startDate": "2012-02-01T00:00:00.000Z", "endDate": "2012-07-01T00:00:00.000Z", "description": "Developing a graph rewriting system to transform syntactic trees (dependency parses from a syntactic parser such as FrMG or Malt) into semantic structures (such as DMRS) with a hybrid approach based on a simplified form of a Single Push Out approach and a constraints propagation system.", "jobTitle": "Intern in NLP" }, { "name": "Alpage -- INRIA", "location": "Région de Paris, France", "lid": "164385", "urn": "urn:li:fs_salesCompany:164385", "startDate": "2011-11-01T00:00:00.000Z", "endDate": "2012-01-01T00:00:00.000Z", "description": "I had to implement a program capable of retrieving information from a TAG parser (FrMG parser) in order to reinject deep syntax information in the French Treebank, such as deep extractions.", "jobTitle": "Creation of a prototype for semi-automatic annotation in deep syntax based on a TAG parser" }, { "name": "VirtuOz", "location": "Région de Paris, France", "lid": "53594", "urn": "urn:li:fs_salesCompany:53594", "startDate": "2011-04-01T00:00:00.000Z", "endDate": "2011-08-01T00:00:00.000Z", "description": "Internship for training a probabilistic tagger for English, following by an evaluation process of the new tagger with the tools developed by the company.", "jobTitle": "Intern in NLP for training a new probabilistic tagger for English" } ], "volunteeringExperiences": [], "deleted": false } }, { "searchId": "#YOUR_SEARCH_ID_2#", "status": "NOT_FOUND", "result": null } ] } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `data` | array | | | `data[].searchId` | string | The id of each search item (if you need to fetch the result later using the dedicated route) | | `data[].status` | string | FOUND when the profile was found & NOT_FOUND when not | | `data[].result` | object | The profile or null if no profile was found | | `data[].result.id` | string | Icypeas unique identifier. | | `data[].result.firstname` | string | First name. | | `data[].result.lastname` | string | Last name. | | `data[].result.numOfConnections` | number | Number of connections. | | `data[].result.headline` | string | Profile headline. | | `data[].result.url` | string | Profile URL. | | `data[].result.ContactInfo` | object | Contact information. | | `data[].result.ContactInfo.phoneNumber` | string | Phone number. | | `data[].result.ContactInfo.linkedinEmail` | string | Email address. | | `data[].result.skills` | array | Skills. | | `data[].result.skills[].value` | string | Data value. | | `data[].result.languages` | array | Languages. | | `data[].result.languages[].value` | string | Data value. | | `data[].result.languages[].norm` | string | Data normalized (country code, language code, etc.). | | `data[].result.lid` | string | LinkedIn ID (the `my-linkedin-id` in https://linkedin.com/in/my-linkedin-id). | | `data[].result.linkedinEmail` | string | Email address. | | `data[].result.description` | string | Profile description. | | `data[].result.address` | object | Address information. | | `data[].result.address.streetAddress` | string | Street address. | | `data[].result.address.addressLocality` | string | Address locality. | | `data[].result.address.addressRegion` | string | Address region. | | `data[].result.address.postalCode` | string | Postal code. | | `data[].result.address.addressCountry` | string | Address country. | | `data[].result.address.addressCountryCode` | string | Address country code (ISO Alpha 2 or UNK). | | `data[].result.educations` | array | Education history. | | `data[].result.educations[].fieldsOfStudy` | array | Fields of study. | | `data[].result.educations[].fieldsOfStudy[].value` | string | Data value. | | `data[].result.educations[].urn` | string | URN of the school | | `data[].result.educations[].name` | string | School name. | | `data[].result.educations[].degree` | string | Degree earned. | | `data[].result.educations[].startDate` | string | Start date (ISO 8601 format). | | `data[].result.educations[].endDate` | string | End date (ISO 8601 format). | | `data[].result.educations[].schoolInformation` | string | Additional school information. (if the education is still in progress this will be 0) | | `data[].result.worksFor` | array | Work experience. | | `data[].result.worksFor[].name` | string | Organization name. | | `data[].result.worksFor[].location` | string | Organization location. | | `data[].result.worksFor[].lid` | string | LinkedIn ID of the company. | | `data[].result.worksFor[].urn` | string | URN of the organization entry. | | `data[].result.worksFor[].startDate` | string | Start date (ISO 8601 format). | | `data[].result.worksFor[].endDate` | string | End date (ISO 8601 format). If the | | `data[].result.worksFor[].description` | string | Job description. | | `data[].result.worksFor[].jobTitle` | string | Job title. | | `data[].result.alumniOf` | array | Alumni of organizations. | | `data[].result.alumniOf[].name` | string | Organization name. | | `data[].result.alumniOf[].location` | string | Organization location. | | `data[].result.alumniOf[].lid` | string | LinkedIn ID of the company. | | `data[].result.alumniOf[].urn` | string | URN of the organization entry. | | `data[].result.alumniOf[].startDate` | string | Start date (ISO 8601 format). | | `data[].result.alumniOf[].endDate` | string | End date (ISO 8601 format). If the | | `data[].result.alumniOf[].description` | string | Job description. | | `data[].result.alumniOf[].jobTitle` | string | Job title. | | `data[].result.volunteeringExperiences` | array | Volunteering experiences. | | `data[].result.volunteeringExperiences[].name` | string | Organization name. | | `data[].result.volunteeringExperiences[].location` | string | Organization location. | | `data[].result.volunteeringExperiences[].lid` | string | LinkedIn ID of the company. | | `data[].result.volunteeringExperiences[].urn` | string | URN of the organization entry. | | `data[].result.volunteeringExperiences[].startDate` | string | Start date (ISO 8601 format). | | `data[].result.volunteeringExperiences[].endDate` | string | End date (ISO 8601 format). If the | | `data[].result.volunteeringExperiences[].description` | string | Job description. | | `data[].result.volunteeringExperiences[].jobTitle` | string | Job title. | | `data[].result.openProfile` | boolean | Indicates if the profile is open to connections. | | `data[].result.deleted` | boolean | Indicates if the profile has been deleted. | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "url", "message": "wrong_linkedin_url", "humanReadableMessage": "All URLs in the data array should be a profile URL. Please check URLs at indices: 1", "type": "WrongLinkedinUrl", "expected": null, "actual": [ "https://www.linkedin.com/in/corentin-ribeyre-30085a3b", "https://linkedin.com/xxx/martin-alejandro-espino-huaman-003a39206" ] } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `stringEmpty` | `validation_string_empty` | `url` | Error sent when the parameter is missing | | `url` | `validation_url` | `url` | Error sent when the parameter is not a valid URL | | `WrongLinkedinUrl` | `wrong_linkedin_url` | `url` | The URL should be a user profile URL. | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Company scraper: get a company's data from its page URL > GET /scrape/company: turn a company page URL into structured data (industry, headcount, location, website, growth). 0.5 credit per company found. Bulk version for 50 companies per call. Source: https://api-doc.icypeas.com/reference/scrape-company Turn a company page URL into structured data: description, industry, headcount, headquarters, website, specialties… Costs **0.5 credit** per company found. Unlike email searches, the single endpoint answers **directly** with the result. The bulk endpoint takes up to **50 items** per call. See the [rate limits](https://api-doc.icypeas.com/rate-limits). ## Scrape one company **Endpoint:** `GET https://app.icypeas.com/api/scrape/company?url=PROFILE_URL` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | The company URL Rule: should be a correctly formatted company profile (e.g. https://www.linkedin.com/company/my-linkedin-id). | #### Request example ```bash curl -X GET "https://app.icypeas.com/api/scrape/company?url=PROFILE_URL" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" ``` #### Responses ##### 200: Response when the company is found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "FOUND", "result": { "name": "NEC Technologies", "lid": "nec-technologies", "url": "https://www.linkedin.com/company/nec-technologies/", "description": "NEC Technologies", "urn": "urn:li:fs_salesCompany:1693", "address": { "streetAddress": "", "addressLocality": "Greater Tokyo Area", "addressRegion": "", "postalCode": "", "addressCountry": "Japan", "addressCountryCode": "JP" }, "headcountRange": "50-100", "phone": "+33 3 83 23 34 50", "numberOfEmployees": 62, "industry": "Computer Hardware Manufacturing", "website": "https://www.nec.com/", "specialties": [], "type": "Public Company", "deleted": false, "foundedYear": "1899", "pictureUrl": "https://icypeas-li-profile-pictures-production.s3.sbg.io.cloud.ovh.net/nec-technologies-8e6dd020f4614db796efc654ecb348fadcad0ed0f0674e71868bdaf129960a64", "estimatedRevenuRange": { "estimatedMaxRevenue": { "amount": 1000, "unit": "BILLION", "currency": "USD" }, "estimatedMinRevenue": { "amount": 1, "unit": "BILLION", "currency": "USD" } }, "employeeGrowth": [ { "percentage": 16, "timespan": "SIX_MONTHS" }, { "percentage": 40, "timespan": "YEAR" }, { "percentage": 40, "timespan": "TWO_YEAR" } ] } } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | FOUND because the profile has been found | | `result` | object | The company | | `result.id` | string | Icypeas unique identifier. | | `result.name` | string | Company name | | `result.url` | string | LinkedIn company URL. | | `result.urn` | string | LinkedIn company URN. | | `result.website` | string | Company website | | `result.phone` | string | Main phone number given by the company (unnormalized, we keep what was given) | | `result.slogan` | string | Company slogan | | `result.industry` | string | Company industry | | `result.foundedYear` | number | Company founded year (-1 when not known) | | `result.specialties` | array | Company specialties | | `result.specialties[].value` | string | Data value. | | `result.lid` | string | LinkedIn ID (the `my-linkedin-id` in https://linkedin.com/company/my-linkedin-id). | | `result.description` | string | Company description. | | `result.pictureUrl` | string | Company image URL. | | `result.type` | string | Company legal form (LLC, SAS, SARL, GmbH, ...) | | `result.address` | object | Address information. | | `result.address.streetAddress` | string | Street address. | | `result.address.addressLocality` | string | Address locality. | | `result.address.addressRegion` | string | Address region. | | `result.address.postalCode` | string | Postal code. | | `result.address.addressCountry` | string | Address country. | | `result.address.addressCountryCode` | string | Address country code (ISO Alpha 2 or UNK). | | `result.deleted` | boolean | Indicates if the company has been deleted. | | `result.numberOfEmployees` | number | Number of employees (-1 when not known). | | `result.headcountRange` | string | Declarative headcount range (e.g. 1-10 or 50-100) | | `result.estimatedRevenuRange` | object | Estimated revenue | | `result.estimatedRevenuRange.estimatedMaxRevenue` | object | Estimation of max revenue | | `result.estimatedRevenuRange.estimatedMaxRevenue.amount` | number | Amount | | `result.estimatedRevenuRange.estimatedMaxRevenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `result.estimatedRevenuRange.estimatedMaxRevenue.currency` | string | Amount currency (three letters) | | `result.estimatedRevenuRange.estimatedMinRevenue` | object | Estimation of min revenue | | `result.estimatedRevenuRange.estimatedMinRevenue.amount` | number | Amount | | `result.estimatedRevenuRange.estimatedMinRevenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `result.estimatedRevenuRange.estimatedMinRevenue.currency` | string | Amount currency (three letters) | | `result.revenue` | object | When the revenue is known (because the company has to disclose it, for instance) | | `result.revenue.amount` | number | Amount | | `result.revenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `result.revenue.currency` | string | Amount currency (three letters) | | `result.employeeGrowth` | array | Employee growth over the year | | `result.employeeGrowth[].growth` | object | employee growth for a given timespan | | `result.employeeGrowth[].growth.percentage` | number | Employee growth percentage | | `result.employeeGrowth[].growth.timespan` | string | Time span (SIX_MONTHS, YEAR, TWO_YEAR, ...) | ##### 200: Response when the company is not found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "NOT_FOUND", "result": null } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | NOT_FOUND because the company was not found | | `result` | object | The field is null | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "url", "message": "wrong_linkedin_url", "humanReadableMessage": "The URL should be a company URL", "type": "WrongLinkedinUrl", "expected": null, "actual": "https://linkedin.com/xaeaea/zzz" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `stringEmpty` | `validation_string_empty` | `url` | Error sent when the parameter is missing | | `url` | `validation_url` | `url` | Error sent when the parameter is not a valid URL | | `WrongLinkedinUrl` | `wrong_linkedin_url` | `url` | The URL should be a user profile URL. | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Scrape up to 50 companies **Endpoint:** `POST https://app.icypeas.com/api/scrape` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | The type of data Rule: should always be 'company'. | | `data` | array | yes | An array containing the company URLs to scrape. Max number of companies per request is 50 Rule: should be valid company URLs. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/scrape" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "type": "company", "data": [ "https://www.linkedin.com/company/nec-technologies", "https://www.linkedin.com/company/icypessaezeaze" ] }' ``` #### Responses ##### 200: Response when your input does not contain any validation errors ```json { "success": true, "data": [ { "searchId": "#YOUR_SEARCH_ID_1#", "status": "FOUND", "result": { "name": "NEC Technologies", "lid": "nec-technologies", "url": "https://www.linkedin.com/company/nec-technologies/", "description": "NEC Technologies", "urn": "urn:li:fs_salesCompany:1693", "address": { "streetAddress": "", "addressLocality": "Greater Tokyo Area", "addressRegion": "", "postalCode": "", "addressCountry": "Japan", "addressCountryCode": "JP" }, "headcountRange": "50-100", "phone": "+33 3 83 23 34 50", "numberOfEmployees": 62, "industry": "Computer Hardware Manufacturing", "website": "https://www.nec.com/", "specialties": [], "type": "Public Company", "deleted": false, "foundedYear": "1899", "pictureUrl": "https://icypeas-li-profile-pictures-production.s3.sbg.io.cloud.ovh.net/nec-technologies-8e6dd020f4614db796efc654ecb348fadcad0ed0f0674e71868bdaf129960a64", "estimatedRevenuRange": { "estimatedMaxRevenue": { "amount": 1000, "unit": "BILLION", "currency": "USD" }, "estimatedMinRevenue": { "amount": 1, "unit": "BILLION", "currency": "USD" } }, "employeeGrowth": [ { "percentage": 16, "timespan": "SIX_MONTHS" }, { "percentage": 40, "timespan": "YEAR" }, { "percentage": 40, "timespan": "TWO_YEAR" } ] } }, { "searchId": "#YOUR_SEARCH_ID_2#", "status": "NOT_FOUND", "result": null } ] } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `data` | array | | | `data[].searchId` | string | The id of each search item (if you need to fetch the result later using the dedicated route) | | `data[].status` | string | FOUND when the company was found & NOT_FOUND when not | | `data[].result` | object | The company or null if no company was found | | `data[].result.id` | string | Icypeas unique identifier. | | `data[].result.name` | string | Company name | | `data[].result.url` | string | LinkedIn company URL. | | `data[].result.urn` | string | LinkedIn company URN. | | `data[].result.website` | string | Company website | | `data[].result.phone` | string | Main phone number given by the company (unnormalized, we keep what was given) | | `data[].result.slogan` | string | Company slogan | | `data[].result.industry` | string | Company industry | | `data[].result.foundedYear` | number | Company founded year (-1 when not known) | | `data[].result.specialties` | array | Company specialties | | `data[].result.specialties[].value` | string | Data value. | | `data[].result.lid` | string | LinkedIn ID (the `my-linkedin-id` in https://linkedin.com/company/my-linkedin-id). | | `data[].result.description` | string | Company description. | | `data[].result.pictureUrl` | string | Company image URL. | | `data[].result.type` | string | Company legal form (LLC, SAS, SARL, GmbH, ...) | | `data[].result.address` | object | Address information. | | `data[].result.address.streetAddress` | string | Street address. | | `data[].result.address.addressLocality` | string | Address locality. | | `data[].result.address.addressRegion` | string | Address region. | | `data[].result.address.postalCode` | string | Postal code. | | `data[].result.address.addressCountry` | string | Address country. | | `data[].result.address.addressCountryCode` | string | Address country code (ISO Alpha 2 or UNK). | | `data[].result.deleted` | boolean | Indicates if the company has been deleted. | | `data[].result.numberOfEmployees` | number | Number of employees (-1 when not known). | | `data[].result.headcountRange` | string | Declarative headcount range (e.g. 1-10 or 50-100) | | `data[].result.estimatedRevenuRange` | object | Estimated revenue | | `data[].result.estimatedRevenuRange.estimatedMaxRevenue` | object | Estimation of max revenue | | `data[].result.estimatedRevenuRange.estimatedMaxRevenue.amount` | number | Amount | | `data[].result.estimatedRevenuRange.estimatedMaxRevenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `data[].result.estimatedRevenuRange.estimatedMaxRevenue.currency` | string | Amount currency (three letters) | | `data[].result.estimatedRevenuRange.estimatedMinRevenue` | object | Estimation of min revenue | | `data[].result.estimatedRevenuRange.estimatedMinRevenue.amount` | number | Amount | | `data[].result.estimatedRevenuRange.estimatedMinRevenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `data[].result.estimatedRevenuRange.estimatedMinRevenue.currency` | string | Amount currency (three letters) | | `data[].result.revenue` | object | When the revenue is known (because the company has to disclose it, for instance) | | `data[].result.revenue.amount` | number | Amount | | `data[].result.revenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `data[].result.revenue.currency` | string | Amount currency (three letters) | | `data[].result.employeeGrowth` | array | Employee growth over the year | | `data[].result.employeeGrowth[].growth` | object | employee growth for a given timespan | | `data[].result.employeeGrowth[].growth.percentage` | number | Employee growth percentage | | `data[].result.employeeGrowth[].growth.timespan` | string | Time span (SIX_MONTHS, YEAR, TWO_YEAR, ...) | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "url", "message": "wrong_linkedin_url", "humanReadableMessage": "All URLs in the data array should be a profile URL. Please check URLs at indices: 1", "type": "WrongLinkedinUrl", "expected": null, "actual": [ "https://www.linkedin.com/company/nec-technologies", "https://www.linkedin.com/xx/apple/" ] } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `stringEmpty` | `validation_string_empty` | `url` | Error sent when the parameter is missing | | `url` | `validation_url` | `url` | Error sent when the parameter is not a valid URL | | `WrongLinkedinUrl` | `wrong_linkedin_url` | `url` | The URL should be a company URL. | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Profile URL finder: find a person's profile URL > POST /url-search/profile: find the profile URL of a person from their name, company and job title. 1 credit per URL found. Bulk version for 50 people per call. Source: https://api-doc.icypeas.com/reference/find-profile-url Find the profile URL of a person from what you know about them: first name, last name, company, job title… Costs **1 credit** per URL found. Pair it with the [profile scraper](https://api-doc.icypeas.com/reference/scrape-profile) to get their full data. Unlike email searches, the single endpoint answers **directly** with the result. The bulk endpoint takes up to **50 items** per call. See the [rate limits](https://api-doc.icypeas.com/rate-limits). ## Find one profile URL **Endpoint:** `POST https://app.icypeas.com/api/url-search/profile` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `firstname` | string | yes | First name of your prospect | | `lastname` | string | yes | Last name of your prospect | | `companyOrDomain` | string | no | It should contain either the company name or its domain name (or website) Rule: Can be empty if jobTitle is given. | | `jobTitle` | string | no | It should contain the job title of your prospect Rule: Can be empty if companyOrDomain is given. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/url-search/profile" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "firstname": "Marc", "lastname": "Lachabody", "companyOrDomain": "nec technologies" }' ``` #### Responses ##### 200: Response when the URL is found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "FOUND", "result": "https://www.linkedin.com/in/my-profile" } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | FOUND because the profile has been found | | `result` | string | The profile URL | ##### 200: Response when the URL is not found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "NOT_FOUND", "result": "" } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | NOT_FOUND because the profile was not found | | `result` | string | The field is an empty string | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "companyOrDomain", "message": "validation_string_empty", "humanReadableMessage": "The string is empty", "actual": "" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `firstname`, `lastname`, `companyOrDomain`, `jobTitle` | Error sent when the parameter is null or undefined | | `stringEmpty` | `validation_string_empty` | `firstname`, `lastname`, `companyOrDomain`, `jobTitle` | Error sent when the parameter is missing | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Find up to 50 profile URLs **Endpoint:** `POST https://app.icypeas.com/api/url-search` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | The type of data Rule: should always be 'profile'. | | `data` | array | yes | An array containing the profiles' information. Max number of profiles per request is 50 | | `data[].firstname` | string | yes | First name of your prospect | | `data[].lastname` | string | yes | Last name of your prospect | | `data[].companyOrDomain` | string | no | It should contain either the company name or its domain name (or website) Rule: Can be empty if jobTitle is given. | | `data[].jobTitle` | string | no | It should contain the job title of your prospect Rule: Can be empty if companyOrDomain is given. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/url-search" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "type": "profile", "data": [ { "firstname": "Marc", "lastname": "Lachabody", "companyOrDomain": "nec technologies" }, { "firstname": "Katrina", "lastname": "Larenkova", "companyOrDomain": "apple" } ] }' ``` #### Responses ##### 200: Response when your input does not contain any validation errors ```json { "success": true, "data": [ { "searchId": "#YOUR_SEARCH_ID_1#", "status": "FOUND", "result": "https://www.linkedin.com/in/my-profile" }, { "searchId": "#YOUR_SEARCH_ID_2#", "status": "NOT_FOUND", "result": "" } ] } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `data` | array | | | `data[].searchId` | string | The id of each search item (if you need to fetch the result later using the dedicated route) | | `data[].status` | string | FOUND when the profile was found & NOT_FOUND when not | | `data[].result` | string | The profile URL or an empty string if the profile is not found | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "companyOrDomain", "message": "validation_string_empty", "humanReadableMessage": "The string is empty", "actual": "" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `firstname`, `lastname`, `companyOrDomain`, `jobTitle` | Error sent when the parameter is null or undefined | | `stringEmpty` | `validation_string_empty` | `firstname`, `lastname`, `companyOrDomain`, `jobTitle` | Error sent when the parameter is missing | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Company URL finder: find a company's page URL > POST /url-search/company: find the company page URL of a company from its name or website. 1 credit per URL found. Bulk version for 50 companies per call. Source: https://api-doc.icypeas.com/reference/find-company-url Find the page URL of a company from its name or website. Costs **1 credit** per URL found. Pair it with the [company scraper](https://api-doc.icypeas.com/reference/scrape-company) to get its full data. Unlike email searches, the single endpoint answers **directly** with the result. The bulk endpoint takes up to **50 items** per call. See the [rate limits](https://api-doc.icypeas.com/rate-limits). ## Find one company URL **Endpoint:** `POST https://app.icypeas.com/api/url-search/company` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `companyOrDomain` | string | yes | It should contain either the company name or its domain name (or website) | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/url-search/company" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "companyOrDomain": "nec technologies" }' ``` #### Responses ##### 200: Response when the company URL is found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "FOUND", "result": "https://www.linkedin.com/company/my-company" } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | FOUND because the company has been found | | `result` | string | The company URL | ##### 200: Response when the company URL is not found ```json { "success": true, "searchId": "#YOUR_SEARCH_ID#", "status": "NOT_FOUND", "result": "" } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | NOT_FOUND because the company was not found | | `result` | string | The field is an empty string | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "companyOrDomain", "message": "validation_string_empty", "humanReadableMessage": "The string is empty", "actual": "" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `stringEmpty` | `validation_string_empty` | `companyOrDomain` | Error sent when the parameter is missing | | `required` | `validation_required` | `companyOrDomain` | Error sent when the parameter is null or undefined | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Find up to 50 company URLs **Endpoint:** `POST https://app.icypeas.com/api/url-search` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | The type of data Rule: should always be 'company'. | | `data` | array | yes | An array containing the companies' information. Max number of companies per request is 50 | | `data[].companyOrDomain` | string | yes | It should contains either the company name or its domain name (or website) | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/url-search" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "type": "company", "data": [ { "companyOrDomain": "nec technologies" }, { "companyOrDomain": "icypessaezeaze" } ] }' ``` #### Responses ##### 200: Response when your input does not contain any validation errors ```json { "success": true, "data": [ { "searchId": "#YOUR_SEARCH_ID_1#", "status": "FOUND", "result": "https://www.linkedin.com/in/nec-technologies" }, { "searchId": "#YOUR_SEARCH_ID_2#", "status": "NOT_FOUND", "result": "" } ] } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `data` | array | | | `data[].searchId` | string | The id of each search item (if you need to fetch the result later using the dedicated route) | | `data[].status` | string | FOUND when the company was found & NOT_FOUND when not | | `data[].result` | string | The company URL or an empty string is the company page is not found | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "field": "url", "message": "wrong_linkedin_url", "humanReadableMessage": "All URLs in the data array should be a profile URL. Please check URLs at indices: 1", "type": "WrongLinkedinUrl", "expected": null, "actual": [ "https://www.linkedin.com/company/nec-technologies", "https://www.linkedin.com/xx/apple/" ] } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `stringEmpty` | `validation_string_empty` | `url` | Error sent when the parameter is missing | | `url` | `validation_url` | `url` | Error sent when the parameter is not a valid URL | | `WrongLinkedinUrl` | `wrong_linkedin_url` | `url` | The URL should be a company URL. | | `InsufficientCredits` | `insufficient_credits` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Reverse email lookup: find the person behind an email > POST /reverse-email-lookup: find the profile URL of the person behind a professional email address. 10 credits per profile found. Bulk version for 50 emails per call. Source: https://api-doc.icypeas.com/reference/reverse-email-lookup Start from a professional email address and find the profile of the person behind it. Costs **10 credits** per profile found. The bulk endpoint takes up to **50 emails** per call. See the [rate limits](https://api-doc.icypeas.com/rate-limits). ## Look up one email **Endpoint:** `POST https://app.icypeas.com/api/reverse-email-lookup` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | A professional email address | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/reverse-email-lookup" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "email": "pierre.landoin@icypeas.com" }' ``` #### Responses ##### 200: Response when your input is compliant and a profile URL has been found ```json { "success": true, "results": "https://www.linkedin.com/in/pierre-baptiste-landoin-icypeas", "searchId": "RUiDo5sBptEgZPBRAC7O", "status": "FOUND" } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | FOUND because the profile URL was found | | `result` | string | The profile URL | ##### 200: Response when your input is compliant but the profile URL cannot be found ```json { "success": true, "results": "", "searchId": "RUiDo5sBptEgZPBRAC8O", "status": "NOT_FOUND" } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `status` | string | NOT_FOUND because nothing was found | | `result` | string | An empty string, because nothing was found | ##### 200 (validation error): Response when there are validation errors ```json { "validationErrors": [ { "expected": "email", "type": "required", "field": "email", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `email` | Error sent when the field is missing | | `stringEmpty` | `validation_string_empty` | `email` | Error sent when the field is empty | | `email` | `validation_email` | `email` | Error sent when the field is not an email address | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `user` | Error sent when you have a pending payment and your subscription is therefore not paid | | `InsufficientCredits` | `insufficient_credits` | `user` | Error sent when you do not have enough credits to run the search | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Look up to 50 emails **Endpoint:** `POST https://app.icypeas.com/api/reverse-email-lookups` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `data` | array | yes | An array of professional email addresses. Minimum of 2 addresses, maximum of 50 addresses Rule: should be valid email addresses. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/reverse-email-lookups" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "data": [ "pierre.landoin@icypeas.com", "magnus@icypeas.com" ] }' ``` #### Responses ##### 200: Response when your input is compliant ```json { "success": true, "data": [ { "searchId": "RUiDo5sBptEgZPBRAC7O", "status": "FOUND", "result": "https://www.linkedin.com/in/pierre-baptiste-landoin-icypeas" }, { "searchId": "RDiDo5sBptEgZPBRAC81", "status": "NOT_FOUND", "result": "" } ] } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `data` | array | | | `data[].searchId` | string | The id of the search item (if you need to fetch the result later using the dedicated route) | | `data[].status` | string | FOUND if the profile URL was found. NOT_FOUND otherwise | | `data[].result` | string | The profile URL (if FOUND). An empty string otherwise | ##### 200 (validation error): Response when there are validation errors ```json { "validationErrors": [ { "expected": "email", "type": "required", "field": "email", "message": "validation_required" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `arrayEmpty` | `The 'data' field must not be an empty array.` | `data` | Error sent when the data array is empty | | `arrayLength` | `The 'data' field must contain 2 >= value <= 50 items.` | `data` | Error sent when the data does not contain at least 2 emails and at most 50 emails | | `EmailArray` | `email_array` | `data` | Error sent when the data array does not contain valid email addresses | | `UnpaidSubscriptionError` | `unpaid_subscription_error` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | | `InsufficientCredits` | `insufficient_credits` | `` | Error sent when you do not have enough credits to run the search | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Find people: search the B2B lead database > POST /find-people: search millions of professionals by job title, company, location, skills, seniority and more. Count matches for free, then fetch them for 0.02 credit each. Source: https://api-doc.icypeas.com/reference/find-people Search the Icypeas lead database for people matching your criteria: job title, company, location, skills, experience… Counting is **free**, each result fetched costs **0.02 credit**. **How to use it:** 1. Build a `query` with the [filters below](#available-filters). 2. Call [`/find-people/count`](#count-matching-people-free) to see how many people match. It's free, so tweak your filters until the number looks right. 3. Call [`/find-people`](#fetch-matching-people) to get the results, 100 per page by default (up to 200). Use the [pagination token](#pagination) to get the next pages. > **Note** > > Unlike email searches, these endpoints answer **immediately**: no waiting, no second call. See the [rate limits](https://api-doc.icypeas.com/rate-limits). ## Available filters Below is the list of available filters that can be used to search the people dataset. They come in two families: the filters matching **the person**, and the filters matching **the company** the person works, or used to work, for. ### Filters on the person | Filter name | Description | Type | Examples | Constraints | |--------------------|----------------------------------------------------------|-----------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| | firstname | The first name of a person | Include/Exclude | `"firstname": { "include": ["John", "Jane"], "exclude": ["Alice"] }` | This is a **open text field**. You can put whatever you want. | | lastname | The last name of a person | Include/Exclude | `"lastname": { "include": ["Doe", "Smith"], "exclude": ["Brown"] }` | This is a **open text field**. You can put whatever you want. | | currentJobTitle | The current job title of a person | Include/Exclude | `"currentJobTitle": { "include": ["Software Engineer", "Manager"], "exclude": ["Intern"] }` | This is a **open text field**. You can put whatever you want. | | pastJobTitle | The past job title of a person | Include/Exclude | `"pastJobTitle": { "include": ["Developer", "Analyst"], "exclude": ["Consultant"] }` | This is a **open text field**. You can put whatever you want. | | school | The name of the school attended by the person | Include/Exclude | `"school": { "include": ["Stanford", "Harvard"], "exclude": ["MIT"] }` | This is an **open text field**. You can put whatever you want. | | languages | The languages spoken by the person | Include/Exclude | `"languages": { "include": ["English", "Spanish"], "exclude": ["French"] }` | This is a **text field**. It should contain valid language names or alpha-2 language codes. | | skills | The skills possessed by the person | Include/Exclude | `"skills": { "include": ["JavaScript", "Python"], "exclude": ["Java"] }` | This is a **text field**. It should contain valid skill names. | | location | The location of the person, **or** the location of their current job | Include/Exclude | `"location": { "include": ["New York", "Paris, France"], "exclude": ["Los Angeles"] }` | You can put the name of a city, a country, a state, but for best results we strongly advised to use alpha-2 country code (IN, FR, US, ...). Use `profileLocation` or `workLocation` to match only one of the two. Use a comma to narrow a location down, see [below](#narrowing-a-location-down) | | profileLocation | The location of the person only, whatever the location of their job | Include/Exclude | `"profileLocation": { "include": ["Paris, France"] }` | Same values as `location`. | | workLocation | The location of the person's current job only, whatever the location of the person | Include/Exclude | `"workLocation": { "include": ["FR"] }` | Same values as `location`, except that a comma separated location is **not** narrowed down, see [below](#narrowing-a-location-down) | | totalYearsOfExperience | The total number of years of experience of the person | Range | `"totalYearsOfExperience": { ">=": 5, "<=": 10 }` | This is a range of integers, expressed in years. Please see how to define a range [here](#range-specs) | | timeInCurrentCompany | The number of years the person has been working in their current company | Range | `"timeInCurrentCompany": { "<=": 1 }` | This is a range of integers, expressed in years. Please see how to define a range [here](#range-specs) | | keyword | A special filter that search across the entire profile (past & current job titles, job descriptions, skills, profile headline & descriptions, educations, ...) | Include/Exclude | `"keyword": { "include": ["CEO", "Co-Founder"] }` | This is an **open text field**, you can put whatever you want. | ### Filters on the company Every filter below comes in two flavours: `currentCompany*` matches the company the person **currently works for**, `pastCompany*` matches a company the person **used to work for**. | Filter name | Description | Type | Examples | Constraints | |--------------------|----------------------------------------------------------|-----------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| | currentCompanyName | The name of the company where the person currently works | Include/Exclude | `"currentCompanyName": { "include": ["Google", "Microsoft"], "exclude": ["Facebook"] }` | This is a **open text field**. You can put whatever you want. | | pastCompanyName | The name of a company the person worked for before | Include/Exclude | `"pastCompanyName": { "include": ["Amazon", "IBM"], "exclude": ["Oracle"] }` | This is a **open text field**. You can put whatever you want. | | currentCompanyId | An id representing the company where the person currently works | Include/Exclude | `"currentCompanyId": { "include": ["https://www.icypeas.com", "5678", "icypeas", "https://linkedin.com/company/icypeas"] }` | This is a **textual field**. It should contain valid ids. We support: company domain or website / URN numeric id / LinkedIn URL of the company / Directly the company vanity name: https://linkedin.com/company/icypeas -> icypeas. | | pastCompanyId | An id representing a company the person worked for before | Include/Exclude | `"pastCompanyId": { "include": ["https://www.icypeas.com", "5678", "icypeas", "https://linkedin.com/company/icypeas"] }` | This is a **textual field**. It should contain valid ids. We support: company domain or website / URN numeric id / LinkedIn URL of the company / Directly the company vanity name: https://linkedin.com/company/icypeas -> icypeas. | | currentCompanyWebsite | The website or domain of the company where the person currently works | Include/Exclude | `"currentCompanyWebsite": { "include": ["https://www.icypeas.com", "microsoft.com"] }` | This is a **textual field**. It should contain valid websites or domains only. We strongly advise to use the web domain and not a formatted URL (i.e. use `icypeas.com` instead of `https://www.icypeas.com`). | | pastCompanyWebsite | The website or domain of a company the person worked for before | Include/Exclude | `"pastCompanyWebsite": { "include": ["https://www.microsoft.com", "google.com"] }` | This is a **textual field**. It should contain valid websites or domains only. | | currentCompanyUrn | The URN of the company where the person currently works | Include/Exclude | `"currentCompanyUrn": { "include": ["1234", "5678"] }` | This is a **numeric field**. It should contain valid company URNs. | | pastCompanyUrn | The URN of a company the person worked for before | Include/Exclude | `"pastCompanyUrn": { "include": ["3456", "7890"] }` | This is a **numeric field**. It should contain valid company URNs. | | currentCompany.type | The type of the company where the person currently works (Privately Held, Public Company, ...) | Include/Exclude | `"currentCompany.type": { "include": ["Privately Held"], "exclude": ["Public Company"] }` | Available types are: `Privately Held`, `Sole Proprietorship`, `Self-Employed`, `Partnership`, `Public Company`, `Nonprofit`, `Educational Institution`, `Government Agency` | | pastCompany.type | The type of a company the person worked for before | Include/Exclude | `"pastCompany.type": { "include": ["Nonprofit"] }` | Same values as `currentCompany.type`. | | currentCompany.industry | The industry of the company where the person currently works (Software development, ...) | Include/Exclude | `"currentCompany.industry": { "include": ["Real Estate"] }` | The list of available industries can be downloaded [here](https://api-doc.icypeas.com/industries.txt) | | pastCompany.industry | The industry of a company the person worked for before | Include/Exclude | `"pastCompany.industry": { "include": ["Real Estate"] }` | Same values as `currentCompany.industry`. | | currentCompany.location | The headquarters' location of the company where the person currently works | Include/Exclude | `"currentCompany.location": { "include": ["US", "Paris, France"] }` | You can put the name of a city, a country, a state, but for best results we strongly advised to use alpha-2 country code (IN, FR, US, ...). This is the location of the **company**, not the one of the person: see `location`. Use a comma to narrow a location down, see [below](#narrowing-a-location-down) | | pastCompany.location | The headquarters' location of a company the person worked for before | Include/Exclude | `"pastCompany.location": { "include": ["US", "Paris, France"] }` | Same values as `currentCompany.location`. | | currentCompany.headcount | The headcount of the company where the person currently works | Range | `"currentCompany.headcount": { ">": 10 }` | This is a range of integers. Please see how to define a range [here](#range-specs) | | pastCompany.headcount | The headcount of a company the person worked for before | Range | `"pastCompany.headcount": { ">": 10 }` | This is a range of integers. Please see how to define a range [here](#range-specs) | | currentCompany.headcountGrowth | The headcount growth (in percentage) of the company where the person currently works, in a given timespan (6 months, 12 months, 24 months) | HeadCountGrowth | `"currentCompany.headcountGrowth": { "min": -10, "max": 50, "timespan": "6months" }` | This is a HeadCountGrowth kind of type. Please see how to define it [here](#headcount-growth-specs) | | pastCompany.headcountGrowth | The headcount growth (in percentage) of a company the person worked for before, in a given timespan (6 months, 12 months, 24 months) | HeadCountGrowth | `"pastCompany.headcountGrowth": { "min": 0, "max": 100, "timespan": "24months" }` | This is a HeadCountGrowth kind of type. Please see how to define it [here](#headcount-growth-specs) | | currentCompany.revenue | The revenue of the company where the person currently works | Range | `"currentCompany.revenue": { ">": 1000000 }` | This is a range of integers. Please see how to define a range [here](#range-specs) | | pastCompany.revenue | The revenue of a company the person worked for before | Range | `"pastCompany.revenue": { ">": 1000000 }` | This is a range of integers. Please see how to define a range [here](#range-specs) | | currentCompany.keyword | A special filter that search across the profile of the company where the person currently works (name, specialties, ...) | Include/Exclude | `"currentCompany.keyword": { "include": ["Higher education"] }` | This is an **open text field**, you can put whatever you want. | | pastCompany.keyword | A special filter that search across the profile of a company the person worked for before (name, specialties, ...) | Include/Exclude | `"pastCompany.keyword": { "include": ["Higher education"] }` | This is an **open text field**, you can put whatever you want. | > **Note** > > The filters matching the current experience — `currentJobTitle`, `workLocation`, `timeInCurrentCompany` and every `currentCompany*` filter — all apply to the **same job**. Combining them returns the people whose current job matches all of them at once, and not the people who match each of them on a different job. The same goes for `pastJobTitle` and the `pastCompany*` filters on past experiences. ### Narrowing a location down A location made of comma separated parts only matches when **every one** of its parts does. `"Paris, France"` matches the people in `Paris` **and** in `France`, which is how you tell the French Paris from `Paris, Texas`. Each part is matched against the country, the state and the city, so the order of the parts does not matter: `"Paris, France"` and `"France, Paris"` are the same filter. Several locations remain alternatives to each other, whether they hold a comma or not. `"location": { "include": ["Paris, France", "US"] }` matches the people in Paris (France) **or** anywhere in the United States. This applies to `location`, `profileLocation`, `currentCompany.location` and `pastCompany.location`. > **Warning** > > `workLocation` is the exception: it is matched against the location of the job as it is written on the profile, which already is a comma separated string. `"workLocation": { "include": ["Paris, France"] }` therefore looks for that exact wording, and is **not** narrowed down to `Paris` and `France`. Prefer `location` or `currentCompany.location` when you need a location narrowed down. ### Query examples ```json title="Search for all people living in Paris, France" { "query": { "profileLocation": { "include": ["Paris, France"] } } } ``` ```json title="Search for all people currently employed as CTO in the US" { "query": { "currentJobTitle": { "include": ["CTO", "Chief Technical Officer"] }, "location": { "include": ["US"] } } } ``` ```json title="Search for all people speaking English in France" { "query": { "languages": { "include": ["EN"] }, "location": { "include": ["FR"] } } } ``` ```json title="Search for all engineers working at Apple" { "query": { "currentJobTitle": { "include": ["engineer"] }, "currentCompanyName": { "include": ["apple"] } } } ``` ```json title="Search for seasoned people that recently joined a fast growing company" { "query": { "totalYearsOfExperience": { ">=": 10 }, "timeInCurrentCompany": { "<=": 1 }, "currentCompany.headcountGrowth": { "min": 20, "max": 100, "timespan": "12months" } } } ``` ```json title="Search for all people working in a small privately held software company in France" { "query": { "currentCompany.type": { "include": ["Privately Held"] }, "currentCompany.industry": { "include": ["Software Development"] }, "currentCompany.headcount": { "<=": 50 }, "currentCompany.location": { "include": ["FR"] } } } ``` ## Count matching people (free) When you need to assess the number of people returned by a specific query, you should use this route. We do not charge anything when using this route. **Endpoint:** `POST https://app.icypeas.com/api/find-people/count` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | object | yes | The query Rule: should be a valid query (see available fields above). | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/find-people/count" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" ``` #### Responses ##### 200: Response when they are no errors ```json { "success": true, "total": 10 } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `total` | number | The number of people | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "object with props defined in the schema", "type": "objectStrict", "actual": { "x": 10 }, "field": "query.headcount", "message": "The object 'query.headcount' contains forbidden keys." } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `` | `query` | Error sent when the query field is not passed inside the request body | | `EmptyQueryError` | `` | `query` | Error sent when the query is empty | | `objectStrict` | `` | `query.[field]` | Error sent when a field contains forbidden keys (for instance { urn: { x: ["test"] } } instead of { urn: { exclude: ["test"] } }) | | `arrayEmpty` | `` | `query.[field].include`, `query.[field].exclude` | Error sent when a Include/Exclude field contains an empty include or exclude array | | `objectLength` | `` | `query.[field]` | Error sent when a field is empty (for instance { urn: {} } instead of { urn: { include: ["1527"] } }) | | `InsufficientCredits` | `` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Fetch matching people When you want to retrieve the results for your query, you should use this route. The route is paginated, please see below on how to handle pagination. **Endpoint:** `POST https://app.icypeas.com/api/find-people` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | object | yes | The query Rule: should be a valid query (see available fields above). | | `pagination` | object | no | This is optional and used only if you want to retrieve more people than the first page. Rule: should be a valid pagination object. | | `pagination.token` | string | yes | The token returned with the previous page. Valid 15 minutes; each response returns a new token with a new 15-minute validity. Rule: should be a valid pagination token. | | `pagination.size` | number | no | The number of people returned per page Rule: should be a valid integer between 1 and 200. Default: `100`. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/find-people" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" ``` #### Responses ##### 200: Response when no errors are sent ```json { "success": true, "total": 4, "leads": [ { "firstname": "Pierre-Baptiste", "lastname": "Landoin", "headline": "CEO @Icypeas | Love discussing lead generation, sales automation, marketing hacks and entrepreneurship", "description": "I have co-founded:\n- Icypeas (Email Finder & Verifier, www.icypeas.com)\n- NSW PRIM (Model mapping, www.nswprim.com)\n- Superpitch (Animated Videos, www.superpitch.fr)\n- FileVirtuelle (Welcome Kiosks, www.filevirtuelle.com)\n- Cerbair (Drone Detection, www.cerbair.com)\n- MySeat (Connected Objects, www.myseat.fr)\n\nIn total, over 50 jobs created and €6M in revenue in 2021.", "profileUrl": "https://www.linkedin.com/in/pierre-baptiste-landoin-icypeas", "lastJobTitle": "Chief Executive Officer", "lastJobDescription": "Icypeas is a SaaS to find and verify email addresses. \n\n- unparalleled email discovery rate\n- lowest bounce rate \n\nDiscover more on https://www.icypeas.com.", "lastJobStartDate": "10-2022", "address": "Paris, Ile-de-France, France", "lastCompanyName": "Icypeas", "lastCompanyUrn": "urn:li:fs_salesCompany:89667826", "lastCompanyUrl": "https://www.linkedin.com/company/icypeas/", "lastCompanyWebsite": "https://www.icypeas.com", "lastCompanyDescription": "", "lastCompanySize": 4, "lastCompanyIndustry": "Software Development", "lastCompanyAddress": "Paris, Île-de-France, France" }, { "firstname": "Corentin", "lastname": "Ribeyre", "headline": "CTO at Icypeas", "description": "", "profileUrl": "https://www.linkedin.com/in/corentin-ribeyre-30085a3b", "lastJobTitle": "Chief Technology Officer / Founder", "lastJobDescription": "", "lastJobStartDate": "10-2022", "address": "Paris, Ile-de-France, France", "lastCompanyName": "Icypeas", "lastCompanyUrn": "urn:li:fs_salesCompany:89667826", "lastCompanyUrl": "https://www.linkedin.com/company/icypeas/", "lastCompanyWebsite": "https://www.icypeas.com", "lastCompanyDescription": "", "lastCompanySize": 4, "lastCompanyIndustry": "Software Development", "lastCompanyAddress": "Paris, Île-de-France, France" } ], "pagination": { "size": 2, "token": "xxxxxx" } } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `total` | number | The number of people | | `pagination` | object | This is used if you want to retrieve more people than the first page. | | `pagination.token` | string | Token to request the next page. Valid 15 minutes from this response: request the next page before it expires. | | `pagination.size` | number | The number of people returned per page Default: `100`. | | `leads` | array | An array containing the people information | | `leads[].firstname` | string | The firstname of the lead | | `leads[].lastname` | string | The lastname of the lead | | `leads[].headline` | string | A brief summary or description of the lead's professional identity or role | | `leads[].description` | string | Detailed information about the lead's background, experience, and accomplishments | | `leads[].profileUrl` | string | The URL to the lead's LinkedIn profile | | `leads[].lastJobTitle` | string | The title of the lead's most recent job position | | `leads[].lastJobDescription` | string | A more detailed description about his/her most recent position | | `leads[].lastJobStartDate` | string | The start date of the lead's most recent job position | | `leads[].address` | string | The geographical location or address associated with the lead | | `leads[].lastCompanyName` | string | The name of the company where the lead was most recently employed | | `leads[].lastCompanyUrn` | string | The unique identifier (URN) of the most recent company on LinkedIn | | `leads[].lastCompanyUrl` | string | The URL to the LinkedIn page of the lead's most recent company | | `leads[].lastCompanyWebsite` | string | The website address of the lead's most recent company | | `leads[].lastCompanyDescription` | string | A brief description or overview of the lead's most recent company | | `leads[].lastCompanySize` | number | The number of employees inside the lead's most recent company | | `leads[].lastCompanyIndustry` | string | The industry in which the lead's most recent company operates | | `leads[].lastCompanyAddress` | string | The geographical location or address of the lead's most recent company | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "object with props defined in the schema", "type": "objectStrict", "actual": { "x": 10 }, "field": "query.currentJobTitle", "message": "The object 'query.headcount' contains forbidden keys." } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `` | `query` | Error sent when the query field is not passed inside the request body | | `EmptyQueryError` | `` | `query` | Error sent when the query is empty | | `objectStrict` | `` | `query.[field]` | Error sent when a field contains forbidden keys (for instance { urn: { x: ["test"] } } instead of { urn: { exclude: ["test"] } }) | | `arrayEmpty` | `` | `query.[field].include`, `query.[field].exclude` | Error sent when a Include/Exclude field contains an empty include or exclude array | | `objectLength` | `` | `query.[field]` | Error sent when a field is empty (for instance { urn: {} } instead of { urn: { include: ["1527"] } }) | | `InsufficientCredits` | `` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | | `number` | `` | `pagination.size` | Error sent when the size in pagination is not an integer | | `numberMin` | `` | `pagination.size` | Error sent when pagination is less than the min | | `numberMax` | `` | `pagination.size` | Error sent when pagination is greater than the max | | `InvalidPaginationTokenError` | `` | `pagination.token` | Error sent when the pagination token is not in the right format | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Pagination Results come in pages of `pagination.size` items (100 by default, from 1 to 200). With 1,000 matches and the default size, you need 10 calls to get everything. When there are more results, the response contains a `pagination` object with a `token`. **Send that object back with the same query** to get the next page: ```json title="1. First request" { "query": { "headcount": { ">": 10 } }, "pagination": { "size": 2 } } ``` ```json title="1. First response (shortened)" { "success": true, "total": 845, "leads": [ { "name": "Harvard Graduate School of Education", "numberOfEmployees": 1134, "industry": "Higher Education" }, { "name": "Trammell Crow Company", "numberOfEmployees": 989, "industry": "Real Estate" } ], "pagination": { "size": 2, "token": "eyJzYSI6W1syXSxbMTI4ODQ5MDE5MDJdXSwiaWQiOi…" } } ``` ```json title="2. Next request: same query + the pagination object you received" { "query": { "headcount": { ">": 10 } }, "pagination": { "size": 2, "token": "eyJzYSI6W1syXSxbMTI4ODQ5MDE5MDJdXSwiaWQiOi…" } } ``` Each response brings a new token: always use the latest. Stop when no `pagination` object comes back. ### Token expiration A token is valid for **15 minutes**. Every request that uses it returns a **new token, with a fresh 15-minute window**. So the 15 minutes is the maximum time **between two pages**, not the time you have to go through the whole search. As long as you ask for the next page within 15 minutes of the previous one, you can keep paging for as long as you need, even to retrieve hundreds of thousands or millions of results. | Request | Uses token | Returns token | Valid until | | --- | --- | --- | --- | | Page 1 at 10:00 | none | A | 10:15 | | Page 2 at 10:12 | A | B | 10:27 | | Page 3 at 10:20 | B | C | 10:35 | | … | … | … | … | If a token expires, start again from the first page with the same query. > **Warning** > > - Always send the **latest** token. An older one may have expired even if you're still paging. > - Don't change the `query` between pages: the token belongs to that exact query. > - Pausing your script for more than 15 minutes between two pages (for example while processing a batch) makes the token expire. ## Include/Exclude specs The Include/Exclude field can contains two arrays: 1. `include` array which lists what needs to be included in the query. 2. `exclude` array which lists what needs to be excluded from the query. For instance, when looking for leads, you can include some `currentJobTitle` and exclude some others: ```json { "currentJobTitle": { "include": ["CTO"], "exclude": ["Senior engineer"] } } ``` > **Warning** > > `include` and `exclude` arrays are limited to **200** values per array per field. ## Range specs The Range field contains comparison operators and numerical values only. Some examples are listed below: ```json { "headcount": { ">=": 10, "<": 20 } } ``` ```json { "headcount": { ">": 10, "<=": 20 } } ``` ```json { "headcount": { ">": 10, "<": 20 } } ``` The operators can be `>` (greater than), `<` (less than), `>=` (greater or equal), `<=` (less or equal). You are responsible for giving a range that makes sense. ## Headcount growth specs Filter companies by their employee growth rate over a specific period. This criterion is useful for identifying scaling companies, detecting workforce reductions, or finding organizations at particular growth stages. ### Headcount growth parameters | Parameter | Type | Description | |-----------|------|-------------| | `timespan` | string | The measurement period: `6months`, `12months`, or `24months` | | `min` | number | Minimum growth rate (percentage). Range: -100 to 10,000 | | `max` | number | Maximum growth rate (percentage). Range: -100 to 10,000 | ### Growth rate scale - `0%` = No change in headcount - Positive values = Headcount increase - Negative values = Headcount decrease - `-100%` = Company lost all employees ### Headcount growth example ```json { "headcountGrowth": { "timespan": "6months", "min": -10, "max": 50 } } ``` Returns all companies that experienced between -10% and +50% headcount growth in the past 6 months. --- # Find companies: search the B2B company database > POST /find-companies: search companies by industry, location, headcount, growth, revenue and keywords. Count matches for free, then fetch them for 0.02 credit each. Source: https://api-doc.icypeas.com/reference/find-companies Search the Icypeas database for companies matching your criteria: industry, location, headcount, growth, revenue… Counting is **free**, each result fetched costs **0.02 credit**. **How to use it:** 1. Build a `query` with the [filters below](#available-filters). 2. Call [`/find-companies/count`](#count-matching-companies-free) to see how many companies match. It's free, so tweak your filters until the number looks right. 3. Call [`/find-companies`](#fetch-matching-companies) to get the results, 100 per page by default (up to 200). Use the [pagination token](#pagination) to get the next pages. > **Note** > > Unlike email searches, these endpoints answer **immediately**: no waiting, no second call. See the [rate limits](https://api-doc.icypeas.com/rate-limits). ## Available filters Below is the list of available filters that can be used to search the companies dataset: | Filter name | Description | Type | Examples | Constraints | |-------------|--------------------------------------------------------|-----------------|--------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | name | The company name | Include/Exclude | `"name": { "include": ["icypeas", "bell security solutions inc."], "exclude": ["apple"] }` | This is an __open field__. You can put whatever you want | | lid | The Linkedin company public ID | Include/Exclude | `"lid": { "include": ["google"] }` | The ID can be found at the end of URLs like `https://www.linkedin.com/company/google`. Here the ID is `google`. | | urn | The Linkedin company URN (numerical ID) | Include/Exclude | `"urn": { "exclude": ["1527"] }` | This is a numerical ID only. | | companyId | An id matching a company | Include/Exclude | `"companyId": { "include": ["https://www.icypeas.com", "5678", "icypeas", "https://linkedin.com/company/icypeas"] }` | This is a **textual field**. It should contain valid ids. We support: company domain or website / URN numeric id / LinkedIn URL of the company / Directly the company vanity name: https://linkedin.com/company/icypeas -> icypeas. | | type | The company type (Privately Held, Public Company, ...) | Include/Exclude | `"type": { "include": ["Privately Held"], "exclude": ["Public Company"] }` | Available types are: `Privately Held`, `Sole Proprietorship`, `Self-Employed`, `Partnership`, `Public Company`, `Nonprofit`, `Educational Institution`, `Government Agency` | | industry | The company industry (Software development, ...) | Include/Exclude | `"industry": { "include": ["Real Estate"] }` | The list of available industries can be downloaded [here](https://api-doc.icypeas.com/industries.txt) | | location | The company headquarters' location | Include/Exclude | `"location": { "include": ["US", "Paris, France"] }` | You can put the name of a city, a country, a state, but for best results we strongly advised to use alpha-2 country code (IN, FR, US, ...). Use a comma to narrow a location down, see [below](#narrowing-a-location-down) | | headcount | The company headcount | Range | `"headcount": { ">": 10 }` | This is a range of integers. Please see how to define a range [here](#range-specs) | | headcountGrowth | The headcount growth (in percentage) for a company in a given timespan (6 months, 12 months, 24 months)| HeadCountGrowth| `"headcountGrowth": { "min": -10, "max": 50, "timespan": "6months" }` | This is a HeadCountGrowth kind of type . Please see how to define it [here](#headcount-growth-specs) | | revenue | The revenue of the company | Range | `"revenue": { ">": 1000000 }` | This is a range of integers. Please see how to define a range [here](#range-specs) | | keyword | A special filter that search across the entire company profile (name, description, specialties, ...) | Include/Exclude | `"keyword": { "include": ["Higer education"] }` | This is an **open text field**, you can put whatever you want. | | domain | The company website or domain | Include/Exclude | `"keyword": { "include": ["icypeas.com"] }` | We strongly advise to use the web domain and not a formatted URL (i.e. use `icypeas.com` instead of `https://www.icypeas.com`). | ### Narrowing a location down A location made of comma separated parts only matches when **every one** of its parts does. `"Paris, France"` matches the companies whose headquarters are in `Paris` **and** in `France`, which is how you tell the French Paris from `Paris, Texas`. Each part is matched against the country, the state and the city of the company, so the order of the parts does not matter: `"Paris, France"` and `"France, Paris"` are the same filter. Several locations remain alternatives to each other, whether they hold a comma or not. `"location": { "include": ["Paris, France", "US"] }` matches the companies located in Paris (France) **or** anywhere in the United States. ### Query examples ```json title="Search for all companies headquartered in Paris, France" { "query": { "location": { "include": ["Paris, France"] } } } ``` ```json title="Search for all companies named icypeas or apple" { "query": { "name": { "include": ["icypeas", "apple"] } } } ``` ```json title="Search for all privately held companies that have more than 10 employees" { "query": { "type": { "include": ["Privately Held"] }, "headcount": { ">": 10 } } } ``` ```json title="Search for all companies excluding self-employed people that work in the real estate industry" { "query": { "type": { "exclude": ["Self-Employed"] }, "industry": { "include": ["Real Estate"] } } } ``` ```json title="Search for a specific company using it's public ID" { "query": { "lid": { "include": ["google"] }, } } ``` ```json title="Search for a specific company using it's numerical URN" { "query": { "urn": { "include": ["1527"] }, } } ``` ## Count matching companies (free) When you need to assess the number of companies returned by a specific query, you should use this route. We do not charge anything when using this route. **Endpoint:** `POST https://app.icypeas.com/api/find-companies/count` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | object | yes | The query Rule: should be a valid query (see available fields above). | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/find-companies/count" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" ``` #### Responses ##### 200: Response when they are no errors ```json { "success": true, "total": 10 } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `total` | number | The number of companies | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "object with props defined in the schema", "type": "objectStrict", "actual": { "x": 10 }, "field": "query.headcount", "message": "The object 'query.headcount' contains forbidden keys." } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `` | `query` | Error sent when the query field is not passed inside the request body | | `EmptyQueryError` | `` | `query` | Error sent when the query is empty | | `objectStrict` | `` | `query.[field]` | Error sent when a field contains forbidden keys (for instance { urn: { x: ["test"] } } instead of { urn: { exclude: ["test"] } }) | | `arrayEmpty` | `` | `query.[field].include`, `query.[field].exclude` | Error sent when a Include/Exclude field contains an empty include or exclude array | | `objectLength` | `` | `query.[field]` | Error sent when a field is empty (for instance { urn: {} } instead of { urn: { include: ["1527"] } }) | | `InsufficientCredits` | `` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Fetch matching companies When you want to retrieve the results for your query, you should use this route. The route is paginated, please see below on how to handle pagination. **Endpoint:** `POST https://app.icypeas.com/api/find-companies` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | object | yes | The query Rule: should be a valid query (see available fields above). | | `pagination` | object | no | This is optional and used only if you want to retrieve more companies than the first page. Rule: should be a valid pagination object. | | `pagination.token` | string | yes | The token returned with the previous page. Valid 15 minutes; each response returns a new token with a new 15-minute validity. Rule: should be a valid pagination token. | | `pagination.size` | number | no | The number of companies returned per page Rule: should be a valid integer between 1 and 200. Default: `100`. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/find-companies" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" ``` #### Responses ##### 200: Response when no errors are sent ```json { "success": true, "total": 845, "leads": [ { "name": "Harvard Graduate School of Education", "lid": "hgse", "urn": "urn:li:fs_salesCompany:1653", "url": "https://www.linkedin.com/company/hgse/", "description": "Education touches every aspect of human activity. At the Harvard Graduate School of Education (HGSE), we believe studying and improving the enterprise of education are central to the health and future of society.", "address": "Cambridge, Massachusetts, United States", "numberOfEmployees": 1134, "industry": "Higher Education", "website": "http://www.gse.harvard.edu", "specialties": [], "employeeGrowth": [], "_id": "sMMXT5MBIateb5xQKCCK" }, { "name": "Trammell Crow Company", "lid": "trammell-crow-company", "urn": "urn:li:fs_salesCompany:2320", "url": "https://www.linkedin.com/company/trammell-crow-company/", "description": "Trammell Crow Company (TCC) is a global commercial real estate developer and wholly-owned subsidiary of CBRE Group, Inc. (NYSE:CBRE), a Fortune 500 and S&P 500 company headquartered in Dallas. Founded in 1948, TCC has developed or acquired nearly 2,900 buildings valued at $75 billion and over 655 million square feet. As of September 30, 2024, TCC had $19 billion of projects in process and $13.4 billion in its pipeline. With 575 employees throughout the United States and Europe in 27 offices, the company serves users of and investors in office, industrial/logistics, healthcare, life science, data center and mixed-use projects, as well as multi-family residential through its operating subsidiary High Street Residential. For more information visit www.TrammellCrow.com.", "address": "Dallas, Texas, United States", "numberOfEmployees": 989, "industry": "Real Estate", "website": "http://www.trammellcrow.com", "specialties": [], "employeeGrowth": [], "_id": "-sMXT5MBIateb5xQeCIO" } ], "pagination": { "size": 2, "token": "xxxxxx" } } ``` | Name | Type | Description | | --- | --- | --- | | `success` | boolean | true means that your search succeeded (no error was raised on our side) | | `total` | number | The number of companies | | `pagination` | object | This is used if you want to retrieve more companies than the first page. | | `pagination.token` | string | Token to request the next page. Valid 15 minutes from this response: request the next page before it expires. | | `pagination.size` | number | The number of companies returned per page Default: `100`. | | `leads` | array | An array containing the companies information | | `leads[]._id` | string | Icypeas unique identifier. | | `leads[].name` | string | Company name | | `leads[].url` | string | LinkedIn company URL. | | `leads[].urn` | string | LinkedIn company URN. | | `leads[].website` | string | Company website | | `leads[].phone` | string | Main phone number given by the company (unnormalized, we keep what was given) | | `leads[].industry` | string | Company industry | | `leads[].foundedYear` | number | Company founded year (-1 when not known) | | `leads[].specialties` | array | Company specialties | | `leads[].specialties[].value` | string | Data value. | | `leads[].lid` | string | LinkedIn ID (the `my-linkedin-id` in https://linkedin.com/company/my-linkedin-id). | | `leads[].description` | string | Company description. | | `leads[].pictureUrl` | string | Company image URL. | | `leads[].type` | string | Company legal form (LLC, SAS, SARL, GmbH, ...) | | `leads[].address` | string | Company headquarters address | | `leads[].numberOfEmployees` | number | Number of employees (-1 when not known). | | `leads[].headcountRange` | string | Declarative headcount range (e.g. 1-10 or 50-100) | | `leads[].estimatedRevenuRange` | object | Estimated revenue | | `leads[].estimatedRevenuRange.estimatedMaxRevenue` | object | Estimation of max revenue | | `leads[].estimatedRevenuRange.estimatedMaxRevenue.amount` | number | Amount | | `leads[].estimatedRevenuRange.estimatedMaxRevenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `leads[].estimatedRevenuRange.estimatedMaxRevenue.currency` | string | Amount currency (three letters) | | `leads[].estimatedRevenuRange.estimatedMinRevenue` | object | Estimation of min revenue | | `leads[].estimatedRevenuRange.estimatedMinRevenue.amount` | number | Amount | | `leads[].estimatedRevenuRange.estimatedMinRevenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `leads[].estimatedRevenuRange.estimatedMinRevenue.currency` | string | Amount currency (three letters) | | `leads[].revenue` | object | When the revenue is known (because the company has to disclose it, for instance) | | `leads[].revenue.amount` | number | Amount | | `leads[].revenue.unit` | string | Amount unit (BILLION, MILLION, THOUSAND, ...) | | `leads[].revenue.currency` | string | Amount currency (three letters) | | `leads[].employeeGrowth` | array | Employee growth over the year | | `leads[].employeeGrowth[].growth` | object | employee growth for a given timespan | | `leads[].employeeGrowth[].growth.percentage` | number | Employee growth percentage | | `leads[].employeeGrowth[].growth.timespan` | string | Time span (SIX_MONTHS, YEAR, TWO_YEAR, ...) | ##### 200 (validation error): Response when there are validation errors. ```json { "validationErrors": [ { "expected": "object with props defined in the schema", "type": "objectStrict", "actual": { "x": 10 }, "field": "query.headcount", "message": "The object 'query.headcount' contains forbidden keys." } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `` | `query` | Error sent when the query field is not passed inside the request body | | `EmptyQueryError` | `` | `query` | Error sent when the query is empty | | `objectStrict` | `` | `query.[field]` | Error sent when a field contains forbidden keys (for instance { urn: { x: ["test"] } } instead of { urn: { exclude: ["test"] } }) | | `arrayEmpty` | `` | `query.[field].include`, `query.[field].exclude` | Error sent when a Include/Exclude field contains an empty include or exclude array | | `objectLength` | `` | `query.[field]` | Error sent when a field is empty (for instance { urn: {} } instead of { urn: { include: ["1527"] } }) | | `InsufficientCredits` | `` | `` | You need more credits than the ones you have to run this search. | | `UnpaidSubscriptionError` | `` | `` | Error sent when you have a pending payment and your subscription is therefore not paid | | `number` | `` | `pagination.size` | Error sent when the size in pagination is not an integer | | `numberMin` | `` | `pagination.size` | Error sent when pagination is less than the min | | `numberMax` | `` | `pagination.size` | Error sent when pagination is greater than the max | | `InvalidPaginationTokenError` | `` | `pagination.token` | Error sent when the pagination token is not in the right format | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ## Pagination Results come in pages of `pagination.size` items (100 by default, from 1 to 200). With 1,000 matches and the default size, you need 10 calls to get everything. When there are more results, the response contains a `pagination` object with a `token`. **Send that object back with the same query** to get the next page: ```json title="1. First request" { "query": { "headcount": { ">": 10 } }, "pagination": { "size": 2 } } ``` ```json title="1. First response (shortened)" { "success": true, "total": 845, "leads": [ { "name": "Harvard Graduate School of Education", "numberOfEmployees": 1134, "industry": "Higher Education" }, { "name": "Trammell Crow Company", "numberOfEmployees": 989, "industry": "Real Estate" } ], "pagination": { "size": 2, "token": "eyJzYSI6W1syXSxbMTI4ODQ5MDE5MDJdXSwiaWQiOi…" } } ``` ```json title="2. Next request: same query + the pagination object you received" { "query": { "headcount": { ">": 10 } }, "pagination": { "size": 2, "token": "eyJzYSI6W1syXSxbMTI4ODQ5MDE5MDJdXSwiaWQiOi…" } } ``` Each response brings a new token: always use the latest. Stop when no `pagination` object comes back. ### Token expiration A token is valid for **15 minutes**. Every request that uses it returns a **new token, with a fresh 15-minute window**. So the 15 minutes is the maximum time **between two pages**, not the time you have to go through the whole search. As long as you ask for the next page within 15 minutes of the previous one, you can keep paging for as long as you need, even to retrieve hundreds of thousands or millions of results. | Request | Uses token | Returns token | Valid until | | --- | --- | --- | --- | | Page 1 at 10:00 | none | A | 10:15 | | Page 2 at 10:12 | A | B | 10:27 | | Page 3 at 10:20 | B | C | 10:35 | | … | … | … | … | If a token expires, start again from the first page with the same query. > **Warning** > > - Always send the **latest** token. An older one may have expired even if you're still paging. > - Don't change the `query` between pages: the token belongs to that exact query. > - Pausing your script for more than 15 minutes between two pages (for example while processing a batch) makes the token expire. ## Include/Exclude specs The Include/Exclude field can contains two arrays: 1. `include` array which lists what needs to be included in the query. 2. `exclude` array which lists what needs to be excluded from the query. For instance, when looking for leads, you can include some `currentJobTitle` and exclude some others: ```json { "currentJobTitle": { "include": ["CTO"], "exclude": ["Senior engineer"] } } ``` > **Warning** > > `include` and `exclude` arrays are limited to **200** values per array per field. ## Range specs The Range field contains comparison operators and numerical values only. Some examples are listed below: ```json { "headcount": { ">=": 10, "<": 20 } } ``` ```json { "headcount": { ">": 10, "<=": 20 } } ``` ```json { "headcount": { ">": 10, "<": 20 } } ``` The operators can be `>` (greater than), `<` (less than), `>=` (greater or equal), `<=` (less or equal). You are responsible for giving a range that makes sense. ## Headcount growth specs Filter companies by their employee growth rate over a specific period. This criterion is useful for identifying scaling companies, detecting workforce reductions, or finding organizations at particular growth stages. ### Headcount growth parameters | Parameter | Type | Description | |-----------|------|-------------| | `timespan` | string | The measurement period: `6months`, `12months`, or `24months` | | `min` | number | Minimum growth rate (percentage). Range: -100 to 10,000 | | `max` | number | Maximum growth rate (percentage). Range: -100 to 10,000 | ### Growth rate scale - `0%` = No change in headcount - Positive values = Headcount increase - Negative values = Headcount decrease - `-100%` = Company lost all employees ### Headcount growth example ```json { "headcountGrowth": { "timespan": "6months", "min": -10, "max": 50 } } ``` Returns all companies that experienced between -10% and +50% headcount growth in the past 6 months. --- # Subscription and remaining credits > POST /a/actions/subscription-information: get your current Icypeas plan, its renewal date and your remaining credits. Free. Source: https://api-doc.icypeas.com/reference/subscription Check your plan and how many credits you have left, for example before launching a big bulk search. This endpoint is **free**. The response also contains your user id, also shown on the [API page](https://app.icypeas.com/bo/api). > **Deprecated field** > > `quotas.daily` is not used anymore. It's still in the response for backward compatibility, but don't rely on it: use `credits` to know what you can spend. **Endpoint:** `POST https://app.icypeas.com/api/a/actions/subscription-information` **Headers:** `Authorization: YOUR_API_KEY` (the key alone, no `Bearer` prefix) and `Content-Type: application/json`. Retrieve all the informations about your subscription and your credits. #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | The email of the user you want to retrieve the subscription. Rule: has to be an email. | #### Request example ```bash curl -X POST "https://app.icypeas.com/api/a/actions/subscription-information" \ -H "Content-Type: application/json" \ -H "Authorization: YOUR_API_KEY" \ -d '{ "email": "example@icypeas.com" }' ``` #### Responses ##### 200: Response when your input is good. ```json { "userId": "ax782432ezeze89087", "customerId": "cus_NUigN0lSe4hOh9", "currency": "eur", "checkoutSession": "https://checkout.stripe.com/c/pay/cs_test_a1ekmO20jcbyBsDsNQjI5UUuM9VK4YIPgswGG9h7K6EQI9N7GENBcocA0z#fidkdWxOYHwnPyd1blpxYHZxWmNzZFBIfW5tfHJ1SE1VTE9cdEFDNWgycTU1R3BHM2tMPEQnKSdjd2poVmB3c2B3Jz9xd3BgKSdpZHxqcHFRfHVgJz8ndmxrYmlgWmxxYGgnKSdga2RnaWBVaWRmYG1qaWFgd3YnP3F3cGB4JSUl", "lastInvoiceUrl": "", "priceId": "price_1JPMUiIEPTjHbN3mQjFJqD46", "plan": "Basic Plan", "status": "paid", "credits": 24, "quotas": { "daily": 1000 } } ``` | Name | Type | Description | | --- | --- | --- | | `userId` | string | Icypeas user ID | | `customerId` | string | Stripe user ID | | `status` | string | Status of the user's payment | | `credits` | number | Number of credits | | `plan` | string | Name of the user's plan | | `priceId` | string | ID of the price the user subscribed to | | `lastInvoiceUrl` | string | URL for the user's last invoice | | `checkoutSession` | string | Stripe checkout session | | `currency` | string | Chosen currency of the user. It is an empty string is the user has never had a plan | | `quotas` | object | Quotas available in Icypeas | | `quotas.daily` | number | **Deprecated.** Not used anymore. The value is still returned for backward compatibility but has no effect: ignore it. Daily quota that was used by the SalesNav extractor. | ##### 200 (validation error): Response when there are validation errors ```json { "validationErrors": [ { "expected": "email", "type": "stringEmpty", "actual": "", "field": "email", "message": "validation_string_empty" } ], "success": false } ``` | Type | Message | Fields | When it happens | | --- | --- | --- | --- | | `required` | `validation_required` | `email` | Error sent when the field is missing | | `string` | `validation_string` | `email` | Error sent when the field is not a string | | `stringEmpty` | `validation_string_empty` | `email` | Error sent when the field is empty | | `email` | `validation_email` | `email` | Error sent when the field is not an email address | ##### 401: Response when we could not authenticate you. ```json { "message": "Unauthorized access.", "error": "UnauthorizedAccessError", "status": 401, "code": "unauthorized_access_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | ##### 429: Error received when rate limit is exceeded ```json { "message": "You have exceeded the max number of requests per minute.", "error": "RateLimitExceededError", "status": 429, "code": "rate_limit_exceeded_error" } ``` | Name | Type | Description | | --- | --- | --- | | `message` | string | Human-readable message. | | `error` | string | Error name | | `status` | number | HTTP error code | | `code` | string | Error code | --- # Change or cancel your plan > How to upgrade, downgrade or cancel your Icypeas subscription, and when the change takes effect. Source: https://api-doc.icypeas.com/reference/change-plan Plans are managed in the Icypeas app, not through the API. 1. Open the [plans page](https://app.icypeas.com/bo/billing?o=plans) (or *Your profile* › *Invoices & Plans*). 2. Click **Change my plan**. ![The plans page of the Icypeas app](https://api-doc.icypeas.com/img/screenshots/upgrade-sub-1.png) - **Upgrade:** immediate. New credits are available right away. - **Downgrade or cancel:** at the end of the current billing period. Check your plan and credits from code with the [subscription endpoint](https://api-doc.icypeas.com/reference/subscription).