Command reference, config format, SDK, and troubleshooting.
No install required — use npx:
npx @shyguy_studio/shipkit@0.2.4 init
Or install globally:
npm install -g @shyguy_studio/shipkit@0.2.4 shipkit init
Verify the version:
shipkit --version # 0.2.4
Interactive setup wizard. Detects your project type (Xcode, XcodeGen, workspace), generates shipkit.yml.
shipkit init
Detects: .xcodeproj, .xcworkspace, project.yml (XcodeGen). Auto-extracts bundle ID, team ID, scheme name.
Validate your App Store Connect API credentials.
shipkit auth
Reads key from shipkit.yml, generates a JWT, and tests it against the ASC API. Reports which apps are accessible.
You need a Team Key (not Individual Key) from App Store Connect → Users and Access → Integrations → Team Keys. Role must be Admin or App Manager.
Build and archive your Xcode project. Produces an .ipa file.
shipkit build shipkit build --clean shipkit build --skip-export
Runs pre-build hooks (e.g. xcodegen generate), auto-increments build number from ASC, archives with xcodebuild, exports .ipa.
--clean — clean before building--skip-export — archive only, don't export .ipa
Upload an .ipa to App Store Connect.
shipkit upload shipkit upload --ipa ./path/to/app.ipa shipkit upload --skip-validation
Uses xcrun altool under the hood. Validates before uploading unless skipped.
Push metadata from your shipkit.yml to App Store Connect. Creates a new version if no editable version exists. As of v0.2.0, ShipKit pushes both version-level fields and app-level fields in a single command.
shipkit metadata push
Version-level fields (per locale): description, keywords, what's new, marketing URL, support URL, promotional text
App-level fields (per locale, via appInfoLocalizations): name, subtitle, privacy policy URL, privacy choices URL
Other fields: primary/secondary category (via appInfo relationships), App Review Information (notes, contact, demo account)
Note: Apple moved privacyPolicyUrl from version-level to app-level in 2024. ShipKit v0.2.1+ handles this automatically — the field stays at the same location in your YAML.
Pull current metadata from ASC to your terminal.
shipkit metadata pull
Submit (or re-submit) the current editable version for App Store review.
shipkit submit
Uses Apple's 3-step reviewSubmissions API: creates a reviewSubmission, attaches the version, marks submitted: true. Build must be attached and processed first.
If your version was previously rejected, ShipKit will automatically clean up the orphaned submission state before resubmitting:
1. Lists every existing reviewSubmission for the app
2. If one is in IN_REVIEW or WAITING_FOR_REVIEW, bails with a clear error
3. If one is in UNRESOLVED_ISSUES (rejected) and still holds your version, deletes its items to release the version (Apple won't let you cancel terminal-state submissions, but DELETEing items works)
4. If one is a READY_FOR_REVIEW orphan from a partial earlier submit, deletes its items + cancels it
5. Creates a fresh submission, attaches the version, marks submitted
Editable states for resubmission: PREPARE_FOR_SUBMISSION, REJECTED, METADATA_REJECTED, DEVELOPER_REJECTED, INVALID_BINARY.
Inspect and manage individual reviewSubmissions. Useful for debugging stuck states.
List every reviewSubmission for the current app, with state and submitted date.
shipkit submissions list # Output: # c107d4cf-... WAITING_FOR_REVIEW 4/10/2026, 3:36:56 AM # d868e031-... COMPLETE 4/9/2026, 8:11:38 PM # 5b490ebd-... COMPLETE 4/7/2026, 10:50:51 PM
Explicitly cancel a stuck submission by ID. Only works on non-terminal states (READY_FOR_REVIEW). Terminal states (UNRESOLVED_ISSUES, COMPLETE) can't be canceled — but `shipkit submit` knows how to release versions from them.
shipkit submissions cancel 7970750e-fce2-4845-ac03-bfa9dc06848c
Check your app's current review status.
shipkit status
Shows: app name, version, state (color-coded). States: PREPARE_FOR_SUBMISSION, WAITING_FOR_REVIEW, IN_REVIEW, PENDING_DEVELOPER_RELEASE, READY_FOR_SALE, REJECTED.
The full pipeline in one command.
shipkit ship shipkit ship --dry-run shipkit ship --skip-build shipkit ship --skip-metadata shipkit ship --skip-submit
Chains: hooks → build → upload → wait for processing → metadata push → submit. Each step can be skipped with flags.
--dry-run shows what would happen without executing anything.
ShipKit collects anonymous usage data (command name, success/fail, duration, OS). No app names, API keys, or personal data.
shipkit telemetry status # check if enabled shipkit telemetry disable # opt out shipkit telemetry enable # opt back in
Every field in shipkit.yml:
version: 1 # config version
auth:
key_id: "42NQL368GJ" # ASC API Key ID
issuer_id: "95e8ae19-..." # ASC Issuer ID (Team Keys page)
key_path: ./keys/AuthKey.p8 # path to .p8 file
app:
bundle_id: com.example.app # your app's bundle ID
apple_id: "6761797247" # optional, ASC app ID
team_id: 5UF3Q334K6 # Apple Developer Team ID
name: "My App" # app name
build:
project: MyApp.xcodeproj # or workspace: MyApp.xcworkspace
scheme: MyApp # Xcode scheme
configuration: Release # Debug or Release
export:
method: app-store # app-store | ad-hoc | development
version:
marketing: "1.0.0" # CFBundleShortVersionString
build: auto # "auto" = latest from ASC + 1
metadata:
default_locale: en-US
locales:
en-US:
# App-level (pushed via appInfoLocalizations)
name: "My App"
subtitle: "Short tagline" # 30 char limit
privacy_url: "https://..." # moved to app-level by Apple
# Version-level (pushed via appStoreVersionLocalizations)
description: |
Multi-line description... # 4000 char limit
keywords: "keyword1,keyword2" # 100 char limit
promotional_text: "..." # 170 char limit
marketing_url: "https://..."
support_url: "https://..."
whats_new: "Bug fixes." # 4000 char limit
# Optional: set primary/secondary category
categories:
primary: "REFERENCE" # ENTERTAINMENT | UTILITIES | etc.
secondary: "BOOKS"
# Optional: App Review Information (notes for the App Reviewer)
review:
first_name: "Chad"
last_name: "Neal"
email: "you@example.com"
phone: "+1-555-0123"
notes: |
Hi reviewer,
A short message explaining what this build adds, demo
account credentials if needed, etc. Apple's reviewer reads
this when they pick up your submission.
release:
type: manual # manual | automatic | phased
hooks:
pre_build:
- "xcodegen generate" # runs before build
post_build:
- "echo done" # runs after buildEnvironment variables are interpolated: ${ASC_KEY_ID} reads from process.env.ASC_KEY_ID.
Every ShipKit operation is available as a TypeScript import:
import { ShipKit } from "@shyguy_studio/shipkit";
const kit = new ShipKit({
keyId: "42NQL368GJ",
issuerId: "95e8ae19-...",
keyPath: "./keys/AuthKey.p8",
});
const app = await kit.apps.find("com.example.app");
const status = await kit.submissions.getStatus(app.id);
console.log(status.state); // "WAITING_FOR_REVIEW"Modules: kit.apps, kit.appInfo, kit.builds, kit.metadata, kit.submissions
The appInfo module (added in v0.2.0) handles app-level metadata: name, subtitle, privacy URL, and primary/secondary categories. The submissions module handles the full reviewSubmission lifecycle including listing, cancelling, and item-deletion-based cleanup of stuck submissions.
App rejection is a normal part of the lifecycle. ShipKit v0.2.4+ handles the full resubmission flow without requiring you to drop into the App Store Connect web UI. Here's the recommended sequence:
Apple emails you the rejection with the violated guideline number and a description. Don't panic — most rejections are metadata fixes, not code fixes.
If the issue is in your app metadata (description, keywords, name, subtitle, screenshots, support URL), update shipkit.yml. If it's a code issue, fix the code and bump the build number.
shipkit metadata push
Pushes everything: version-level fields, app-level fields (name/subtitle/privacy URL), categories, and the App Review Information notes. Use the notes to acknowledge the rejection and explain what changed — Apple's reviewer reads this before re-reviewing.
The Resolution Center messaging system isn't exposed via the public ASC API, so you have to paste your reply manually at appstoreconnect.apple.com/apps/{your-app} → click into the rejected version → look for the "Resolution Center" link in the rejection banner. This reply threads under the original rejection so the same review team sees your acknowledgment.
shipkit submit
ShipKit automatically detects orphaned and rejected submissions and cleans them up before creating a fresh one. See shipkit submit for the full algorithm.
shipkit submissions list shipkit status
You're using an Individual Key instead of a Team Key. Individual keys don't work with the ASC REST API. Generate a new key at App Store Connect → Users and Access → Integrations → Team Keys.
You're trying to push metadata to a version that's already live (READY_FOR_SALE). Set build.version.marketing in your config to a new version number — ShipKit will create the new version automatically.
Apple moved the privacy policy URL from version-level to app-level (appInfoLocalizations). Upgrade to ShipKit v0.2.1+ which handles the new location automatically. The field stays at metadata.locales.<locale>.privacy_url in your YAML — only the underlying API call changed.
A previous reviewSubmission is still "holding" your version. Usually this happens after a rejection (the rejected submission is in UNRESOLVED_ISSUES terminal state) or after a partial submit attempt left an orphan in READY_FOR_REVIEW. Upgrade to ShipKit v0.2.4+ — shipkit submit automatically detects these and releases the version by deleting their items before creating a new submission.
To inspect what's stuck: shipkit submissions list
Apple deprecated this endpoint. ShipKit v0.1.0+ uses the newer reviewSubmissions API. Make sure you're on the latest version.
Set build.version.build: auto in your config. ShipKit will query ASC for the latest build number and increment it. If you have a hardcoded buildNumber in app.json (Expo), remove it — it overrides auto-increment.
EAS submit errors are often vague. Common causes: duplicate build number, build not yet processed, or Apple API transient failure. Retry after a few minutes, or use shipkit upload directly.
This was a v0.2.1 bug. Upgrade to v0.2.2+ which allows resubmission from any editable state including REJECTED, METADATA_REJECTED, and DEVELOPER_REJECTED.
Resubmission lifecycle. Auto-detects stuck reviewSubmissions (UNRESOLVED_ISSUES, READY_FOR_REVIEW orphans) and releases the version by deleting their items before creating a fresh submission. Adds submissions list and submissions cancel <id> commands.
Surface Apple's associated errors (multi-line, with field pointers) instead of just the primary error. Per-step error wrapping in submit so you know which step failed (create / attach / submit).
Allow shipkit submit from rejected states (REJECTED, METADATA_REJECTED, DEVELOPER_REJECTED, INVALID_BINARY). Previously locked to PREPARE_FOR_SUBMISSION only.
Removed deprecated privacyPolicyUrl from version-level push (Apple moved it to app-level). The field still works in your YAML — ShipKit now pushes it via appInfoLocalizations.
App-level metadata. New AppInfoModule pushes name, subtitle, privacy URL, and categories via appInfoLocalizations and appInfos. New metadata.pushReviewDetail() pushes notes + contact info to appStoreReviewDetail. CLI metadata push now does version locale → app-info locale → categories → review detail in one pass.
Initial release. CLI commands: init, auth, build, upload, metadata push/pull, submit, status, ship. SDK with apps, builds, metadata, submissions modules. Telemetry. JWT auth via .p8 keys. reviewSubmissions API.
Built by Shy Guy Studio. Open Dashboard