career-sift

The full education and experience of a LinkedIn member — from a profile URL, or from a name search with strict filters.

What it does

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.

Try it

— or search by name —

Auth

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).

Endpoints

EndpointWhat
GET/api/scrapeScrape now (seconds to a minute)
POST/api/jobskeyQueue a scrape, returns at once
GET/api/jobs/{id}Job status and result
GET/api/profilesStored results, newest first
GET/api/profiles/{id}One stored result in full
POST/api/scheduleskeyCreate a recurring search
GET/api/schedulesList recurring searches
DELETE/api/schedules/{id}keyStop a recurring search

GET/api/scrape

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"

POST/api/jobs  ·  GET/api/jobs/{id}

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

GET/api/profiles  ·  GET/api/profiles/{id}

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

POST/api/schedules  ·  GET  ·  DELETE/api/schedules/{id}

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"

Example responses

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
}

How scheduling works

Apify actor

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"}'