# GitHub Issues, PRs, Discussions & Releases Exporter (`vellumlabs/github-issues-exporter`) Actor

Export issues, pull requests, discussions and releases from public GitHub repos as clean records or as Markdown wiki notes with YAML front matter. Optional comments, label/state/date filters, token support for higher rate limits. Pay per item.

- **URL**: https://apify.com/vellumlabs/github-issues-exporter.md
- **Developed by:** [Vellum Kasane](https://apify.com/vellumlabs) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 item exporteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## GitHub Issues, PRs, Discussions & Releases Exporter

Export the **knowledge locked in a GitHub repository's issues, pull requests, discussions and releases** as clean, Markdown-ready records, through the official GitHub API. One record per item with title, state, author, labels, assignees, milestone, reactions, timestamps, the body as GitHub-flavoured Markdown, and (optionally) the comment thread. Choose the **wiki-notes** format and you additionally get one Markdown file per item with YAML front matter (`type`, `title`, `status`, `updated`, `source_url`), shaped like the source pages of an LLM wiki, plus an `INDEX.md`.

Built for:

- **LLM wikis and agent memory**: feed a "decisions behind the codebase" wiki (Obsidian, Claude Code, Karpathy-style LLM wiki) with the discussions and issues where the decisions were actually made. The notes drop straight into a `raw/` or `sources/` folder.
- **RAG over a project's history**: bodies and comments are Markdown already; no HTML cleaning.
- **Release notes and changelog pipelines**: releases with tag, prerelease flag, assets and body.
- **Support and community analytics**: label, reaction and comment counts per issue across many repositories.
- **AI agents and MCP clients** that need "what did people say about X in repo Y" (call it from the [Apify MCP server](https://mcp.apify.com), read the dataset).

### How it works

1. List the repositories (`owner/name` or `https://github.com/owner/name`).
2. Pick what to export: issues, pull requests, discussions, releases. Filter by state, labels and "updated in the last N days"; cap with `maxItems` (per repository and type).
3. The Actor pages through the GitHub REST API (`/issues`, `/pulls`, `/releases`, comments) - and the GraphQL Discussions API when you provide a token - and writes one record per item.
4. In `wiki-notes` format each record is also written to the key-value store as `note-<owner>--<repo>--<type>-<number>.md`, and `INDEX.md` lists them all.

Without a token GitHub allows **60 requests per hour per IP address**; with a personal access token (no scopes needed for public repositories) **5,000 per hour**. Each list page (100 items) is one request; each item with comments costs one more request. The Actor reads the `x-ratelimit-*` headers, tells you in the log how many requests remain and when the window resets, and when the limit is hit it stops with the items exported so far (remaining items are still exported without comments) instead of failing. Turn on `waitForRateLimitReset` to sleep until the window resets and continue.

### Input

| Field                   | Type          | Default   | Notes                                                                                                   |
| ----------------------- | ------------- | --------- | ------------------------------------------------------------------------------------------------------- |
| `repos`                 | string\[]      | required  | `owner/name` or GitHub URLs. Private repositories are not supported.                                    |
| `types`                 | string\[]      | all four  | Any of `issues`, `pulls`, `discussions`, `releases`.                                                    |
| `state`                 | string        | `all`     | `all`, `open`, `closed` (issues, PRs, discussions).                                                     |
| `labels`                | string\[]      | `[]`      | Items must carry **all** listed labels.                                                                 |
| `sinceDays`             | integer       | 0         | Only items updated (releases: published) in the last N days. 0 = no limit.                              |
| `maxItems`              | integer       | 200       | Cap per repository and per type.                                                                        |
| `includeComments`       | boolean       | `true`    | Fetch comments (issues, PRs, discussions).                                                              |
| `maxCommentsPerItem`    | integer       | 50        | Up to 100, oldest first.                                                                                |
| `githubToken`           | secret string | -         | Personal access token; raises the limit to 5,000 requests/hour and enables the GraphQL Discussions API. |
| `waitForRateLimitReset` | boolean       | `false`   | Sleep until the rate-limit window resets (max 60 min) instead of stopping with partial results.         |
| `outputFormat`          | string        | `records` | `records` or `wiki-notes` (records + one `.md` per item + `INDEX.md`).                                  |

Example input:

```json
{
    "repos": ["apify/apify-sdk-js", "apify/crawlee"],
    "types": ["issues", "discussions", "releases"],
    "state": "closed",
    "labels": ["bug"],
    "sinceDays": 90,
    "maxItems": 100,
    "includeComments": true,
    "maxCommentsPerItem": 30,
    "githubToken": "github_pat_...",
    "outputFormat": "wiki-notes"
}
```

### Output

One dataset item per issue, pull request, discussion or release (download as JSON, CSV, Excel or HTML):

```json
{
    "repo": "apify/apify-sdk-js",
    "type": "issue",
    "number": 566,
    "id": 3123456789,
    "title": "Actor.pushData charges twice when eventName is set and the dataset is full",
    "state": "closed",
    "state_reason": "completed",
    "author": "someuser",
    "author_association": "NONE",
    "labels": ["bug", "t-tooling"],
    "assignees": ["B4nan"],
    "milestone": null,
    "category": null,
    "answered": null,
    "draft": null,
    "merged_at": null,
    "base_ref": null,
    "head_ref": null,
    "tag_name": null,
    "prerelease": null,
    "assets": [],
    "created_at": "2026-06-02T09:11:40Z",
    "updated_at": "2026-06-05T12:40:03Z",
    "closed_at": "2026-06-05T12:40:03Z",
    "published_at": null,
    "url": "https://github.com/apify/apify-sdk-js/issues/566",
    "body_markdown": "### Which package is this bug report for?\n\napify\n\n### Issue description\n\nWhen `Actor.pushData(item, 'my-event')` ...",
    "reactions": {
        "total": 2,
        "plus_one": 2,
        "minus_one": 0,
        "laugh": 0,
        "hooray": 0,
        "confused": 0,
        "heart": 0,
        "rocket": 0,
        "eyes": 0
    },
    "comments_count": 3,
    "comments": [
        {
            "id": 2987654321,
            "author": "B4nan",
            "author_association": "MEMBER",
            "created_at": "2026-06-02T10:02:11Z",
            "updated_at": "2026-06-02T10:02:11Z",
            "url": "https://github.com/apify/apify-sdk-js/issues/566#issuecomment-2987654321",
            "body_markdown": "Thanks, reproduced. The charge happens in ...",
            "reactions": {
                "total": 0,
                "plus_one": 0,
                "minus_one": 0,
                "laugh": 0,
                "hooray": 0,
                "confused": 0,
                "heart": 0,
                "rocket": 0,
                "eyes": 0
            },
            "is_answer": null,
            "replies_count": null
        }
    ],
    "comments_truncated": false,
    "markdown_file_key": "note-apify--apify-sdk-js--issue-566.md",
    "exported_at": "2026-09-22T15:08:01.412Z"
}
```

| Field                                                | Meaning                                                                                                                                                                                                                                            |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`                                               | `issue`, `pull`, `discussion`, `release`.                                                                                                                                                                                                          |
| `state`                                              | issue: `open` / `closed`; pull: `open` / `draft` / `merged` / `closed`; discussion: `open` / `closed`; release: `published` / `prerelease`.                                                                                                        |
| `state_reason`                                       | GitHub's reason (`completed`, `not_planned`, `reopened`, `duplicate`, `resolved`, `outdated`) when present.                                                                                                                                        |
| `category`, `answered`                               | Discussions only: category name and whether an answer was chosen. Discussion comments carry `is_answer` and `replies_count`.                                                                                                                       |
| `draft`, `merged_at`, `base_ref`, `head_ref`         | Pull requests only.                                                                                                                                                                                                                                |
| `tag_name`, `prerelease`, `assets`, `published_at`   | Releases only. Assets: `{ name, size, download_count, url }`.                                                                                                                                                                                      |
| `comments_count` / `comments` / `comments_truncated` | Total on GitHub, the stored comments (up to `maxCommentsPerItem`), and whether more exist (cap or rate limit). For pull requests the total is known when the PR appeared in the most recent issues pages; otherwise comments are fetched directly. |
| `body_markdown`                                      | Exactly what the author wrote (GitHub-flavoured Markdown), no conversion needed.                                                                                                                                                                   |

#### wiki-notes format

Every record is also stored as a Markdown file, for example `note-apify--apify-sdk-js--issue-566.md`:

```markdown
---
type: 'source'
title: 'Actor.pushData charges twice when eventName is set and the dataset is full'
status: 'closed'
updated: '2026-06-05'
source_url: 'https://github.com/apify/apify-sdk-js/issues/566'
source_kind: 'github-issue'
repo: 'apify/apify-sdk-js'
number: 566
author: 'someuser'
labels: ['bug', 't-tooling']
created: '2026-06-02'
closed: '2026-06-05'
exported: '2026-09-22T15:08:01.412Z'
---

## Actor.pushData charges twice when eventName is set and the dataset is full

**Source:** https://github.com/apify/apify-sdk-js/issues/566 · **Repo:** apify/apify-sdk-js · **Kind:** issue #566 · **State:** closed (completed) · **Author:** someuser · **Labels:** bug, t-tooling · **Created:** 2026-06-02 · **Updated:** 2026-06-05 · **Closed:** 2026-06-05 · **Reactions:** 2

### Body

#### Which package is this bug report for?

...

### Comments (3)

#### B4nan — 2026-06-02

Thanks, reproduced. The charge happens in ...

Source: https://github.com/apify/apify-sdk-js/issues/566#issuecomment-2987654321
```

The front matter keys `type: source`, `title`, `status`, `updated` and `source_url` follow the LLM-wiki source-page convention, so an agent can ingest the notes into `wiki/sources/` and cite them from decision pages. `INDEX.md` groups all notes by repository and type with one line per note (link, state, last update, GitHub URL). `SUMMARY.json` holds per-repository counts, the discussions source used (`graphql` or `rest`), the number of API requests made and the rate-limit state at the end.

### Pricing

This Actor uses **pay-per-event** pricing:

| Event              | Price                                     | When                                                                                                             |
| ------------------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `item-exported`    | **$0.001 per item** ($1 per 1,000)        | Once per issue, pull request, discussion or release written to the dataset.                                      |
| `comment-exported` | **$0.0002 per comment** ($0.20 per 1,000) | Once per comment stored on a record. Comments are only fetched when they can still be billed within your budget. |
| Actor start        | $0.005 per run                            | Apify's standard start event; covers the first seconds of compute.                                               |

Exporting 500 issues with on average 4 comments each costs $0.50 + $0.40 = about **$0.90**. Set **Max total charge per run** to hard-cap spend; the Actor stops gracefully before exceeding it.

### Data sources and limits

| Data                     | Endpoint                                                                                                                                   | Documentation                                                                                                                                                                                                                                                                                                  |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Issues                   | `GET /repos/{owner}/{repo}/issues` (`state`, `labels`, `since`, `sort=updated`); pull requests returned by this endpoint are skipped       | https://docs.github.com/en/rest/issues/issues                                                                                                                                                                                                                                                                  |
| Pull requests            | `GET /repos/{owner}/{repo}/pulls` (`state`, `sort=updated`); comment counts and reactions are taken from the issues listing when available | https://docs.github.com/en/rest/pulls/pulls                                                                                                                                                                                                                                                                    |
| Issue / PR comments      | `GET /repos/{owner}/{repo}/issues/{number}/comments`                                                                                       | https://docs.github.com/en/rest/issues/comments                                                                                                                                                                                                                                                                |
| Releases                 | `GET /repos/{owner}/{repo}/releases` (drafts skipped)                                                                                      | https://docs.github.com/en/rest/releases/releases                                                                                                                                                                                                                                                              |
| Discussions (with token) | GraphQL `repository.discussions` ordered by `UPDATED_AT`, comments inline                                                                  | https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions                                                                                                                                                                                                                                |
| Discussions (no token)   | `GET /repos/{owner}/{repo}/discussions` and `.../discussions/{number}/comments`                                                            | Responds live (verified 2026-09-22) but is not part of GitHub's published REST API description (https://github.com/github/rest-api-description); GitHub documents Discussions for GraphQL only, and GraphQL is unavailable without a token (0 requests/hour). The Actor logs a warning when it uses this path. |
| Rate limits              | `x-ratelimit-*` headers, `retry-after`                                                                                                     | https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api                                                                                                                                                                                                                                |

Limits:

- Public repositories only (the token is used for rate limits, not for private access).
- The unauthenticated quota is shared by every process behind the same IP address. On the Apify platform that can include other Actors, so the sample run may see a smaller quota than 60; add a token for anything beyond a quick look.
- `sinceDays` uses `updated_at` for issues, PRs and discussions and `published_at` for releases. Pull-request and discussion listings are ordered by update time and stop at the first older item.
- Comment threads are capped at 100 per item (one page). Review comments on PR diffs are not exported, only the conversation comments.
- Reactions on discussions are exported in both modes; discussion `state_reason` and `closed_at` are only available through GraphQL.

### Tips

- **Feed an LLM wiki**: `outputFormat: "wiki-notes"`, `types: ["issues", "discussions"]`, `state: "closed"`, `sinceDays: 30`, schedule weekly, then download the `note-*.md` files from the key-value store into `raw/` and let the wiki agent ingest them.
- **Only the decisions**: use `labels: ["decision"]` (or `adr`, `rfc`) if the project labels them.
- **Big repositories**: set `maxItems` and `sinceDays` first; an unauthenticated run on a repository with thousands of issues stops at the rate limit with partial results.
- **Token**: create a fine-grained PAT with no repository access (public data only). It is stored encrypted on Apify and never written to the dataset or logs.

### Verified

Local run on 2026-09-22 with `apify run --purge` (Apify CLI 1.10.0, Apify SDK 3.7.2, Node 24), prefill input (`apify/apify-sdk-js`, `types: issues, pulls, releases`, `maxItems: 20`, `includeComments: false`, `outputFormat: wiki-notes`), unauthenticated:

- 60 items exported (20 issues, 20 pull requests, 20 releases), 60 `note-*.md` files + `INDEX.md` + `SUMMARY.json` written; **4 counted API requests** (issues page, pulls page, releases page, plus the free `/rate_limit` check), runtime about 5 seconds. Every pull request had its comment count and reactions resolved from the issues listing (no extra requests).
- Comments and discussions checked with `types: ["issues", "discussions"], maxItems: 4, includeComments: true, maxCommentsPerItem: 3` (still unauthenticated): 4 issues + 1 discussion exported, 4 comments stored (1 issue comment, 3 discussion comments from the REST discussions endpoint, `discussionsSource: "rest"` in `SUMMARY.json`, warning logged as documented), 5 requests. Notes spot-checked: front matter, meta line, body, comment sections with source links.
- Pay-per-event path tested with `ACTOR_TEST_PAY_PER_EVENT=1` (local test price $1 per event): with `ACTOR_MAX_TOTAL_CHARGE_USD=6` the run charged 5 `item-exported` + 1 `comment-exported` and stopped with "Run budget reached"; with a $4 cap it charged 4 items + 0 comments. Charges never exceeded the cap; comments that no longer fit the budget stay stored but unbilled.
- The GraphQL discussions path (used when `githubToken` is set) is covered by unit tests against a fixture only; it was not run live because this verification used no credentials.
- Unit tests: `npm test` (19 tests: repository parsing, all normalisers on real API fixtures, wiki notes and index rendering, rate-limit header parsing, 403/429 handling, `retry-after`, wait-for-reset, pagination, GraphQL rate-limit mapping).

### Support

Open an issue on the Actor's **Issues** tab. Issues are answered within one business day; bug fixes ship as new builds without changing the input schema.

Made by Vellum Labs.

# Actor input Schema

## `repos` (type: `array`):

Public repositories as `owner/name` or GitHub URLs (`https://github.com/apify/apify-sdk-js`).

## `types` (type: `array`):

Item types to export from each repository.

## `state` (type: `string`):

Filter issues, pull requests and discussions by state. Releases are not affected.

## `labels` (type: `array`):

Keep only items that carry **all** of these labels (exact names, e.g. `bug`). Not applied to releases.

## `sinceDays` (type: `integer`):

Only items updated (releases: published) within the last N days. `0` = no time limit.

## `maxItems` (type: `integer`):

Cap per repository **and** per type (e.g. 20 issues + 20 pulls + 20 releases). Each exported item is one `item-exported` billing event.

## `includeComments` (type: `boolean`):

Fetch comments for issues, pull requests and discussions (one extra API request per item that has comments; discussions with a token fetch them inline). Each stored comment is one `comment-exported` billing event. The sample input leaves this off so it fits the unauthenticated quota; turn it on together with a token.

## `maxCommentsPerItem` (type: `integer`):

Cap on stored comments per item (oldest first).

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

Personal access token (classic or fine-grained, no scopes needed for public data). Raises the API limit from 60 to 5,000 requests per hour and enables the documented GraphQL Discussions API. Stored encrypted.

## `waitForRateLimitReset` (type: `boolean`):

When the GitHub rate limit is exhausted, sleep until it resets (up to 60 minutes) and continue, instead of finishing with partial results.

## `outputFormat` (type: `string`):

`records` = one JSON record per item in the dataset. `wiki-notes` = the same records **plus** one Markdown file per item (YAML front matter: type, title, status, updated, source URL) and an `INDEX.md` in the key-value store, shaped like LLM-wiki source pages.

## Actor input object example

```json
{
  "repos": [
    "apify/apify-sdk-js"
  ],
  "types": [
    "issues",
    "pulls",
    "releases"
  ],
  "state": "all",
  "labels": [],
  "sinceDays": 0,
  "maxItems": 20,
  "includeComments": true,
  "maxCommentsPerItem": 50,
  "waitForRateLimitReset": false,
  "outputFormat": "wiki-notes"
}
```

# Actor output Schema

## `items` (type: `string`):

No description

## `notes` (type: `string`):

No description

## `index` (type: `string`):

No description

## `summary` (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 = {
    "repos": [
        "apify/apify-sdk-js"
    ],
    "types": [
        "issues",
        "pulls",
        "releases"
    ],
    "maxItems": 20,
    "includeComments": false,
    "outputFormat": "wiki-notes"
};

// Run the Actor and wait for it to finish
const run = await client.actor("vellumlabs/github-issues-exporter").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 = {
    "repos": ["apify/apify-sdk-js"],
    "types": [
        "issues",
        "pulls",
        "releases",
    ],
    "maxItems": 20,
    "includeComments": False,
    "outputFormat": "wiki-notes",
}

# Run the Actor and wait for it to finish
run = client.actor("vellumlabs/github-issues-exporter").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "repos": [
    "apify/apify-sdk-js"
  ],
  "types": [
    "issues",
    "pulls",
    "releases"
  ],
  "maxItems": 20,
  "includeComments": false,
  "outputFormat": "wiki-notes"
}' |
apify call vellumlabs/github-issues-exporter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vellumlabs/github-issues-exporter"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zhxreYTHd0ZaXzvuv/builds/9mwPNwgAfinGTn9n8/openapi.json
