![Hunter.io Domain Search avatar](https://images.apifyusercontent.com/dvGmrwfteV80CBI-xdIaGwBye26ZaAi3UlnE1MKivKc/rs:fill:250:250/aHR0cHM6Ly9hcGlmeS1pbWFnZS11cGxvYWRzLXByb2QuczMuYW1hem9uYXdzLmNvbS80YTd5ekdyeWIzSjdPaHVwNy9IZURzZWRpd2g4alhUNDNaVy1odW50ZXItaWNvbl81MTJ4NTEyLnBuZw.webp)
Hunter.io Domain Search
DeprecatedView all Actors![Hunter.io Domain Search](https://images.apifyusercontent.com/dvGmrwfteV80CBI-xdIaGwBye26ZaAi3UlnE1MKivKc/rs:fill:250:250/aHR0cHM6Ly9hcGlmeS1pbWFnZS11cGxvYWRzLXByb2QuczMuYW1hem9uYXdzLmNvbS80YTd5ekdyeWIzSjdPaHVwNy9IZURzZWRpd2g4alhUNDNaVy1odW50ZXItaWNvbl81MTJ4NTEyLnBuZw.webp)
This Actor is deprecated
This Actor is unavailable because the developer has decided to deprecate it. Would you like to try a similar Actor instead?
See alternative Actors![Hunter.io Domain Search](https://images.apifyusercontent.com/dvGmrwfteV80CBI-xdIaGwBye26ZaAi3UlnE1MKivKc/rs:fill:250:250/aHR0cHM6Ly9hcGlmeS1pbWFnZS11cGxvYWRzLXByb2QuczMuYW1hem9uYXdzLmNvbS80YTd5ekdyeWIzSjdPaHVwNy9IZURzZWRpd2g4alhUNDNaVy1odW50ZXItaWNvbl81MTJ4NTEyLnBuZw.webp)
Hunter.io Domain Search
spiralcrew/hunter-domain-search
Hunter.io's Domain Search is a robust tool for discovering all email addresses linked to a specific domain. It offers confidence scores, department filters, and detailed sources, making it an invaluable resource for efficient contact information retrieval.
package.json
1{
2 "name": "hunter.io-domain-search",
3 "version": "0.0.1",
4 "description": "Discover emails across multiple domains, set limits, filter by type/seniority, specify departments, and obtain required fields with the 'Domain Email Search' actor. Pay only for returned emails. Simplify contact discovery for sales, marketing, and outreach efforts.",
5 "author": "Adriaan",
6 "dependencies": {
7 "apify": "^3.1.4",
8 "dotenv": "^16.1.3",
9 "node-fetch": "^2.6.11"
10 },
11 "devDependencies": {
12 "@apify/eslint-config-ts": "^0.2.3",
13 "@apify/tsconfig": "^0.1.0",
14 "@types/jest": "^29.2.5",
15 "@types/node": "^18.16.16",
16 "@types/node-fetch": "^2.6.4",
17 "@typescript-eslint/eslint-plugin": "^5.55.0",
18 "@typescript-eslint/parser": "^5.55.0",
19 "eslint": "^8.37.0",
20 "jest": "^29.3.1",
21 "ncp": "^2.0.0",
22 "prettier": "^2.8.3",
23 "ts-jest": "^29.0.5",
24 "ts-loader": "^9.4.2",
25 "ts-node": "^10.9.1",
26 "typescript": "^4.9.5"
27 },
28 "scripts": {
29 "start": "node dist/main.js",
30 "start:dev": "ts-node-esm -T src/main.ts",
31 "build": "tsc",
32 "lint": "eslint . --ext .ts",
33 "lint:fix": "eslint ./src --ext .ts --fix",
34 "test": "npm run test",
35 "push:actor": "apify push"
36 }
37}
.actor/actor.json
1{
2 "actorSpecification": 1,
3 "name": "hunter-domain-search",
4 "title": "Hunter Domain Search",
5 "version": "0.1",
6 "buildTag": "latest",
7 "input": "./input_schema.json",
8 "storages": {
9 "dataset": "./dataset_schema.json"
10 }
11}
.actor/dataset_schema.json
1{
2 "actorSpecification": 1,
3 "views": {
4 "overview": {
5 "title": "Overview",
6 "transformation": {
7 "fields": [
8 "domain",
9 "value",
10 "type",
11 "confidence",
12 "sources",
13 "first_name",
14 "last_name",
15 "position",
16 "seniority",
17 "department",
18 "linkedin",
19 "twitter",
20 "phone_number",
21 "verification"
22 ]
23 },
24 "display": {
25 "component": "table",
26 "properties": {}
27 }
28 }
29 }
30}
.actor/input_schema.json
1{
2 "title": "Domain Search",
3 "type": "object",
4 "schemaVersion": 1,
5 "properties": {
6 "api_key": {
7 "title": "🔑 API Key",
8 "description": "Your Hunter.io API key.",
9 "type": "string",
10 "editor": "textfield",
11 "prefill": "fill in your API key",
12 "nullable": false
13 },
14 "domain": {
15 "title": "Domain or company name from which you want to find the email addresses.",
16 "description": "For example, 'stripe.com' or 'stripe'. Note that you'll get better results by supplying the domain name as we won't have to find it. It doesn't need to be in lowercase.",
17 "type": "string",
18 "editor": "textfield",
19 "prefill": "stripe.com",
20 "nullable": false
21 },
22 "limit": {
23 "title": "Specifies the max number of email addresses to return.",
24 "type": "string",
25 "description": "The default is 10.",
26 "editor": "select",
27 "default": "10",
28 "enum": ["10", "20", "30", "40", "50", "60", "70", "80", "90", "100"],
29 "nullable": false
30 },
31 "offset": {
32 "title": "Specifies the number of email addresses to skip.",
33 "type": "string",
34 "description": "The default is 0.",
35 "editor": "select",
36 "default": "0",
37 "enum": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
38 "nullable": false
39 },
40 "type": {
41 "title": "Get only personal or generic email addresses.",
42 "type": "string",
43 "description": "Choose personal emails, generic emails, or select all.",
44 "editor": "select",
45 "default": "all",
46 "enum": ["all", "personal", "generic"],
47 "enumTitles": ["All Emails", "Personal", "Generic"],
48 "nullable": true
49 },
50 "junior": {
51 "sectionCaption": "Get only email addresses for people with the selected seniority level.",
52 "sectionDescription": "",
53 "title": "Junior",
54 "description": "",
55 "type": "boolean",
56 "default": false,
57 "nullable": true
58 },
59 "senior": {
60 "title": "Senior",
61 "description": "",
62 "type": "boolean",
63 "default": false,
64 "nullable": true
65 },
66 "executive": {
67 "title": "Executive",
68 "description": "",
69 "type": "boolean",
70 "default": false,
71 "nullable": true
72 },
73 "executives": {
74 "sectionCaption": "Get only email addresses for people working in the selected department(s).",
75 "sectionDescription": "",
76 "title": "Executive",
77 "description": "",
78 "type": "boolean",
79 "default": false,
80 "nullable": true
81 },
82 "it": {
83 "title": "IT",
84 "description": "",
85 "type": "boolean",
86 "default": false,
87 "nullable": true
88 },
89 "finance": {
90 "title": "Finance",
91 "description": "",
92 "type": "boolean",
93 "default": false,
94 "nullable": true
95 },
96 "management": {
97 "title": "Management",
98 "description": "",
99 "type": "boolean",
100 "default": false,
101 "nullable": true
102 },
103 "sales": {
104 "title": "Sales",
105 "description": "",
106 "type": "boolean",
107 "default": false,
108 "nullable": true
109 },
110 "legal": {
111 "title": "Legal",
112 "description": "",
113 "type": "boolean",
114 "default": false,
115 "nullable": true
116 },
117 "support": {
118 "title": "Support",
119 "description": "",
120 "type": "boolean",
121 "default": false,
122 "nullable": true
123 },
124 "hr": {
125 "title": "HR",
126 "description": "",
127 "type": "boolean",
128 "default": false,
129 "nullable": true
130 },
131 "marketing": {
132 "title": "Marketing",
133 "description": "",
134 "type": "boolean",
135 "default": false,
136 "nullable": true
137 },
138 "communication": {
139 "title": "Communication",
140 "description": "",
141 "type": "boolean",
142 "default": false,
143 "nullable": true
144 },
145 "education": {
146 "title": "Education",
147 "description": "",
148 "type": "boolean",
149 "default": false,
150 "nullable": true
151 },
152 "design": {
153 "title": "Design",
154 "description": "",
155 "type": "boolean",
156 "default": false,
157 "nullable": true
158 },
159 "health": {
160 "title": "Health",
161 "description": "",
162 "type": "boolean",
163 "default": false,
164 "nullable": true
165 },
166 "operations": {
167 "title": "Operations",
168 "description": "",
169 "type": "boolean",
170 "default": false,
171 "nullable": true
172 },
173 "full_name": {
174 "sectionCaption": "Get only email addresses that have the selected field(s).",
175 "sectionDescription": "",
176 "title": "Full Name",
177 "description": "",
178 "type": "boolean",
179 "default": false,
180 "nullable": true
181 },
182 "position": {
183 "title": "Position",
184 "description": "",
185 "type": "boolean",
186 "default": false,
187 "nullable": true
188 },
189 "phone_number": {
190 "title": "Phone Number",
191 "description": "",
192 "type": "boolean",
193 "default": false,
194 "nullable": true
195 }
196 },
197 "required": ["api_key", "domain", "limit", "offset"]
198}
dist/main.js
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const dotenv_1 = __importDefault(require("dotenv"));
7dotenv_1.default.config();
8const node_fetch_1 = __importDefault(require("node-fetch"));
9const apify_1 = require("apify");
10const utils_1 = require("./utils");
11const types_1 = require("./types");
12(async () => {
13 try {
14 await apify_1.Actor.init();
15 const { domain, apiKey, limit, offset, type, seniorityLevels, departments, requiredFields, } = await getActorInput();
16 const API_ENDPOINT = `https://api.hunter.io/v2/domain-search?api_key=${apiKey}`;
17 const baseUrl = buildBaseUrl(API_ENDPOINT, domain, limit, offset, type, seniorityLevels, departments, requiredFields);
18 const data = await fetchEmailData(baseUrl);
19 if (!data?.emails?.length)
20 apify_1.log.info(`No emails found for ${domain}`);
21 if (data.emails.length)
22 await logAndSaveData(domain, data);
23 // add rate limit of 10 requests per second
24 await (0, utils_1.delay)(100);
25 }
26 catch (err) {
27 apify_1.log.error(err.message);
28 }
29 finally {
30 await apify_1.Actor.exit();
31 }
32})();
33const getActorInput = async () => {
34 const input = await apify_1.Actor.getInput();
35 const { domain, api_key: apiKey, limit, offset, type, } = input;
36 if (!apiKey)
37 throw new Error("No API key supplied!");
38 if (!domain)
39 throw new Error("No domain or company provided!");
40 return {
41 domain,
42 apiKey,
43 limit,
44 offset,
45 type,
46 seniorityLevels: (0, utils_1.getEnumVal)(types_1.Seniority, input),
47 departments: (0, utils_1.getEnumVal)(types_1.Departments, input),
48 requiredFields: (0, utils_1.getEnumVal)(types_1.RequiredFields, input),
49 };
50};
51const fetchEmailData = async (baseUrl) => {
52 const response = await (0, node_fetch_1.default)(baseUrl);
53 if (!response.ok) {
54 throw new Error(`HTTP error! status: ${response.status}`);
55 }
56 const jsonResponse = await response.json();
57 const { data } = jsonResponse;
58 return data;
59};
60const logAndSaveData = async (domain, data) => {
61 const emailCount = data.emails.length;
62 apify_1.log.info(`Found ${emailCount} emails for ${domain}`);
63 await saveResults(domain, data);
64};
65function buildBaseUrl(endpoint, domain, limit, offset, type, seniorityLevels, departments, requiredFields) {
66 const url = new URL(endpoint);
67 if ((0, utils_1.isNumeric)(limit))
68 url.searchParams.append("limit", limit);
69 if ((0, utils_1.isNumeric)(offset))
70 url.searchParams.append("offset", offset);
71 if (type === "personal" || type === "generic")
72 url.searchParams.append("type", type);
73 if (seniorityLevels.length)
74 url.searchParams.append("seniority", seniorityLevels.join());
75 if (departments.length)
76 url.searchParams.append("department", departments.join());
77 if (requiredFields.length)
78 url.searchParams.append("required_field", requiredFields.join());
79 // check if domain or company is provided
80 const isDomainProvided = (0, utils_1.isDomain)(domain);
81 if (isDomainProvided)
82 url.searchParams.append("domain", domain);
83 if (!isDomainProvided)
84 url.searchParams.append("company", domain);
85 return url.toString();
86}
87async function saveResults(domain, data) {
88 for (const email of data.emails) {
89 await apify_1.Actor.pushData(Object.assign({ domain }, email));
90 }
91}
dist/types.js
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.RequiredFields = exports.Departments = exports.Seniority = void 0;
4var Seniority;
5(function (Seniority) {
6 Seniority["junior"] = "junior";
7 Seniority["senior"] = "senior";
8 Seniority["executive"] = "executive";
9})(Seniority = exports.Seniority || (exports.Seniority = {}));
10var Departments;
11(function (Departments) {
12 Departments["executives"] = "executive";
13 Departments["it"] = "it";
14 Departments["finance"] = "finance";
15 Departments["management"] = "management";
16 Departments["sales"] = "sales";
17 Departments["legal"] = "legal";
18 Departments["support"] = "support";
19 Departments["hr"] = "hr";
20 Departments["marketing"] = "marketing";
21 Departments["communication"] = "communication";
22 Departments["education"] = "education";
23 Departments["design"] = "design";
24 Departments["health"] = "health";
25 Departments["operations"] = "operations";
26})(Departments = exports.Departments || (exports.Departments = {}));
27var RequiredFields;
28(function (RequiredFields) {
29 RequiredFields["full_name"] = "full_name";
30 RequiredFields["position"] = "position";
31 RequiredFields["phone_number"] = "phone_number";
32})(RequiredFields = exports.RequiredFields || (exports.RequiredFields = {}));
dist/utils.js
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.getEnumVal = exports.isDomain = exports.isNumeric = exports.delay = void 0;
4async function delay(ms) {
5 return new Promise((r) => setTimeout(r, ms));
6}
7exports.delay = delay;
8function isNumeric(str) {
9 const parsedNr = parseFloat(str);
10 return !isNaN(parsedNr) && isFinite(parsedNr);
11}
12exports.isNumeric = isNumeric;
13function isDomain(str) {
14 const domainRegex = /^(?:[-A-Za-z0-9]+\.)+[A-Za-z]{2,}$/;
15 return domainRegex.test(str);
16}
17exports.isDomain = isDomain;
18function getEnumVal(ENUM, input) {
19 return Object.entries(input)
20 .map(([key]) => {
21 const enumVal = ENUM[key];
22 return input[key] === true ? enumVal : null;
23 })
24 .filter((val) => val);
25}
26exports.getEnumVal = getEnumVal;
Developer
Maintained by Community
Categories