Get observed email addresses

GET https://api.vamotalent.ai/v2/developers/emails
Access
Authenticated
search:read
Cost
Free
Rate limit
30 / minute per account
Quota
None

The email addresses we observe for developer ids you already hold. Each row is the SAME developer object the other routes return; the addresses land under details.contact.emails — no parallel shape is invented. You get EVERY address we observe for a developer. A developer we hold no address for comes back with no contact.emails (the contact section is omitted); an input we hold nothing at all for is omitted. Duplicate inputs resolve once.

Query parameters

  • ids string

    Developer ids, comma separated, at most 25. A developer's id is GitHub's public NUMERIC user id, stringified — deterministic, rename-proof, and exactly what `developer.developerId` carries on every response from this API, so ids from search paste straight in. Only have usernames? Use `logins` instead (mixing both is fine). Repeating an id changes nothing and costs nothing: ids are deduplicated before we fetch or bill.

    max 1625 chars
  • logins string

    GitHub usernames, comma separated, at most 25 — the friendly alternative to `ids`, freely mixable with it. Logins resolve to the same developers their numeric ids name; a login that resolves to nobody is omitted from the results and costs nothing, the same contract an unknown id has. Prefer `ids` when you carry results between calls: a login can be renamed and then belongs to someone else.

    max 1625 chars

Responses

The developers we hold, each with the addresses we observe

  • results array of PublicDeveloper required

    The developers we hold data for; any addresses we observe land under `details.contact.emails`. Ids we hold nothing for are omitted.

    max 250 items
    • id string required

      The stable GitHub numeric id for this developer. Immutable (unlike the login) — this is the handle to pass to every other endpoint.

      max 64 chars
    • login string required

      The GitHub handle as of the last snapshot. Display it, do not key on it: handles are renameable and reusable.

      max 200 chars
    • name string | null required

      Display name as the developer set it on GitHub. Null when they set none.

      max 200 chars
    • avatarUrl string | null required

      GitHub avatar image URL. Null when absent.

      max 2048 chars
    • hasEmail boolean required

      Whether we hold at least one email address for this developer. It says we have an address on file, not that the address works — we do not check deliverability. The addresses themselves are never returned by a search or a base profile read: this boolean is the free signal, and the paid `contact.emails` facet is the only way to get them. Use `hasEmail` on a search request to filter to developers we hold an address for, without paying.

    • hasLinkedin boolean required

      Whether we hold a LinkedIn match for this developer — i.e. whether `enriched` will return `identity.*` and `contact.socials` for them. Available on the same base row as `hasEmail`: read both to decide what depth is worth requesting.

    • hasLocation boolean required

      Whether we hold any resolved location for this developer (from LinkedIn or a geocoded GitHub string). A free presence flag alongside `hasEmail`/`hasLinkedin`. The resolved `location` OBJECT rides `details.contact.location` at `enriched` (like the LinkedIn URL), not on the spine. Filter on it with `requireLocation=true`.

    • details PublicDetails required

      Every detail about this developer beyond the identity spine, as a KEYED object grouped by section. A section and its members appear only when resolved; absent details are omitted entirely. The set grows with depth: `core` fills `githubProfile.*` and `score.cracked`; `enriched` adds `identity.*`, `contact.socials` and `contact.location`; `deep` adds `github.garden`. À-la-carte: `tags.repos` folds onto `githubProfile.repos[]`, the emails route fills `contact.emails`.

      • githubProfile object

        The free GitHub profile: `githubProfile.core` (bio, org, languages, snapshot counts) and `githubProfile.repos`. Named for its source — everything here comes straight from GitHub.

        • core object

          bio, currentRole, organization, university, languages, joinedAt, followers, following, totalStars.

        • repos array of ProfileRepoWithTags

          Top repositories (default 4), with `owned`/`commits` and, when resolved, `subjects`/`technologies` (`tags.repos`) and a plain-English `summary`/`themes` (`ai.repo_summaries`) folded on. The full set of blurbs is also on `details.ai.repo_summaries.value`.

          • name string required

            The repository name alone, without the owner (`linux`).

            max 200 chars
          • fullName string required

            GitHub `owner/name`. This is the join key: an evidence repo and a profile repo describing the same repository carry the same `fullName`.

            max 200 chars
          • description string | null required

            The repository's own GitHub description, verbatim. Null when it has none.

            max 5000 chars
          • language string | null required

            GitHub's primary language for the repository. Null when GitHub reports none (an empty or docs-only repo); never inferred from the code.

            max 200 chars
          • stars integer required

            Stargazer count at the time this profile was snapshotted, not at request time.

            min 0, max 100000000
          • commits integer

            How many commits THIS developer authored to this repository over the trailing year. This is the difference between a project they wrote and a project they landed one drive-by fix in — a 385k-star repository with 4 of their commits is not their work. Absent when no commit signal was captured; 0 means none in the trailing year, which for older dormant work is not the same as none ever.

            min 0, max 100000000
          • owned boolean

            True when the repository sits under the developer's own account, false when it is someone else's (an organisation's or another person's) and they are a contributor to it. This is the difference between "they built this" and "they committed to this", and it is the ONLY honest basis for crediting a repository's stars to a person. Absent when no ownership signal was captured; treat absent as unknown, never as false.

          • url string required

            The public github.com URL of the repository.

            max 2048 chars
          • subjects array of string

            Subject/domain tags for this repository. Present only when `tags.repos` was resolved and this repo carried tags.

          • technologies array of string

            Technology tags for this repository. Present only when `tags.repos` was resolved and this repo carried tags.

          • summary string

            A one-line, plain-English blurb on what this repository is, written for a non-technical reader. Present only when `ai.repo_summaries` was resolved and the model could describe this repo. Folded onto the repos shown here; `details.ai.repo_summaries.value.repoSummaries` lists every repository that was summarized, including ones past `repoLimit`.

          • themes array of string

            A few short themes the AI drew from this repository. Present alongside `summary` when `ai.repo_summaries` was resolved.

      • score object

        The cracked score (`score.cracked`).

        • cracked object

          crackedScore, tier.

      • identity object

        The resolved external identity (`enriched`): `identity.linkedin`, `identity.experience`, `identity.education`.

        • linkedin object

          The resolved LinkedIn identity scalars (linkedin, headline, title, company, seniority, expertise, location).

        • experience array of object

          Employment history, newest first. Present only when non-empty.

          • title string | null

            Job title.

            max 200 chars
          • company string | null

            Employer name.

            max 200 chars
          • startDate string | null

            Start date exactly as the source rendered it. Free text, not normalised.

            max 200 chars
          • endDate string | null

            End date, same unnormalised free-text form. Null on a current role.

            max 200 chars
          • current boolean | null

            The source's own "still there" flag.

        • education array of object

          Education history, in the order the source listed it. Present only when non-empty. Entries are not ordered by level and can include secondary schools, so read every entry when confirming a university or a graduation year.

          • school string | null

            Institution name.

            max 200 chars
          • degree string | null

            Degree awarded.

            max 200 chars
          • fieldOfStudy string | null

            Field of study.

            max 200 chars
          • startDate string | null

            Start date as the source rendered it.

            max 200 chars
          • endDate string | null

            End date as the source rendered it.

            max 200 chars
      • contact object

        Contact channels: `contact.socials` (enriched), `contact.location` (enriched), `contact.emails` (the emails route).

        • socials object

          github, linkedin social links.

        • emails array of string

          Every address we observe for this developer, ranked, the best one first. Present only when we hold at least one, resolved via the emails route.

        • bestEmail string | null

          The address to use: the first unflagged personal address, else the first unflagged one; null when every address is flagged. On v2 only.

        • emailDetails array of object

          Every address with its type and flags, ranked, the most reliable first; within a source, personal, then work, then other. On v2 only.

          • address string required
          • type string required

            personal: a free-mail or own-name domain; work: a company domain; other: academic or government.

            personalworkother
          • flags array of string required

            Why it may not reach this person. `name_mismatch`: it names neither their current name nor their login (a prior name, or someone else). `low_link_confidence`: it came through a LinkedIn link we are not sure of. `ambiguous_link`: they map to several LinkedIn profiles. Flagged addresses are kept, never dropped.

        • location object | null

          Where this developer is, resolved once and LinkedIn-preferred: `source` says whether the clean city/country came from their LinkedIn (`linkedin`) or from geocoding their GitHub location string (`github`). Present at `enriched`+ whenever `hasLocation` is true — the spine carries only the `hasLocation` flag. Note the GitHub geocode is rough (it often drops a country string into `city`); trust `source: "linkedin"` over `"github"`.

          • raw string | null required

            The location string exactly as the developer typed it on GitHub ("SF / remote").

            max 200 chars
          • city string | null required

            City resolved from `raw` by geocoding. Null when `raw` was empty or unresolvable.

            max 200 chars
          • state string | null

            The country subdivision (US state, province, region) as a full name, e.g. "Ohio". Present only from the clean LinkedIn overlay, for the developers we hold a match for. Absent otherwise: the GitHub geocoder does not reliably resolve it (so we do not surface its guess), and it is legitimately absent for city-states and metro-only profiles.

            max 200 chars
          • country string | null required

            Country resolved from `raw`. Null when `raw` was empty or unresolvable. It can also be null while `city` is set, so read `city` and `raw` beside it before treating a location as unknown.

            max 200 chars
          • source string

            Where the resolved city/country came from: `linkedin` when our LinkedIn overlay supplied it (clean, self-reported on LinkedIn), `github` when it is geocoded from the GitHub `raw` string. Most developers have no LinkedIn match, so `github` is the common case and its city/country are often sparse. Absent on unresolved payloads.

            linkedingithub
      • github object

        The live-GitHub block (`deep`): `github.gardenSummary` by default, or the full `github.garden` with `garden=full`.

        • garden object

          The full contribution heatmap: every day’s count for the trailing year. Only with `garden=full`.

        • gardenSummary GardenSummary

          The contribution garden as a compact read (the default `garden=summary`): totals, active days and weeks, the last 90 days, per-month counts.

          • from string required

            First day of the garden window, `YYYY-MM-DD`, inclusive.

          • to string required

            Last day of the garden window, `YYYY-MM-DD`, inclusive.

          • total integer required

            Public contributions across the window.

          • activeDays integer required

            Days in the window with at least one contribution.

          • activeWeeks integer required

            Distinct weeks in the window with at least one contribution. Steady contributors score close to 52.

          • last90Days integer required

            Contributions in the 90 days ending on `to`.

          • lastActiveDay string | null required

            The most recent day with a contribution, `YYYY-MM-DD`. Null when the window is empty.

          • byMonth object required

            Contributions per calendar month, `YYYY-MM` → count. Months with none are omitted.

          • fetchedAt string required

            When the underlying garden was read from GitHub, as an ISO 8601 instant.

          • followers integer | null required

            Current GitHub follower count, live. Null on older gardens.

          • totalStars integer | null required

            Stars on repositories they OWN, live. Null on older gardens.

        • status string

          Present only when the garden did not come back, in place of it: `pending` (being read, re-fetch after `retryAfterMs`), `failed` (the GitHub read errored; a retry can succeed), `unavailable` (no garden exists for this developer). None of the three is charged.

          pendingfailedunavailable
        • retryable boolean

          With `status`: whether re-fetching this call can return the garden.

        • retryAfterMs number

          With `status: pending`: milliseconds to wait before re-fetching.

      • ai object

        À-la-carte AI prose. `person_summary`/`repo_summaries` come from the `/developers/summaries` route or `facets=ai.person_summary,ai.repo_summaries` on enrich, at every depth (never on the v2 search or similar). `match_rationale` comes from `facets=ai.match_rationale` on SEARCH, judged against your `q`. Compute is async, so each member carries a `status`: `ok` (the `value` is here), `pending` (with `retryAfterMs`: wait that long, then re-fetch this call once; the value is computed once, so a retry returns quickly), `failed` (with `retryable: true`: the write errored, and a re-fetch requests it again) or `unavailable` (with `retryable: true`: there is nothing to summarize yet, and a later read may return it). A member you asked for is never silently left out. A pending `person_summary`/`repo_summaries` can also be collected from `/developers/summaries` with the same ids: there, reading a developer is free while a summary your account has paid for is still being written, and for the one read that returns it finished. Read status before value.

        • person_summary object

          A short pitch for this developer. On `ok`, `value` is `{ personSummary, headline, body? }`: `personSummary` is the whole pitch as one string of two lines, a headline (who they are across their career) over one or two plain sentences on the kind of work they do and their most notable projects. `headline` is the first line, ready to set as a title. `body` is the rest, absent when the pitch is one line.

          • status string required

            `ok`: `value` is here. `pending`: being written, re-fetch after `retryAfterMs`. `failed`: the write errored and a re-fetch requests it again. `unavailable`: there is nothing to summarize yet, and a later read may return it.

            okpendingfailedunavailable
          • value object

            On `ok` only.

          • retryable boolean

            On `failed` and `unavailable`: whether re-fetching this call can return the value.

          • retryAfterMs number

            On `pending` only: milliseconds to wait before re-fetching this call. The value is computed once, so a retry returns `ok` quickly once it lands.

        • repo_summaries object

          The per-repo AI blurbs. On `ok`, `value` is `{ repoSummaries: [{ fullName, blurb, themes? }] }`, one entry for every repository that was summarized (not only the ones `repoLimit` shows). The same blurbs also fold onto `githubProfile.repos[].summary`/`.themes` for the repos shown.

          • status string required

            `ok`: `value` is here. `pending`: being written, re-fetch after `retryAfterMs`. `failed`: the write errored and a re-fetch requests it again. `unavailable`: there is nothing to summarize yet, and a later read may return it.

            okpendingfailedunavailable
          • value object

            On `ok` only.

          • retryable boolean

            On `failed` and `unavailable`: whether re-fetching this call can return the value.

          • retryAfterMs number

            On `pending` only: milliseconds to wait before re-fetching this call. The value is computed once, so a retry returns `ok` quickly once it lands.

        • match_rationale object

          Whether and why this developer matches the search query, judged against it. SEARCH ONLY (there is no query to judge against on an id lookup). `value` is `{ matched, reasons? }`: a real match is `{ matched: true, reasons: ["Currently Staff Engineer at Stripe on payments", "Maintains a Go ISO-20022 library", "8 years shipping Go"] }` — a list of short, positive, recruiter-facing bullets (repos, languages, LinkedIn role/company, organisation, location, tenure). A weak/non-match is `{ matched: false }` (no reasons — an honest verdict, not a manufactured one). Check `matched` before reading `reasons`.

          • status string required

            `ok`: `value` is here. `pending`: being written, re-fetch after `retryAfterMs`. `failed`: the write errored and a re-fetch requests it again. `unavailable`: there is nothing to summarize yet, and a later read may return it.

            okpendingfailedunavailable
          • value object

            On `ok` only.

          • retryable boolean

            On `failed` and `unavailable`: whether re-fetching this call can return the value.

          • retryAfterMs number

            On `pending` only: milliseconds to wait before re-fetching this call. The value is computed once, so a retry returns `ok` quickly once it lands.