Sync active GitHub Enterprise or Organization members into Snipe-IT as license seat assignments.
Supports both GitHub Enterprise (cloud) and standalone GitHub Organizations. Auth via a Personal Access Token (PAT). Runs fully headless — suitable for cron or CI.
Part of the *2snipe integration family, inspired by CampusTech's Snipe-IT integrations.
On each sync run, github2snipe:
- Fetches all active members from the configured GitHub Enterprise or Organization.
- Optionally includes outside collaborators and pending invitations (org mode).
- Resolves each member's email using a priority chain: SAML/SCIM identity from the org's SSO provider → org verified domain email (visible to org admins) → public GitHub profile email. SAML/SCIM always wins when present, even if a public profile email also exists — the IdP identity is the authoritative match for Snipe-IT records.
- Finds or creates a matching Snipe-IT license record.
- Checks out seats for active members; checks in seats for members who have left.
- Writes member role and type into each seat's notes field.
Member role (owner, admin, member) and type (direct member, outside collaborator, pending invitation) are recorded in Snipe-IT seat notes on checkout and updated automatically when they change.
- Go 1.22+ (to build from source)
- A GitHub Personal Access Token — required scope depends on your GitHub Enterprise type:
- Enterprise mode (EMU tenants):
admin:enterprisescope; PAT owner must be an Enterprise Owner - Enterprise mode (traditional GHEC):
read:orgscope; setgithub.organizationsin config - Organization mode:
read:orgscope
- Enterprise mode (EMU tenants):
- A Snipe-IT instance with an API key that has license management permissions
- GitHub users must be resolvable by email: public GitHub profile email, SAML/SCIM identity, or org verified domain email. All three GraphQL lookups require the PAT owner to be an org admin; without admin access only public profile emails are used.
Download a pre-built binary from the latest release:
# macOS (Apple Silicon)
curl -L https://github.com/jackvaughanjr/github2snipe/releases/latest/download/github2snipe-darwin-arm64 -o github2snipe
chmod +x github2snipe
# Linux (amd64)
curl -L https://github.com/jackvaughanjr/github2snipe/releases/latest/download/github2snipe-linux-amd64 -o github2snipe
chmod +x github2snipe
# Linux (arm64)
curl -L https://github.com/jackvaughanjr/github2snipe/releases/latest/download/github2snipe-linux-arm64 -o github2snipe
chmod +x github2snipe
Or build from source:
git clone https://github.com/jackvaughanjr/github2snipe
cd github2snipe
go build -o github2snipe .
Copy settings.example.yaml to settings.yaml and fill in your values:
cp settings.example.yaml settings.yamlsettings.yaml is gitignored and must never be committed. See settings.example.yaml
for all available options with inline documentation.
github:
mode: "enterprise"
enterprise: "your-enterprise-slug"
token: "" # or set GITHUB_TOKEN env var; requires admin:enterprise scope
snipe_it:
url: "https://your-snipe-it-instance.example.com"
api_key: ""
license_category_id: 0 # requiredgithub:
mode: "enterprise"
enterprise: "your-enterprise-slug"
organizations:
- "your-org-slug"
# - "another-org-slug"
token: "" # or set GITHUB_TOKEN env var; requires read:org scope
snipe_it:
url: "https://your-snipe-it-instance.example.com"
api_key: ""
license_category_id: 0 # requiredgithub:
mode: "organization"
organization: "your-org-name"
token: "" # or set GITHUB_TOKEN env var
snipe_it:
url: "https://your-snipe-it-instance.example.com"
api_key: ""
license_category_id: 0 # required| Variable | Config key |
|---|---|
GITHUB_TOKEN |
github.token |
SNIPE_URL |
snipe_it.url |
SNIPE_TOKEN |
snipe_it.api_key |
SLACK_WEBHOOK |
slack.webhook_url |
./github2snipe testReports the GitHub member count (by role) and current Snipe-IT license state without making any changes.
./github2snipe sync./github2snipe sync --dry-run./github2snipe sync --email user@example.com./github2snipe sync --create-users./github2snipe sync --force./github2snipe sync --no-slack| Flag | Description |
|---|---|
--config FILE |
Path to config file (default: settings.yaml) |
-v, --verbose |
INFO-level logging |
-d, --debug |
DEBUG-level logging |
--log-file FILE |
Append logs to a file |
--log-format |
text (default) or json |
--version |
Print version and exit |
The Snipe-IT license name defaults to:
"GitHub Enterprise"— whenmode: enterprise"GitHub"— whenmode: organization
Use github.license_name_prefix and github.license_name_suffix to distinguish
multiple GitHub tenants:
github:
license_name_prefix: "Acme - "
# Result: "Acme - GitHub Enterprise"In enterprise mode the sync automatically fetches the total purchased seat count
from GitHub via the consumed-licenses API and sets that on the Snipe-IT license.
This requires the PAT to have read:enterprise or admin:enterprise scope.
If auto-fetch is not available (org mode, or PAT lacks the required scope), set
snipe_it.license_seats as a manual override:
snipe_it:
license_seats: 34 # total purchased GitHub Enterprise licensesThe sync never shrinks seats. If the resolved seat count falls below the active member count, the active member count is used as the floor.
Each Snipe-IT seat checkout includes notes identifying the GitHub tenant and the member's role or type:
| Member type | Example notes |
|---|---|
| Enterprise member | enterprise: acme-corprole: membergithub_login: agilemofo |
| Enterprise owner | enterprise: acme-corprole: ownergithub_login: agilemofo |
| Org member | organization: acme-corprole: membergithub_login: agilemofo |
| Org admin | organization: acme-corprole: admingithub_login: agilemofo |
| Outside collaborator | organization: acme-corptype: outside_collaboratorgithub_login: agilemofo |
| Pending (by login) | organization: acme-corpstatus: pending_invitationgithub_login: agilemofo |
| Pending (email-only) | organization: acme-corpstatus: pending_invitation |
Set the following in settings.yaml to include users who consume seats beyond
direct org members:
github:
include_outside_collaborators: true # users with repo access but not org membership
include_pending_invitations: true # users with pending org membership invitationsBoth are disabled by default. Supported in:
- Organization mode — queries the configured org directly.
- Enterprise mode with
github.organizationsset — queries each configured org and deduplicates across them.
Not supported in EMU enterprise mode (where github.organizations is empty),
because outside collaborators and pending invitations are org-level concepts not
exposed by the enterprise members API.
- Private emails: GitHub users with private email settings cannot be matched via their public profile. The sync resolves email through a three-tier fallback: (1) SAML NameID or SCIM username from the org's identity provider — typically the company-managed email for orgs using Okta, Azure AD, or Google Workspace; (2) org verified domain email, visible to org admins for members who joined before SSO was enforced; (3) public GitHub profile email. All three GraphQL lookups require the PAT owner to be an org admin. Users who cannot be resolved by any method are warned and skipped. None of these GraphQL lookups are available in EMU enterprise mode.
- EMU vs traditional GHEC: The enterprise members API (
GET /enterprises/{slug}/members) only works for EMU (Enterprise Managed Users) tenants. Traditional GitHub Enterprise Cloud accounts must setgithub.organizationsto enumerate members via org APIs. Using the wrong path results in a 404 from GitHub (not 403 — this is GitHub's intentional security behaviour). - SAML SSO enforcement: If your organization enforces SAML SSO, the PAT must be explicitly SSO-authorized for each org in addition to having the correct scope. Go to github.com/settings/tokens → click your token → "Configure SSO" → "Authorize" next to the org. This must be repeated each time a new PAT is created.
- Rate limits: The sync makes one additional API call per member to resolve email addresses. For large enterprises (1,000+ members), allow a few minutes for the enrichment phase before Snipe-IT writes begin.
A Dockerfile is included for containerized deployments. To build locally:
docker build -t github2snipe:latest .For automated scheduling via Cloud Run Jobs, use snipemgr — it handles image publishing, secret storage, and scheduling. See the snipemgr README for complete GCP setup instructions.
| Version | Key changes |
|---|---|
| v1.6.0 | Make Snipe-IT API rate limit configurable via sync.rate_limit_ms and SNIPE_RATE_LIMIT_MS env var |
| v1.5.0 | Fixed seat state tracking; auto license seat count from GitHub API |
| v1.4.0 | Improved email resolution — SAML identity takes priority; SCIM and verified domain fallbacks added |
| v1.3.0 | Added --no-slack flag; SAML SSO email fallback for member enumeration |
| v1.2.1 | Documented EMU vs traditional GHEC split and syncer bug fix in CONTEXT.md |
| v1.2.0 | Support traditional GHEC orgs via github.organizations (in addition to EMU) |
| v1.1.0 | Detect SAML SSO 403 with actionable remediation instructions |
| v1.0.3 | Fixed enterprise PAT scope (admin:enterprise required, not read:enterprise) |
| v1.0.2 | Improved enterprise 404 error message explaining the owner slug requirement |
| v1.0.1 | Added github_login to seat notes |
| v1.0.0 | Initial scaffold — GitHub Enterprise Cloud → Snipe-IT license seat sync |