The full education and experience of a LinkedIn member — from a profile URL, or from a name search with strict filters.
Give it a LinkedIn profile url, or a name plus optional filters
(education, company, location). For a name search career-sift finds candidates,
checks each one against every filter (fuzzy matching with aliases, so DTU = Delhi College of Engineering),
and returns only a profile that really matches. If no one matches, nothing is scraped and you get
{"status": "profile_not_found"}.
Results are normalized JSON: name, headline, location, url,
education[] (school, degree, field, start, end) and experience[] (title, company, location, start, end, description),
plus extras such as about, certifications[] and languages[] when the profile shows them.
Dates are "YYYY-MM" or "YYYY", null when unknown.
When LinkedIn rate limits us you get rate_limited with a retry_after hint — or queue a job and the
server retries for you with backoff. Every result is stored and can be listed later.
Reads are public: GET /api/scrape, /api/jobs/{id}, /api/profiles, /api/schedules.
Write endpoints (POST / DELETE) need the header x-api-key: <API_KEY>
when the server has API_KEY set; otherwise you get 401.
curl -X POST https://linkedin.greygoose.baby/api/jobs \
-H "x-api-key: $API_KEY" -H "content-type: application/json" \
-d '{"name": "Keshav Singhal", "education": "Delhi Technological University"}'
Errors are always {"error": "<friendly message>"} with a matching HTTP status
(400 bad input, 401 key, 404 not found, 429 rate limited).
| Endpoint | What |
|---|---|
GET/api/scrape | Scrape now (seconds to a minute) |
POST/api/jobskey | Queue a scrape, returns at once |
GET/api/jobs/{id} | Job status and result |
GET/api/profiles | Stored results, newest first |
GET/api/profiles/{id} | One stored result in full |
POST/api/scheduleskey | Create a recurring search |
GET/api/schedules | List recurring searches |
DELETE/api/schedules/{id}key | Stop a recurring search |
Query params: url or name, plus optional education, company, location.
The response includes profile_id of the stored result.
curl -G https://linkedin.greygoose.baby/api/scrape \
--data-urlencode "name=Keshav Singhal" \
--data-urlencode "education=Delhi Technological University"
curl -G https://linkedin.greygoose.baby/api/scrape \
--data-urlencode "url=https://www.linkedin.com/in/keshav-singhal-0541b123b"
Queue a search (JSON body, same fields as above) and poll it. status goes
queued → running → done (or error); once done, result holds the profile or profile_not_found.
curl -X POST https://linkedin.greygoose.baby/api/jobs \
-H "x-api-key: $API_KEY" -H "content-type: application/json" \
-d '{"name": "Keshav Singhal", "education": "Delhi Technological University", "company": "Google"}'
# → 202 {"id": 7, "status": "queued", ...}
curl https://linkedin.greygoose.baby/api/jobs/7
Everything ever scraped (by /api/scrape, jobs and schedules). Paging with limit (≤500) and offset.
curl "https://linkedin.greygoose.baby/api/profiles?limit=20&offset=0"
curl https://linkedin.greygoose.baby/api/profiles/1
curl -X POST https://linkedin.greygoose.baby/api/schedules \
-H "x-api-key: $API_KEY" -H "content-type: application/json" \
-d '{"name": "Keshav Singhal", "education": "Delhi Technological University", "interval": "@daily"}'
curl https://linkedin.greygoose.baby/api/schedules
curl -X DELETE https://linkedin.greygoose.baby/api/schedules/1 -H "x-api-key: $API_KEY"
HTTP 200
{
"status": "ok",
"url": "https://www.linkedin.com/in/keshav-singhal-0541b123b",
"name": "Keshav Singhal",
"headline": "Software Engineer",
"location": "New Delhi, Delhi, India",
"education": [
{"school": "Delhi Technological University", "degree": "Bachelor of Technology",
"field": "Computer Science", "start": "2016", "end": "2020"}
],
"experience": [
{"title": "Software Engineer", "company": "Google", "location": "Bengaluru",
"start": "2020-07", "end": null, "description": null}
],
"about": "…",
"certifications": [], "languages": [], "projects": [],
"source": "public",
"candidates_checked": 2,
"fetched_at": "2026-10-19T12:00:00Z",
"profile_id": 1
}
HTTP 200
{
"status": "profile_not_found",
"reason": "No candidate studied at Delhi Technological University.",
"profile_id": 2
}
HTTP 429
{
"error": "LinkedIn is rate limiting us right now. Try again later or queue a job (POST /api/jobs) and it will retry automatically.",
"status": "rate_limited",
"retry_after": 600
}
MIN_DELAY_SECONDS, default 20 s) between them.max(retry_after, 60·2attempts) seconds, capped at 6 h, up to 8 attempts.
next_run_at on the job tells you when it will run again.every_hours (min 0.25) or every interval:
30m, 6h, 1d, 2w, @hourly, @daily, @weekly.
The scheduler checks every minute, so the first job is queued within a minute of creating it.GET /api/profiles, so recurring searches fill in data over time.The same scraper runs as the Apify actor career-sift (find it in your Apify console after
apify push, as <your-username>/career-sift). Input JSON:
{
"name": "Keshav Singhal",
"education": "Delhi Technological University",
"company": "",
"location": "",
"maxCandidates": 5
}
or {"url": "https://www.linkedin.com/in/keshav-singhal-0541b123b"}. Each run pushes one item to the
default dataset: the profile (status: ok), profile_not_found, or rate_limited.
Run it from the API:
curl -X POST "https://api.apify.com/v2/acts/<your-username>~career-sift/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "content-type: application/json" \
-d '{"name": "Keshav Singhal", "education": "Delhi Technological University"}'