API reference
Enrich People
Fetches each profile live from LinkedIn: current and past positions, education, skills, languages and certifications. Send up to 25 URLs per call; each gets its own entry in results, in the order you sent them.
A profile that does not exist, or a URL that is not a LinkedIn profile, costs nothing. With include_email, we also look for a work email (name and company domain, checked over SMTP) — if we only find a personal address, you get that one instead.
Calls with many profiles take a while: count on about 5 seconds per profile, spread over parallel lookups. Set your HTTP timeout to at least 120 seconds. A found profile costs 1 credit. A found email adds 1. This endpoint has no phone field: a phone number is Find Phone (POST /person/phone).
Work email
Send include_email: true to also look for a work email. We build the address from the name and the company domain and check it over SMTP; when we only find a personal address, you get that one instead. A found address adds 1 credit; no address costs nothing extra.
The address is in person.email (address, type work or personal, deliverable, catch_all and quality_score). results[].email_status says what the check found: verified, catch_all, unverifiable, or not_found when there is no address. save_input carries email and email_status, so POST /tables/leads saves the address with the person.
{
"profile_urls": [
"https://www.linkedin.com/in/jane-example"
],
"include_email": true
}
Authentication
Send Authorization: Bearer gtk_…. This People endpoint answers when your key is valid. max_credits works with every key. person_ids on enrich is accepted only when the Signals API is open for your organization; otherwise that field is unknown and the call is 400.
Body parameters
profile_urlsstring[]— 1 to 25 LinkedIn profile URLs, e.g.https://www.linkedin.com/in/jane-example. The member-id form that Search People returns works too. Send this orperson_ids, not both.person_idsstring[]— 1 to 25 person ids (per_…) from an earlier response of your organization, kept for 30 days. An id this organization does not know isnot_foundand costs nothing. Accepted when the Signals API is open for your organization.include_emailboolean— Also look for an email address. Adds 1 credit per address found. Defaults to false. This endpoint does not look up a phone number: that is Find Phone (POST /person/phone).max_creditsinteger— Stop before the call costs more than this. Every profile counts at what it can cost at most (withinclude_email, the profile and an email), in the order you sent them; a profile that would not fit is not looked up, getsskipped, and the response carriestruncated_by: "max_credits". Below the cost of one profile the call is402max_credits_exceededand nothing is looked up.
Response
resultsobject[]— One entry per URL you sent, in the same order.results[].inputstring— The URL as you sent it.results[].statusstring—matched,not_found(no such profile),invalid_url(not a LinkedIn profile URL) orerror(lookup failed; retry later). One of:matched,not_found,invalid_url,error.results[].personobject | null— The profile when the status ismatched, otherwise null. See the fields below.person.linkedin_urlstring— Public profile URL.person.linkedin_idstring | null— LinkedIn member id.person.public_identifierstring | null— The slug in the profile URL.person.first_name / last_name / full_namestring | null— Name.person.headlinestring | null— Headline under the name.person.aboutstring | null— The About section.person.locationobject—textas LinkedIn shows it, pluscity,state,countryandcountry_code(ISO 3166-1 alpha-2) when known.person.picture_urlstring | null— Profile photo. LinkedIn photo URLs expire after a few weeks.person.connections_count / follower_countinteger | null— Network size.person.open_to_work / hiring / premiumboolean | null— Profile badges.person.current_positionsobject[]— The entries ofexperiencethat are still ongoing.person.experienceobject[]— Positions, newest first:title,company_name,company_linkedin_url,location,employment_type,workplace_type,start_dateandend_date(year,month;end_dateis null while ongoing),is_current,duration,description.person.educationobject[]—school_name,school_linkedin_url,degree,field_of_study,start_date,end_date,description.person.skillsstring[]— Skills, top skills first.person.languagesobject[]—nameandproficiency.person.certificationsobject[]—name,issued_by,issued_at.person.emailobject | null— Only withinclude_email:address,type(workorpersonal),deliverableandcatch_all(booleans from the SMTP check; null when unknown) andquality_score. Null when nothing was found.results[].email_statusstring— Only withinclude_emailand a matched profile: what the check says, in the values the Leads table takes (verified,catch_all,unverifiable, ornot_foundwhen no address was found). One of:verified,catch_all,unverifiable,not_found.results[].save_inputobject— With a matched profile: this person as one entry ofleadsfor POST /tables/leads (name,linkedin_urlas you sent it,title,company,company_linkedin_url,location,country_code, plusemailandemail_statuswhen found). Saving it after you saved the same person from a search updates that row.credits_usedinteger— Credits this call used.
Credits
1 credit per profile found, +1 per email found. Errors and empty lookups are free. A call that spent credits puts that number in credits_used, in X-Credits-Used and in X-Credits-Charged. When the wallet can be read, the response also sends X-Credits-Remaining.
Errors
- 400
invalid_request— The body is not valid JSON, a field is unknown or has the wrong type, or a value is not allowed. - 401
authentication_error— The Authorization header is missing, or the key is unknown or revoked. - 403
permission_error— API access is not part of your plan, or your account is suspended. - 429
rate_limit_error— More than 60 requests per minute, or more than 3 at the same time, on one key. Wait for the Retry-After header. - 429
daily_limit_reached— The request could take your organization over its daily credit limit. The limit resets at midnight (Europe/Brussels). - 502
upstream_error— The data source did not answer. Retry later. - 503
service_unavailable— The API is temporarily unavailable. Retry later. - 500
internal_error— Something went wrong on our side. - 402
max_credits_exceeded— max_credits is below the price of one row, so nothing was looked up. Nothing was charged.
Example
cURL
curl -X POST "https://mysdr.app/api/v1/person/enrich" \
-H "Authorization: Bearer $MYSDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_urls": [
"https://www.linkedin.com/in/jane-example",
"https://www.linkedin.com/in/does-not-exist-123"
],
"include_email": true
}'
JavaScript
const response = await fetch('https://mysdr.app/api/v1/person/enrich', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"profile_urls": [
"https://www.linkedin.com/in/jane-example",
"https://www.linkedin.com/in/does-not-exist-123"
],
"include_email": true
})
});
const body = await response.json();
Python
import json
import os
import urllib.request
payload = {
"profile_urls": [
"https://www.linkedin.com/in/jane-example",
"https://www.linkedin.com/in/does-not-exist-123"
],
"include_email": True
}
request = urllib.request.Request(
"https://mysdr.app/api/v1/person/enrich",
headers={
"Authorization": f"Bearer {os.environ['MYSDR_API_KEY']}",
"Content-Type": "application/json"
},
data=json.dumps(payload).encode(),
method="POST",
)
with urllib.request.urlopen(request) as response:
body = json.load(response)
Example response
{
"results": [
{
"input": "https://www.linkedin.com/in/jane-example",
"status": "matched",
"person": {
"linkedin_url": "https://www.linkedin.com/in/jane-example",
"linkedin_id": "ACwAAExample123",
"public_identifier": "jane-example",
"first_name": "Jane",
"last_name": "Example",
"full_name": "Jane Example",
"headline": "CFO at Acme Analytics",
"about": null,
"location": {
"text": "Ghent, Flemish Region, Belgium",
"city": "Ghent",
"state": "Flemish Region",
"country": "Belgium",
"country_code": "BE"
},
"picture_url": null,
"connections_count": 500,
"follower_count": 1204,
"open_to_work": false,
"hiring": false,
"premium": true,
"current_positions": [
{
"title": "Chief Financial Officer",
"company_name": "Acme Analytics",
"company_linkedin_url": "https://www.linkedin.com/company/acme-analytics",
"location": "Ghent, Belgium",
"employment_type": "Full-time",
"workplace_type": "Hybrid",
"start_date": {
"year": 2026,
"month": 7
},
"end_date": null,
"is_current": true,
"duration": "3 mos",
"description": null
}
],
"experience": [
{
"title": "Chief Financial Officer",
"company_name": "Acme Analytics",
"company_linkedin_url": "https://www.linkedin.com/company/acme-analytics",
"location": "Ghent, Belgium",
"employment_type": "Full-time",
"workplace_type": "Hybrid",
"start_date": {
"year": 2026,
"month": 7
},
"end_date": null,
"is_current": true,
"duration": "3 mos",
"description": null
}
],
"education": [
{
"school_name": "Ghent University",
"school_linkedin_url": null,
"degree": "Master of Science",
"field_of_study": "Business Economics",
"start_date": {
"year": 2008,
"month": null
},
"end_date": {
"year": 2012,
"month": null
},
"description": null
}
],
"skills": [
"Financial Planning",
"M&A"
],
"languages": [
{
"name": "Dutch",
"proficiency": "Native or bilingual proficiency"
}
],
"certifications": [],
"email": {
"address": "jane@acme-analytics.example",
"type": "work",
"deliverable": true,
"catch_all": false,
"quality_score": 95
}
},
"email_status": "verified",
"save_input": {
"name": "Jane Example",
"linkedin_url": "https://www.linkedin.com/in/jane-example",
"title": "Chief Financial Officer",
"headline": "CFO at Acme Analytics",
"company": "Acme Analytics",
"company_linkedin_url": "https://www.linkedin.com/company/acme-analytics",
"location": "Ghent, Flemish Region, Belgium",
"country_code": "BE",
"email": "jane@acme-analytics.example",
"email_status": "verified"
}
},
{
"input": "https://www.linkedin.com/in/does-not-exist-123",
"status": "not_found",
"person": null
}
],
"credits_used": 2
}