Documentation navigation
POST /v2/developers/search

Search developers

Access
Authenticated
search:read
Cost
Free
Rate limit
10 / second per actor
Quota
None

v2 of GET /v1/developers/search. One thin YAML v2 query, as JSON or application/yaml: Turbopuffer filters (facts), rank_by (ANN for meaning, BM25 for jargon, attribute order, score expressions), up to 8 lanes under queries fused by person, name sugar ({place|company|school: [...]}, metros by default), seeds ({like}, {repos_of}), repos, sort, rerank (none, rules, clef, jev). Results are the v1 developer shape at depth (core, enriched, deep) with evidence and the index card; billed per developer returned, as v1. At core (no facets) the page is read from the index alone: details holds the GitHub profile, top repos and devRank, and hasEmail is null. cursor pages. Which fields a query may use, and for what (filter, sort, boost, include), is listed live by GET /v2/developers/fields; a disabled field, or a use its flags refuse, is a 400.

Request body DeveloperQuery

  • version number default: 1

    Optional here: 1.

    1
  • ask string

    The ask in words; the re-rank reads it.

    max 2000 chars
  • filters one of

    A Turbopuffer filter: `[attr, Op, value]`, `[And|Or, [...]]` or `[Not, f]`.

  • rank_by one of

    A Turbopuffer rank_by: ANN (`[Embed, text]`, a vector, or `{like: [...]}`), exact kNN (a vector or `{like}`; pair it with filters), BM25 (options as a 4th element), attribute order (up to 8 keys), or a score expression.

  • limit one of

    Single lane: a number counts people (1 to 100); `{total, per}` counts rows. With queries: people.

  • include array of string
    max 50 items
  • compute any
  • queries array of DevsLane

    2 or more lanes (3 by default), fused by person.

    min 2 items, max 32 items
    • filters one of

      A Turbopuffer filter: `[attr, Op, value]`, `[And|Or, [...]]` or `[Not, f]`.

    • rank_by one of

      A Turbopuffer rank_by: ANN (`[Embed, text]`, a vector, or `{like: [...]}`), exact kNN (a vector or `{like}`; pair it with filters), BM25 (options as a 4th element), attribute order (up to 8 keys), or a score expression.

    • limit one of

      Rows: a number, or `{total, per}` (per = rows per person, 1 to 5, default 3).

    • include array of string
      max 50 items
    • compute any
  • fuse one of
  • rerank string

    none, rules (default when the query has text), clef or jev.

    nonerulesclefjev
  • sort string

    relevance (default) or a person field, highest first. With no text and no rank_by the index sorts by it; otherwise the people found are re-ordered by it (nulls last). recent = last_active_at (GH Archive, partial).

    relevancedevrankstarsfollowersrecent
  • consistency string

    eventual (default; the index is written in batches) or strong (every write before the query).

    eventualstrong
  • repos object

    People who own and/or contributed to 1 to 10 repos (owner/name), combined with every other filter, text and sort.

    • names array of string required
      min 1 items, max 10 items
    • role string default: "either"

      owner, contributor, or either (default).

      ownercontributoreither
    • hops one of

      1: the people on the repos; 2: worked with them (people who share another repo with them, members excluded).

  • judge object

    clef / jev: how many of the rules top the model scores (default 20) and the per-call cap (default 900 ms).

    • top integer
      min 1, max 30
    • timeout_ms integer
      min 100, max 3000
  • depth string default: "core"

    The depth ladder: core (the default), enriched, deep. Each step returns more of the profile.

    coreenricheddeep
  • facets array of string default: []

    À-la-carte facet keys on top of the depth tier.

    max 5 items
  • garden string

    Which shape the contribution garden comes back in. `summary` (the default) is a compact `gardenSummary`: totals, active days and weeks, the last 90 days, the last active day and per-month counts, with `garden` null. `full` is the whole `garden`: every day’s count for the trailing year, for drawing a heatmap.

    fullsummary
  • repoLimit integer default: 4
    min 1, max 12
  • cursor string

    The previous page’s `cursor`: the next people for the same query.

    max 16384 chars
  • exclude array of string

    Developer ids never to return (up to 1000), e.g. people you already hold.

    max 1000 items

Responses

  • 200 Developers with evidence and their index card
    • results array of DeveloperV2 required

      Best match first, in the v1 developer shape.

      • 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 nested by namespace. A namespace and a member 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 AI blurbs live HERE, on the repos shown, not in a separate list.

            • 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. The summaries are folded ONTO the repos you see here — there is no separate list — so what is described is exactly what is shown.

            • 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. Present only when we hold at least one, resolved via the emails route.

          • 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 a garden cached before live stats existed.

            • totalStars integer | null required

              Stars on repositories they OWN, live. Null on a garden cached before live stats existed.

        • 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 any route. `match_rationale` comes from `facets=ai.match_rationale` on SEARCH — it is judged against your `q`. Compute is async: each member carries a `status` — `ok` or `pending` (with `retryAfterMs`: wait that long, then re-fetch this call once — the value is computed once and cached, so the retry returns fast). 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. On `ok`, `person_summary` and `match_rationale` carry their `value` here; `repo_summaries` instead folds its blurbs onto `githubProfile.repos[]` and carries status only. Read status before value.

          • person_summary object

            A three-line pitch for this developer, as one string of up to three lines, each opening with a bullet: who they are across their career, then the range of what they build and their most notable work.

            • status string required
              okpending
            • value object
            • retryAfterMs number

              On `pending` only: milliseconds to wait before re-fetching this call. The value is compute-once/cached, so the retry returns `ok` fast once it lands.

          • repo_summaries object

            Poll marker for the per-repo AI blurbs. The blurbs themselves fold onto `githubProfile.repos[].summary`/`.themes` (one-to-one with the repos shown), so this cell carries only `status`: `ok` means the blurbs are on the repos, `pending` (with `retryAfterMs`) means re-fetch. There is no `value` here.

            • status string required
              okpending
            • value object
            • retryAfterMs number

              On `pending` only: milliseconds to wait before re-fetching this call. The value is compute-once/cached, so the retry returns `ok` fast 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
              okpending
            • value object
            • retryAfterMs number

              On `pending` only: milliseconds to wait before re-fetching this call. The value is compute-once/cached, so the retry returns `ok` fast once it lands.

      • match DeveloperMatch required

        Query attribution for this result: the matched repositories and whether the lane could attribute at all.

        • repos array of MatchRepo required

          The repositories that connect this developer to your query. Empty whenever `status` is `unattributed`.

          max 12 items
          • fullName string required

            GitHub `owner/name`. The join key onto `developer.repos[].fullName`, so you can match an evidence repo back to the profile repo it refers to.

            max 160 chars
          • stars integer required

            Stargazer count at retrieval time. 0 also means "the source returned no count", so do not read 0 as proof the repo is unstarred.

            min 0, max 100000000
          • language string | null

            GitHub’s primary language for the repository. Null when none was returned; never inferred from the code. Absent on pages produced before this field existed.

            max 200 chars
          • role string | null

            This person’s relationship to the repository, and it is deliberately conservative. `owner` means the repository sits under THEIR namespace. `contributor` means everything else, including the person who wrote most of the code in someone else’s or an organisation’s repository — a top committer on a company repo is a `contributor`, so do not read `contributor` as "minor" or `owner` as "wrote it". Null means the lane that produced this row genuinely cannot tell; absent means an older producer did not emit the field.

            ownercontributor
        • status string required

          Whether `repos` genuinely answers "why did this person come back for my query". `attributed` = it does. `unattributed` = the lane that answered cannot attribute repositories to the query, and `repos` is empty.

          attributedunattributed
      • hasEmail boolean | null

        null at depth core: the index card does not carry it. enriched and deep read it.

      • evidence object required

        matches, rerank, score, similarity, sort?, hop?: as POST /v2/developers/search.

      • index object required

        The index card the search ran on: profile, top_repos, recent_roles.

        • profile object | null required
        • top_repos array of object required
        • recent_roles array of object
    • cursor string | null required

      Send it back as `cursor` with the same query for the next page; null at the end.

    • countStatus one of required
    • cached boolean required
    • rerank_applied string required
      nonerulesclefjev
    • rerank_skipped string | null required
    • rerank_judged object | null required
      • scored integer required
      • of integer required
    • filters_applied array of object required
      • lane number required
      • attr string required
      • op string required
      • value any
    • ids_resolved object required
    • places_resolved array of object required

      Each place that is a metro core city: the scope it ran with, its metro and how many city ids it filtered on.

      • place string required
      • scope string required
        metrocity
      • metro string required
      • member_count integer required
    • not_live array of string required
    • normalizer_version string required
    • timings object required
    • gardens_pending array of string required

      Returned developers whose contribution garden is not held yet: it is being read now, so POST /v2/developers/gardens with these ids a few seconds later returns it.

  • 400 The query does not compile; `hint` says what to change
    • ok boolean required
      false
    • error string required
    • hint string required
  • 401 Missing or invalid API key
    • code string required

      Stable machine-readable error code. Branch on this, never on the numeric status.

      bad_requestunauthorizedsignature_requiredpayment_requiredforbiddennot_foundconflictgonepayload_too_largeunprocessable_entitytoo_many_requestsinternal_errornot_implementedbilling_unavailablenot_contactablemailbox_link_unavailablemailbox_requiredmail_engine_unavailablewebhook_publisher_unavailabledatabase_unavailableclient_errorbuild_failed
    • message string required

      Human-readable explanation of the refusal.

    • status integer required

      The HTTP status code, repeated in the body.

    • remedy object

      A self-serve path forward, when one exists (a 402 points at how to restore access).

      • kind string required

        What kind of remedy this is, so a client can route it: whether the caller can clear the condition through the API, or a person must act in the web app.

        topupconnect_mailbox
      • url string required

        Where to go to clear the condition: an API path, or a web app page when only a person can.

  • 402 Your plan does not include this capability
    • code string required

      Stable machine-readable error code. Branch on this, never on the numeric status.

      bad_requestunauthorizedsignature_requiredpayment_requiredforbiddennot_foundconflictgonepayload_too_largeunprocessable_entitytoo_many_requestsinternal_errornot_implementedbilling_unavailablenot_contactablemailbox_link_unavailablemailbox_requiredmail_engine_unavailablewebhook_publisher_unavailabledatabase_unavailableclient_errorbuild_failed
    • message string required

      Human-readable explanation of the refusal.

    • status integer required

      The HTTP status code, repeated in the body.

    • remedy object

      A self-serve path forward, when one exists (a 402 points at how to restore access).

      • kind string required

        What kind of remedy this is, so a client can route it: whether the caller can clear the condition through the API, or a person must act in the web app.

        topupconnect_mailbox
      • url string required

        Where to go to clear the condition: an API path, or a web app page when only a person can.

  • 403 The key lacks search:read
    • code string required

      Stable machine-readable error code. Branch on this, never on the numeric status.

      bad_requestunauthorizedsignature_requiredpayment_requiredforbiddennot_foundconflictgonepayload_too_largeunprocessable_entitytoo_many_requestsinternal_errornot_implementedbilling_unavailablenot_contactablemailbox_link_unavailablemailbox_requiredmail_engine_unavailablewebhook_publisher_unavailabledatabase_unavailableclient_errorbuild_failed
    • message string required

      Human-readable explanation of the refusal.

    • status integer required

      The HTTP status code, repeated in the body.

    • remedy object

      A self-serve path forward, when one exists (a 402 points at how to restore access).

      • kind string required

        What kind of remedy this is, so a client can route it: whether the caller can clear the condition through the API, or a person must act in the web app.

        topupconnect_mailbox
      • url string required

        Where to go to clear the condition: an API path, or a web app page when only a person can.

  • 404 The seed is not in the index
    • ok boolean required
      false
    • error string required
    • hint string required
  • 429 Rate limit (10 requests per second per key), or the index is busy; see Retry-After
    • ok boolean required
      false
    • error string required
    • hint string required
  • 503 Search is not enabled in this environment, or the index is unavailable; see Retry-After
    • ok boolean required
      false
    • error string required
    • hint string required