Skip to content
Developers

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.

01 · Authentication

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_KEY

Keys 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.

02 · Envelope

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.

03 · Endpoints

Three endpoints, and no more.

MethodPathScopeDescription
GET/api/v1/searchsearchSearch a corpus. Returns masked rows; rows your workspace has unlocked come back complete.
POST/api/v1/unlockunlockUnlock up to 100 contacts by id. Charges one credit per newly unlocked contact.
GET/api/v1/menoneIdentity of the key, the workspace it belongs to, and the live credit balance.
05 · POST /api/v1/unlock

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": []
  }
}
FieldMeaning
unlockedContacts revealed by this call, with full email and phone, the verification status, and whether the credit was refunded.
already_unlockedContacts your workspace already owned. Returned in full, charged nothing.
credits_chargedCredits deducted by this call.
credits_refundedCredits returned automatically because the address was invalid or disposable.
balanceCredit balance after the call, so you do not need a second request to track it.
stopped_for_creditsTrue when the balance ran out partway through the batch. The contacts before that point are unlocked and charged; the rest are untouched.
missingIds that could not be resolved in the corpus — a stale id, or a record that has since been suppressed.
06 · GET /api/v1/me

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
  }
}
07 · Limits and errors

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.

StatusWhen it happens
400The request was malformed — an unknown filter, a limit above 100, an unlock body that is not valid JSON, or more than 100 ids.
401The Authorization header is missing, or the key is unknown, revoked or expired.
403The key is valid but not permitted: it lacks the required scope, the workspace plan does not include API access, or the account is suspended.
429The rate limit of 120 requests per minute per key was exceeded. The response carries a retry-after header, in seconds.
502The search backend could not be reached. Safe to retry.

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.

08 · End to end

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.

Create an accountPlans with API access