# GitHub Contributor Email Scraper (`fetch_cat/github-contributor-email-scraper`) Actor

Export public GitHub commit-author emails, contributor identity fields, timestamps, and commit evidence from repositories or organizations.

- **URL**: https://apify.com/fetch\_cat/github-contributor-email-scraper.md
- **Developed by:** [Hanna Nosova](https://apify.com/fetch_cat) (community)
- **Categories:** Developer tools, Lead generation, Automation
- **Stats:** 3 total users, 2 monthly users, 96.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.02 / 1,000 contributor email rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## GitHub Contributor Email Scraper

Export public GitHub commit-author emails, contributor identity fields, timestamps, and commit evidence from repositories or organizations.

GitHub Contributor Email Scraper uses GitHub's official APIs and saves clean dataset rows for CSV, Excel, JSON, API, automation, and authorized research workflows. It supports tokenless REST for small public jobs and authenticated GraphQL for efficient, quota-aware commit-history queries.

### What data does it export?

- Public commit-author email and name
- GitHub login and profile URL when GitHub links the commit to an account
- Repository owner and name
- Commit SHA and evidence URL
- Commit timestamp and a short message snippet
- Noreply-email status
- Explicit-repository or organization-discovery provenance
- REST or GraphQL route provenance

The Actor does not infer private emails, access private repositories, use login cookies, or bypass GitHub privacy settings.

### Ready-to-run examples

- [Extract public contributor emails from Apify Crawlee](https://apify.com/fetch_cat/github-contributor-email-scraper/examples/github-crawlee-contributor-emails)
- [Scan multiple GitHub repositories](https://apify.com/fetch_cat/github-contributor-email-scraper/examples/github-multiple-repo-contributor-emails)
- [Include GitHub noreply emails](https://apify.com/fetch_cat/github-contributor-email-scraper/examples/github-include-noreply-emails)
- [Find recent contributors since a date](https://apify.com/fetch_cat/github-contributor-email-scraper/examples/github-recent-contributor-emails-since-date)
- [Scan a GitHub organization](https://apify.com/fetch_cat/github-contributor-email-scraper/examples/github-organization-contributor-emails)
- [Export commit-level evidence without email deduplication](https://apify.com/fetch_cat/github-contributor-email-scraper/examples/github-commit-evidence-no-dedupe)

[View all ready-to-run examples](https://apify.com/fetch_cat/github-contributor-email-scraper/examples).

### Example input

```json
{
  "repositories": [
    "apify/crawlee",
    "apify/apify-sdk-js"
  ],
  "maxCommitsPerRepo": 100,
  "since": "2026-01-01",
  "includeNoreplyEmails": false,
  "dedupeByEmail": true,
  "apiMode": "auto"
}
```

Use `repositoryUrls` when you have full GitHub URLs, `repositories` for `owner/repo` values, or `organization` to discover public repositories belonging to an organization.

### Example output

```json
{
  "repository": "apify/crawlee",
  "owner": "apify",
  "repo": "crawlee",
  "commitSha": "abc123",
  "commitUrl": "https://github.com/apify/crawlee/commit/abc123",
  "authorName": "Example Developer",
  "authorEmail": "dev@example.com",
  "isNoreplyEmail": false,
  "githubLogin": "exampledev",
  "profileUrl": "https://github.com/exampledev",
  "committedAt": "2026-01-01T12:00:00Z",
  "messageSnippet": "Improve retry handling",
  "sourceType": "repository",
  "scrapedAt": "2026-07-20T12:00:00Z",
  "apiMode": "graphql"
}
```

Every dataset row keeps a commit URL and SHA so you can review the public source evidence.

### Input settings

| Setting | API key | Description |
| --- | --- | --- |
| Repository URLs | `repositoryUrls` | Up to 100 public GitHub repository URLs. |
| Repository names | `repositories` | Up to 100 `owner/repo` values or full repository URLs. |
| Organization login | `organization` | Optional GitHub organization login or profile URL. |
| Maximum organization repositories | `maxRepositories` | Public organization repositories to discover, from 1 to 100. Default: 20. |
| Maximum commits per repository | `maxCommitsPerRepo` | Recent commits inspected per repository, from 1 to 1,000. Default: 20. |
| Since date | `since` | Optional ISO date or timestamp lower bound. |
| Until date | `until` | Optional ISO date or timestamp upper bound. |
| Branch | `branch` | Optional branch name. Blank uses each repository's default branch. |
| Include noreply emails | `includeNoreplyEmails` | Keep GitHub noreply addresses. Default: false. |
| Deduplicate by email | `dedupeByEmail` | Save the first row for each email across the run. Default: true. |
| GitHub API mode | `apiMode` | `auto`, `rest`, or `graphql`. Default: auto. |
| Maximum active run seconds | `maxRunSeconds` | Optional active-work cutoff from 1 to 270 seconds. Pending work is checkpointed. |
| GitHub token | `githubToken` | Optional secret token for GraphQL and higher GitHub limits. Public-repository read access is sufficient. |

At least one repository input or an organization is required. Invalid GitHub hosts, malformed repository names, invalid dates, and unsafe branch names are rejected before paid or network work.

### REST and GraphQL modes

`apiMode: "auto"` is recommended:

- With a `githubToken`, the Actor uses GitHub GraphQL.
- Without a token, the Actor uses GitHub REST.

Choose `rest` to force REST even when a token is present. Choose `graphql` to force GraphQL; this mode requires `githubToken`.

GraphQL queries the default branch through `defaultBranchRef` or the selected `branch`, requests only the fields used in the dataset, and follows forward cursors. REST remains available for backward-compatible tokenless tasks.

Neither route is an unlimited-rate workaround. The Actor honors GitHub retry/reset guidance, bounds retries, detects GraphQL errors returned with HTTP 200, and stops admitting work before the Apify timeout.

### Repository and organization behavior

Explicit repositories retain `sourceType: "repository"`.

Repositories discovered from `organization` use `sourceType: "organization"`. If the same repository appears in both places, the explicit source wins and the row is not duplicated for discovery provenance.

`maxRepositories` limits only organization discovery. Explicit repository inputs remain separate, and all sources are deduplicated by canonical `owner/repo`.

### Date and branch filters

`since` and `until` accept ISO dates or timestamps:

```json
{
  "repositories": ["apify/crawlee"],
  "since": "2026-01-01T00:00:00Z",
  "until": "2026-06-30T23:59:59Z"
}
```

Use `branch` for a non-default branch:

```json
{
  "repositories": ["owner/repository"],
  "branch": "release/v1",
  "maxCommitsPerRepo": 100
}
```

If a valid date range or empty repository has no matching commits, the run succeeds with zero rows and records the reason in `RUN_SUMMARY`. If every repository is unavailable or fails, the run fails instead of presenting a misleading empty success.

### Noreply filtering and email deduplication

GitHub users can author commits with privacy-protecting addresses ending in `users.noreply.github.com`.

- Keep `includeNoreplyEmails: false` to exclude these addresses.
- Set it to `true` for complete public commit evidence.

With `dedupeByEmail: true`, only the first row for each case-insensitive email is saved across all repositories. Disable it when you need a row for every inspected commit that contains an included email.

### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `repository` | string | Canonical `owner/repo`. |
| `owner` | string | Repository owner login. |
| `repo` | string | Repository name. |
| `commitSha` | string | Git commit object ID. |
| `commitUrl` | string | Public source commit URL. |
| `authorName` | string or null | Author name stored in the commit. |
| `authorEmail` | string | Author email stored in the public commit. |
| `isNoreplyEmail` | boolean | Whether the address is a GitHub noreply address. |
| `githubLogin` | string or null | Linked GitHub login when available. |
| `profileUrl` | string or null | Linked public GitHub profile URL. |
| `committedAt` | string or null | Commit author timestamp. |
| `messageSnippet` | string or null | First 240 normalized characters of the commit message. |
| `sourceType` | string | `repository` or `organization`. |
| `scrapedAt` | string | Row-save timestamp. |
| `apiMode` | string | `rest` or `graphql`. |

### Run summary and pending work

The default key-value store contains:

- `RUN_SUMMARY` — route, requested/completed/failed repository counts, processed commits, saved rows, skip counts, empty reason, warnings, and classified repository errors.
- `PENDING_WORK` — the repository and page/cursor checkpoint used when work remains.

Rows are saved progressively. Saving a paid row and charging the `item` event are one operation. If a later repository fails or the active-work deadline is reached, completed rows remain available.

On a successful complete run, `PENDING_WORK` is cleared. On a forced cutoff, the run fails honestly with partial output and a resumable checkpoint.

### Pricing

This Actor uses pay-per-event pricing. Prices below match the active Actor configuration.

| Event | Charged unit | Price |
| --- | --- | ---: |
| `start` | Once per run after valid input | $0.005 |

| Event | Free / no discount | Starter / Bronze | Scale / Silver | Business / Gold | Custom / Platinum | Custom / Diamond |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `item` per saved row | $0.000030184 | $0.000026247 | $0.000020472 | $0.000015748 | $0.000010499 | $0.000007349 |

Only successfully saved contributor-email rows receive the `item` charge. Repository errors, malformed records, filtered commits, duplicate rows, and summary/checkpoint records are not charged as items.

Apify can separately charge platform usage for compute, storage, and data transfer. This Actor does not use a paid proxy by default.

### Who is this for?

This Actor is designed for teams that have authorization to work with public Git commit metadata, including:

- developer relations and open-source program teams;
- recruiting and talent research teams;
- repository maintainers and engineering analytics teams;
- security, compliance, and software-supply-chain analysts;
- data teams building approved contributor directories or audits.

It is not a private-email discovery service. It exports only author identity fields already attached to public commits returned by GitHub's official APIs.

### Common workflows

**Authorized contributor research:** Export recent public commit evidence for repositories you are permitted to analyze.

**Developer relations:** Identify contributors across owned or authorized open-source projects and retain a source commit for review.

**Repository auditing:** Include noreply addresses and disable deduplication to examine commit-level author metadata.

**Organization reporting:** Discover a bounded set of public organization repositories and export a unified dataset.

**Recurring monitoring:** Use `since` with Apify schedules to collect recent commit-author records at a controlled cadence.

### Schedule recurring exports

Save a task with a bounded repository list and date filter, then attach it to an Apify schedule.

For recurring jobs:

- Keep repository and commit limits conservative.
- Use a GitHub token for GraphQL or larger workloads.
- Advance `since` in your automation when you need incremental windows.
- Check `RUN_SUMMARY` before treating a partial multi-repository result as complete.
- Use the dataset's stable repository/SHA/email combination when merging runs.

### API usage

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('fetch_cat/github-contributor-email-scraper').call({
  repositories: ['apify/crawlee'],
  maxCommitsPerRepo: 100,
  includeNoreplyEmails: false,
  dedupeByEmail: true,
  apiMode: 'auto',
  githubToken: process.env.GITHUB_TOKEN
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

run = client.actor("fetch_cat/github-contributor-email-scraper").call(run_input={
    "repositories": ["apify/crawlee"],
    "maxCommitsPerRepo": 100,
    "includeNoreplyEmails": False,
    "dedupeByEmail": True,
    "apiMode": "rest",
})

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/fetch_cat~github-contributor-email-scraper/runs" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "repositories": ["apify/crawlee"],
    "maxCommitsPerRepo": 100,
    "apiMode": "rest"
  }'
```

Pass Apify authentication in the header so it does not enter URL history or request logs. Put `githubToken` in the secret input field rather than source code.

### Use with MCP and AI agents

This Actor works through the official Apify MCP server. The focused endpoint exposes only this Actor:

```text
https://mcp.apify.com?tools=fetch_cat/github-contributor-email-scraper
```

Claude Code:

```bash
claude mcp add apify-github-contributor-email \
  --url "https://mcp.apify.com?tools=fetch_cat/github-contributor-email-scraper"
```

Claude Desktop:

```json
{
  "mcpServers": {
    "apify-github-contributor-email": {
      "url": "https://mcp.apify.com?tools=fetch_cat/github-contributor-email-scraper"
    }
  }
}
```

Example prompts:

- "Run the GitHub Contributor Email Scraper for apify/crawlee with 50 commits and exclude noreply addresses."
- "Export commit-level author evidence from these authorized repositories without email deduplication."
- "Scan the default branches of these repositories since 2026-01-01 and summarize RUN\_SUMMARY before using the rows."

### Tips for better results

- Start with one repository and 20-100 commits.
- Use `apiMode: "auto"` with a GitHub token for GraphQL.
- Use `branch` only when you know the requested branch exists across the repositories.
- Keep `dedupeByEmail` enabled for one row per address.
- Disable deduplication for commit-level evidence and expect more rows.
- Review `RUN_SUMMARY` for partial repository failures and rate-limit diagnostics.
- If GitHub returns a reset time, wait rather than immediately launching repeated large runs.

### Limits and practical notes

- Public repositories only.
- The selected branch history is linear and follows GitHub's API semantics.
- A commit email may not belong to a deliverable mailbox.
- GitHub may return no linked login even when the commit has an author name and email.
- Noreply addresses intentionally protect a contributor's contact address.
- Rewritten Git history can change or remove earlier commit evidence.
- REST and GraphQL have separate primary limits but share secondary protections.
- The Actor retries transient failures only within a bounded run deadline.
- It does not use residential proxies, browser automation, cookies, or private-repository permissions.

### Legality and responsible use

Use this Actor only within the GitHub authorization and legal basis that applies to your workflow. Follow applicable privacy, employment, communications, anti-spam, security, and data-retention requirements.

Do not assume a public commit email is consent for unsolicited messaging. Maintain appropriate suppression, removal, and do-not-contact handling for downstream systems.

### FAQ

#### Does it require a GitHub token?

No for small public REST runs. GraphQL requires `githubToken`, and a token also provides higher authenticated API limits.

#### Does it scrape private GitHub emails?

No. It exports the author email stored in a public Git commit. It does not infer hidden addresses or bypass GitHub privacy controls.

#### Can it export CSV, Excel, or JSON?

Yes. Use the Apify dataset export controls or API to download CSV, Excel, JSON, XML, and other supported formats.

#### Can it scan an entire organization?

It can discover up to 100 public organization repositories per run. The default is 50. Combine `organization` with explicit repositories when needed.

#### Why are some profile fields null?

GitHub does not always link a Git commit author to a GitHub account. The commit email, author name, and linked account are separate pieces of metadata.

#### Why did a valid run return zero rows?

The date range may contain no commits, the repository may be empty, every matching commit may lack an email, or all addresses may have been excluded as noreply. `RUN_SUMMARY.emptyReason` explains the observed case.

#### What happens when one repository fails?

Completed repositories remain saved. The failed repository is classified in `RUN_SUMMARY.repositoryErrors`. The whole run fails only when every repository fails or when the shared run deadline stops unfinished work.

#### Is GraphQL always faster?

Not necessarily. It provides a compact authenticated query and a separate primary quota, but query cost, node limits, timeouts, and secondary limits still apply. `auto` chooses GraphQL only when a token is available.

### Related Actors

- [GitHub Profile Scraper](https://apify.com/fetch_cat/github-profile-scraper)
- [GitHub Repositories Search Scraper](https://apify.com/fetch_cat/github-repositories-search-scraper)
- [GitHub Issues and Pull Requests Scraper](https://apify.com/fetch_cat/github-issues-pull-requests-scraper)
- [Website Contact Finder](https://apify.com/fetch_cat/website-contact-finder)
- [Bulk URL Status Checker](https://apify.com/fetch_cat/bulk-url-status-checker)

### Support

If a run fails or the output looks wrong, open an issue or report a bug from the Actor page.

Please include:

- Apify run ID or run URL
- Input JSON with secret tokens removed
- Expected output
- Actual output
- One reproducible public repository URL

This lets support distinguish GitHub availability, rate limits, branch/date filters, schema changes, and Actor behavior without exposing credentials.

For one reproducible public URL, use https://github.com/apify/crawlee and compare against the [Crawlee contributor-email example](https://apify.com/fetch_cat/github-contributor-email-scraper/examples/github-crawlee-contributor-emails).

# Actor input Schema

## `repositoryUrls` (type: `array`):

Public GitHub repository URLs, for example https://github.com/apify/crawlee. Up to 100 entries.

## `repositories` (type: `array`):

Public repositories as owner/repo values or full GitHub URLs. Up to 100 entries.

## `organization` (type: `string`):

Optional GitHub organization login or profile URL. Public repositories are added to the explicit repository list.

## `maxRepositories` (type: `integer`):

Maximum public repositories to discover from the organization. Explicit repository inputs are not counted against this limit.

## `maxCommitsPerRepo` (type: `integer`):

Maximum recent commits inspected in each repository. Use a small value for tests and up to 1,000 for deeper coverage.

## `since` (type: `string`):

Optional ISO date or timestamp. Only commits on or after this time are inspected.

## `until` (type: `string`):

Optional ISO date or timestamp. Only commits on or before this time are inspected.

## `branch` (type: `string`):

Optional branch name such as main or release/v1. Leave empty to use each repository's default branch.

## `includeNoreplyEmails` (type: `boolean`):

Keep GitHub privacy-protecting noreply addresses in the dataset. Disabled by default.

## `dedupeByEmail` (type: `boolean`):

Save only the first commit row for each email across all repositories. Disable for commit-level evidence.

## `apiMode` (type: `string`):

Auto uses GraphQL when a token is available and REST otherwise. Choose REST or GraphQL to force a route; GraphQL requires a token.

## `maxRunSeconds` (type: `integer`):

Optional active-work budget before the Actor checkpoints pending repositories and stops safely. Platform cleanup time is reserved separately.

## `githubToken` (type: `string`):

Optional GitHub token for higher rate limits and GraphQL. Public-repository read access is sufficient; the token is never written to output.

## Actor input object example

```json
{
  "repositoryUrls": [
    {
      "url": "https://github.com/apify/crawlee"
    }
  ],
  "repositories": [
    "apify/crawlee"
  ],
  "maxRepositories": 20,
  "maxCommitsPerRepo": 20,
  "includeNoreplyEmails": false,
  "dedupeByEmail": true,
  "apiMode": "auto"
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

## `pendingWork` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "repositoryUrls": [
        {
            "url": "https://github.com/apify/crawlee"
        }
    ],
    "repositories": [
        "apify/crawlee"
    ],
    "organization": "",
    "since": "",
    "until": "",
    "branch": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetch_cat/github-contributor-email-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "repositoryUrls": [{ "url": "https://github.com/apify/crawlee" }],
    "repositories": ["apify/crawlee"],
    "organization": "",
    "since": "",
    "until": "",
    "branch": "",
}

# Run the Actor and wait for it to finish
run = client.actor("fetch_cat/github-contributor-email-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "repositoryUrls": [
    {
      "url": "https://github.com/apify/crawlee"
    }
  ],
  "repositories": [
    "apify/crawlee"
  ],
  "organization": "",
  "since": "",
  "until": "",
  "branch": ""
}' |
apify call fetch_cat/github-contributor-email-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=fetch_cat/github-contributor-email-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/LDpNlPzaCotpWp9SH/builds/vz1DwCgUebsPbAVYC/openapi.json
