Start

Token-Free GitHub Endpoints

Token-Free GitHub Endpoints

This is the canonical inventory of GitHub reads Octopool can make without a PAT or GitHub App installation token. All repository routes remain public-repository only and still pass Octopool's public-repo guard.

There are two different token-free transports:

  • Anonymous GitHub API: REST-shaped JSON from api.github.com, without an Authorization header. These consume GitHub's shared anonymous API quota and still pass Octopool's normal response sanitization.
  • No-API-quota sources: public github.com pages and Git smart HTTP endpoints. These do not consume GitHub API quota. Some return exact REST shapes; others are bounded shapes used only by supported top-level gh --json commands.

Cache hits reuse a stored body. Visibility, membership, and revalidation checks can still contact GitHub, so a cache hit is not proof of zero upstream requests.

#Selection rules

  • When a route has both transports, Octopool tries no-API-quota alternatives before the anonymous API, then a pooled PAT/App token where permitted.
  • Diff and patch media use public web endpoints directly.
  • A parser that cannot prove completeness or exactness returns no result. Octopool then tries the anonymous API in the same request cycle or falls through to the pooled identity.
  • Shaped page fallbacks require an internal x-octopool-public-shape header generated by supported top-level CLI commands. Raw gh api requests do not opt into these reduced page shapes.
  • Supported repo-scoped gh search issues|prs shapes stay token-free-only when pooled search is disabled: Octopool uses anonymous API and the shared cache, but never a pooled identity or the caller's local token. If anonymous API and bounded stale cache are unavailable, the read fails closed.
  • Only GET with the default JSON accept variants is eligible for anonymous API JSON fallback.

#No-API-quota mappings

#Diff and patch media

Relay requestPublic source
GET /repos/{owner}/{repo}/pulls/{number} with diff/patch Accepthttps://github.com/{owner}/{repo}/pull/{number}.diff or .patch
GET /repos/{owner}/{repo}/commits/{sha} with diff/patch Accepthttps://github.com/{owner}/{repo}/commit/{sha}.diff or .patch
GET /repos/{owner}/{repo}/compare/{comparison} with diff/patch Accepthttps://github.com/{owner}/{repo}/compare/{comparison}.diff or .patch

GitHub may redirect these to patch-diff.githubusercontent.com; Octopool permits only that known patch host.

#Git refs

Relay requestPublic sourceLimits
GET /repos/{owner}/{repo}/git/ref/heads/{branch}https://github.com/{owner}/{repo}.git/info/refs?service=git-upload-packExact branch ref
GET /repos/{owner}/{repo}/git/matching-refs/heads/{prefix}Same Git smart HTTP advertisementExact matching branch refs
GET /repos/{owner}/{repo}/git/ref/tags/{tag}Same Git smart HTTP advertisementAnnotated tags only
GET /repos/{owner}/{repo}/git/matching-refs/tags/{prefix}Same Git smart HTTP advertisementOnly when every matched tag is annotated

Git refs require an application/x-git-upload-pack-advertisement response and a complete bounded v0 envelope: the upload-pack service packet, its header flush, nonempty supported ref records, and a separate terminal flush at exact end of body. Packets use byte lengths and are limited to 65,520 bytes including the four-byte prefix. Truncation, reserved records, empty ref records, trailing bytes, and unsupported version/shallow/metadata forms fall back to the existing exact anonymous API. The streamed response cap still applies; Content-Length is not completion evidence. This is a conservative adapter subset, not a full Git protocol implementation.

Only after accepting the whole advertisement do Git ref responses read https://github.com/{owner}/{repo}/issues?q=is%3Aissue to recover the repository node ID needed for exact REST-compatible ref node IDs. Lightweight tags remain anonymous API-only because the advertisement cannot prove their target object type.

Git-ref JSON adapters accept missing, empty, and whitespace-only Accept as well as the supported JSON media types. Their cache representation generation covers these eligible blanks while preserving distinct blank-header keys.

#Bounded CLI shapes

These mappings are used when a supported CLI owner requests the documented public shape. Machine gh run list/view --json deliberately omits Actions shape headers and uses shared exact REST instead, including jobs and lazy workflow-name metadata. Human run output and watch still use the bounded Actions page shapes below. See the CLI export contract for native defaults, requested/returned attempt ownership, safe-integer limits and bounds.

Current shape IDs are pr-summary-v1, pr-files-v1, pr-list-v1, issue-summary-v1, issue-list-v1, label-list-v1, workflow-list-v1, workflow-view-v1, actions-summary-v1, and actions-jobs-v1. The release-summary-v1 wire shape uses the exact anonymous API as described below.

Relay requestPublic sourceShape/limits
GET /repos/{owner}/{repo}/pulls/{number}https://github.com/{owner}/{repo}/pull/{number}PR summary fields; no query
GET /repos/{owner}/{repo}/pullshttps://github.com/{owner}/{repo}/issues?q=is%3Apr...First page; complete embedded result required
GET /repos/{owner}/{repo}/issues/{number}https://github.com/{owner}/{repo}/issues/{number}Issue summary fields; no query
GET /repos/{owner}/{repo}/issueshttps://github.com/{owner}/{repo}/issues?q=is%3Aissue...First page; complete embedded result required
GET /repos/{owner}/{repo}/labelshttps://github.com/{owner}/{repo}/labelsFirst page; complete embedded label set required
GET /repos/{owner}/{repo}/actions/workflowshttps://github.com/{owner}/{repo}/actionsUp to 10 workflow pages
GET /repos/{owner}/{repo}/actions/workflows/{workflow}Same Actions workflow listLookup by workflow ID or YAML filename
GET /repos/{owner}/{repo}/actions/runshttps://github.com/{owner}/{repo}/actionsUp to 25 unfiltered runs; shared public-page superset
GET /repos/{owner}/{repo}/actions/workflows/{workflow}/runshttps://github.com/{owner}/{repo}/actions/workflows/{workflow}Up to 25 unfiltered runs; shared per-workflow public-page superset
GET /repos/{owner}/{repo}/actions/runs/{id}https://github.com/{owner}/{repo}/actions/runs/{id}Run summary; no query
GET /repos/{owner}/{repo}/actions/runs/{id}/attempts/{attempt}/jobshttps://github.com/{owner}/{repo}/actions/runs/{id}/job_groups_batch?attempt={attempt}, then each public job pageExact attempt; up to 25 job pages

Supported field sets:

  • PR view: number, title, state, url, createdAt, closedAt, mergedAt, headRefName, headRefOid, baseRefName.
  • PR files: path, additions, deletions, changeType, and originalPath; the pr-files-v1 shape uses exact anonymous API data plus a verified head discriminator, not a reduced public-page parser.
  • PR list: number, title, state, url, author, createdAt, updatedAt, closedAt, mergedAt, isDraft, labels.
  • Issue view: number, title, body, state, url, author, createdAt, updatedAt, labels.
  • Issue list: number, title, state, url, author, createdAt, updatedAt, closedAt, labels.
  • Labels and workflows: id, name, description, color, url for labels; id, name, path, state for workflows.
  • Actions summary shapes supply human/watch run metadata; their reconstructed names and timestamps are not native machine-export evidence.
  • Actions jobs shapes add bounded job and step metadata for human/watch output, not run JSON.

Workflow pagination uses https://github.com/{owner}/{repo}/actions/workflows_partial?query=&page={page}. Actions run enrichment may read a run page and https://github.com/{owner}/{repo}/commit/{sha}.patch.

#Public-repository proof

The guard normally checks GET https://api.github.com/repos/{owner}/{repo}. If that proof is rate-limited or unavailable, Octopool can inspect https://github.com/{owner}/{repo} for GitHub's public-repository marker. This proves visibility only; it does not provide a relay response. Token-free-only shaped search always uses this page-marker proof and never a configured verification token. Its issue-search-v1 CLI shape gates an exact first-page anonymous API request; it is not a reduced public-page response shape.

#Anonymous API routes

Every path below maps directly to GET https://api.github.com{path} without an Authorization header. Query parameters accepted by the corresponding relay route are preserved. Repository responses are cached only after the public-repo guard succeeds.

#Exact contents responses

Contents JSON reads, with or without an explicit ref, use the anonymous REST API. Octopool preserves GitHub's file, symlink, submodule, and directory responses instead of constructing file metadata from raw.githubusercontent.com bytes. GitHub may return a symlink target's contents or describe the symlink itself; only the REST endpoint knows which response is correct.

These cache misses consume anonymous API quota. If the anonymous API is unavailable, the existing public-repository guard, pooled API, and bounded stale-cache paths still apply. Explicit raw, HTML, and object media keep their existing exact API handling. The contents cache generation retires old reconstructed JSON responses; see cache keys.

#Exact release bodies

gh release view [tag] --json uses the exact anonymous API for both latest and tagged releases, including metadata-only projections. Its release-summary-v1 shape supports tagName, name, url, isDraft, isPrerelease, createdAt, publishedAt, and body. The decoded body string preserves the API's raw Markdown byte for byte, including headings, tight lists, reference links, code fences, whitespace, line endings, and an explicitly empty string. Cache reads preserve that same source string.

Rendered release HTML does not prove the original Markdown. Octopool does not reconstruct it or substitute a changelog. Release cache misses therefore consume anonymous API quota instead of using the public release page. If the API is unavailable, only an eligible exact cached response may be served through the existing bounded stale policy; otherwise the existing guarded local-gh fallback applies. Releases never use pooled credentials, and draft filtering remains in place.

#Public issue-event visibility

Issue timelines (/issues/{number}/timeline), per-issue events (/issues/{number}/events), repository issue events (/issues/events), and individual events (/issues/events/{id}) are anonymous-only, including caller conditional requests. GitHub's cross-references and closing-commit references depend on access to the source repository. Proving the target public or removing a nested repository object does not prove the enclosing issue/commit details public.

These routes preserve anonymous REST bodies and headers. Anonymous quota exhaustion or unavailability uses eligible public stale data or guarded native fallback; unsupported media also falls back locally. Old event representations are retired by the server's cache generation. Repository activity events use a different payload schema, and network events explicitly list public activity; neither is changed by this issue-reference restriction.

#Generated route catalog

<!-- token-free-api-routes:start -->

GET /users/{login}
GET /users/{login}/repos
GET /users/{login}/orgs
GET /users/{login}/gists
GET /users/{login}/followers
GET /users/{login}/following
GET /users/{login}/events
GET /users/{login}/received_events
GET /users/{login}/keys
GET /users/{login}/gpg_keys
GET /orgs/{org}/repos
GET /orgs/{org}/events
GET /orgs/{org}/public_members
GET /orgs/{org}/public_members/{login}
GET /gists/{gist}
GET /emojis
GET /meta
GET /licenses
GET /licenses/{slug}
GET /gitignore/templates
GET /gitignore/templates/{template}
GET /repos/{owner}/{repo}
GET /repos/{owner}/{repo}/commits
GET /repos/{owner}/{repo}/commits/{sha}
GET /repos/{owner}/{repo}/commits/{ref}
GET /repos/{owner}/{repo}/commits/{sha}/comments
GET /repos/{owner}/{repo}/commits/{sha}/pulls
GET /repos/{owner}/{repo}/commits/{sha}/branches-where-head
GET /repos/{owner}/{repo}/commits/{sha}/statuses
GET /repos/{owner}/{repo}/commits/{ref}/statuses
GET /repos/{owner}/{repo}/comments/{id}
GET /repos/{owner}/{repo}/compare/{comparison}
GET /repos/{owner}/{repo}/contents/{path}
GET /repos/{owner}/{repo}/readme
GET /repos/{owner}/{repo}/readme/{dir}
GET /repos/{owner}/{repo}/pulls/{number}
GET /repos/{owner}/{repo}/pulls
GET /repos/{owner}/{repo}/pulls/{number}/files
GET /repos/{owner}/{repo}/pulls/{number}/commits
GET /repos/{owner}/{repo}/pulls/{number}/comments
GET /repos/{owner}/{repo}/pulls/comments
GET /repos/{owner}/{repo}/pulls/comments/{id}
GET /repos/{owner}/{repo}/pulls/comments/{id}/reactions
GET /repos/{owner}/{repo}/pulls/{number}/reviews
GET /repos/{owner}/{repo}/pulls/{number}/reviews/{id}
GET /repos/{owner}/{repo}/pulls/{number}/reviews/{id}/comments
GET /repos/{owner}/{repo}/pulls/{number}/requested_reviewers
GET /repos/{owner}/{repo}/commits/{sha}/check-runs
GET /repos/{owner}/{repo}/commits/{ref}/check-runs
GET /repos/{owner}/{repo}/commits/{sha}/check-suites
GET /repos/{owner}/{repo}/commits/{ref}/check-suites
GET /repos/{owner}/{repo}/commits/{sha}/status
GET /repos/{owner}/{repo}/commits/{ref}/status
GET /repos/{owner}/{repo}/statuses/{sha}
GET /repos/{owner}/{repo}/actions/runs
GET /repos/{owner}/{repo}/actions/runs/{id}
GET /repos/{owner}/{repo}/actions/runs/{id}/attempts/{attempt}
GET /repos/{owner}/{repo}/actions/runs/{id}/jobs
GET /repos/{owner}/{repo}/actions/runs/{id}/attempts/{attempt}/jobs
GET /repos/{owner}/{repo}/actions/runs/{id}/artifacts
GET /repos/{owner}/{repo}/actions/jobs/{id}
GET /repos/{owner}/{repo}/check-runs/{id}/annotations
GET /repos/{owner}/{repo}/issues/{number}
GET /repos/{owner}/{repo}/issues
GET /repos/{owner}/{repo}/issues/{number}/comments
GET /repos/{owner}/{repo}/issues/comments
GET /repos/{owner}/{repo}/issues/comments/{id}
GET /repos/{owner}/{repo}/issues/comments/{id}/reactions
GET /repos/{owner}/{repo}/issues/{number}/events
GET /repos/{owner}/{repo}/issues/events
GET /repos/{owner}/{repo}/issues/events/{id}
GET /repos/{owner}/{repo}/issues/{number}/labels
GET /repos/{owner}/{repo}/issues/{number}/reactions
GET /repos/{owner}/{repo}/issues/{number}/timeline
GET /repos/{owner}/{repo}/assignees
GET /repos/{owner}/{repo}/assignees/{login}
GET /repos/{owner}/{repo}/labels
GET /repos/{owner}/{repo}/labels/{label}
GET /repos/{owner}/{repo}/milestones
GET /repos/{owner}/{repo}/milestones/{id}
GET /repos/{owner}/{repo}/branches
GET /repos/{owner}/{repo}/branches/{branch}
GET /repos/{owner}/{repo}/tags
GET /repos/{owner}/{repo}/languages
GET /repos/{owner}/{repo}/contributors
GET /repos/{owner}/{repo}/license
GET /repos/{owner}/{repo}/topics
GET /repos/{owner}/{repo}/community/profile
GET /repos/{owner}/{repo}/forks
GET /repos/{owner}/{repo}/stargazers
GET /repos/{owner}/{repo}/subscribers
GET /repos/{owner}/{repo}/deployments
GET /repos/{owner}/{repo}/events
GET /networks/{owner}/{repo}/events
GET /repos/{owner}/{repo}/stats/contributors
GET /repos/{owner}/{repo}/stats/commit_activity
GET /repos/{owner}/{repo}/stats/code_frequency
GET /repos/{owner}/{repo}/stats/participation
GET /repos/{owner}/{repo}/stats/punch_card
GET /repos/{owner}/{repo}/git/blobs/{sha}
GET /repos/{owner}/{repo}/git/commits/{sha}
GET /repos/{owner}/{repo}/git/tags/{sha}
GET /repos/{owner}/{repo}/git/trees/{sha}
GET /repos/{owner}/{repo}/git/ref/{ref}
GET /repos/{owner}/{repo}/git/matching-refs/{ref}
GET /repos/{owner}/{repo}/actions/workflows
GET /repos/{owner}/{repo}/actions/workflows/{workflow}
GET /repos/{owner}/{repo}/actions/workflows/{workflow}/runs
GET /repos/{owner}/{repo}/releases
GET /repos/{owner}/{repo}/releases/latest
GET /repos/{owner}/{repo}/releases/tags/{tag}
GET /repos/{owner}/{repo}/releases/{id}
GET /repos/{owner}/{repo}/releases/{id}/assets
GET /repos/{owner}/{repo}/releases/assets/{id}
GET /search/issues
GET /search/commits
GET /search/repositories

<!-- token-free-api-routes:end -->

Actions job logs are deliberately absent: log downloads require authenticated GitHub and follow signed redirects. Release list/latest/tag/id reads remove drafts from anonymous responses; asset routes use the exact anonymous API response. Search requires pool policy allow_search: true and the relay's scoped query validation; GET /search/code is intentionally not token-free.

#Explicit exclusions

These supported relay routes are not token-free:

  • Actions job logs.
  • GitHub code search.
  • GET /rate_limit.
  • Private repository reads.
  • Any mutation or non-GET request.
  • Any route or media type not listed above.

GitHub can still rate-limit, change, or remove public HTML. Every page parser therefore fails closed and preserves the normal anonymous or pooled API fallback.