GitHub API with Octokit skill for Codex

Paginate to the end, stay under every rate limit. Codex writes GitHub API scripts that choose the narrowest token, paginate every list instead of stopping at 30 items, and pick REST or GraphQL by data shape. It configures Octokit throttling for primary and secondary limits, uses ETags for cheap polling, pins the API version header, and runs bulk edits as a dry run you approve before any issue or branch changes.

  • Integrations
  • $github-api
  • 182 lines
  • 2 files
  • ~1,950 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- github-api

Installs to ~/.agents/skills/github-api/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $github-api, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Calls the GitHub REST and GraphQL APIs from scripts and services with Octokit, gh api or plain HTTP - token selection, API version headers, pagination, primary and secondary rate limits, conditional requests, retries, search limits and safe bulk writes. Use when automating issues, pull requests, releases or repository settings, exporting data across many repos, hitting 403 or 429 rate limit errors, choosing between REST and GraphQL, or writing a GitHub integration script in Node or Python.

Use it when

  • Automating issues, pull requests, releases or repo settings with code
  • Collecting data across many repositories or an organisation
  • Scripts hit 403 or 429 rate limit responses
  • Deciding between REST, GraphQL and gh api for a task

Use something else when

How to check the result

Item counts for one repo match the GitHub UI, the job ends with rate limit headroom logged, and bulk changes were applied only after a confirmed dry run.

The mistake it prevents

Reading only the first page of results, since GitHub returns 30 items by default and silently omits the rest unless you paginate.

~/.agents/skills/github-api/SKILL.md

--- name: github-api description: Calls the GitHub REST and GraphQL APIs from scripts and services with Octokit, gh api or plain HTTP - token selection, API version headers,… license: MIT ---

Done well means: the script uses the narrowest token that works, paginates to the end instead of stopping at 30 items, stays under primary and secondary rate limits by design, retries only what is safe to retry, and runs every bulk write as a dry run first, with the user confirming before anything is created, closed, merged or deleted.

Before writing code

  1. Read AGENTS.md and check for an existing client (octokit, @octokit/rest, PyGithub, gh scripts). Reuse its auth and retry setup.
  2. Pick the token:
ContextToken
One-off script on your machinegh auth token or a fine-grained PAT scoped to the repos needed
GitHub Actions in the same repoGITHUB_TOKEN with permissions: set in the workflow
Service acting for an organisation, many reposGitHub App installation token: use $github-apps
Acting as a specific user in your web appOAuth or GitHub App user access token

Prefer fine-grained PATs over classic ones. Never print tokens or put them in URLs. 3. Decide REST or GraphQL:

NeedUse
Single resource reads and writes, webhooks-style objects, simple listsREST
Nested data across many objects in one round trip (PRs with reviews and checks)GraphQL
Projects (v2), discussionsGraphQL (REST coverage is partial)
Quick ad hoc query from a shellgh api
  1. Writes to repositories the user does not own, mass edits, and anything destructive (closing issues in bulk, deleting branches or releases, changing settings) need explicit confirmation with a dry-run list shown first.

API calls need network access; in a Codex sandbox, ask for approval. gh reuses the user’s login, so confirm which account it is (gh auth status) before writes.

Octokit setup (Node)

import { Octokit } from "octokit";          // bundles REST, GraphQL, pagination, retry and throttling

const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN,
  userAgent: "acme-repo-audit/1.0",
  throttle: {
    onRateLimit: (retryAfter, options, o, retryCount) => {
      o.log.warn(`Rate limit on ${options.method} ${options.url}, retry in ${retryAfter}s`);
      return retryCount < 2;               // retry twice, then fail
    },
    onSecondaryRateLimit: (retryAfter, options, o) => {
      o.log.warn(`Secondary limit on ${options.method} ${options.url}`);
      return true;                          // the plugin waits retryAfter before retrying
    },
  },
});

Pin the REST version with the X-GitHub-Api-Version header. Requests without it use 2022-11-28; 2026-03-10 is the first calendar version with breaking changes. Upgrade deliberately and re-test.

Pagination

// All open issues across pages (default page size is 30; max per_page is 100)
const issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
  owner, repo, state: "open", per_page: 100,
});

// Stream large result sets without holding everything in memory
for await (const { data: repos } of octokit.paginate.iterator(octokit.rest.repos.listForOrg, { org, per_page: 100 })) {
  for (const r of repos) await audit(r);
}
gh api --paginate "repos/OWNER/REPO/pulls?state=closed&per_page=100" --jq '.[] | [.number, .merged_at] | @tsv'
gh api graphql --paginate -f query='
  query($endCursor: String) { repository(owner: "OWNER", name: "REPO") {
    pullRequests(first: 100, after: $endCursor, states: MERGED) {
      nodes { number title mergedAt } pageInfo { hasNextPage endCursor } } } }'
  • listForRepo on issues also returns pull requests; filter out items with pull_request set.
  • GraphQL: first/last max 100; paginate with pageInfo { hasNextPage endCursor } or octokit.graphql.paginate. Nested connections each need their own pagination.
  • Search API returns at most 1,000 results per query; split by date ranges (created:2025-01-01..2025-03-31) to cover more.

Rate limits

LimitValue (github.com)
REST primary, user token or PAT5,000 requests per hour
GITHUB_TOKEN in Actions1,000 requests per hour per repository
Unauthenticated60 per hour per IP
GraphQL primary5,000 points per hour (cost depends on query shape)
Search30 requests per minute (code search lower)
SecondaryNo more than 100 concurrent requests; per-minute point caps; content-creating requests about 80 per minute and 500 per hour
  • Read x-ratelimit-remaining and x-ratelimit-reset (REST) or query rateLimit { cost remaining resetAt } (GraphQL) and slow down before hitting zero.
  • Secondary limits return 403 or 429, sometimes with retry-after. Wait at least that long; without the header, wait a minute and back off exponentially.
  • Make write-heavy jobs serial with a small delay (about one second between mutations).
  • Conditional requests: send If-None-Match with the stored ETag; a 304 response does not count against the primary limit. Use this for polling.

Safe bulk writes

const targets = (await octokit.paginate(octokit.rest.issues.listForRepo, { owner, repo, state: "open", labels: "stale", per_page: 100 }))
  .filter((i) => !i.pull_request);
console.table(targets.map((i) => ({ number: i.number, title: i.title, updated: i.updated_at })));
if (!process.argv.includes("--apply")) process.exit(0);      // dry run by default

for (const i of targets) {
  await octokit.rest.issues.update({ owner, repo, issue_number: i.number, state: "closed", state_reason: "not_planned" });
  await new Promise((r) => setTimeout(r, 1000));
}

Show the dry-run table to the user and run with --apply only after they confirm. Log each change so it can be reversed.

Python

Use httpx or requests with a session, or PyGithub for object-style access:

import httpx, os
s = httpx.Client(base_url="https://api.github.com", headers={
    "Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
    "Accept": "application/vnd.github+json",
    "X-GitHub-Api-Version": "2022-11-28",
})

def all_issues(owner: str, repo: str):
    url = f"/repos/{owner}/{repo}/issues?state=all&per_page=100"
    while url:
        r = s.get(url)
        r.raise_for_status()
        yield from r.json()
        url = r.links.get("next", {}).get("url")   # follow the Link header, never build page numbers

Gotchas

  • 404 on a private repo usually means the token lacks access, not that the repo is missing.
  • Fine-grained PATs need explicit repository selection and per-permission grants; organisation owners may need to approve them.
  • Creating content in parallel (issues, comments, PRs) trips secondary limits fast. Serialise.
  • Timestamps are ISO 8601 UTC; since filters use updated_at, not created_at.
  • Large diffs and files: the contents API caps file size; use the Git Data API (blobs) or Accept: application/vnd.github.raw for big files.
  • GitHub Enterprise Server: base URL is https://HOST/api/v3 (GraphQL at /api/graphql) and available endpoints follow the server version.

Verification

  • Compare item counts with the web UI or gh for one repo before running across many.
  • Log remaining rate limit at start and end; the job should finish with headroom.
  • Run bulk jobs against a test repository or with the dry-run flag first.
  • For GraphQL, check rateLimit.cost of the main query; split it if a single call costs more than a few dozen points.

Report template

**Goal:** close issues labelled stale with no activity for 90 days in acme/api
**Auth:** fine-grained PAT, issues:write on acme/api only
**Method:** REST issues.listForRepo (paginated, 412 items) -> filter -> dry run (37 matches)
**Applied:** 37 issues closed as not_planned after user confirmation; log in out/closed.csv
**Rate limit:** 4,563 remaining at end; no secondary limit hits

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "GitHub API with Octokit"
  short_description: "Paginate to the end, stay under every rate limit"
  default_prompt: "$github-api Export every merged PR from our org's repos in 2025 to a CSV"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc