API documentation.
A small REST API over the same data, the same masking rules and the same credit balance as the web app. Three endpoints: search, unlock, and account. JSON in, JSON out, bearer authentication, 120 requests per minute per key.
Every request carries a bearer key.
Keys authenticate a workspace, not a person.
Every request carries an API key in the Authorization header as a bearer token. Keys look like bl_live_….
Authorization: Bearer bl_live_YOUR_KEYKeys are created in the app under API keys. The full key is shown once, at the moment it is created, and is stored only as a hash afterwards — if you lose it, create a new one and revoke the old. Each key carries scopes: a key without the search scope cannot call the search endpoint, and a key without unlock cannot spend credits. Issuing a read-only key with search alone is a good way to let a system explore the data without any risk of it spending your balance.
Credits, unlocked contacts and rate limits are all shared with the web app. Treat a key as a secret: it can spend money.
Base URL and response envelope.
One shape for every response, so you branch on a single field.
All endpoints live under https://businesslist.ai/api/v1. Every response uses the same envelope:
{ "ok": true, "data": { … } }
{ "ok": false, "error": "Human-readable message" }A non-2xx status always comes with ok: false and a message in error. Nothing is ever returned as a bare array, so adding fields later cannot break your parser.
Three endpoints, and no more.
Search one corpus, free and unlimited.
Rows come back masked. Rows your workspace has already unlocked come back complete.
Searching is free and does not consume credits, so you can page through a segment, count it, and refine the filters as often as you like. The email is reduced to its first character plus the full domain, and the phone to its area code plus the last two digits, which is enough to judge a contact without revealing what a credit buys. Rows your workspace has already unlocked come back complete, with unlocked: true.
Filters combine with AND. The one exception is industry, where comma-separated values combine with OR. Text filters match within the field rather than requiring an exact whole-string match; code and location filters match the stored value exactly.
Request
curl -G https://businesslist.ai/api/v1/search \
-H "Authorization: Bearer bl_live_YOUR_KEY" \
--data-urlencode "corpus=us" \
--data-urlencode "title=owner" \
--data-urlencode "industry=Dental Offices,Medical Offices" \
--data-urlencode "state=FL" \
--data-urlencode "email_domain_type=corporate" \
--data-urlencode "limit=25"Response
data carries the corpus key, the total number of matching records, the page window, how long the query took in milliseconds, and the rows themselves. Row fields are the raw columns of the corpus, so a US row carries email_address and naics_code while a LinkedIn row carries email, title_level and linkedin_url.
{
"ok": true,
"data": {
"corpus": "us",
"total": 41208,
"limit": 25,
"offset": 0,
"took_ms": 412,
"rows": [
{
"id": 41208871,
"company_name": "Harborline Logistics",
"first_name": "Jordan",
"last_name": "Alvarez",
"title": "Operations Manager",
"city": "Miami",
"state": "FL",
"industry": "Freight & Trucking",
"email_address": "j\u2022\u2022\u2022@harborline-example.com",
"phone": "(305) \u2022\u2022\u2022-\u2022\u202241",
"email_domain_type": "corporate",
"email_status": "valid",
"unlocked": false,
"verification": null
}
]
}
}Note total is the size of the whole result set, not of the page — use it to size a segment before spending anything. Contacts that have been suppressed through a Do Not Sell or Share request are removed from results entirely and are never returned by this endpoint.
Unlock reveals the email and the phone.
One credit per newly unlocked contact, up to 100 contacts per call.
Pass the corpus and the row ids returned by search. Contacts your workspace already owns are returned in already_unlocked and cost nothing.
Each email is verified as it is unlocked. If the check returns invalid or disposable, the credit is refunded in the same request and the contact comes back with refunded: true — you still receive the record, you simply are not charged for it. If verification cannot complete, the status is unknown, you keep the contact and no refund is issued.
Request body
{
"corpus": "us", // "us" or "linkedin", required
"ids": [41208871, …], // 1 to 100 row ids, required
"verify": true // optional; verification runs by default
}curl -X POST https://businesslist.ai/api/v1/unlock \
-H "Authorization: Bearer bl_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"corpus":"us","ids":[41208871,41208904,41209117]}'Response
{
"ok": true,
"data": {
"unlocked": [
{
"id": 41208871,
"email": "jordan.alvarez@harborline-example.com",
"phone": "(305) 555-0141",
"contact_name": "Jordan Alvarez",
"company_name": "Harborline Logistics",
"title": "Operations Manager",
"verification": "valid",
"refunded": false
}
],
"already_unlocked": [],
"credits_charged": 1,
"credits_refunded": 0,
"balance": 499,
"stopped_for_credits": false,
"missing": []
}
}Account, key and live balance.
Useful as a health check and for showing a balance in your own tooling.
Returns the workspace the key belongs to, the key name, prefix and scopes, and the live credit balance. Requires no scope beyond a valid key.
curl https://businesslist.ai/api/v1/me \
-H "Authorization: Bearer bl_live_YOUR_KEY"{
"ok": true,
"data": {
"org": { "id": "0f2c…", "name": "Acme Growth", "plan": "growth" },
"key": { "name": "Production", "prefix": "bl_live_7f3a", "scopes": ["search", "unlock"] },
"credits": 499
}
}Rate limits and errors.
Each key is limited to 120 requests per minute, counted per key rather than per workspace.
A background job on its own key cannot starve your interactive traffic. Exceeding the limit returns 429 with a retry-after header in seconds.
Retrying safely
Search is a read and is always safe to retry. Unlock is not idempotent across different id sets, but it is safe in the way that matters: a contact your workspace already owns is never charged again, so retrying a failed unlock with the same ids costs at most the contacts that did not go through the first time. On a 429, wait for retry-after. On a 502, back off and retry.
Search then unlock, in JavaScript.
Search for free to find the segment, filter out what you already own, then unlock a bounded batch and skip anything that came back refunded.
const KEY = process.env.BUSINESSLIST_API_KEY;
const BASE = "https://businesslist.ai/api/v1";
async function call(path, init) {
const res = await fetch(BASE + path, {
...init,
headers: { Authorization: "Bearer " + KEY, ...(init?.headers || {}) },
});
const body = await res.json();
if (!body.ok) throw new Error(res.status + ": " + body.error);
return body.data;
}
// 1. Search — free, unlimited, returns masked contact details.
const params = new URLSearchParams({
corpus: "linkedin",
title_level: "C-Level",
industry: "Construction,Building Materials",
state: "TX",
email_domain_type: "corporate",
exclude_unlocked: "1",
limit: "50",
});
const search = await call("/search?" + params.toString());
console.log(search.total + " matches, showing " + search.rows.length);
// 2. Unlock — one credit per newly revealed contact, max 100 ids per call.
const ids = search.rows.filter((r) => !r.unlocked).map((r) => r.id).slice(0, 100);
const result = await call("/unlock", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ corpus: "linkedin", ids }),
});
console.log("charged", result.credits_charged, "refunded", result.credits_refunded);
if (result.stopped_for_credits) console.warn("Balance ran out before the whole batch was unlocked.");
for (const c of result.unlocked) {
if (c.refunded) continue; // invalid address, credit already returned
console.log(c.contact_name, c.email, c.phone);
}Need a higher rate limit or a dedicated API plan? Email hello@businesslist.ai with the volume and the shape of the workload.
Get a key.
Create an account, open API keys in the app, and issue one. Search calls cost nothing, so you can build and test the whole integration before a single credit is spent.