Generate AltStore
apps.json source files from GitHub Releases IPA assets.
Static app metadata (name, bundle identifier, icon and screenshot URLs, descriptions…) comes from a TOML config; everything dynamic (version, build version, release date, download URL, file size, release notes) is read live from the GitHub Releases API.
GitHub Releases API ──┐
├──► altgen ──► apps.json
app config TOML ──────┘
pip install altgen
altgen --versionRequires Python ≥ 3.10.
One TOML config = one app = one apps.json:
altgen -c piliplus.toml # writes apps.json next to the config
altgen -c piliplus.toml -o out/piliplus.json # override output pathOr skip the config file entirely for a quick single-app source (any CLI flag overrides its TOML counterpart):
altgen --repo owner/App --app-name App --bundle-id com.owner.app -o apps.jsonHosting many sources is just many configs — loop over them or use a CI
matrix, one altgen -c <config> per app.
Combine several apps.json files into one source (e.g. host many apps under a single source while keeping one config per app):
altgen merge a.json b.json -o merged.json
altgen merge -c merge.toml a.json b.json # root values + output from TOML
altgen merge --name MySource --tint-color "#00AEEF" a.json b.jsonappsare taken from the inputs and sorted by name (case-insensitive);newsentries from all inputs (rootnewsplus each app'snews) are combined and sorted newest-first.- Root values (
name,subtitle,description,icon_url,website,tint_color) come from the CLI flags or the config's[source]table — same rules as build mode, and--name(or[source] name) is required. - Merge configs only support
[source]and[output]tables; build-mode tables ([github],[app], …) are rejected. - A duplicate
bundleIdentifieror duplicate newsidentifieracross inputs is an error (exit 2).
See examples/merge.toml for a full merge config.
Unauthenticated requests are limited to 60/hour; a token raises that to
5,000/hour. Precedence: --token > GITHUB_TOKEN env var > [github] token.
Only [github] repo, [app] name, and [app] bundle_identifier are
required. Keys are snake_case in TOML and become the AltStore camelCase
JSON keys (bundle_identifier → bundleIdentifier, icon_url →
iconURL, min_os_version → minOSVersion, …). Unknown keys are
rejected with an error.
See examples/piliplus.toml for a full example.
[github]
repo = "owner/App" # REQUIRED: GitHub repo with releases
token = "" # optional (see above)
[source] # the source this apps.json describes
name = "App" # defaults to the repo name
subtitle = ""
description = ""
icon_url = "" # omitted from JSON when unset
website = ""
tint_color = "#00AEEF" # must be #RRGGBB
[app] # the app inside the source
name = "App" # REQUIRED
bundle_identifier = "com.x.y" # REQUIRED
developer_name = "" # defaults to the repo owner (CLI mode)
subtitle = ""
description = "" # fallback when a release body is empty
icon_url = "" # falls back to [source].icon_url
screenshots = ["https://…"]
tint_color = "" # falls back to [source].tint_color
min_os_version = "14.0" # omitted from versions when unset
[versions]
strip_v_prefix = true # tag "v1.2.3" → version "1.2.3"
include_prereleases = false # drafts are always skipped
asset_pattern = "\\.ipa$" # regex, case-insensitive search on asset name
build_version_pattern = "\\+(\\d+)\\.ipa$" # group 1 = buildVersion; no match → key omitted
# version_pattern = "…" # optional: extract version with this regex instead of the tag
# (for projects whose tags lack a version); no match → tag-derived version
# version_source = "release" # where version_pattern runs: "release" (default, release name) or "filename" (IPA asset name)
max_versions = 1 # default: newest version only; 0 = all versions
[news]
enabled = true
title_template = "{name} {version} - {date}" # default; placeholders: {name} {version} {tag} {date} (e.g. 07 Aug 2026)
caption_template = "" # optional; default: "{name} {version} is available."
image_url = "" # optional; omitted from JSON when unset
# news appID = [app] bundle_identifier
max_entries = 0 # 0 = unlimited; caps after sorting; news already limited to versions kept by max_versions
[output]
path = "apps.json" # resolved against THIS file's directory- Versions are sorted newest-first by
(date, version); one version entry per matching release asset. A release with several IPAs produces several entries — sharing one version by default, or one version each whenversion_source = "filename". - The version is the release tag by default (
strip_v_prefixstrips a leadingv). For projects whose tags carry no version (e.g.youproextra-ipa2), setversion_patternto extract it from the release name or IPA filename instead; on no match the version falls back to the tag-derived value. - By default only the newest version is emitted (
max_versions = 1); setmax_versions = 0(or--max-versions 0) to include all versions. - News follows the same convention: one news entry per release, dropped when
max_versionscaps away all of that release's versions.[news] max_entriescan further cap the list. - News entries follow the AltStore spec:
appIDfirst (the app'sbundle_identifier), a full ISOdatetimestamp,identifierderived from the release tag (release-<tag>), and an optionalimageURL;title_template/caption_templatesupport{name},{version},{tag},{date}placeholders. - A release with no matching asset contributes nothing — not even a news entry. One news entry is emitted per release that has assets.
- Empty output (no releases, only drafts, …) is a valid source: altgen warns on stderr and exits 0.
altgen [-c PATH] [--repo OWNER/REPO] [--token TOKEN]
[--name] [--subtitle] [--description] [--icon-url] [--website] [--tint-color]
[--app-name] [--bundle-id] [--developer-name] [--app-subtitle]
[--app-description] [--app-icon-url] [--app-tint-color] [--min-os-version]
[--screenshots URL …] [--include-prereleases] [--max-versions N]
[-o PATH] [-q] [-v] [--version]
altgen merge APPS_JSON… [-c PATH] [--name] [--subtitle] [--description]
[--icon-url] [--website] [--tint-color] [-o PATH] [-q]
- Without
-c,--repo,--app-name, and--bundle-idare required (build mode). --max-versions Ncaps the output after sorting (newest first); it defaults to1(latest version only) and0means all versions.- CLI flags override TOML values;
-oresolves against the current directory while[output] pathresolves against the config file's directory (so a config next to its sources works from any CWD). altgen mergecombines apps.json files; see Merge sources.-vlogs skipped releases (draft / prerelease / no matching assets) to stderr;-qsilences the success message.- Exit codes:
0success,1GitHub, IO, or write error,2usage or configuration error.
pip install -e ".[dev]"
pytest # fully offline — fixtures captured from the GitHub API