Storeship
Ship any iOS or Android app's store presence from one config — capture, style, AI imagery, metadata, and publish to App Store Connect + Google Play.
What is Storeship?
Storeship ships any iOS or Android app's store presence from one config: capture screenshots, frame and caption them on your brand, generate AI marketing imagery, write metadata, and publish to App Store Connect and Google Play — no fastlane, no Firebase. The same pipeline powers a CLI, an HTTP API, and a web dashboard, so it scales from one command on your machine to a hosted service.
Install in one command
The fastest path installs the CLI and everything it needs (Python tooling via pipx) and
prints guided next steps.
# macOS / Linux
curl -fsSL https://web.taskjunky.app/storeship/install.sh | sh# Windows (PowerShell)
irm https://web.taskjunky.app/storeship/install.ps1 | iexOr install manually — the CLI is a Python package (Python 3.9+):
pip install "storeship[all]"Quickstart
storeship init scaffolds a storeship.yml plus a gitignored .env.local. Edit them,
dry-run, then ship.
storeship init
storeship ship --config storeship.yml --platform ios --dry-run
storeship ship --config storeship.yml --platform iosOr run the stages individually:
storeship capture --config storeship.yml --platform android --out raw
storeship style --config storeship.yml --raw raw --out styled
storeship metadata --config storeship.yml --platform ios --dir metadata/en-US
storeship screenshots --config storeship.yml --platform ios --dir styled
storeship build --config storeship.yml --platform ios
storeship upload --config storeship.yml --platform ios --artifact App.ipaAutomatic screenshot capture
storeship capture boots a simulator/emulator and drives your app through a scripted
sequence of screens, saving a PNG per step. On iOS it runs an Xcode UI test (macOS
only). On Android it drives the emulator over adb — which works on Windows,
macOS, and Linux, with no Xcode or Gradle. Each step lists actions (taps, swipes,
text, key events) that run before the screenshot is taken.
android:
package: com.example.app
capture:
avd: Pixel_8_API_34 # AVD to boot; omit to use a running device
steps:
- { name: "01_home", settle_ms: 2500 }
- { name: "02_discover", actions: ["tap 540 1800"] }
- { name: "03_search", actions: ["tap 320 140", "text vinyl", "key ENTER"] }Platform support
| Stage | iOS | Android |
|---|---|---|
| capture | macOS only (xcodebuild UI test) | Win / Mac / Linux (adb) |
| style | any OS | any OS |
| metadata / screenshots | any OS | any OS |
| build | macOS only (xcodebuild) | any OS (gradlew) |
| upload / publish | any OS | any OS |
The Google Play path (style, metadata, screenshots, upload) is pure Python and runs
anywhere — so you can ship Android entirely from a Windows PC. The iOS simulator, capture,
and build rely on Apple's macOS-only toolchain (xcodebuild / simctl), so iOS requires
a Mac or a cloud-Mac CI runner.
Configuration
One YAML describes a project. Secrets are referenced by path or ${ENV} var, never
inlined — they live in .env.local, auto-loaded from the nearest directory upward.
name: What's Spinning
repo: ~/developer/whatisspinning
locales: [en-US]
ios:
bundle_id: com.example.app
scheme: App
metadata_dir: fastlane/metadata/en-US
screenshots: [fastlane/screenshots/en-US]
asc:
key_id: "${ASC_KEY_ID}"
issuer_id: "${ASC_ISSUER_ID}"
key_path: ~/.appstoreconnect/private_keys/AuthKey_XXXXXXXXXX.p8
android:
package: com.example.app
play:
service_account_json: ~/.config/storeship/play-service-account.json
brand:
paper: "#F4F1E9"
ink: "#16140E"
accent: "#FF4A1F"Connect from TaskJunky
Set up the integration in Settings → Storeship. Your connection points TaskJunky at a
Storeship tenant; the credentials below are stored encrypted (AES‑256‑GCM) and are
write‑only — once saved, a key is shown as •••• (saved) and never returned.
Connection
| Field | Required | What it is |
|---|---|---|
| API URL | Yes | Base URL of your Storeship API (e.g. https://api.storeship.example.com). |
| Tenant API key | Yes | Your Storeship tenant key (sk_live_…). Used as x-api-key for every call. |
| Default project | No | Project id used when you trigger a ship from here. Required only to ship. |
Narration
| Field | Required | What it is |
|---|---|---|
| ElevenLabs API key | Only for narration | Generates marketing‑video voiceovers. Leave blank if you don't use narration. |
Admins can set an instance default (API URL + tenant key + default project) under the same panel; it applies to anyone who hasn't set their own connection. A personal connection always overrides the instance default.
Marketing-video narration
The app produces a silent replay clip; Storeship turns a config‑driven script into an ElevenLabs voiceover and muxes it onto the clip with ffmpeg — so ElevenLabs never lives in an app repo.
narration:
voice_id: UgBBYS2sOqTuMpoF3BR0 # ElevenLabs voice
model_id: eleven_multilingual_v2
script: "Meet the app that…" # or script_file: marketing/voiceover.txt
api_key_env: ELEVEN_LABS_API_KEY # the key itself is a secret, never inlined
# music: marketing/bed.mp3 # optional background music, ducked under the voicestoreship narrate --config storeship.yml \
--clip replay.mp4 --out marketing.mp4Internal / TestFlight builds
Ship a non‑Release configuration (e.g. Internal, which defines INTERNAL_BUILD) straight
to TestFlight for dogfooding — binary only, no store‑listing changes.
ios:
internal:
scheme: App Internal # falls back to `scheme` if omitted
configuration: Internalstoreship ship --config storeship.yml --internalSecrets & encryption
Keys are never committed. There are three places config and secrets live:
| Where | Holds | Committed? |
|---|---|---|
Project config (storeship.yml) | non‑secret settings + ${ENV} / api_key_env references | Yes (no secret values) |
.env.local | local‑CLI secret values (ASC_*, ELEVEN_LABS_API_KEY, …) | No — gitignored |
| Vault (DB) | hosted secrets: ASC .p8, Play JSON, ElevenLabs key | No — Fernet‑encrypted at rest |
Vault secrets are encrypted with the STORESHIP_SECRET master key (server env only), so
database access alone can't read or reuse them. App Store Connect API keys can't be
created programmatically — mint the .p8 once in App Store Connect, then save it once at
the developer level and every project inherits it.
Full config schema, the multi-tenant SaaS API, and the web dashboard live in the storeship repository.