An App Store submission gatekeeper for Claude Code.
Heimdall audits your iOS app against 141 checks derived from the App Store Review Guidelines and the highest-volume rejection reasons, scores your odds of passing review out of 100, and fixes what it finds.
Heimdall guards the Bifröst. Nothing crosses without being seen.
Roughly a third of first submissions get rejected, and most of those rejections are preventable — a dead privacy policy URL, a missing Restore Purchases button, a demo account that expired, a screenshot of a feature you cut. Heimdall finds those before Apple does.
git clone https://github.com/UppercutLabs/heimdall.git ~/.claude/skills/heimdallThat's it. Claude Code picks up skills from ~/.claude/skills/ automatically. Restart your
session and run /heimdall.
For a project-scoped install, clone into .claude/skills/heimdall inside the repo instead.
Heimdall works standalone, but about 60 of its 141 checks live on Apple's side — metadata, screenshots, IAP state, agreements, age rating, build status, review history. Without API access those become "unknown" and cost you points.
brew install ascThen create an API key at App Store Connect → Integrations and log in:
asc auth login --name "Heimdall" --key-id "KEY_ID" --issuer-id "ISSUER_ID" --private-key ./AuthKey.p8asc is App-Store-Connect-CLI by Rork —
a separate project, and a very good one. Heimdall will prompt you to install it if it's
missing, and runs in a degraded mode if you'd rather not.
/heimdall
| Command | What it does |
|---|---|
/heimdall |
Full audit — codebase plus App Store Connect |
/heimdall quick |
Codebase only, no API calls, ~2 minutes |
/heimdall fix |
Audit, then apply every safe fix |
/heimdall paywall |
Guideline 3.1 deep dive — IAP, subscriptions, paywall UX |
/heimdall metadata |
Guideline 2.3 — name, keywords, screenshots, description |
/heimdall privacy |
Guideline 5.1 — labels, ATT, purpose strings, account deletion |
/heimdall completeness |
Guideline 2.1 — the single largest rejection bucket |
/heimdall report |
Re-render the last findings as a shareable page |
Works with native Swift/Objective-C, React Native, Expo, Flutter, and Capacitor projects.
141 checks across nine categories, weighted by how often each one actually causes a rejection rather than by how many checks it contains:
| Category | Guideline | Weight | Checks |
|---|---|---|---|
| App completeness & functionality | 2.1 | 22 | 22 |
| Privacy, data & permissions | 5.1 | 16 | 20 |
| IAP, subscriptions & paywalls | 3.1 | 15 | 27 |
| Metadata, screenshots & listing | 2.3 | 14 | 18 |
| Design, minimum functionality & spam | 4.0–4.3 | 10 | 10 |
| Login, accounts & reviewer access | 4.8 / 5.1.1 | 8 | 9 |
| Technical, build & binary | 2.5 | 8 | 19 |
| Account, agreements & declarations | — | 4 | 10 |
| Safety & user-generated content | 1.x | 3 | 6 |
A sample of what that means in practice:
- Paywalls that show "$4.99/mo" for an annual plan without the annual total
- Free-trial toggle switches (rejected as misleading, and very common)
- Missing Restore Purchases, Terms of Use, or Privacy Policy links on the paywall
- Apps with accounts and no in-app account deletion
- Tracking SDKs that initialise before the ATT prompt
- Missing
PrivacyInfo.xcprivacyor required-reason API declarations - Staging URLs, debug menus, and
Lorem ipsumin the release build - Demo accounts that expired, or 2FA codes only your phone can receive
- Social login without Sign in with Apple
- 1024px app icons with an alpha channel
- Support and privacy policy URLs that 404
The score is computed by scripts/score.py, not estimated by the model.
Each check carries points by severity — blocker 5, major 3, minor 1 — and earns a fraction of them by status:
| Status | Credit |
|---|---|
pass |
100% |
warn |
50% |
unknown |
35% |
fail |
0% |
na |
excluded |
Two decisions worth explaining:
unknown costs points. A check that couldn't be verified scores below a pass, so guessing
is never cheaper than asking you. Any check that goes unreported is scored as unknown
automatically — silence can't pass as a pass.
Confirmed blockers cap the score. One caps it at 55, two at 45, three or more at 35. Without this, an app with a dead privacy policy URL and everything else immaculate scores in the low 90s — which is exactly backwards, since that one thing gets it rejected.
Categories that don't apply drop out and the remaining weights renormalise, so a free app with no accounts isn't scored against IAP or account-deletion checks.
| Score | Band |
|---|---|
| 90–100 | Bifröst Open — known preventable failure modes cleared |
| 75–89 | Watch Set — likely pass, a few soft spots |
| 60–74 | Horn Within Reach — coin flip, real fixable risk |
| 40–59 | Gates Barred — likely rejection, fix blockers first |
| 0–39 | Gjallarhorn — will be rejected as-is |
Heimdall predicts. It does not guarantee. App Review is done by people with discretion, and clean apps are still occasionally rejected — often for something subjective about the concept rather than compliance. A high score means the known preventable failure modes are cleared.
Heimdall never runs asc review submit, asc publish appstore, or any other command that
puts your app in front of Apple. It prepares the submission; you press the button.
Every write to App Store Connect is shown to you and confirmed first, with a dry run where the API supports one. Reads happen freely. Age rating and content-rights answers are legal declarations about your app, so Heimdall writes only what you tell it — it never guesses.
SKILL.md the workflow Claude follows
references/checks.json 141 checks: IDs, categories, severities, applicability
references/checklist.md how to verify each check, and what makes it a failure
references/asc-cookbook.md App Store Connect CLI commands mapped to check IDs
references/codebase-audit.md grep patterns per stack — native, RN, Expo, Flutter, Capacitor
references/paywall.md Guideline 3.1 deep dive
references/fixes.md remediation playbook and the auto-fix boundary
references/report.md scoring model and report format
scripts/score.py the scoring engine
assets/findings.template.json every check ID, pre-listed, so nothing gets skipped
checks.json is the machine-readable spine. If you want to add a check, add it there and give
it a verification row in checklist.md — score.py will pick it up and flag it as unreported
until the audit covers it.
The App Store Review Guidelines change, and anything with a date attached goes stale. Heimdall
re-checks the version-dependent items (SDK requirements, external purchase link rules,
age rating tiers, screenshot dimensions) against Apple's live documentation during a full
audit, and prefers asc screenshots sizes --all over any hardcoded dimension list. If you spot
a bundled fact that's out of date, please open an issue.
App Privacy nutrition labels aren't exposed by the App Store Connect API, so those checks are confirmed by asking you — Heimdall lists the data types it found in your code so the confirmation is a real comparison rather than a rubber stamp.
Issues and pull requests welcome, particularly:
- Checks that caught a real rejection Heimdall would have missed
- Rejection reasons from recent submissions, with the guideline cited
- Framework coverage — grep patterns that miss on a stack not listed above
- Guideline changes that make a bundled fact stale
MIT — see LICENSE.
Not affiliated with or endorsed by Apple Inc.