PPeakAI API
Base URL https://build.thepeakai.com

PeakAI API Reference

Find verified phone numbers and email addresses from a public professional profile, and search for people and companies.

Base URL: https://build.thepeakai.com All endpoints below live under /api. JSON in, JSON out.


#Quick start

BASH
# 1. sign in (token is short-lived — reuse it, don't sign in per call)
TOKEN=$(curl -s -X POST "https://build.thepeakai.com/token" \
  -H "Content-Type: application/json" \
  -d '{"id":"you@company.com","password":"YOUR_PASSWORD"}' | jq -r .access_token)

# 2. look up a phone number
curl -X POST "https://build.thepeakai.com/api?type=phone_no&profile_url=https://www.linkedin.com/in/williamhgates" \
  -H "Authorization: Bearer $TOKEN"
JSON
{
  "linkedin_url": "https://www.linkedin.com/in/williamhgates",
  "phone_no": ["+1 425 555 0142"],
  "from_cache": false,
  "credits_charged": 5,
  "credits_remaining": 4815
}

#How it works

The LinkedIn profile URL is the key to everything. Give us a URL and ask for what you want — each call is independent, and you only pay for what you ask for.

TEXT
https://www.linkedin.com/in/janedoe
        │
        ├── type=enrich       → full profile: name, headline, current role,
        │                       experience, education, skills, location
        ├── type=phone_no     → personal / mobile numbers
        ├── type=email        → personal email addresses
        └── type=work_email   → work email addresses

A typical integration is two steps:

  1. Enrich — type=enrich on the URL to get who the person is and where they work. Use that to score, segment or filter your list.
  2. Get contacts — for the ones worth contacting, call type=phone_no and/or type=email on the same URL.

Doing it in that order means you only spend contact credits on profiles that passed your own filtering — enrichment is the cheaper call.

Don't have URLs yet? Use lead search to find people by job title, company, location and so on. It returns profiles you can then enrich and contact exactly as above.


#1. Authentication

Sign in with the credentials issued to you and use the returned token on every request.

POST /token

BASH
curl -X POST "https://build.thepeakai.com/token" \
  -H "Content-Type: application/json" \
  -d '{"id":"you@company.com","password":"YOUR_PASSWORD"}'
JSON
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "email": "you@company.com",
  "token_expires_in": "900"
}

Send it as a bearer token:

TEXT
Authorization: Bearer <access_token>

token_expires_in is seconds (returned as a string). Tokens are short-lived — request a new one when it expires, or refresh shortly before. Don't sign in on every call; reuse the token until it expires.

Need credentials? Email support@thepeakai.com. API access is enabled per account — if /api returns 403, that's what's missing.

Long-lived API keys (pk_…) are also supported for server-to-server use and never expire — create one under API & Integrations in the dashboard, and send it exactly the same way. Revoke it immediately if it leaks; it carries your account's full authority.

#2. Credits and pricing

Everything is billed in credits. Prices differ per account (negotiated rates, team plans), so read them at runtime instead of hardcoding:

GET /api/balance

JSON
{
  "credits": 4820,
  "account_type": "user",
  "pricing": {
    "phone_no": 5,
    "email": 1,
    "work_email": 1,
    "enrich": 1,
    "reverse_lookup": 1
  },
  "plan": "api",
  "is_unlimited": false
}
Action Cost
Phone / email / work email pricing.<type> — typically 5 for phone, 1 for email
Profile enrichment pricing.enrich
Lead or company search 2 credits per page
Export 0.5 credits per row, minimum 2

#You are never charged for

  • a lookup that finds nothing
  • the same contact again within 28 days (served from cache)
  • re-exporting rows you already exported
  • any failure on our side or at a data provider (502, 504)

is_unlimited: true means your plan uses daily caps rather than credits; all prices read 0.


#3. Contact lookup

The core endpoint. One profile, one contact type, one call.

POST /api

Parameter Required Notes
type yes see table below
profile_url for profile types a linkedin.com/in/... URL
din for din_phone, din_email Indian director identification number
lookupValue for reverse_lookup an email address or phone number

Parameters may be sent as query string or JSON body.

type Returns
phone_no Personal / mobile numbers
email Personal email addresses
work_email Work email addresses
enrich Full profile — experience, education, skills
reverse_lookup Finds a profile from an email or phone
din_phone Indian company-director phone
din_email Indian company-director email

Found

JSON
{
  "linkedin_url": "https://www.linkedin.com/in/williamhgates",
  "phone_no": ["+1 425 555 0142"],
  "from_cache": false,
  "credits_charged": 5,
  "credits_remaining": 4815
}

Not found — no charge, and note the field is a string, not an array:

JSON
{ "linkedin_url": "https://www.linkedin.com/in/williamhgates", "phone_no": "Not Found" }

Check Array.isArray(response.phone_no) to distinguish a hit from a miss.

We query several data providers in sequence and return the first verified result, so a call may take a few seconds.


Search for people. 2 credits per page, 25 results per page.

POST /api/lead-search

JSON
{
  "filters": {
    "jobTitles": ["Head of Growth"],
    "locations": ["India"],
    "seniorityLevelIds": ["310"],
    "industries": ["4"]
  },
  "name": "India growth leads",
  "page": 1
}
JSON
{
  "search_id": "7d073fcc-6b7a-4230-bf2e-6972894e180c",
  "search_name": "India growth leads",
  "page": 1,
  "has_more": true,
  "total": 8231494,
  "count": 25,
  "ignored_filters": [],
  "credits_charged": 2,
  "leads": [
    {
      "id": "ade75cfb-2cb1-4260-b7a1-9f59a324123e",
      "name": "Jane Doe",
      "title": "Head of Growth",
      "company": "Acme",
      "location": "Bengaluru, India",
      "linkedin_url": "https://www.linkedin.com/in/...",
      "status": "listed"
    }
  ]
}

#Paging

Send the same search_id with the next page number. Pages accumulate under one search; each costs 2 credits.

JSON
{ "filters": { ... }, "page": 2, "search_id": "7d073fcc-..." }

Keep paging while has_more is true. A single search returns up to roughly 2,500 people; narrow the filters if you need more coverage.

#ignored_filters

Names any filter we could not apply — we never silently drop one. If it is non-empty, your results do not reflect those filters.

#Filters

Free text (arrays of strings): search, jobTitles, pastJobTitles, firstNames, lastNames, companies, pastCompanies, locations, companyHeadquarterLocations, schools, profileLanguages

IDs — call GET /filter-options (no auth) for valid ids and labels: industries, seniorityLevelIds, functionIds, yearsOfExperienceIds, yearsAtCurrentCompanyIds, companyHeadcount

Booleans: recentlyChangedJobs, recentlyPostedOnLinkedIn

Exclusions: every text filter has an exclude* twin — excludeCurrentJobTitles, excludeLocations, excludeCurrentCompanies, excludePastCompanies, excludeSchools, excludeIndustryIds, excludeSeniorityLevelIds, excludeFunctionIds, excludeCompanyHeadquarterLocations

firstNames matches a first name only. Putting a full name there returns nothing — use search for full names.

POST /api/company-search — same shape, 50 results per page. Filters: search, industries, locations (only the first is applied), headcount.


#5. Revealing contacts on search results

Search returns names and titles, not contacts. To reveal one, call the normal lookup endpoint with the lead's id:

POST /api?type=phone_no&lead_id=<lead id>

Charged at your normal per-contact rate. The result attaches to the search, so a later export returns it.

You can pass profile_url + search_id instead — the URL from a search response works directly.


#6. Export

Returns your selected rows including any contacts you have already revealed.

Both ids come from the search response you already have: search_id is at the top level, and each lead_ids entry is a leads[].id.

Keep them. Store search_id and the lead ids on your side when you run a search — they are how you export, and how you re-export later. Re-exporting rows you have already exported is free, so storing them costs you nothing and protects you if a file is lost.

POST /api/lead-export

JSON
{ "search_id": "7d073fcc-...", "lead_ids": ["ade75cfb-...", "..."] }
JSON
{
  "search_id": "7d073fcc-...",
  "search_name": "India growth leads",
  "credits_charged": 15,
  "charged_rows": 30,
  "credits_remaining": 4800,
  "enriched_count": 0,
  "leads": [
    {
      "id": "ade75cfb-...",
      "name": "Jane Doe",
      "title": "Head of Growth",
      "company": "Acme",
      "location": "Bengaluru, India",
      "linkedin_url": "https://www.linkedin.com/in/janedoe",
      "phone_no": ["+91 90000 00000"],
      "email": ["jane@example.com"],
      "work_email": null
    }
  ]
}

0.5 credits per row, minimum 2 — charged on new rows only. Rows you already exported come back free, so losing a file never costs twice. charged_rows tells you how many were billed.

#Reveal and export in one call

Add enrich_types to reveal contacts for any selected row that lacks them:

JSON
{
  "search_id": "7d073fcc-...",
  "lead_ids": ["..."],
  "enrich_types": ["phone_no", "email"]
}

Contacts are charged at your normal per-contact rate on top of the export fee. Rows already revealed are skipped, never re-charged. If credits run out part-way we stop revealing and still return what succeeded.

POST /api/company-export works the same way.


#7. Errors

Every error returns { "error": "human readable message" }.

Status Meaning Retry?
400 Malformed request — see error Fix and retry
401 Missing, invalid or revoked key No
402 Not enough credits After topping up
403 API access not enabled on this account Contact support
404 Unknown lead or search No
422 LinkedIn profile could not be resolved — not charged Yes, shortly
429 Rate limit or daily cap reached Later
502 Data provider unavailable — not charged Yes
504 Data provider timed out — not charged. Usually an over-broad search Yes, narrow filters

422, 502 and 504 never charge credits and never consume a daily search. Retry them safely.


#8. Limits and timeouts

  • Lookups: no fixed rate limit on /api — your credit balance is the constraint. Keep concurrency reasonable (≤10 in flight).
  • Search: no daily cap on /api — you pay per page.
  • Max results: ~2,500 people or ~1,000 companies per search. Narrow the filters and run several searches for broader coverage.
  • Timeouts: a lookup takes a few seconds; a search up to 45s when a provider is slow. Set your client timeout to at least 60s.

#9. A complete example

BASH
API="https://build.thepeakai.com/api"

# 0. sign in
TOKEN=$(curl -s -X POST "https://build.thepeakai.com/token" \
  -H "Content-Type: application/json" \
  -d '{"id":"you@company.com","password":"YOUR_PASSWORD"}' | jq -r .access_token)

# 1. search (2 credits)
SEARCH=$(curl -s -X POST "$API/lead-search" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"filters":{"jobTitles":["Head of Growth"],"locations":["India"]},"name":"India growth"}')

SEARCH_ID=$(echo "$SEARCH" | jq -r .search_id)
LEAD_ID=$(echo "$SEARCH"   | jq -r .leads[0].id)

# 2. reveal one contact (charged at your phone rate)
curl -s -X POST "$API?type=phone_no&lead_id=$LEAD_ID" -H "Authorization: Bearer $TOKEN"

# 3. export, revealing emails for anything not yet revealed
curl -s -X POST "$API/lead-export" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"search_id\":\"$SEARCH_ID\",\"lead_ids\":[\"$LEAD_ID\"],\"enrich_types\":[\"email\"]}"

#Support

support@thepeakai.com — include the search_id or profile URL and roughly when the call was made, and we can trace the exact request.

PeakAI API Reference · Base URL https://build.thepeakai.com · support@thepeakai.com