<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Erik Strömberg</title>
    <description>The latest articles on DEV Community by Erik Strömberg (@apa512).</description>
    <link>https://dev.to/apa512</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909710%2Fddbe8ebe-8884-4764-a811-a74fa8343740.jpeg</url>
      <title>DEV Community: Erik Strömberg</title>
      <link>https://dev.to/apa512</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/apa512"/>
    <language>en</language>
    <item>
      <title>Why finding a GitHub user's email is harder than you'd think</title>
      <dc:creator>Erik Strömberg</dc:creator>
      <pubDate>Sun, 03 May 2026 01:21:36 +0000</pubDate>
      <link>https://dev.to/apa512/why-finding-a-github-users-email-is-harder-than-youd-think-gjd</link>
      <guid>https://dev.to/apa512/why-finding-a-github-users-email-is-harder-than-youd-think-gjd</guid>
      <description>&lt;p&gt;You've found a contributor whose work you depend on. The maintainer of a package you use, a developer who fixed something for you upstream, the author of a CVE you need to coordinate with. You have their GitHub username. You'd like their email.&lt;/p&gt;

&lt;p&gt;You'd think this would be a &lt;code&gt;GET&lt;/code&gt; away. It isn't. Here's why — and what it actually takes to find one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitHub API doesn't have it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GET /users/:login&lt;/code&gt; returns an &lt;code&gt;email&lt;/code&gt; field. For the vast majority of users, that field is &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;GitHub flipped private-by-default years ago. When you sign up today, your commit email is set to &lt;code&gt;&amp;lt;id&amp;gt;+&amp;lt;login&amp;gt;@users.noreply.github.com&lt;/code&gt; and the public profile email field is empty. Older accounts that opted in still expose addresses, but they're a minority — and the people you actually want to reach (active maintainers, security-conscious developers) are exactly the ones who turned this off.&lt;/p&gt;

&lt;p&gt;So that's out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The commits don't have it either (mostly)
&lt;/h2&gt;

&lt;p&gt;The next obvious move: look at the user's commits. Every commit has an author email in its metadata. Pick a public repo, fetch the commit, get the email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.github.com/repos/torvalds/linux/commits | jq &lt;span class="s1"&gt;'.[0].commit.author.email'&lt;/span&gt;
&lt;span class="c"&gt;# "torvalds@linux-foundation.org"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works for Linus. It does not work for most people. Run this against any reasonably modern repo and you'll see a lot of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"49699333+dependabot[bot]@users.noreply.github.com"
"12345678+somecontributor@users.noreply.github.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub rewrites commit emails to the &lt;code&gt;noreply&lt;/code&gt; form whenever the author has the "Keep my email addresses private" setting on, which is the default. The &lt;code&gt;&amp;lt;id&amp;gt;+&amp;lt;login&amp;gt;&lt;/code&gt; part is the user's GitHub ID and login — useful if all you wanted was to identify them, but you already had their login. You wanted to &lt;em&gt;email&lt;/em&gt; them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The events archive: harder, but real data
&lt;/h2&gt;

&lt;p&gt;There's another source that dev tooling people sometimes forget about: the public events stream. GitHub publishes a firehose of public events (pushes, opens, comments, releases) and &lt;a href="https://www.gharchive.org/" rel="noopener noreferrer"&gt;GH Archive&lt;/a&gt; has been recording it hourly since 2011 — terabytes of newline-delimited JSON, gzipped, freely downloadable.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;PushEvent&lt;/code&gt; carries the underlying commit metadata, including author name and email. In principle, if a developer ever pushed a commit &lt;em&gt;before&lt;/em&gt; they turned on private email — or if they push from a CI pipeline that uses a real address — that email is in the archive.&lt;/p&gt;

&lt;p&gt;The job that processes it looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Zlib&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;GzipReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archive_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_line&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"PushEvent"&lt;/span&gt;

  &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"actor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"commits"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;author_name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;author_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ... we have a (login, name, email) triple. Now what?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You scan a few hours of archive and immediately find a problem. A lot of those emails look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8a3f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f80@gmail.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forty hex characters. That's a SHA-1 hash. The local part of the email has been one-way-hashed; only the domain is in the clear. This is a historical artifact of how the events feed has been emitted for stretches of GitHub's history — commit emails arriving with the local part obfuscated.&lt;/p&gt;

&lt;p&gt;Great. Now you have a hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reversing the hash
&lt;/h2&gt;

&lt;p&gt;SHA-1 of an arbitrary string is a one-way function. SHA-1 of an email local part is not, because email local parts are not arbitrary. They're drawn from a tiny, predictable distribution: &lt;code&gt;firstname&lt;/code&gt;, &lt;code&gt;firstname.lastname&lt;/code&gt;, &lt;code&gt;f.lastname&lt;/code&gt;, &lt;code&gt;firstnamelastname&lt;/code&gt;, &lt;code&gt;firstname_lastname&lt;/code&gt;, &lt;code&gt;firstname1985&lt;/code&gt;, and a few hundred other patterns layered over a finite list of names.&lt;/p&gt;

&lt;p&gt;If you precompute a table of &lt;code&gt;sha1(local_part) → local_part&lt;/code&gt; for every plausible candidate — every name you've ever encountered, every email you've ever seen published — you can reverse most of these in O(1).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^([a-f0-9]{40})@(.+)$/&lt;/span&gt;
  &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vg"&gt;$2&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sha1Hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;sha1_hash: &lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;real_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lookup table is the asset. Building it well is most of the work. Mine is hundreds of millions of rows and grows every time the world publishes another address.&lt;/p&gt;

&lt;h2&gt;
  
  
  The harder problem: was that actually them?
&lt;/h2&gt;

&lt;p&gt;You now have a &lt;code&gt;(login, author_name, real_email)&lt;/code&gt; triple. The temptation is to claim the email belongs to the login. Don't.&lt;/p&gt;

&lt;p&gt;Anyone can configure git locally. People commit with &lt;code&gt;user.name&lt;/code&gt; set to their full legal name, their nickname, "John", "John D.", "johndoe", "John Doe via Acme Corp", "Acme CI Bot", or — frequently — &lt;em&gt;someone else's name entirely&lt;/em&gt;, because they cloned a repo on a coworker's machine and never reconfigured. A login pushes hundreds of commits over the years; many of them carry author names that don't actually identify the person behind the login.&lt;/p&gt;

&lt;p&gt;So you need a confidence layer. Mine is a separate pass over the same archive that builds a &lt;code&gt;(login, author_name) → observation_count&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;github_login_author_name_mappings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;login&lt;/span&gt;              &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;author_name&lt;/span&gt;        &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;observation_count&lt;/span&gt;  &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a hash reverses to a candidate email, I look up every author name that login has ever been observed pushing under, and ask: what fraction of this login's total commits use &lt;em&gt;this&lt;/em&gt; author name?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;total_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;all_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;name_count&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;all_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;author_name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;percentage&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name_count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="c1"&gt;# Need at least 10 commits for any signal at all&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="c1"&gt;# With a long history, 50% co-occurrence is enough; with little, demand 80%&lt;/span&gt;
&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;50.0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;80.0&lt;/span&gt;
&lt;span class="n"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rejects the noise. The contributor who once pushed a commit signed "Test User" doesn't get linked to a &lt;code&gt;test.user@example.com&lt;/code&gt; reversal. The CI bot pushing under a real engineer's login but with &lt;code&gt;git config user.name "Buildkite"&lt;/code&gt; doesn't pollute the index. What survives is the set of (login, name) pairs that consistently co-occur — a fairly trustworthy proxy for "this is the human behind this login."&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left
&lt;/h2&gt;

&lt;p&gt;Doing this for one user, end to end:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify which monthly archive shards likely contain their activity.&lt;/li&gt;
&lt;li&gt;Stream and decompress hundreds of gigabytes of JSON.&lt;/li&gt;
&lt;li&gt;Maintain a SHA-1 lookup table of every plausible email local part you've ever seen.&lt;/li&gt;
&lt;li&gt;Maintain a parallel &lt;code&gt;(login, author_name)&lt;/code&gt; co-occurrence index across the entire archive.&lt;/li&gt;
&lt;li&gt;For every reversed hash, run the confidence check.&lt;/li&gt;
&lt;li&gt;Validate the resulting email isn't already claimed by a different GitHub login (people misconfigure git constantly).&lt;/li&gt;
&lt;li&gt;Verify the address actually accepts mail before you rely on it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a multi-day backfill the first time, hundreds of gigabytes resident, and a continuous trickle of new data forever. Perfectly reasonable to build if finding email addresses for GitHub users is your full-time job. Absurd to build if you just need to email three maintainers about a CVE.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shortcut
&lt;/h2&gt;

&lt;p&gt;This is the work &lt;a href="https://peopledb.io" rel="noopener noreferrer"&gt;PeopleDB&lt;/a&gt; does in the background. The pipeline above — archive ingestion, hash reversal, identity correlation, deduplication, SMTP validation — runs continuously. The answer is one HTTP call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://peopledb.io/api/v1/people?github_login=octocat"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$PEOPLEDB_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"github_login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"octocat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"github_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;583231&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"linkedin_public_identifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email_addresses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"personal_email_addresses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"work_email_addresses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint also accepts &lt;code&gt;github_id&lt;/code&gt;, &lt;code&gt;linkedin_id&lt;/code&gt;, and &lt;code&gt;linkedin_public_identifier&lt;/code&gt; — the same identity-merging logic runs across all of them, so if a person has both a GitHub and a LinkedIn record in the index, you get the union.&lt;/p&gt;

&lt;p&gt;If you're doing security disclosure, contributor outreach, or any kind of identity resolution where you start with a username and need to actually reach the human, that's the trade: spin up the pipeline, or skip it.&lt;/p&gt;

</description>
      <category>github</category>
      <category>api</category>
      <category>opensource</category>
      <category>security</category>
    </item>
  </channel>
</rss>
