Get started
Quickstart
Your first working email in three calls. It takes about five minutes.
1. Get a key
Log in, open the API page and click Create key. Pick Test for now: test keys return made-up results, never charge credits and never contact anyone. Copy the key right away; we only show it once.
export MOLEMAN_API_KEY="mm_test_..."2. Run the three calls
GET /api/v1/accountchecks the key and shows your credits.POST /api/v1/find-contactsasks for Sarah Miller's email at brightsmiledental.com and waits up to 30 seconds.GET /api/v1/jobs/{id}reads the result (and keeps reading if the job needs longer than 30 seconds).
curl
#!/usr/bin/env bash
# Moleman quickstart: your first working email in 3 calls.
# Needs: curl. Set MOLEMAN_API_KEY first (start with your mm_test_ key: free, made-up results).
set -euo pipefail
BASE="${MOLEMAN_BASE_URL:-https://moleman.io}"
AUTH="Authorization: Bearer ${MOLEMAN_API_KEY:?Set MOLEMAN_API_KEY to your API key}"
# 1. Check the key and your credits.
curl -sS "$BASE/api/v1/account" -H "$AUTH"
echo
# 2. Find Sarah Miller's email at brightsmiledental.com (waits up to 30 s for the answer).
ANSWER=$(curl -sS -X POST "$BASE/api/v1/find-contacts" \
-H "$AUTH" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-$(date +%s)" \
-d '{"rows":[{"firstName":"Sarah","lastName":"Miller","website":"brightsmiledental.com"}],"wait":30}')
echo "$ANSWER"
JOB_ID=$(echo "$ANSWER" | sed -n 's/.*"jobId":\([0-9]*\).*/\1/p')
# 3. Read the job until it is done (a big job takes longer than 30 s; this loop waits for it).
while true; do
JOB=$(curl -sS "$BASE/api/v1/jobs/$JOB_ID" -H "$AUTH")
case "$JOB" in
*'"status":"done"'*|*'"status":"cancelled"'*|*'"status":"failed"'*) break ;;
esac
sleep 5
done
echo "$JOB" | sed -n 's/.*"email":"\([^"]*\)".*"result":"\([^"]*\)".*/Email: \1 (\2)/p'Node.js
// Moleman quickstart: your first working email in 3 calls. Node 18 or newer, no packages.
// Run: MOLEMAN_API_KEY=mm_test_... node quickstart.mjs (start with your test key: free, made-up results)
import { randomUUID } from 'node:crypto';
const BASE = process.env.MOLEMAN_BASE_URL ?? 'https://moleman.io';
const KEY = process.env.MOLEMAN_API_KEY;
if (!KEY) throw new Error('Set MOLEMAN_API_KEY to your API key.');
async function call(method, path, body) {
const response = await fetch(`${BASE}${path}`, {
method,
headers: {
Authorization: `Bearer ${KEY}`,
...(body ? { 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message} (${data.error.docs_url})`);
return data;
}
// 1. Check the key and your credits.
const account = await call('GET', '/api/v1/account');
console.log(`Key works (${account.key.mode}). Credits left: ${account.credits.balance}`);
// 2. Find Sarah Miller's email at brightsmiledental.com (waits up to 30 s for the answer).
let job = await call('POST', '/api/v1/find-contacts', {
rows: [{ firstName: 'Sarah', lastName: 'Miller', website: 'brightsmiledental.com' }],
wait: 30,
});
// 3. Read the job until it is done (a big job takes longer than 30 s; this loop waits for it).
while (!['done', 'cancelled', 'failed'].includes(job.status)) {
await new Promise((resolve) => setTimeout(resolve, 5000));
job = await call('GET', `/api/v1/jobs/${job.jobId}`);
}
for (const row of job.results) console.log(`Email: ${row.email ?? 'none'} (${row.result ?? row.findStatusText})`);Python
# Moleman quickstart: your first working email in 3 calls. Python 3.8 or newer, no packages.
# Run: MOLEMAN_API_KEY=mm_test_... python quickstart.py (start with your test key: free, made-up results)
import json
import os
import time
import urllib.error
import urllib.request
import uuid
BASE = os.environ.get("MOLEMAN_BASE_URL", "https://moleman.io")
KEY = os.environ["MOLEMAN_API_KEY"]
def call(method, path, body=None):
headers = {"Authorization": f"Bearer {KEY}"}
data = None
if body is not None:
headers["Content-Type"] = "application/json"
headers["Idempotency-Key"] = str(uuid.uuid4())
data = json.dumps(body).encode()
request = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(request, timeout=60) as response:
return json.load(response)
except urllib.error.HTTPError as error:
problem = json.load(error)["error"]
raise SystemExit(f"{problem['code']}: {problem['message']} ({problem['docs_url']})")
# 1. Check the key and your credits.
account = call("GET", "/api/v1/account")
print(f"Key works ({account['key']['mode']}). Credits left: {account['credits']['balance']}")
# 2. Find Sarah Miller's email at brightsmiledental.com (waits up to 30 s for the answer).
job = call("POST", "/api/v1/find-contacts", {
"rows": [{"firstName": "Sarah", "lastName": "Miller", "website": "brightsmiledental.com"}],
"wait": 30,
})
# 3. Read the job until it is done (a big job takes longer than 30 s; this loop waits for it).
while job["status"] not in ("done", "cancelled", "failed"):
time.sleep(5)
job = call("GET", f"/api/v1/jobs/{job['jobId']}")
for row in job["results"]:
print(f"Email: {row['email'] or 'none'} ({row['result'] or row['findStatusText']})")With a test key you get sarah.miller@brightsmiledental.com and the result Works. These examples are tested against our server with a test key on every change.
3. Go live
Create a live key (mm_live_…) on the API page and use your own rows. Each email that Works costs 1 credit; everything else is free. Your account starts with free trial credits; see Credits.
Next
- Send a whole list at once (up to 50,000 rows) and get a webhook when it is done.
- Find businesses in an area instead of uploading a list.
- Put the results into Instantly or Smartlead, or use Clay, Make, Zapier or n8n.