CLI / API reference
foxstore-cli is Fox Store's non-interactive publishing API for CI/CD. API here means the command-line process interface: arguments, files, stdout/stderr, and exit codes; no HTTP service is provided. It is a single executable statically linking foxstore-engine, requires no desktop client installation or startup, and does not read desktop SQLite credentials.
CLI currently automates releases for Apple, Google Play, Huawei, WeChat Mini Programs, Tencent MyApp, Honor, Samsung, Pgyer, vivo, Xiaomi, and OPPO. Object storage, reviews, Analytics, routine certificate management, image optimization, and AI app checks remain in Engine and Desktop, outside CLI.
Start with the CI/CD integration guide for GitHub Actions, GitLab CI, or Jenkins, then consult this page for parameters.
Global syntax
foxstore-cli [--output human|json|ndjson] <command>| Parameter | Default | Description |
|---|---|---|
--output | human | human for terminal reading; json for one structured result; ndjson for one compact final result, currently without live progress events. May appear before or after the subcommand. |
--help | - | Show the current command's live help. |
--version | - | Show the binary version. |
--help / -h always prints help text; --version / -V always prints version text, without an envelope even in machine formats. Use version --output json for machine-readable version information.
Types below: string is a string (IDs remain strings); path is a local Runner path; i64 / i32 are signed integers; u64 / u8 are nonnegative integers; f64 is floating point. “Optional” means omittable without a default. Boolean “flags” use only the argument name; boolean “values” require true or false. All publish and status commands inherit their Provider's credential parameters.
Basic commands:
foxstore-cli version
foxstore-cli providers --output json
foxstore-cli capabilities --provider apple --output json
foxstore-cli artifact inspect --file ./build/App.ipa --output json
foxstore-cli preflight apple --file ./build/App.ipa --output json
foxstore-cli preflight android --file ./build/App.aab --expected-package-name com.example.app --output json
foxstore-cli preflight huawei --file ./build/App.app --output json| Command | Parameters | Result |
|---|---|---|
version | None | Returns version and engineSchemaVersion. |
providers | None | Returns Providers compiled into this binary and availability. |
capabilities | Required --provider <string> | Provider description, cliPublish, and excluded capabilities; not a per-command schema. |
artifact inspect | Required --file <path> | Artifact type, size, and SHA-256. |
preflight apple | Required --file <path> | Runs Apple IPA preflight. |
preflight android | Required --file <path>; optional --expected-package-name <string> | Android package preflight with optional package-name check. |
preflight huawei | Required --file <path> | Analyzes HarmonyOS .app only; use preflight android for Huawei APK/AAB. |
providers lists all 15 static Providers; object-storage entries have cliPublish: excluded. capabilities --provider accepts IDs: apple, google-play, huawei-appgallery, wechat-miniprogram, tencent-appstore, honor, samsung-galaxy-store, pgyer, vivo, xiaomi, oppo, aliyun, tencent-cloud, aws, minio. Huawei and Samsung subcommands use huawei and samsung, not their catalog IDs.
artifact inspect identifies only type, size, and SHA-256, not signatures. preflight is local analysis without publishing. Even an Apple success envelope / exit 0 may have data.status: blocked; check data.status == "ready" and data.checks[].blocksUpload. Handle Android warnings under team policy too. publish apple repeats preflight and rejects blocked packages; passing preflight is not store acceptance.
Credential sources
Secrets cannot be ordinary command-line arguments. *-env takes an environment-variable name and *-file a CI-created temporary path. Both are mutually exclusive for the same Secret; all *-env are optional strings and *-file optional paths. Without explicit sources, CLI uses the default environment variables below.
| Provider | Default environment variables | Override parameters |
|---|---|---|
| Apple | APPLE_PRIVATE_KEY | --private-key-env / --private-key-file; Issuer ID and Key ID use --issuer-id, --key-id |
| Google Play | GOOGLE_PLAY_CREDENTIALS_JSON | --credentials-env / --credentials-file |
| Huawei HarmonyOS | HUAWEI_SERVICE_ACCOUNT_JSON | --service-account-env / --service-account-file |
| Huawei Android | HUAWEI_ANDROID_API_CLIENT_JSON | --android-api-client-env / --android-api-client-file |
| WeChat Mini Programs | WECHAT_MINIPROGRAM_PRIVATE_KEY | --private-key-env / --private-key-file |
| Tencent MyApp | TENCENT_APPSTORE_USER_ID, TENCENT_APPSTORE_ACCESS_SECRET | --user-id-env / --user-id-file, --access-secret-env / --access-secret-file |
| Honor | HONOR_CLIENT_ID, HONOR_API_SECRET | --client-id-env / --client-id-file, --api-secret-env / --api-secret-file |
| Samsung | SAMSUNG_CREDENTIALS_JSON | --credentials-env / --credentials-file |
| Pgyer | PGYER_API_KEY, PGYER_USER_KEY | --api-key-env / --api-key-file, --user-key-env / --user-key-file |
| vivo | VIVO_ACCESS_KEY、VIVO_ACCESS_SECRET | --access-key-env / --access-key-file、--access-secret-env / --access-secret-file |
| Xiaomi | XIAOMI_USER_NAME, XIAOMI_API_SECRET, XIAOMI_PUBLIC_CERTIFICATE | --user-name-env / --user-name-file, --api-secret-env / --api-secret-file, --public-certificate-env / --public-certificate-file |
| OPPO | OPPO_CLIENT_ID、OPPO_CLIENT_SECRET | --client-id-env / --client-id-file、--client-secret-env / --client-secret-file |
Apple --issuer-id and --key-id are required strings for doctor/publish/status. JSON credentials contain the complete document, not a JSON-file path string. Google/Huawei use platform-exported JSON; Samsung uses { "serviceAccountId": "…", "privateKey": "…", "accessToken": null } (first two are required strings, accessToken an optional string). Preserve PEM newlines. Never write real Secrets into commands, repositories, or logs.
Keep each credential JSON's exported field format; do not normalize to camelCase:
| Credentials | Document fields (all string, required unless noted) |
|---|---|
| Google Play | type fixed to service_account, project_id, private_key_id, private_key, client_email, token_uri. |
| Huawei Service Account | key_id, private_key, sub_account, auth_uri, token_uri, auth_provider_cert_uri, client_cert_uri; optional project_id. |
| Huawei Android API Client | client_id、client_secret。 |
| Samsung | serviceAccountId, privateKey; optional accessToken. |
CLI does not automatically read *_FILE environment variables; explicitly pass arguments such as --private-key-file "$APPLE_KEY_FILE". All four Huawei source arguments are mutually exclusive. doctor/status defaults to HarmonyOS Service Account; Android requires --android-api-client-env HUAWEI_ANDROID_API_CLIENT_JSON or --android-api-client-file <path>. publish chooses credentials by .app / .apk|.aab; explicit sources are still recommended to avoid confusion.
Connection diagnostics
doctor checks credentials, Provider connections, or the local runtime without uploading or submitting apps. It usually makes remote requests and is not an offline argument validator.
foxstore-cli doctor apple --issuer-id "$APPLE_ISSUER_ID" --key-id "$APPLE_KEY_ID" --output json
foxstore-cli doctor google-play --package-name com.example.app --output json
foxstore-cli doctor huawei --output json
foxstore-cli doctor pgyer --output json
foxstore-cli doctor tencent-appstore --output json
foxstore-cli doctor vivo --package-name com.example.app --output json
foxstore-cli doctor samsung --output json
foxstore-cli doctor wechat-miniprogram --output json
foxstore-cli doctor oppo --output json
foxstore-cli doctor honor --output json
foxstore-cli doctor xiaomi --output jsonApple requires --issuer-id, --key-id; Google Play requires --package-name; vivo requires --package-name and allows --endpoint to override production. Tencent MyApp also requires string environment variables TENCENT_APPSTORE_APP_ID and TENCENT_APPSTORE_PACKAGE_NAME (no doctor command-line equivalents). Other commands take their credential sources. WeChat only checks private-key sources and local Node.js, miniprogram-ci, miniprogram-mp-ci; both packages must resolve from node_modules in the current or a parent directory. It does not verify remote upload permissions. Xiaomi probes a fixed package name, not the target app's status.
Publishing commands and parameters
Apple App Store
foxstore-cli publish apple \
--issuer-id "$APPLE_ISSUER_ID" --key-id "$APPLE_KEY_ID" \
--app-id 1234567890 --file ./build/App.ipa --output ndjsonRequired strings: --issuer-id, --key-id, --app-id; required path: --file, plus a private-key environment variable or file. CLI uploads IPAs through Build Upload API and returns { "provider": "apple", "buildUploadId": "…" } on success. It does not submit for App Store review or guarantee Apple processing or TestFlight distribution is complete.
Google Play
foxstore-cli publish google-play \
--package-name com.example.app --file ./build/App.aab \
--track internal --release-status completed \
--release-notes-file ./release-notes.json --output ndjson| Parameter | Required/default | Description |
|---|---|---|
--package-name, --file | Required string / path | Play package name and APK/AAB path. |
--track | string, internal | Target Track. |
--release-status | string, completed | Only draft, inProgress, completed; rejects halted. |
--user-fraction | Optional f64 | Required for inProgress, 0 < value < 1; forbidden for other statuses. |
--release-name | string, empty | Release name. |
--release-notes-file | Optional path | JSON array; each item requires string language and text; defaults to an empty array. |
Example release-notes.json:
[{ "language": "zh-CN", "text": "修复已知问题" }, { "language": "en-US", "text": "Bug fixes" }]Huawei AppGallery
foxstore-cli publish huawei \
--app-id 123456 --expected-package-name com.example.app \
--file ./build/App.app --submit true --timeout-seconds 240 --output ndjson| Parameter | Required/default | Description |
|---|---|---|
--app-id, --expected-package-name, --file | Required string / string / path | Supports .app, .apk, .aab; credential type must match the package platform. |
--chinese-mainland-flag | Optional i32 | HarmonyOS upload-region flag only: 0 or 1. |
--submit | bool value, true | Whether to submit for release after upload. |
--release-time, --remark | Optional string | Release time: yyyy-MM-ddTHH:mm:ssZZ; remarks are HarmonyOS-only and must be 10–300 characters when supplied. |
--release-phase | i64, 0 | HarmonyOS only: 0 full release, 3 phased. |
--phased-release-description | Optional string | HarmonyOS only; required for phase 3, maximum 500 characters. |
--timeout-seconds | u64, 240 | Android compilation polling budget only, > 0; count calculated at roughly one poll per 2 seconds, excluding request duration. Not an overall deadline; unused by HarmonyOS. |
Pgyer
foxstore-cli publish pgyer \
--file ./build/App.ipa --update-description "CI build" \
--timeout-seconds 180 --output ndjson--file is a required path (IPA/APK/HAP). --build-type is an optional string: ios/ipa, android/apk, harmonyos/hap, inferred from the extension by default. --update-description is an optional string. --timeout-seconds is u64, defaults to 180, must exceed 0, and sets the total deadline for authentication, upload tickets, upload, and polling.
Tencent MyApp
foxstore-cli publish tencent-appstore \
--package-name com.example.app --app-id 1000123 --file ./build/App.apk \
--release-notes "修复已知问题" --deploy-type 1 --output ndjson--package-name, --app-id: required string; --file: required APK path. --release-notes: string, empty by default. --deploy-type: i64, default 1, accepts 1/2 (scheduled). --deploy-time: optional i64 timestamp in seconds, required for type 2. --apk-64-only: bool flag, default false. This updates existing apps.
vivo
foxstore-cli publish vivo \
--package-name com.example.app --file ./build/App.apk --version-code 42 \
--online-type 1 --update-description "修复已知问题" --output ndjson--package-name: required string; --file: required APK path; --version-code: required i64. --online-type: i64, default 1, accepts 1/2 (scheduled). --scheduled-online-time: optional string, required for type 2. --update-description: optional string. --endpoint: string, default https://developer-api.vivo.com.cn/router/rest; the only other allowed value is https://sandbox-developer-api.vivo.com.cn/router/rest.
Samsung Galaxy Store
foxstore-cli publish samsung \
--content-id 000007654321 --file ./build/App.apk \
--uses-gms true --submit true --output ndjson--content-id: required string; --file: required APK path. --uses-gms: optional bool value (e.g. --uses-gms false), otherwise inherits the existing store binary's GMS setting. --release-at: optional i64 Unix milliseconds, must be in the future and may be set for this update only when current content is FOR_SALE. --submit: bool value, default true; use --submit false for upload only.
WeChat Mini Programs
foxstore-cli publish wechat-miniprogram \
--appid wx1234567890 --project-path ./miniprogram --version 1.2.3 \
--description "CI build" --robot 1 --output ndjson--appid, --version: required string; --project-path: required path. --description: string, empty by default. --robot: optional u8, 1–30. --project-type: string, default miniProgram; accepts miniProgram, miniProgramPlugin, miniGame, miniGamePlugin. This uploads a code version, not automatic review submission or launch; the Node.js and two npm package prerequisites above apply.
OPPO / HeyTap
foxstore-cli publish oppo --request-file ./oppo-release.json --output ndjson--request-file is a required path to a top-level JSON object. The complete input fields below are case-sensitive.
| Field | JSON type / required | Constraints |
|---|---|---|
packageKind | string, required | ordinary or multi; must be explicit. |
apkFiles | object[], required | Each item needs string filePath and integer cpuCode; ordinary: exactly one item with cpuCode=0; multi: exactly two, 32 and 64. |
packageName, versionCode, appName | string, required | versionCode is a positive-integer string matching the package. |
secondCategoryId, thirdCategoryId | integer, required | Valid store category IDs. |
summary | string, required | Maximum 13 characters, no spaces or punctuation. |
detailDescription, updateDescription | string, required | At least 20 / 5 characters respectively. |
privacyPolicyUrl, iconUrl, copyrightUrl | string, required | URLs for privacy policy, uploaded icon, and copyright materials. |
screenshotUrls | string[], required | 2–5 uploaded portrait screenshot URLs. |
testDescription | string, required | Additional testing notes, nonempty. |
businessContactName, businessContactEmail, businessContactMobile | string, required | Business contact details. |
ageLevel | integer, required | Valid age rating greater than 0. |
adaptiveEquipment | integer, required | 4, 5, or 6. |
onlineType | integer, required | 1 or 2 (scheduled). |
scheduledOnlineTime | string, optional | Required when onlineType=2. |
landscapeScreenshotUrls | string[], optional | Uploaded landscape screenshot URLs. |
icpUrl, specialCertificateUrl, specialCertificateFileUrl | string, optional | Registration/special-qualification URLs as required by the app. |
adaptiveType | integer, optional | Adaptation type. |
Example skeleton: replace category IDs, asset URLs, contacts, and rating with real app data. apkFiles points to built APKs; CLI does not automatically convert local images into remote URLs.
{
"packageKind": "ordinary",
"apkFiles": [{ "filePath": "./build/App.apk", "cpuCode": 0 }],
"packageName": "com.example.app", "versionCode": "42", "appName": "示例应用",
"secondCategoryId": 1, "thirdCategoryId": 1,
"summary": "便捷记录日常生活",
"detailDescription": "这是一款用于记录日常生活与管理个人事项的示例应用。",
"updateDescription": "修复已知问题并改善使用体验",
"privacyPolicyUrl": "https://example.com/privacy",
"iconUrl": "https://example.com/uploaded/icon.png",
"screenshotUrls": ["https://example.com/uploaded/1.png", "https://example.com/uploaded/2.png"],
"copyrightUrl": "https://example.com/uploaded/copyright.png",
"testDescription": "无需登录即可使用基础功能",
"businessContactName": "应用联系人", "businessContactEmail": "[email protected]",
"businessContactMobile": "13800000000", "ageLevel": 1,
"adaptiveEquipment": 4, "onlineType": 1
}Honor App Market
foxstore-cli publish honor \
--app-id 123456 --package-name com.example.app --file ./build/App.apk \
--audit-file ./honor-audit.json --output ndjson--app-id: required i64; --package-name: required string; --file (APK) and --audit-file: required path. Review JSON is a top-level object:
| Field | JSON type / required | Constraints |
|---|---|---|
releaseType | integer, required | 1 normal, 2 scheduled, 3 phased. |
forceUpdate | boolean, required | true/false, not numbers. |
testAccount, testPassword | string, optional | Review test account and password; CI should create password-bearing files temporarily. |
testComment | string, optional | Maximum 500 characters. |
releaseTime | string, optional | Required when releaseType=2. |
phasedReleasePercentage | string, optional | Required for releaseType=3; value > 0 and ≤ 100. |
phasedReleaseStart, phasedReleaseEnd | string, optional | Required when releaseType=3. |
phasedReleaseNote | string, optional | Required for releaseType=3; maximum 500 characters. |
releaseNotesLanguage, releaseNotes | string, optional | Nonempty releaseNotes needs the matching language ID; notes maximum 500 characters. |
Time strings use yyyy-MM-ddTHH:mm:ssZZ, for example 2026-10-01T09:00:00+0800. Minimal normal-release honor-audit.json:
{ "releaseType": 1, "forceUpdate": false }Xiaomi App Store
foxstore-cli publish xiaomi --request-file ./xiaomi-release.json --output ndjson--request-file is a required path. JSON is a top-level object without appInfo / RequestData wrappers:
| Field | JSON type / required | Constraints |
|---|---|---|
synchroType | integer, required | 0 create, 1 update version, 2 update metadata; subject to account and app permissions. |
appName, packageName, privacyUrl, iconPath | string, required | Nonempty; iconPath is a local file path. |
publisherName, versionName | string, optional | Developer and version names. |
category, keyWords, description, brief | string, optional | All required when synchroType=0. |
updateDescription | string, optional | Required when synchroType=1. |
testAccount | string, optional | Serialized JSON string; see below. |
onlineTime | integer, optional | Release time. |
suitableType | integer, optional | 0, 1, or 2. |
apkPath | string, optional | Required for synchroType=0/1; local APK. |
secondApkPath | string, optional | Second local APK. |
screenshotPaths | string[], default [] | Maximum 5; new apps require at least 3 local screenshots. |
tabletScreenshotPaths | string[], default [] | Maximum 5; new apps with suitableType=1/2 require at least 4. |
Version-update example xiaomi-release.json (files must exist and meet target-app requirements):
{
"synchroType": 1, "appName": "示例应用", "packageName": "com.example.app",
"privacyUrl": "https://example.com/privacy", "iconPath": "./store/icon.png",
"apkPath": "./build/App.apk", "updateDescription": "修复已知问题"
}testAccount is a string containing nested encoded JSON, e.g. "{\"zh_CN\":{\"auditNotes\":\"无需登录\"}}". Each language key maps to an object with optional auditNotes (maximum 500 characters) and accounts (maximum 5 items). Each account object requires integer t (1 or 2), with optional strings a (account), p (password), c (extra value), each maximum 50 characters. Supply account and password together or leave both empty. Do not pass a JSON object directly to testAccount.
Status queries
WeChat Mini Program uploads return synchronously from local CI and have no separate status command; check the WeChat console if the result is unknown. Other queries below require the same Provider credentials. All arguments are required strings unless marked otherwise. Every status command performs one query, not continuous waiting.
| Command | Parameters |
|---|---|
status apple | --issuer-id, --key-id, private-key source; lists visible Builds newest-upload first. No --app-id filter or direct buildUploadId lookup. |
status google-play | --package-name; returns Tracks. |
status huawei | --app-id; HarmonyOS uses Service Account; Android uses API Client and at least one repeatable --package-id. |
status pgyer | --app-key; optional --build-key. |
status tencent-appstore | --package-name、--app-id。 |
status vivo | --package-name; optional --endpoint. |
status samsung | --content-id。 |
status oppo | --package-name; optional --version-code; add --multi-package for multi-package queries (bool flag, default false). |
status honor | --app-id and one or more --release-id (app-id is i64; each release-id is a repeatable string). |
status xiaomi | --package-name。 |
Machine output and exit codes
Current schemaVersion is the string "1". json is indented JSON; ndjson is one compact JSON line. Both emit only the final result, with no upload-progress event stream. Success envelopes go to stdout, error envelopes (including argument errors) to stderr. Do not parse only stdout or combine both streams into one JSON file.
Success example (Apple upload, not approval or store release):
{ "schemaVersion": "1", "status": "success", "data": { "provider": "apple", "buildUploadId": "example-upload-id" } }Error example (structure only):
{ "schemaVersion": "1", "status": "error", "error": { "code": "CLI_ARGUMENT_INVALID", "category": "input", "message": "缺少必填参数", "retryable": false } }data depends on the command, can be an object or array, and need not include provider. error.code is a string; category is a fixed string from the table below; message explains the error; retryable is boolean. Upstream Secrets/authentication data are redacted, but input paths and business text in request JSON still need protection under your team's logging policy.
| Exit code | Category | Pipeline handling |
|---|---|---|
0 | Success | Command succeeded; still inspect preflight/release data. Not approval or store availability. |
2 | input: input or package error | Correct arguments or build artifacts. |
3 | credential: credential error | Check Secret names, temporary files, and permissions. |
4 | network: network error | Retry under policy only if retryable: true. |
5 | provider: Provider rejection or response error | Read redacted codes; fix permissions, materials, or platform state. |
6 | unsupported: unsupported capability | Do not treat the action as successful. |
7 | result-unknown: unknown result | Remote writes may have occurred. Reconcile using status before any repeat release. |
10 | internal: internal CLI error | Retain version and redacted logs, then report. |
Retry and reconciliation boundaries
Exit 7 / result-unknown means remote writes may have occurred, usually with retryable: false. Pgyer may return 4 on timeout before upload starts, but 7 after the request is sent. Exhausted Huawei Android compilation polling also returns 7. This is the known unknown-result classification, not a guarantee every interrupted platform operation yields a complete error envelope. Reconcile first after Runner timeouts, process termination, or power loss too.
Retain command version, artifact hash, app/package name, version, build number, and returned remote IDs. Use the status commands above to match these identifiers against remote state; exit 0 from status alone does not prove the version is published. Apple needs app/build matching in build lists; Google Play needs Track and versionCodes; Honor needs releaseId; Huawei Android needs packageId. If no IDs were obtained before failure, or the version is not listed yet, check the store console. Never invent IDs or immediately publish again.
There are no cross-Provider wait, cancel, reconcile, unified --timeout, or --dry-run commands. retryable: true does not guarantee a repeat publish is safe; consider whether remote writes already occurred. See the CI/CD integration guide for complete Secret injection, output retention, and pipeline-failure preservation examples.
