Quickstart
API access is included with the Pro and Team plans. Create a key on the API keys page, export it as BRC_API_KEY and make your first call.
curl https://buyrealclients.com/api/v1/account \ -H "Authorization: Bearer $BRC_API_KEY"{ "ok": true, "data": { "plan": { "id": "pro", "name": "Pro" }, "credits": { "balance": 1998 } }}Try it
Every example on this page runs as written once BRC_API_KEY is set. Create or revoke keys, and see each key's last use and request count, in Dashboard, API keys .
Authentication
Send your key in the Authorization header as a Bearer token. The X-API-Key header is accepted as an alternative. Keys look like brc_live_ followed by 32 characters and are shown once when created. We store only a hash.
Authorization: Bearer brc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx# orX-API-Key: brc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA missing, malformed or revoked key returns 401 INVALID_API_KEY. A valid key on an account whose plan has no API access (for example after a downgrade) returns 403 PLAN_REQUIRED.
CORS is enabled so you can test from a browser, but never ship a key in client-side code. Call the API from your backend and keep keys in environment variables or a secrets manager.
Rate limits
Requests are limited per API key over a sliding one-minute window. Reveals are additionally limited per minute and per day by your plan, and those limits apply to the app and the API combined.
| Plan | Requests / min | Reveals / min | Reveals / day | Max results per search | Credits / month |
|---|---|---|---|---|---|
| Pro | 60 | 60 | 2,000 | 5,000 | 2,000 |
| Team | 120 | 120 | 6,000 | 10,000 | 6,000 |
Every authenticated response carries these headers:
X-RateLimit-Limitrequests allowed per minute for this key.X-RateLimit-Remainingrequests left in the current window.X-RateLimit-ResetUnix time (seconds) by which your full budget is restored.X-Request-Idunique id of the request. Include it when you contact support.
When you exceed a limit you get 429 with RATE_LIMITED (or DAILY_LIMIT) and a Retry-After header in seconds. Wait that long before retrying.
HTTP/1.1 429 Too Many RequestsX-Request-Id: req_9f1c2d3e4a5b6c7d8e9f0a1bX-RateLimit-Limit: 60X-RateLimit-Remaining: 0X-RateLimit-Reset: 1791717660Retry-After: 12Credits
Searching, fetching a person, suggestions, account and usage calls are free. Revealing a contact field costs 1 credit per field, once.
- Revealing a field you already revealed, in the app or the API, is free and returns the same value.
- If a field turns out to be empty, the credit is refunded automatically and you get
404 NO_DATA. - Out of credits returns
402 INSUFFICIENT_CREDITSand nothing is charged. - Credits are shared with the web app. Check the balance any time with
GET /account.
Responses
Every response is JSON in one of two envelopes: { ok: true, data } or { ok: false, error: { code, message, details? } }. Branch on error.code, which is stable, and show error.message to humans. Field names are snake_case.
Contact values appear only under contact.email.value and contact.phone.value, and only for fields your account has revealed. Everything else, including contact.email.domain and the available flags, is safe to read freely.
{ "ok": false, "error": { "code": "INSUFFICIENT_CREDITS", "message": "Not enough credits", "details": { "required": 1, "balance": 0 } }}Endpoints
All paths are relative to https://buyrealclients.com/api/v1. Switching the language on any example switches all of them.
/api/v1/people/searchFreeSearch people
Filter the people index. Filters are AND-ed together, values inside one list are OR-ed. Results never contain email or phone values, only availability, unless you already revealed that field. Page size is capped at 100; paging is capped at 5,000 results on Pro and 10,000 on Team.
- Send list filters as repeated keys (seniority=vp&seniority=director), as seniority[]=vp, or as a comma list for enum-like filters.
- Unknown query parameters return 400 VALIDATION_ERROR so typos never silently widen a search.
- total is exact up to 20,000 matches; above that total_is_estimate is true and total is a lower bound estimate.
Query parameters
| Name | Type | Description |
|---|---|---|
q | string | Free text over name, title and company. The last word matches as a prefix. |
titles | string[] | Normalized titles or keywords. Get values from /suggest?kind=titles. |
seniority | string[] | owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern. |
functions | string[] | Job functions in snake_case, e.g. sales, marketing, human_resources. |
companies | string[] | Company slugs. Get values from /suggest?kind=companies. |
countries | string[] | Country names, e.g. United States. |
states | string[] | State or region names. |
cities | string[] | City names. |
has_email | boolean | Only people with (true) or without (false) an email on file. |
has_phone | boolean | Only people with (true) or without (false) a phone number on file. |
email_status | string[] | verified, extrapolated or unavailable. |
tier | string[] | premium, standard or hidden. |
sort | string | relevance, name, company, seniority or newest.Default relevance |
page | integer | 1-based page number.Default 1 |
page_size | integer | Results per page, 1 to 100.Default 25 |
facets | boolean | Set to false to skip facet counts for a faster response.Default true |
curl -G https://buyrealclients.com/api/v1/people/search \ -H "Authorization: Bearer $BRC_API_KEY" \ --data-urlencode "q=sales" \ --data-urlencode "seniority=vp" \ --data-urlencode "seniority=c_suite" \ --data-urlencode "countries=United States" \ --data-urlencode "has_phone=true" \ --data-urlencode "page_size=25"{ "ok": true, "data": { "results": [ { "id": "5f2a9c1e7b3d4a6e8c0f1b2d", "slug": "jane-doe-acme-corp-1b2d", "first_name": "Jane", "last_name": "Doe", "full_name": "Jane Doe", "title": "VP of Sales", "seniority": "vp", "functions": [ "sales" ], "company": { "name": "Acme Corp", "slug": "acme-corp" }, "location": { "city": "Chicago", "state": "Illinois", "country": "United States" }, "linkedin_url": "https://www.linkedin.com/in/janedoe-example", "contact": { "email": { "available": true, "status": "verified", "domain": "acme.com" }, "phone": { "available": true } }, "tier": "premium", "profile_url": "https://buyrealclients.com/people/jane-doe-acme-corp-1b2d" } ], "total": 2481, "total_is_estimate": false, "page": 1, "page_size": 25, "max_results": 5000, "facets": { "seniority": [ { "value": "vp", "count": 1630 }, { "value": "c_suite", "count": 851 } ], "country": [ { "value": "United States", "count": 2481 } ], "functions": [ { "value": "sales", "count": 2481 } ], "email_status": [ { "value": "verified", "count": 2210 }, { "value": "extrapolated", "count": 271 } ], "has_phone": [ { "value": "true", "count": 1204 }, { "value": "false", "count": 1277 } ] } }}/api/v1/people/{id}FreeGet a person
Fetch one person by id or by the slug used in profile URLs. Contact values are included only for fields you have already revealed.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | Person id or slug. |
curl https://buyrealclients.com/api/v1/people/5f2a9c1e7b3d4a6e8c0f1b2d \ -H "Authorization: Bearer $BRC_API_KEY"{ "ok": true, "data": { "person": { "id": "5f2a9c1e7b3d4a6e8c0f1b2d", "slug": "jane-doe-acme-corp-1b2d", "first_name": "Jane", "last_name": "Doe", "full_name": "Jane Doe", "title": "VP of Sales", "seniority": "vp", "functions": [ "sales" ], "company": { "name": "Acme Corp", "slug": "acme-corp" }, "location": { "city": "Chicago", "state": "Illinois", "country": "United States" }, "linkedin_url": "https://www.linkedin.com/in/janedoe-example", "contact": { "email": { "available": true, "status": "verified", "domain": "acme.com" }, "phone": { "available": true } }, "tier": "premium", "profile_url": "https://buyrealclients.com/people/jane-doe-acme-corp-1b2d" } }}/api/v1/people/{id}/reveal1 credit per fieldReveal contact details
Reveals the email and/or phone of one person. Each newly revealed field costs 1 credit. Fields you revealed before, in the app or the API, are returned again for free. Fields with no data on file are never charged.
- If some requested fields succeed and others fail, you get 200 with the failures listed under errors.
- If nothing can be revealed, the first error is returned: 402 INSUFFICIENT_CREDITS, 404 NO_DATA, or 429 RATE_LIMITED / DAILY_LIMIT.
- Reveals are also limited per minute and per day by plan (Pro: 60/min, 2,000/day; Team: 120/min, 6,000/day).
Path parameter and JSON body
| Name | Type | Description |
|---|---|---|
idrequired | string | Person id or slug (path). |
fieldsrequired | string[] | One or both of "email" and "phone". |
curl -X POST https://buyrealclients.com/api/v1/people/5f2a9c1e7b3d4a6e8c0f1b2d/reveal \ -H "Authorization: Bearer $BRC_API_KEY" \ -H "Content-Type: application/json" \ -d '{"fields":["email","phone"]}'{ "ok": true, "data": { "person": { "id": "5f2a9c1e7b3d4a6e8c0f1b2d", "slug": "jane-doe-acme-corp-1b2d", "first_name": "Jane", "last_name": "Doe", "full_name": "Jane Doe", "title": "VP of Sales", "seniority": "vp", "functions": [ "sales" ], "company": { "name": "Acme Corp", "slug": "acme-corp" }, "location": { "city": "Chicago", "state": "Illinois", "country": "United States" }, "linkedin_url": "https://www.linkedin.com/in/janedoe-example", "contact": { "email": { "available": true, "status": "verified", "domain": "acme.com", "value": "jane.doe@acme.com" }, "phone": { "available": true, "value": "+1 312-555-0147" } }, "tier": "premium", "profile_url": "https://buyrealclients.com/people/jane-doe-acme-corp-1b2d" }, "credits": { "spent": 2, "balance": 1998 }, "results": { "email": { "source": "vault", "credits_spent": 1 }, "phone": { "source": "vault", "credits_spent": 1 } } }}/api/v1/people/reveal/batch1 credit per new fieldBatch reveal
Reveals up to 25 people in one call. Ids are processed in order. Each id gets its own result: ok, error or skipped.
- The batch stops at the first 402 (out of credits) or 429 (rate limit). Remaining ids come back as skipped with the reason, and are not charged.
- A failed id (for example NOT_FOUND) does not stop the batch.
JSON body
| Name | Type | Description |
|---|---|---|
idsrequired | string[] | 1 to 25 person ids or slugs. Duplicates are ignored. |
fieldsrequired | string[] | One or both of "email" and "phone". |
curl -X POST https://buyrealclients.com/api/v1/people/reveal/batch \ -H "Authorization: Bearer $BRC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ids": [ "5f2a9c1e7b3d4a6e8c0f1b2d", "000000000000000000000000", "5f2a9c1e7b3d4a6e8c0f9999" ], "fields": [ "email" ] }'{ "ok": true, "data": { "results": [ { "id": "5f2a9c1e7b3d4a6e8c0f1b2d", "status": "ok", "person": { "id": "5f2a9c1e7b3d4a6e8c0f1b2d", "slug": "jane-doe-acme-corp-1b2d", "first_name": "Jane", "last_name": "Doe", "full_name": "Jane Doe", "title": "VP of Sales", "seniority": "vp", "functions": [ "sales" ], "company": { "name": "Acme Corp", "slug": "acme-corp" }, "location": { "city": "Chicago", "state": "Illinois", "country": "United States" }, "linkedin_url": "https://www.linkedin.com/in/janedoe-example", "contact": { "email": { "available": true, "status": "verified", "domain": "acme.com", "value": "jane.doe@acme.com" }, "phone": { "available": true, "value": "+1 312-555-0147" } }, "tier": "premium", "profile_url": "https://buyrealclients.com/people/jane-doe-acme-corp-1b2d" }, "credits_spent": 1, "results": { "email": { "source": "vault", "credits_spent": 1 } } }, { "id": "000000000000000000000000", "status": "error", "error": { "code": "NOT_FOUND", "message": "No person found with that id or slug" } }, { "id": "5f2a9c1e7b3d4a6e8c0f9999", "status": "skipped", "reason": "INSUFFICIENT_CREDITS" } ], "totals": { "requested": 3, "succeeded": 1, "failed": 1, "skipped": 1, "credits_spent": 1, "balance": 0 } }}/api/v1/enrichFree to match, credits only with revealEnrich a record
Match one of your own records to a person. A LinkedIn URL is an exact lookup. Otherwise the name is searched and scored against the company and the email domain.
- Only matches with match_confidence of 0.75 or higher are returned. Anything weaker is 404 NO_MATCH, so a wrong person is never charged to you.
- A name on its own is never enough. Add company or a work email (it is used for its domain, and for a first.last name if you send no name). The email address is not stored.
- With reveal: true the match's available fields are revealed and charged like /reveal. Fields the person has no data for are skipped without a charge.
JSON body
| Name | Type | Description |
|---|---|---|
linkedin_url | string | Exact LinkedIn profile URL. Highest confidence. |
first_name / last_name | string | Name of the person. Or send full_name. |
company | string | Current company. Legal suffixes like Inc and LLC are ignored. |
email | string | Work email; only its domain (and first.last name) is used. |
reveal | boolean | Reveal and charge the match's contact fields.Default false |
reveal_fields | string[] | With reveal: which fields to reveal.Default ["email","phone"] |
curl -X POST https://buyrealclients.com/api/v1/enrich \ -H "Authorization: Bearer $BRC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Jane", "last_name": "Doe", "company": "Acme Corp", "reveal": false }'{ "ok": true, "data": { "match_confidence": 0.94, "matched_on": [ "name", "company", "email_domain" ], "person": { "id": "5f2a9c1e7b3d4a6e8c0f1b2d", "slug": "jane-doe-acme-corp-1b2d", "first_name": "Jane", "last_name": "Doe", "full_name": "Jane Doe", "title": "VP of Sales", "seniority": "vp", "functions": [ "sales" ], "company": { "name": "Acme Corp", "slug": "acme-corp" }, "location": { "city": "Chicago", "state": "Illinois", "country": "United States" }, "linkedin_url": "https://www.linkedin.com/in/janedoe-example", "contact": { "email": { "available": true, "status": "verified", "domain": "acme.com" }, "phone": { "available": true } }, "tier": "premium", "profile_url": "https://buyrealclients.com/people/jane-doe-acme-corp-1b2d" }, "credits": { "spent": 0, "balance": 1998 } }}/api/v1/suggestFreeSuggest filter values
Typeahead for the values that search filters expect. Returns up to 10 items.
Query parameters
| Name | Type | Description |
|---|---|---|
kindrequired | string | titles, companies or locations. |
q | string | The prefix typed so far. |
curl -G https://buyrealclients.com/api/v1/suggest \ -H "Authorization: Bearer $BRC_API_KEY" \ --data-urlencode "kind=titles" \ --data-urlencode "q=vp sal"{ "ok": true, "data": { "kind": "titles", "items": [ { "value": "vp sales", "label": "VP Sales", "count": 18234 }, { "value": "vice president of sales", "label": "Vice President Of Sales", "count": 9120 } ] }}/api/v1/accountFreeAccount and limits
Your plan, credit balance, rate and reveal limits, and counters for the key you are calling with.
curl https://buyrealclients.com/api/v1/account \ -H "Authorization: Bearer $BRC_API_KEY"{ "ok": true, "data": { "plan": { "id": "pro", "name": "Pro" }, "credits": { "balance": 1998, "monthly_allowance": 2000, "cost_per_reveal": 1 }, "limits": { "requests_per_minute": 60, "reveals_per_minute": 60, "reveals_per_day": 2000, "reveals_today": 2, "max_search_results": 5000, "max_page_size": 100, "batch_reveal_max_ids": 25 }, "api_key": { "id": "b1f0c5e2-0a7d-4e9a-9f43-2d8c5a1e7c10", "name": "Production", "prefix": "brc_live_Ab3", "created_at": "2026-10-01T09:12:44.000Z", "last_used_at": "2026-10-11T08:30:02.000Z", "request_count": 1542 }, "user": { "id": "809482b5-9ec3-4469-beeb-8ab1b3b4a3c2", "email": "you@company.com" } }}/api/v1/usageFreeUsage
Reveals and credits spent through the API per UTC day. Days without activity are included as zeros.
Query parameters
| Name | Type | Description |
|---|---|---|
days | integer | 1 to 90 days, ending today.Default 30 |
curl -G https://buyrealclients.com/api/v1/usage \ -H "Authorization: Bearer $BRC_API_KEY" \ --data-urlencode "days=3"{ "ok": true, "data": { "period": { "from": "2026-10-09", "to": "2026-10-11", "days": 3 }, "totals": { "reveals": 14, "credits_spent": 14 }, "daily": [ { "date": "2026-10-09", "reveals": 0, "credits_spent": 0 }, { "date": "2026-10-10", "reveals": 9, "credits_spent": 9 }, { "date": "2026-10-11", "reveals": 5, "credits_spent": 5 } ] }}/api/v1/openapi.jsonFree, no keyThe OpenAPI 3.1 description of this API. Import it into Postman, Insomnia or a code generator. Download openapi.json.
Errors
Failed calls return the error envelope with the HTTP status below. Codes are stable; messages may be reworded.
| HTTP | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | A parameter or body field is invalid. details.issues lists each problem by path. |
| 400 | INVALID_JSON | The request body is not valid JSON. |
| 400 | INSUFFICIENT_INPUT | Enrich could not derive a first and last name from the input. |
| 401 | INVALID_API_KEY | The key is missing, malformed, unknown or revoked. |
| 402 | INSUFFICIENT_CREDITS | Not enough credits for the reveal. Nothing was charged. Buy credits or wait for your monthly grant. |
| 403 | PLAN_REQUIRED | The key is valid but the account's plan has no API access (Pro and Team only). |
| 404 | NOT_FOUND | No person with that id or slug. |
| 404 | NO_DATA | The person has no email or phone of that kind on file. You were not charged. |
| 404 | NO_MATCH | Enrich found no match at or above the confidence threshold. |
| 429 | RATE_LIMITED | Too many requests or reveals per minute. Wait Retry-After seconds. |
| 429 | DAILY_LIMIT | Daily reveal cap for your plan reached. Resets at 00:00 UTC. |
| 500 | INTERNAL_ERROR | Unexpected error. Retry; if it persists contact support with the X-Request-Id. |
| 503 | VAULT_UNAVAILABLE | Contact data is temporarily unavailable. You were not charged. Retry shortly. |
SDK snippets
There is no official SDK yet. These small clients handle auth, the response envelope and waiting out a 429 using Retry-After. Copy them into your project.
// brc.mjs: minimal client that waits and retries when rate limitedconst BASE = "https://buyrealclients.com/api/v1";export async function brc(path, { method = "GET", query = {}, body } = {}) { const url = new URL(BASE + path); for (const [key, value] of Object.entries(query)) { for (const item of [].concat(value)) url.searchParams.append(key, String(item)); } for (let attempt = 0; attempt < 4; attempt++) { const res = await fetch(url, { method, headers: { Authorization: `Bearer ${process.env.BRC_API_KEY}`, "Content-Type": "application/json", }, body: body ? JSON.stringify(body) : undefined, }); if (res.status === 429) { const wait = Number(res.headers.get("Retry-After") ?? 1); await new Promise((r) => setTimeout(r, wait * 1000)); continue; } const json = await res.json(); if (!json.ok) { throw Object.assign(new Error(json.error.message), { code: json.error.code, status: res.status }); } return json.data; } throw new Error("Still rate limited after 4 attempts");}// const { results } = await brc("/people/search", { query: { q: "cto", has_email: true } });// const { person } = await brc(`/people/${results[0].id}/reveal`, { method: "POST", body: { fields: ["email"] } });# brc.py: minimal client that waits and retries when rate limitedimport osimport timeimport requestsBASE = "https://buyrealclients.com/api/v1"class BrcError(Exception): def __init__(self, code, message, status): super().__init__(f"{code}: {message}") self.code, self.status = code, statusdef brc(path, method="GET", params=None, json=None): for _ in range(4): res = requests.request( method, BASE + path, params=params, json=json, headers={"Authorization": f"Bearer {os.environ['BRC_API_KEY']}"}, timeout=30, ) if res.status_code == 429: time.sleep(int(res.headers.get("Retry-After", 1))) continue body = res.json() if not body["ok"]: raise BrcError(body["error"]["code"], body["error"]["message"], res.status_code) return body["data"] raise RuntimeError("Still rate limited after 4 attempts")# people = brc("/people/search", params={"q": "cto", "has_email": "true"})["results"]# person = brc(f"/people/{people[0]['id']}/reveal", "POST", json={"fields": ["email"]})["person"]Changelog
- v1
Initial release
People search and lookup, single and batch reveal, enrich, suggestions, account and usage, with Bearer auth, per-key rate limits, CORS and an OpenAPI 3.1 description.
Versioning. The version is in the path (/api/v1). Within v1 we only make backwards-compatible changes: new endpoints, new optional parameters and new response fields. Write your parser to ignore fields it does not know. Breaking changes ship as /api/v2, with v1 kept running for at least 12 months after v2 launches.