Documentation navigation
GET /v1/developers/summaries

AI summaries of developers and their repositories

Access
Authenticated
search:read
Cost
Free
Rate limit
30 / minute per account
Quota
None

The AI-written plain-language summaries for developer ids you already hold: a one-line ai.person_summary (who they are, reading their LinkedIn, projects and full GitHub history) and ai.repo_summaries (a short developer-friendly blurb per key repository). Each row is the SAME developer object the other routes return, with the two summary facets in its details object — no parallel shape invented. The value is cached across accounts. A developer whose summary is not yet computed comes back pending (the job is dispatched); read the same id again to collect the finished ok value.

Summaries are NOT a depth tier — depth is how far out a search reaches; this is a separate call, like emails.

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

  • 200 The developers we hold, each with its AI summary facets
    • results array of PublicDeveloper required

      The developers we hold data for, in the same shape a search result carries. Ids we hold nothing for are omitted rather than returned empty — and are not billed.

      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 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. Present only when non-empty.

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

              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). 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 short, varied one-liner on who this developer is.

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

  • 400 Neither `ids` nor `logins` named anyone
    • 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.

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

  • 429 More than 30 requests a minute
    • 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.