A recruiter spends 30 minutes researching a single candidate.
They open LinkedIn, scan the profile, copy the work history into a spreadsheet, check if the education matches requirements, look for relevant skills, search for contact info, and repeat this process 50 times a day.
That's 25 hours a week on manual research. Per recruiter. At $40 per hour, that's $52,000 a year in labor costs just to gather basic candidate information.
With a recruiting enrichment API, the same research takes 200 milliseconds. Fifty candidates enriched in under 10 seconds. Work history, education, skills, certifications, and contact info all returned in structured JSON, ready to flow into your ATS.
This guide shows you exactly how to build automated candidate enrichment into your recruiting workflows. We'll walk through real code examples, ATS integration patterns, and the specific LinkdAPI endpoints that matter for talent acquisition.
Why Recruiters Need Enrichment APIs
Let's break down what "candidate enrichment" actually means in a recruiting context.
The Manual Research Problem
Every recruiter knows this workflow:
11. Receive candidate application (name + email + resume)
22. Open LinkedIn, search for candidate
33. Verify identity (is this the right John Smith?)
44. Review work history
55. Check education credentials
66. Look for skill keywords
77. Note any certifications
88. Find contact info if available
99. Copy everything into ATS/spreadsheet
1010. Repeat for next candidate
11
12Time per candidate: 15 to 30 minutes
13Candidates per day: 30 to 50
14Daily research time: 7.5 to 25 hoursThe math doesn't work. Recruiters are drowning in manual research when they should be interviewing, evaluating, and closing candidates.
What Enrichment Solves
Enrichment APIs take a basic identifier (LinkedIn username, email, or name) and return complete professional data:
1{
2 "success": true,
3 "data": {
4 "fullName": "Sarah Chen",
5 "headline": "Senior Software Engineer at Stripe",
6 "location": {
7 "city": "San Francisco",
8 "countryCode": "US"
9 },
10 "experience": [
11 {
12 "companyName": "Stripe",
13 "title": "Senior Software Engineer",
14 "duration": "2 years 3 months",
15 "description": "Building payment infrastructure..."
16 },
17 {
18 "companyName": "Google",
19 "title": "Software Engineer",
20 "duration": "3 years",
21 "description": "Worked on Cloud Platform..."
22 }
23 ],
24 "education": [
25 {
26 "university": "Stanford University",
27 "degree": "BS Computer Science",
28 "duration": "2014 to 2018"
29 }
30 ],
31 "skills": ["Python", "Go", "Kubernetes", "AWS", "System Design"],
32 "certifications": ["AWS Solutions Architect"],
33 "contactInfo": {
34 "emailAddress": "[email protected]",
35 "websites": ["github.com/sarahchen"]
36 }
37 }
38}One API call. Complete candidate profile. Structured data ready for your systems.
The ROI Math
Manual approach (per recruiter per year):
- Hours spent on research: 1,300 hours
- Cost at $40/hour: $52,000
- Candidates researched: 10,000
API approach (per recruiter per year):
- API cost at $0.003 per profile: $30
- Time spent: ~20 hours (reviewing enriched data)
- Total cost: $830
- Candidates researched: 10,000
Annual savings per recruiter: $51,170
For a team of 10 recruiters, that's over $500,000 per year in recovered productivity.
What Data Can You Enrich?
We provide comprehensive candidate data through multiple specialized endpoints. Here's what you can access:
Basic Profile Data
Endpoint: /api/v1/profile/overview
1{
2 "fullName": "Sarah Chen",
3 "firstName": "Sarah",
4 "lastName": "Chen",
5 "headline": "Senior Software Engineer at Stripe",
6 "publicIdentifier": "sarahchen",
7 "urn": "ACoAABCD1234...",
8 "followerCount": 2500,
9 "connectionsCount": 890,
10 "premium": true,
11 "creator": false,
12 "location": {
13 "city": "San Francisco",
14 "countryCode": "US",
15 "fullLocation": "San Francisco Bay Area"
16 },
17 "profilePictureURL": "https://..."
18}Complete Work History
Endpoint: /api/v1/profile/full-experience
1{
2 "experience": [
3 {
4 "companyName": "Stripe",
5 "companyId": "2135371",
6 "companyLogo": "https://...",
7 "title": "Senior Software Engineer",
8 "location": "San Francisco, CA",
9 "description": "Building payment infrastructure for internet businesses...",
10 "duration": "2 years 3 months",
11 "durationParsed": {
12 "start": {"year": 2022, "month": 1},
13 "end": null,
14 "present": true
15 }
16 },
17 {
18 "companyName": "Google",
19 "title": "Software Engineer",
20 "duration": "3 years",
21 "durationParsed": {
22 "start": {"year": 2019, "month": 1},
23 "end": {"year": 2021, "month": 12}
24 }
25 }
26 ]
27}Education Credentials
Endpoint: /api/v1/profile/education
1{
2 "education": [
3 {
4 "university": "Stanford University",
5 "universityLink": "https://linkedin.com/school/stanford-university",
6 "degree": "Bachelor of Science, Computer Science",
7 "duration": "2014 to 2018",
8 "durationParsed": {
9 "start": {"year": 2014},
10 "end": {"year": 2018}
11 },
12 "description": "Focus on distributed systems and machine learning"
13 }
14 ]
15}Skills and Endorsements
Endpoint: /api/v1/profile/skills
1{
2 "skills": [
3 {
4 "skillName": "Python",
5 "endorsementsCount": 45,
6 "isPassedLinkedInSkillAssessment": true
7 },
8 {
9 "skillName": "Go",
10 "endorsementsCount": 32,
11 "isPassedLinkedInSkillAssessment": false
12 },
13 {
14 "skillName": "Kubernetes",
15 "endorsementsCount": 28,
16 "isPassedLinkedInSkillAssessment": true
17 }
18 ]
19}Professional Certifications
Endpoint: /api/v1/profile/certifications
1{
2 "certifications": [
3 {
4 "certificationName": "AWS Solutions Architect Professional",
5 "issuedBy": "Amazon Web Services",
6 "issuedDate": "January 2023",
7 "issuedDateParsed": {"year": 2023, "month": 1},
8 "certificationLink": "https://..."
9 }
10 ]
11}Contact Information
Endpoint: /api/v1/profile/contact-info
1{
2 "emailAddress": "[email protected]",
3 "phoneNumber": null,
4 "websites": [
5 {"url": "https://github.com/sarahchen", "category": "PORTFOLIO"},
6 {"url": "https://sarahchen.dev", "category": "PERSONAL"}
7 ]
8}Recommendations
Endpoint: /api/v1/profile/recommendations
1{
2 "TotalReceived": 12,
3 "TotalGiven": 8,
4 "Received": [
5 {
6 "Text": "Sarah is an exceptional engineer who consistently delivers high-quality work...",
7 "Caption": "worked with Sarah at Stripe",
8 "Recommendee": {
9 "name": "John Smith",
10 "headline": "Engineering Manager at Stripe"
11 }
12 }
13 ]
14}Our Endpoints for Recruiting
Here's a quick reference of the most relevant endpoints for recruiting workflows:
Profile Enrichment
| Endpoint | Use Case | Params |
|---|---|---|
/api/v1/profile/overview | Basic candidate info, get URN | username |
/api/v1/profile/full | Complete profile in one call | username or urn |
/api/v1/profile/full-experience | Work history verification |
URN Lookup (Lightweight)
For when you only need the URN to call other endpoints, use the lightweight /api/v1/profile/username-to-urn endpoint:
1{
2 "success": true,
3 "statusCode": 200,
4 "message": "Data retrieved successfully",
5 "errors": null,
6 "data": {
7 "urn": "ACoAAAAKXBwBikfbNJww68eYvcu2dqDYJhHbp4g",
8 "username": "ryanroslansky"
9 }
10}Candidate Sourcing
| Endpoint | Use Case | Key Params |
|---|---|---|
/api/v1/search/people | Find candidates by criteria | title, geoUrn, currentCompany, skills |
/api/v1/profile/similar | Find similar candidates | urn |
/api/v1/geos/name-lookup |
Company Research
| Endpoint | Use Case | Params |
|---|---|---|
/api/v1/companies/company/info | Verify employer | id or name |
/api/v1/companies/company/employees-data | Company team composition | id |
/api/v1/companies/company/universal-name-to-id | Get company ID |
Use Case 1: ATS Integration
The most common recruiting use case is integrating enrichment directly into your Applicant Tracking System.
Workflow: Auto Enrich on Application
1from linkdapi import LinkdAPI
2import json
3
4client = LinkdAPI("your_api_key")
5
6def enrich_candidate(linkedin_url):
7 """
8 Enrich a candidate from their LinkedIn URL
9 """
10 # Extract username from URL
11 # https://linkedin.com/in/sarahchen → sarahchen
12 username = linkedin_url.rstrip('/').split('/')[-1]
13
14 # Get full profile in one call (1 credit)
15 result = client.get_full_profile(username=username)
16
17 if not result['success']:
18 return {'error': result['message']}
19
20 profile = result['data']
21
22 # Get contact info separately
23 contact = client.get_contact_info(username=username)
24
25 # Structure for ATS
26 return {
27 'candidate': {
28 'name': profile.get('fullName'),
29 'email': contact['data'].get('emailAddress') if contact['success'] else None,
30 'phone': contact['data'].get('phoneNumber') if contact['success'] else None,
31 'location': profile.get('location', {}).get('fullLocation'),
32 'headline': profile.get('headline'),
33 'linkedin_url': f"https://linkedin.com/in/{username}",
34 'profile_picture': profile.get('profilePictureURL')
35 },
36 'experience': profile.get('experience', []),
37 'education': profile.get('education', []),
38 'skills': [s['skillName'] for s in profile.get('skills', [])],
39 'certifications': profile.get('certifications', []),
40 'enriched_at': datetime.now().isoformat()
41 }
42
43# Example: Enrich when candidate applies
44candidate_linkedin = "https://linkedin.com/in/sarahchen"
45enriched_data = enrich_candidate(candidate_linkedin)
46
47# Push to ATS
48ats_client.update_candidate(
49 candidate_id=123,
50 enriched_data=enriched_data
51)Batch Processing Applications
1from linkdapi import AsyncLinkdAPI
2import asyncio
3
4async def batch_enrich_candidates(linkedin_urls):
5 """
6 Enrich multiple candidates concurrently
7 """
8 async with AsyncLinkdAPI("your_api_key") as client:
9 tasks = []
10
11 for url in linkedin_urls:
12 username = url.rstrip('/').split('/')[-1]
13 tasks.append(client.get_full_profile(username=username))
14
15 # Process all in parallel
16 results = await asyncio.gather(*tasks, return_exceptions=True)
17
18 enriched = []
19 for url, result in zip(linkedin_urls, results):
20 if isinstance(result, Exception):
21 enriched.append({'url': url, 'error': str(result)})
22 elif result.get('success'):
23 enriched.append({'url': url, 'profile': result['data']})
24 else:
25 enriched.append({'url': url, 'error': result.get('message')})
26
27 return enriched
28
29# Enrich 100 candidates in seconds
30urls = [
31 "https://linkedin.com/in/candidate1",
32 "https://linkedin.com/in/candidate2",
33 # ... 98 more
34]
35
36enriched = asyncio.run(batch_enrich_candidates(urls))
37print(f"Enriched {len([e for e in enriched if 'profile' in e])} candidates")Webhook Integration Pattern
1from flask import Flask, request, jsonify
2from linkdapi import LinkdAPI
3
4app = Flask(__name__)
5client = LinkdAPI("your_api_key")
6
7@app.route('/webhook/candidate-applied', methods=['POST'])
8def handle_application():
9 """
10 Webhook endpoint for ATS to trigger enrichment
11 """
12 data = request.json
13 candidate_id = data['candidate_id']
14 linkedin_url = data.get('linkedin_url')
15
16 if not linkedin_url:
17 return jsonify({'error': 'No LinkedIn URL provided'}), 400
18
19 # Enrich the candidate
20 username = linkedin_url.rstrip('/').split('/')[-1]
21
22 # Get basic info first
23 overview = client.get_profile_overview(username)
24
25 if not overview['success']:
26 return jsonify({'error': 'Could not find LinkedIn profile'}), 404
27
28 urn = overview['data']['urn']
29
30 # Get detailed data
31 experience = client.get_full_experience(urn)
32 education = client.get_education(urn)
33 skills = client.get_skills(urn)
34 contact = client.get_contact_info(username)
35
36 # Compile enriched profile
37 enriched = {
38 'candidate_id': candidate_id,
39 'profile': overview['data'],
40 'experience': experience['data'] if experience['success'] else [],
41 'education': education['data'] if education['success'] else [],
42 'skills': skills['data'] if skills['success'] else [],
43 'contact': contact['data'] if contact['success'] else {}
44 }
45
46 # Return to ATS
47 return jsonify(enriched)
48
49if __name__ == '__main__':
50 app.run(port=5000)Use Case 2: Candidate Verification
Verify that candidates are who they say they are by comparing resume data to LinkedIn data.
Employment Verification
1def verify_employment(candidate_resume, linkedin_username):
2 """
3 Compare resume claims against LinkedIn data
4 """
5 # Get LinkedIn work history
6 urn_result = client.get_profile_urn(linkedin_username)
7 if not urn_result['success']:
8 return {'verified': False, 'error': 'Profile not found'}
9
10 urn = urn_result['data']['urn']
11 experience = client.get_full_experience(urn)
12
13 if not experience['success']:
14 return {'verified': False, 'error': 'Could not fetch experience'}
15
16 linkedin_jobs = experience['data'].get('experience', [])
17
18 # Compare each resume job against LinkedIn
19 verification_results = []
20
21 for resume_job in candidate_resume['jobs']:
22 matched = False
23
24 for linkedin_job in linkedin_jobs:
25 # Check company name match (fuzzy)
26 company_match = (
27 resume_job['company'].lower() in linkedin_job['companyName'].lower() or
28 linkedin_job['companyName'].lower() in resume_job['company'].lower()
29 )
30
31 # Check title match (fuzzy)
32 title_match = (
33 resume_job['title'].lower() in linkedin_job['title'].lower() or
34 linkedin_job['title'].lower() in resume_job['title'].lower()
35 )
36
37 if company_match and title_match:
38 matched = True
39 verification_results.append({
40 'resume_job': resume_job,
41 'linkedin_job': linkedin_job,
42 'status': 'verified'
43 })
44 break
45
46 if not matched:
47 verification_results.append({
48 'resume_job': resume_job,
49 'linkedin_job': None,
50 'status': 'not_found_on_linkedin'
51 })
52
53 # Calculate verification score
54 verified_count = sum(1 for r in verification_results if r['status'] == 'verified')
55 total_count = len(verification_results)
56
57 return {
58 'verified': verified_count == total_count,
59 'verification_score': verified_count / total_count if total_count > 0 else 0,
60 'details': verification_results
61 }
62
63# Example usage
64resume = {
65 'jobs': [
66 {'company': 'Google', 'title': 'Software Engineer', 'years': '2019 to 2021'},
67 {'company': 'Stripe', 'title': 'Senior Engineer', 'years': '2022 to present'}
68 ]
69}
70
71verification = verify_employment(resume, 'sarahchen')
72print(f"Verification score: {verification['verification_score'] * 100}%")Education Verification
1def verify_education(claimed_degree, claimed_school, linkedin_username):
2 """
3 Verify education claims against LinkedIn
4 """
5 urn_result = client.get_profile_urn(linkedin_username)
6 if not urn_result['success']:
7 return {'verified': False, 'error': 'Profile not found'}
8
9 urn = urn_result['data']['urn']
10 education = client.get_education(urn)
11
12 if not education['success']:
13 return {'verified': False, 'error': 'Could not fetch education'}
14
15 for edu in education['data'].get('education', []):
16 school_match = claimed_school.lower() in edu.get('university', '').lower()
17 degree_match = claimed_degree.lower() in edu.get('degree', '').lower()
18
19 if school_match and degree_match:
20 return {
21 'verified': True,
22 'linkedin_education': edu
23 }
24
25 return {
26 'verified': False,
27 'error': 'Education not found on LinkedIn profile'
28 }
29
30# Example
31result = verify_education(
32 claimed_degree="BS Computer Science",
33 claimed_school="Stanford",
34 linkedin_username="sarahchen"
35)
36print(f"Education verified: {result['verified']}")Start building with 100 free credits
Access profiles, companies, jobs, and more through our reliable, high-performance API. No credit card required.
Use Case 3: Candidate Sourcing
Find candidates who match your requirements without manual LinkedIn searching.
Search by Role and Location
1def source_candidates(job_title, location, count=50):
2 """
3 Find candidates matching job requirements
4 """
5 # Get location URN
6 geo_result = client.geo_name_lookup(location)
7 if not geo_result['success'] or not geo_result['data']:
8 raise ValueError(f"Location not found: {location}")
9
10 geo_urn = geo_result['data'][0]['urn']
11
12 # Search for candidates
13 all_candidates = []
14 start = 0
15
16 while len(all_candidates) < count:
17 results = client.search_people(
18 title=job_title,
19 geoUrn=geo_urn,
20 start=start,
21 count=50
22 )
23
24 if not results['success']:
25 break
26
27 candidates = results['data'].get('people', [])
28 if not candidates:
29 break
30
31 all_candidates.extend(candidates)
32 start += 50
33
34 return all_candidates[:count]
35
36# Find 100 Senior Software Engineers in San Francisco
37candidates = source_candidates(
38 job_title="Senior Software Engineer",
39 location="San Francisco",
40 count=100
41)
42
43print(f"Found {len(candidates)} candidates")
44
45for candidate in candidates[:10]:
46 print(f"- {candidate['fullName']}: {candidate['headline']}")Search by Skills and Company
1def source_by_skills_and_company(skills, target_companies, location):
2 """
3 Find candidates with specific skills from target companies
4 """
5 # Get location URN
6 geo_result = client.geo_name_lookup(location)
7 geo_urn = geo_result['data'][0]['urn'] if geo_result['success'] and geo_result['data'] else None
8
9 # Get company IDs
10 company_ids = []
11 for company_name in target_companies:
12 company_result = client.get_company_info(name=company_name)
13 if company_result['success']:
14 company_ids.append(company_result['data']['id'])
15
16 # Build skill keyword string
17 skill_keyword = ' '.join(skills)
18
19 # Search
20 results = client.search_people(
21 keyword=skill_keyword,
22 currentCompany=','.join(company_ids) if company_ids else None,
23 geoUrn=geo_urn,
24 count=50
25 )
26
27 if not results['success']:
28 return []
29
30 return results['data'].get('people', [])
31
32# Find Python developers at FAANG companies in NYC
33candidates = source_by_skills_and_company(
34 skills=['Python', 'Machine Learning', 'AWS'],
35 target_companies=['Google', 'Meta', 'Amazon', 'Apple', 'Netflix'],
36 location='New York'
37)
38
39print(f"Found {len(candidates)} matching candidates")


