LeadGen API
Find the people at a company, reveal their work email and mobile number, export the list, and run cold sequences — from your own code.
https://leadgencopilot.ai/api/v1Overview
Introduction
One REST API over the same data the LeadGen app runs on. There is no SDK to install and nothing to sign — every call is an HTTP request with your API key in the Authorization header. Searching is free; you are charged only when a contact detail is actually found.
Most integrations use three endpoints in a row: search a company, read the people back, then reveal the ones worth contacting. That flow is written out under Find people at a company.
Account access
Get an API key
Keys are created in the app at Settings → Developer → Create API key. A key starts with lgpat_, is shown exactly once, and is stored only as a hash — there is no way to display it again, so paste it into your secret store before you close the dialog. Lost one, revoke it and make another.
New keys carry Read, Search, Enrich and Export. Campaigns is off by default and has to be ticked deliberately.
Using the API
Scopes
A key carries only the permissions you gave it, and they are checked on every request. Pick the narrowest set that does the job — a key that only exports should not be able to spend credits.
| Scope | Grants | On by default |
|---|---|---|
leadgen:read | Read lists, contacts, companies, search results and credit activity | Yes |
leadgen:write:search | Run people and company searches; create and edit lists and folders | Yes |
leadgen:write:enrich | Reveal emails and phones, scrape profiles — the scope that spends credits | Yes |
leadgen:export | Download CSV and XLSX exports | Yes |
leadgen:write:campaigns | Send campaigns — anything that can put mail in front of a real person | No |
A call outside a key's scopes returns 403 naming the one it needed, so you never have to guess which permission to add.
What a key can never do
Account management is not scopable, and every key is refused with 403 session_required: issuing or revoking API keys, deleting the account, revoking sessions, adding or removing teammates, and starting a card payment. Those happen in the web app, signed in. A key cannot widen itself.
Using the API
Find people at a company
The flow most integrations start with. It is two calls, not one: the search runs the query and files the results, then you read the people back. The search response deliberately carries a count rather than the rows, so paging through a large company does not mean re-sending everything you already have.
Search
POST /api/search/people with a company_name. Free. Returns a job_id, a list_id and how many people that page added.
Read
GET /api/search/{job_id}/results. Free. Returns the contacts — names, titles, LinkedIn URLs, company domain.
Reveal
POST /api/contacts/{id}/reveal-email or /get-phone with an id from step 2. This is the step that costs credits.
# 1. Search — free, returns a job_id and how many people it found curl -X POST https://leadgencopilot.ai/api/v1/search/people \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{"company_name": "Notion", "page": 1}' # -> { "job_id": "ec8254b7-...", "list_id": "988b71b2-...", "count": 25, "has_more": true } # 2. Read the people back — free curl https://leadgencopilot.ai/api/v1/search/ec8254b7-.../results \ -H "Authorization: Bearer lgpat_your_key" # -> { "count": 25, "contacts": [ { "id": "b2868147-...", "name": "Aditi Pareek", ... } ] } # 3. Reveal one of them — 2 email credits, 5 phone credits curl -X POST https://leadgencopilot.ai/api/v1/contacts/b2868147-.../reveal-email \ -H "Authorization: Bearer lgpat_your_key" # -> { "email": "a.pareek@example.com", "email_status": "valid", "credits_email": 926 }
Paging through a big company
One call fetches one provider page — 25 people. For page 2, send the same filters again plus the job_id and list_id you were given, and the new rows accumulate into the same search and the same list. Stop when has_more is false. Sending the filters without the ids starts a second, unrelated search.
Using the API
Credits
Searching is free. Contact details are not — and a lookup that finds nothing is not charged.
| Action | Cost | Charged when |
|---|---|---|
| People or company search | Free | Never |
| Reading results, lists, companies | Free | Never |
| LinkedIn count and capabilities | Free | Never |
| Full LinkedIn profile scrape | 0.2 profile | Reserved up front, refunded per profile that comes back empty |
| Email reveal | 2 email | Only when an address is found |
| Phone reveal | 5 phone | Only when a number is found |
Running short returns 402 naming the credit type. Every reveal response echoes the balance that remains, so a script can throttle itself without a second call, and GET /api/payment/credit-activity is the itemised ledger — which key spent what, on which contact. A contact you have already paid for is free forever after and comes back with cached: true.
Using the API
Errors
Every failure is JSON with an error field. The status tells you whether to fix the request, the key, the balance, or to retry.
| Status | Meaning | What to do |
|---|---|---|
| 400 | The body failed validation | error names the field |
| 401 | Key missing, malformed, revoked or expired | check the Authorization header, then the key in Settings |
| 402 | Out of credits for that reveal type | top up; the message names email or phone |
| 403 | Key is missing a scope, or the route is session-only | add the scope in required_scope, or use the web app |
| 404 | No such record — or no such route | the body names the method and path when the route is the problem |
| 409 | strict_filters refused a degraded search | drop the filter named in unsupported_filters |
| 429 | Over an hourly limit | back off; the limit is in the body |
| 5xx | Our side, or an upstream provider | retry with backoff |
A 404 on a route you believe exists is usually the verb — the body names the method and path that did not match. A 401 means the credential, never the URL.
Using the API
Rate limits
10,000 requests per hour per key by default, settable per key when you create it. Each key is throttled on its own, so a runaway integration cannot starve the others.
{ "error": "This API key has exceeded its limit of 10000 requests per hour", "limit": 10000 }
A few endpoints carry a tighter limit on top: people search is 300 per hour per account, profile scrapes 5,000, single reveals 100, and the company pipeline 10 per hour per IP. The counters are hourly windows, so retry with exponential backoff rather than tight polling.
API reference
Every endpoint below was called against production while writing this page — the responses are real, with one person's email and phone number replaced. The complete machine-readable surface is at https://leadgencopilot.ai/api/v1/openapi, browsable at /docs. Both are public and need no key.
Account
Who am I
/api/v1/users/meno scopeConfirms a key is live and returns the account it belongs to, along with the credit balances every reveal draws from. The one endpoint that needs no scope — a key narrowed to a single permission still has to be able to identify itself. Use it as your integration's health check.
curl https://leadgencopilot.ai/api/v1/users/me \ -H "Authorization: Bearer lgpat_your_key"
{ "user_id": "wc7R4W3HRaAFKbqgu5vm", "email": "you@example.com", "plan": "pro", "credits_email": 928, "credits_phone": 452, "team_id": "ee138fa8-4232-4bea-b4f0-38522033142b" }
Search
Find people at a company
/api/v1/search/peopleleadgen:write:searchSend a company, get the people who work there. Costs nothing. Runs one provider page — 25 people — synchronously, files them into a new list, and answers with a count rather than the rows; read them back with the results endpoint below.
Body
namestringcompany_namestringrolesstring[]locationstringindustriesstring[]company_hq_locationstring[]pagenumberjob_idstringlist_idstringreveal_emailsbooleanTo go deeper, send the same filters again with page: 2 plus the job_id and list_id you got back — results accumulate into the same list instead of starting a new search. Stop when has_more is false.
curl -X POST https://leadgencopilot.ai/api/v1/search/people \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{ "company_name": "Notion", "roles": ["Customer Success Manager"], "page": 1 }'
count is this page; total is every match across all pages. total is null when the provider doesn't report one — treat count plus has_more as a lower bound in that case.
{ "job_id": "ec8254b7-9ec7-4346-8b16-325401500a89", "list_id": "988b71b2-d627-4bb2-a358-ac24a78788c4", "company_name": "Notion", "page": 1, "has_more": true, "count": 25, "total": 1432 }
Search
Find companies
/api/v1/search/companiesleadgen:write:searchSearch the company directory rather than the people in it. Costs nothing, spends no credits, and writes nothing — no job and no list, so there is no id to read back. Take a domain from the results into the people search to get its contacts.
Body
company_namestringindustriesstring[]hq_locationstring[]pagenumberAny field can come back null — the provider's coverage varies by company, and size in particular is often missing for private companies.
curl -X POST https://leadgencopilot.ai/api/v1/search/companies \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{ "company_name": "stripe.com", "page": 1 }'
{ "page": 1, "count": 1, "total": 1, "has_more": false, "companies": [ { "id": "1043takN2p", "name": "Stripe", "domain": "stripe.com", "industry": "Financial Services", "location": "South San Francisco, California", "size": 8000, "linkedin_url": "https://www.linkedin.com/company/stripe", "logo_url": null } ] }
Search
Read the people back
/api/v1/search/{job_id}/resultsleadgen:readThe rows the search collected. Free, and safe to call repeatedly — call it again after each page to see what that page added. Take each contact's id into the reveal endpoints.
email and phone_number are null here on purpose: searching is free, and contact details are only fetched when you ask and pay for them. Fields the provider did not supply — linkedin_url, domain, location — also come back null rather than being omitted.
curl https://leadgencopilot.ai/api/v1/search/ec8254b7-9ec7-4346-8b16-325401500a89/results \ -H "Authorization: Bearer lgpat_your_key"
{ "job_id": "ec8254b7-9ec7-4346-8b16-325401500a89", "count": 25, "contacts": [ { "id": "b2868147-b702-4fb1-97d0-25cfa9d261a8", "name": "Aditi Pareek", "first_name": "Aditi", "last_name": "Pareek", "title": "Enterprise Customer Success Manager", "role_category": "Manager", "seniority": "Manager", "department": "Customer Service", "linkedin_url": null, "profile_image_url": "https://media.licdn.com/dms/image/...", "location": null, "company": "notion", "domain": null, "industry": null, "revenue": null, "email": null, "email_status": null, "confidence": null, "phone_number": null, "phone_status": null } ] }
Search
Company pipeline search
/api/v1/searchleadgen:write:searchThe deeper, asynchronous alternative to people search: takes a company name or its LinkedIn URL, works the company over in the background, and files results into the same shape. Returns 202 immediately; poll GET /api/search/{id} until status is completed, then read the results endpoint above.
Body
company_namestringlinkedin_company_urlstringrolesstring[]max_contactsnumberfiltersobjectforce_refreshbooleanreveal_emailsbooleancurl -X POST https://leadgencopilot.ai/api/v1/search \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{ "company_name": "Stripe", "roles": ["VP Sales"], "max_contacts": 50 }'
{ "job_id": "355989f1-402f-4be1-bec8-d580b3f731b5", "status": "queued", "company_name": "Stripe" }
Search
Companies you already have
/api/v1/contacts/companiesleadgen:readEvery company with at least one contact in your account, deduped across every search you have run, with counts of how many of those contacts have an email. Reads your own data — no provider call, no credits. Pair it with /api/contacts/companies/{companyId}/contacts to pull the people back out.
Query
qstringlimitnumbercurl "https://leadgencopilot.ai/api/v1/contacts/companies?q=stripe&limit=5" \ -H "Authorization: Bearer lgpat_your_key"
{ "count": 2, "companies": [ { "id": "32022c09-8d05-4190-962a-204b1a282070", "name": "stripe", "domain": "stripe.com", "logo_url": "https://leadgencopilot.ai/api/company-logo/stripe.com", "linkedin_url": "https://www.linkedin.com/company/stripe/", "total_contacts": 614, "contacts_with_email": 30, "latest_job_id": "29b2a1a1-85b9-4184-961e-f541d90f02bd", "latest_job_created_at": "2026-07-28 18:47:16.009283+00" } ] }
Reveal
Reveal an email
/api/v1/contacts/{id}/reveal-emailleadgen:write:enrichRuns a waterfall across providers until one returns a verified work address. Synchronous and typically takes five to fifteen seconds. Costs 2 email credits, and only when an address comes back.
Path
iduuidrequiredBody
force_refreshbooleanemail_status is "valid" when the address was verified deliverable, "unknown" when a provider returned it without verifying, and "not_found" on a miss. cached: true means you have already paid for this contact and it is free from now on. Only work addresses are returned — if no company address exists the waterfall stops rather than falling back to a personal mailbox.
curl -X POST \ https://leadgencopilot.ai/api/v1/contacts/b2868147-b702-4fb1-97d0-25cfa9d261a8/reveal-email \ -H "Authorization: Bearer lgpat_your_key"
{ "contact_id": "b2868147-b702-4fb1-97d0-25cfa9d261a8", "email": "a.pareek@example.com", "email_status": "valid", "email_source": "leadgen", "cached": false, "credits_email": 926 }
Reveal
Reveal a personal email
/api/v1/contacts/{id}/reveal-personal-emailleadgen:write:enrichFinds the consumer mailbox — gmail, outlook, icloud — rather than the company address. A separate paid lookup from the work reveal, not a fallback for it. Costs 2 email credits from the same balance, and only when an address comes back.
Path
iduuidrequiredBody
force_refreshbooleanThis and the work reveal are two different products, billed and cached separately: paying for one does not make the other free, and neither falls back to the other. Asking here will never hand back a company address, exactly as /reveal-email will never hand back a personal one. personal_email_status reads "valid" when the provider verified the mailbox and "unknown" when it returned the address without verifying. cached: true means you have already paid for this contact's personal address and it is free from now on. Only one provider answers this lookup, so it is usually faster than a work reveal — a few seconds rather than up to fifteen.
curl -X POST https://leadgencopilot.ai/api/v1/contacts/b2868147-b702-4fb1-97d0-25cfa9d261a8/reveal-personal-email -H "Authorization: Bearer lgpat_your_key"
{ "contact_id": "b2868147-b702-4fb1-97d0-25cfa9d261a8", "personal_email": "a.pareek@gmail.com", "personal_email_status": "valid", "cached": false, "credits_email": 924 }
Reveal
Reveal a phone number
/api/v1/contacts/{id}/get-phoneleadgen:write:enrichThe same shape and the same rules as the email reveal, against the mobile-number providers. Costs 5 phone credits, and only when a number comes back.
Path
iduuidrequiredBody
force_refreshbooleanNumbers come back in E.164. Both reveal responses echo the balance that remains, so a script can throttle itself without a second call.
curl -X POST \ https://leadgencopilot.ai/api/v1/contacts/b2868147-b702-4fb1-97d0-25cfa9d261a8/get-phone \ -H "Authorization: Bearer lgpat_your_key"
{ "contact_id": "b2868147-b702-4fb1-97d0-25cfa9d261a8", "phone_number": "+14155550142", "phone_status": "found", "phone_source": "leadgen", "cached": false, "credits_phone": 447 }
Reveal
Reveal a batch
/api/v1/contacts/batch-reveal-emailsleadgen:write:enrichEmails for an explicit set of contacts in one response. /api/contacts/batch-get-phones is identical for numbers. Both are synchronous — for a whole list, use the asynchronous reveal job below instead, because a large batch will outlive the request timeout.
Body
contact_idsuuid[]requiredforce_refreshbooleancurl -X POST https://leadgencopilot.ai/api/v1/contacts/batch-reveal-emails \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{"contact_ids": ["b2868147-...", "1fb5f36f-..."]}'
{ "revealed": 2, "not_found": 0, "credits_email": 922, "results": [ { "contact_id": "b2868147-b702-4fb1-97d0-25cfa9d261a8", "email": "a.pareek@example.com", "email_status": "valid", "cached": false } ] }
Reveal
Reveal a whole list
/api/v1/reveal-jobsleadgen:write:enrichCreates a background job that works through every contact in a list, so you are not holding a connection open. Poll GET /api/reveal-jobs/{id} until it completes, then read GET /api/reveal-jobs/{id}/items for the per-contact outcome.
Body
list_iduuidrequiredThere is no GET /api/reveal-jobs collection endpoint — keep the id you were given. Contacts whose address you already own come back free and are never re-charged.
curl -X POST https://leadgencopilot.ai/api/v1/reveal-jobs \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{"list_id": "988b71b2-d627-4bb2-a358-ac24a78788c4"}'
{ "reveal_job_id": "7c1e0b44-2f9d-4a6e-9f3b-8d2a1c5e4f70", "status": "queued", "total": 25 }
Lists
Read a list
/api/v1/lists/{listId}leadgen:readThe list and the contacts in it. Note there is no GET /api/lists/{listId}/contacts — that path exists only for POST and DELETE, and a GET against it returns 404. GET /api/lists returns every list in the account.
curl https://leadgencopilot.ai/api/v1/lists/988b71b2-d627-4bb2-a358-ac24a78788c4 \ -H "Authorization: Bearer lgpat_your_key"
{ "list": { "id": "988b71b2-d627-4bb2-a358-ac24a78788c4", "name": "Notion", "description": null, "color": "#6366f1", "source_job_id": "ec8254b7-9ec7-4346-8b16-325401500a89", "created_at": "2026-08-07T18:03:13.655Z" }, "contacts": [] }
Lists
Export a list
/api/v1/lists/{listId}/exportleadgen:exportDownloads the list as a file rather than JSON. Columns: Name, Company, Job Title, Department, Website, Industry, Revenue, Location, Email, Email Status, Phone, LinkedIn.
Query
format"csv" | "xlsx"curl "https://leadgencopilot.ai/api/v1/lists/988b71b2-.../export?format=csv" \ -H "Authorization: Bearer lgpat_your_key" -o leads.csv
text/csv, as an attachment — not JSON.
Name,Company,Job Title,Department,Website,Industry,... "Andrew Lam","stripe","Engineer","Engineering and Technical",...
What the backend supports
/api/v1/linkedin/capabilitiesleadgen:readCall this first. It tells you which filters are applied at the source, which are approximated, and what each phase costs — so you never ship a filter that silently does nothing. Search and full-profile capture run on two different backends, both fixed, so this answer is stable and safe to cache.
native means it is applied at the source, before a row is ever collected — those results already match. emulated means we make it mean something anyway: skills and schools fold into a whole-profile text match, and excluded titles are dropped from the rows returned before they ever become contacts, so they never cost a credit. unsupported means it will not narrow the search at all. verified_after_scrape is the separate promise: those filters are re-checked against the full record once you scrape, with a pass/fail/not_evaluated reason per person — so a filter listed as unsupported here can still be answered exactly, just later and only for the profiles you paid to fetch.
curl https://leadgencopilot.ai/api/v1/linkedin/capabilities \ -H "Authorization: Bearer lgpat_your_key"
{ "provider": "leadgen", "profile_provider": "leadgen", "filters": { "native": ["locations", "company_name", "job_titles", "seniority_keywords", "industries", "company_hq_locations", "min_years_experience", "keywords"], "emulated": ["exclude_job_titles", "skills", "schools"], "unsupported": ["name", "max_years_experience", "company_sizes"], "verified_after_scrape": ["job_titles", "exclude_job_titles", "seniority_keywords", "min_years_experience", "max_years_experience", "skills", "schools", "keywords", "locations", "company_name", "industries", "name"] }, "count": { "supported": true, "exact": true, "free": true }, "preview": { "supported": true, "max": 50, "free": true, "typical_latency_ms": 1500 }, "limits": { "max_results_per_search": 1000, "max_scrape_per_job": 1000 }, "credits": { "profile": 0.2, "email": 2, "phone": 5 }, "requires_at_least_one_of": [] }
Search LinkedIn
/api/v1/linkedin/searchleadgen:write:searchStarts a filtered people search. Returns 202 with a job_id and a list_id; poll GET /api/linkedin/search/{jobId}. Searching itself spends nothing — scraping the profiles it finds is the separate, paid call below.
Body — filters applied at the source
job_titlesstring[]seniority_keywordsstring[]min_years_experiencenumberlocationsstring[]company_namestringindustriesstring[]company_hq_locationsstring[]keywordsstring[]Body — filters approximated or deferred
exclude_job_titlesstring[]skillsstring[]schoolsstring[]max_years_experiencenumbercompany_sizesstring[]Body — the run
max_resultsnumberlist_namestringlist_iduuidauto_scrapebooleanstrict_filtersbooleanAlways read unsupported_filters — anything listed there did not narrow the search, so the results are broader than the filters you sent. The same list comes back on the status endpoint. Note the search collects only people who already match the filters it could apply; nothing is collected and then discarded, so max_results is a cap on matches, not on rows inspected.
curl -X POST https://leadgencopilot.ai/api/v1/linkedin/search \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{ "job_titles": ["Backend Developer"], "min_years_experience": 10, "locations": ["Berlin"], "max_results": 200, "strict_filters": true }'
{ "job_id": "4b1a7c90-...", "list_id": "e91f2d33-...", "status": "queued", "provider": "leadgen", "applied_filters": ["locations", "job_titles", "min_years_experience"], "unsupported_filters": [], "max_results": 200, "poll": "/api/linkedin/search/4b1a7c90-..." }
Scrape the full profiles
/api/v1/linkedin/scrapeleadgen:write:enrichFetches the complete LinkedIn record for the people a search found. 0.2 profile credits each, reserved before the job starts and refunded per profile that cannot be fetched. Contacts already scraped are skipped unless you pass force_refresh.
Body — pick exactly one selector
job_iduuidlist_iduuidcontact_idsuuid[]linkedin_urlsstring[]Body — options
max_itemsnumberforce_refreshbooleanlist_namestringExactly one of job_id, list_id, contact_ids or linkedin_urls — two selectors would make the credit estimate a guess, and the point of the 202 is that it states the cost up front. total is what will actually be fetched: people with no LinkedIn URL on file are reported as skipped_no_linkedin_url rather than silently dropped, so check that field if the count is lower than you expected. Poll GET /api/linkedin/scrape/{scrapeJobId} every few seconds and read the results back with GET /api/linkedin/search/{jobId}/results?include=profile, which caps the page at 25. profile is the raw provider payload whose shape can change; normalized is the stable projection to build against.
curl -X POST https://leadgencopilot.ai/api/v1/linkedin/scrape \ -H "Authorization: Bearer lgpat_your_key" \ -H "Content-Type: application/json" \ -d '{"job_id": "4b1a7c90-...", "max_items": 200}'
{ "scrape_job_id": "aa20fd51-...", "status": "queued", "provider": "leadgen", "total": 187, "skipped_no_linkedin_url": 13, "skipped_already_scraped": 0, "truncated": false, "credits_reserved": 37.4, "credits_profile_remaining": 462.6, "poll": "/api/linkedin/scrape/aa20fd51-..." }
Read a scraped profile
/api/v1/linkedin/profiles/{contactId}leadgen:readReturns a profile you have already scraped. Reads from cache only and never spends a credit — if the contact has not been scraped it answers 409 rather than quietly fetching and billing you. Every response carries both shapes: profile is the provider's record verbatim, normalized is the projection this API keeps stable.
Read is_current, not end_date, to find someone's present role — the provider writes the literal string "Present" there, never null, so an `end_date === null` check never matches. Several roles at one employer arrive from the provider grouped under a single entry whose own dates are absent; positions[] flattens them into one row per role, so a 22-year tenure recorded as two promotions appears as two dated rows rather than one undated one. total_experience_months merges overlapping spans instead of summing them — concurrent roles, advisory seats and a promotion logged twice would otherwise inflate a 22-year career into 40. skills is currently always empty: the person dataset does not return a skills array, and it is left empty rather than guessed at from the About text. education, certifications, languages and positions are always arrays, never null — an empty one means the profile had none. The provider also returns honors_and_awards, volunteer_experience, projects, courses, publications, recommendations, posts and activity; those are not in normalized yet and are available on profile.
curl https://leadgencopilot.ai/api/v1/linkedin/profiles/b2868147-... \ -H "Authorization: Bearer lgpat_your_key"
{ "contact_id": "b2868147-...", "linkedin_url": "https://www.linkedin.com/in/sundarpichai", "scraped_at": "2026-08-14T09:12:04.552Z", "email": "s.pichai@example.com", "phone_number": null, "normalized": { "full_name": "Sundar Pichai", "headline": "CEO at Google", "about": "…", "location": "Mountain View", "city": "Mountain View, California, United States", "country_code": "US", "profile_image_url": "https://media.licdn.com/…", "linkedin_url": "https://www.linkedin.com/in/sundarpichai", "connections": 500, "followers": 1204533, "current_position": { "title": "CEO", "company_name": "Google", "company_linkedin_url": "https://www.linkedin.com/company/google", "location": "Mountain View", "start_date": "2015", "end_date": null, "is_current": true, "duration": null, "description": null }, "current_company_industry": "Software Development", "positions": [ { "title": "CEO", "company_name": "Google", "company_linkedin_url": "https://www.linkedin.com/company/google", "location": null, "start_date": "2015", "end_date": "Present", "is_current": true, "duration": null, "description": null }, { "title": "Product Management + Leadership", "company_name": "Google", "company_linkedin_url": "https://www.linkedin.com/company/google", "location": null, "start_date": "Apr 2004", "end_date": "2015", "is_current": false, "duration": null, "description": null } ], "education": [ { "school": "Stanford University", "degree": "MS, Material Sciences", "start_year": "1993", "end_year": "1995", "url": "https://…" } ], "certifications": [ { "title": "…", "issuer": "…", "issued": "…", "credential_url": null } ], "languages": [ { "name": "English", "proficiency": "Native" } ], "skills": [], "total_experience_months": 269, "estimated_years_experience": 22 }, "profile": { "…": "the provider's record, verbatim — shape may change" } }
Credits
Credit ledger
/api/v1/payment/credit-activityleadgen:readItemised: which key spent what, on which contact, and whether it was charged or refunded. The endpoint to reconcile against when a bill looks wrong.
A key cannot buy credits — topping up is session-only, in the web app.
curl "https://leadgencopilot.ai/api/v1/payment/credit-activity?limit=50" \ -H "Authorization: Bearer lgpat_your_key"
{ "count": 2, "activity": [ { "credit_type": "phone", "amount": 5, "status": "charged", "contact_id": "b2868147-...", "description": "Phone lookup for Aditi Pareek", "created_at": "2026-08-07T18:31:44.201Z" }, { "credit_type": "email", "amount": 2, "status": "refunded", "description": "Email not found — credit returned", "created_at": "2026-08-07T18:29:02.884Z" } ] }