A row-based Keboola extractor for the GitHub REST API
(https://api.github.com, version 2022-11-28). Each configuration row defines one
extraction — a single GitHub endpoint applied to a single target (the authenticated user, an
organization, or an owner/repo) — and produces one output table. The component is a thin
orchestrator over a declarative endpoint registry, so 33 endpoints share one code path.
Table of Contents:
[TOC]
| Feature | Description |
|---|---|
| Row-Based Configuration | One row = one endpoint + target; its own state & schedule. |
| PAT & OAuth auth | Personal Access Token or broker OAuth — one HTTP path. |
| Incremental Loading | since cursor or sort-by-updated with per-row watermark. |
| Native data types | Typed output (INTEGER/TIMESTAMP/BOOLEAN/STRING). |
| Rate-limit handling | Bounded wait-and-retry on the 5000 req/hr core budget. |
| Sync-action dropdowns | Org / repository / team pickers; connection test. |
| GitHub Enterprise | Optional api_base_url targets a GHE.com / GHES host. |
The root config field auth_type chooses the credential source. Both reach GitHub as
Authorization: Bearer <token>.
Set auth_type = "pat" and paste a token into the encrypted #github_token field. Mint a
classic (ghp_…) or fine-grained (github_pat_…) PAT in the GitHub UI with at least the
read:org and repo scopes (repo covers private-repo reads; read:org covers
org/team/member data). For SAML-SSO organizations the PAT must be SSO-authorized, or
org/repo endpoints return 403/404.
The optional root field api_base_url points the component at a GitHub Enterprise host. Leave
it empty (the default) for github.com — every existing configuration keeps that behaviour.
| Flavour | api_base_url |
GraphQL endpoint used |
|---|---|---|
| github.com (default) | (empty) | https://api.github.com/graphql |
| Enterprise Cloud with data residency | https://api.SUBDOMAIN.ghe.com |
<base>/graphql |
| Enterprise Server (self-hosted) | https://HOSTNAME/api/v3 |
https://HOSTNAME/api/graphql |
Enterprise Server is the reason GraphQL is derived rather than appended: its REST root is
/api/v3 but its GraphQL endpoint is /api/graphql (see GitHubClient._derive_graphql_url).
Enterprise hosts require auth_type = "pat" with a token created on that host. Keboola's
OAuth broker holds one consumer per component id, registered against github.com, so OAuth can
only ever return a github.com token; the config model rejects oauth + api_base_url with that
reason instead of letting it fail later as an opaque 401. If the Enterprise tenant restricts
access by IP address, the Keboola stack's outbound IPs must be allowed.
Set auth_type = "oauth". The Keboola OAuth broker injects
authorization.oauth_api.credentials at runtime; the component parses the provider's raw
token response from #data (json.loads(#data)["access_token"]). The recommended broker
registration is a standard OAuth App with token expiration disabled (non-expiring gho_…
token, no refresh logic). Read-only scopes: read:org, repo, read:user, user:email.
Production OAuth requires Keboola support to register the GitHub OAuth App in the broker (not
self-serve). Until that lands, the OAuth code path is exercised without the live broker by
injecting the credentials directly via the Storage configuration API. The #-prefixed fields
encrypt/decrypt normally, so the runtime resolver runs exactly as it would in production:
{
"authorization": {
"oauth_api": {
"credentials": {
"appKey": "<client_id>",
"#appSecret": "<client_secret>",
"#data": "{\"access_token\":\"gho_xxx\",\"token_type\":\"bearer\",\"scope\":\"repo,read:org\"}"
}
}
}
}The component only ever reads #data and pulls access_token out of it — it never drives
the OAuth dance itself.
33 endpoints across three target groups, each producing one table named after the registry
key (e.g. repository_issues, organization_members, repository_languages):
- Authenticated user —
user,user_repos,user_orgs. - Organizations —
organizations,organization_members,organization_teams,team_members,organization_repositories,organization_events,organization_outside_collaborators. - Repositories —
repository,repository_branches,repository_tags,repository_commits,repository_contributors,repository_collaborators,repository_languages,repository_topics,repository_releases,repository_issues,repository_issue_comments,repository_pulls,repository_pull_reviews,repository_pull_review_comments,repository_labels,repository_milestones,repository_stargazers,repository_subscribers,repository_forks,repository_workflows,repository_workflow_runs,repository_deployments,repository_webhooks.
If you need additional endpoints, please submit your request to ideas.keboola.com.
In addition to the REST endpoints above, you can write custom GraphQL queries to fetch any GitHub data
not covered by the preset endpoints. Select GraphQL Query as the Data Source and choose a
Query Template or write a Custom query. Output is one flattened all-STRING table; nested
objects become parent_child columns and nested lists become JSON cells.
- Query Templates — built-in queries you can pick instead of writing GraphQL:
- Org repos: AI config files — scans every repository in an organization for
AGENTS.mdandCLAUDE.mdfiles, reporting whether they exist and their content. One row per repository. - Repository: Open PRs with review decision — fetches open pull requests of a repository,
including the GraphQL-only
reviewDecisionfield. - Org: Pull Requests + reviews + comments — all PRs in an organization (within the Since
window) with reviews and review-thread comments — a budget-friendly replacement for the
repository_pulls/_pull_reviews/_pull_review_commentsREST data sources. - Org: Repository languages — language breakdown per repo in an organization (replaces
repository_languages). - Org: Recent commits — recent commit history per repo in an organization within the Since
window (replaces
repository_commits; up to 100 commits per repo per page).
- Org repos: AI config files — scans every repository in an organization for
- Custom — paste a GraphQL query (and optional JSON variables), name an output table, and optionally
set Row path (dotted path to the data list, e.g.
organization.repositories.nodes) if auto-detection fails. For pagination, selectpageInfo { hasNextPage endCursor }on the connection and use a$cursorvariable withafter: $cursor. - Primary key — comma-separated column(s) for incremental upsert (empty = full load each run).
- Rolling window — set Since/Until (relative phrases like
60 days agoare supported) and write{{since}}/{{until}}in the query or variables; the component substitutes the resolved date (YYYY-MM-DD) at runtime, so a window likeupdated:>={{since}}rolls automatically each run. - Null nested objects — when a probed file is absent, the API returns
nullfor that object; the flattener collapses it to a single empty column named after the file (e.g.agents_md) rather than emitting the full set ofagents_md_*sub-columns.
Row fields appear conditionally based on the chosen endpoint:
- endpoint — the data source (selects the registry entry).
- organization / repository / team_slug — the target (async dropdowns populated by
sync actions).
repositoryisowner/repo. - pull_number — required only for
repository_pull_reviews. - load_type —
full_loadorincremental_load(default incremental). - initial_since — first-run lower bound (ISO-8601, e.g.
2024-01-01T00:00:00Z) for cursor-based endpoints. - issue_state / pull_state / milestone_state —
all/open/closedfilters.
State stores a single last_run (ISO-8601 UTC) captured before the fetch and written only on
success. Endpoints with a server-side since filter (issues, commits, issue comments, PR
review comments) send it directly; pulls / *_repos sort by updated descending and stop
paging once a record predates the watermark. First run uses initial_since if set, otherwise
fetches full history. Incremental output upserts on the table's primary key.
GitHub's /issues endpoint returns pull requests mixed in (every PR is also an issue). The
repository_issues extraction drops rows carrying a pull_request object, so the issues
table contains only true issues. Pull requests are covered by the dedicated repository_pulls
extraction.
One table per row, written headerless with an authoritative schema manifest (native types).
Default bucket is on, so tables land in in.c-keboola-ex-github-v2-<configId>; do not set a
hardcoded destination (the default bucket silently overrides it).
Run locally with uv:
uv sync
uv run ruff check .
uv run pytest
For details about deployment and integration with Keboola, refer to the deployment section of the developer documentation.