GitHub is where developers leave evidence. Your agent searches by language, location and project, reads what someone has actually shipped, and scores it against the brief. Sometimes the email is right there on the profile. Often it is not, and enrichment picks that up.
GitHub runs before enrichment. It finds and qualifies the engineer; the waterfall finds the email.
GitHub rarely carries the email, this finds it
the waterfall for developers with private commit addresses
find the companies running the stack these engineers know
file the technical assessment on the candidate
A skill is a whole pipeline your agent already knows. These ones call GitHub as part of the run.
PredictLeads signals fresh funding. Build a candidate pool via GitHub + Apollo weeks before the JDs go live.
Top GitHub contributors at top companies. Enrich via Prospeo for a never-resting senior engineering pipeline.
Every tool ships with a written playbook, and the agent loads it before the first call. Auth, rate limits, what each call costs, which actions need your approval, and the mistakes worth avoiding. It is the difference between an agent that knows the tool and one that guesses at it.
Read the GitHub docs →HOW in lib/github.py. Sourcing-relevant subset only; index: reference/docs/github/raw/endpoints.md.
https://api.github.com · GraphQL: https://api.github.com/graphql.Authorization: Bearer <token> (env GITHUB_TOKEN; never hardcode) + X-GitHub-Api-Version: 2022-11-28.page/per_page (≤100); iter_search_users respects the cap. Adapter auto-sleeps on 403/429 rate-limit + transient 5xx.Discovery: search_users/iter_search_users, search_repositories, search_code. Profile + skill signal: get_user, list_user_repos, get_repo_contributors, list_user_followers. Ops: get_rate_limit. graphql(query, variables) — generic passthrough (one endpoint; you specify the fields) for rich single-call dossiers: profile + socialAccounts (linked LinkedIn/Twitter) + top languages + pinned repos + contributionsCollection.
GitHub does expose contact info three ways — try these before falling back to the waterfall:
get_user(username) → email (if the dev set one public), blog, twitter_username, bio.get_user_social_accounts(username) → [{provider, url}] (linkedin, mastodon, …). Also in GraphQL user.socialAccounts.find_user_emails(username) walks their recent owned repos' commits and returns real emails, dropping *@users.noreply.github.com proxies. (Primitives: list_repo_commits(author=…), list_user_events.)name + company + GitHub/LinkedIn to the enrichment waterfall. GitHub slots in before enrichment.find_user_emails is bounded by max_repos). Keep outreach human-paced (ToS).search_users/get_repo_contributors by language+location → get_user+list_user_repos (skill signal) → score vs the JD (agent, free) → enrich email (waterfall) → validate → ATS (Vincere/Bullhorn) → sequencer (SourceWhale/Lemlist/HeyReach). The engineer-sourcing front-end for the "source & shortlist" recipe.
lib/github.pyImport: from lib.github import GitHub → instantiate GitHub() (reads key from env). Base: https://api.github.com. Generic passthrough: request(method, path, *, params, json).
find_user_emails(username: str, *, max_repos: int = 10) -> list[str] — Harvest candidate emails from a user's public commits (when the profile email is private).get_rate_limit() -> Any — GET /rate_limit — remaining REST/Search/GraphQL budget (safe read-only pilot).get_repo_contributors(owner: str, repo: str, **params) -> Any — GET /repos/{owner}/{repo}/contributors — source the people who build a given project.get_user(username: str) -> Any — GET /users/{username} — profile: name, company, location, blog, email (if public), hireable, counts.get_user_social_accounts(username: str) -> Any — GET /users/{username}/social_accounts — the linked social accounts shown on the profilegraphql(query: str, variables: dict | None = None) -> Any — POST /graphql — run any GraphQL query/mutation. Best for rich single-call candidate dossiersiter_search_users(q: str, *, per_page: int = 100, max_results: int = 1000, **params) -> Iterator[dict] — Paginate /search/users up to GitHub's 1,000-result cap.list_repo_commits(owner: str, repo: str, *, author: str | None = None, per_page: int = 100, **params) -> Any — GET /repos/{owner}/{repo}/commits — commits (filter by author=username). Each carrieslist_user_events(username: str, **params) -> Any — GET /users/{username}/events/public — public activity (PushEvents carry commit author emails).list_user_followers(username: str, **params) -> Any — GET /users/{username}/followers — network expansion.list_user_repos(username: str, *, sort: str = 'pushed', per_page: int = 100, **params) -> Any — GET /users/{username}/repos — skill signal: languages, stars, recency. sort: pushed|updated|created|full_name.search_code(q: str, *, per_page: int = 30, page: int = 1, **params) -> Any — GET /search/code — find code (derive authors using a specific tech). Stricter rate limit.search_repositories(q: str, *, sort: str | None = None, order: str | None = None, per_page: int = 30, page: int = 1) -> Any — GET /search/repositories — q e.g. 'language:rust stars:>500 topic:cli'. sort: stars|forks|updated.search_users(q: str, *, sort: str | None = None, order: str | None = None, per_page: int = 30, page: int = 1) -> Any — GET /search/users — find developers. q e.g. 'location:Berlin language:Go followers:>50 repos:>10'.