skip to content

How does a fastlane upload lane authenticate to App Store Connect and to Google Play without a human login?

level: juniorimportance: must knowfreq 78%

answer

  1. both stores want a machine credential
  2. no human account in the lane
  3. Apple issues an API key
  4. Google Play uses a service account
  5. json_key_data suits a string secret

basics

~20 s

Apple and Google use different credentials. upload_to_app_store takes an App Store Connect API key through api_key or api_key_path; upload_to_play_store takes a Google service-account JSON key through json_key or json_key_data. There is no shared credential model.

solid answer

~40 s

Each store gets a machine identity, and the two are unrelated. On Apple's side, `upload_to_app_store` accepts an **App Store Connect API key** — `api_key_path` points at a JSON file holding the key's identifiers and private key text, or `api_key` takes the same material as a hash already loaded in the lane. On Google's side, `upload_to_play_store` accepts a **service-account JSON key** — `json_key` for a file path, `json_key_data` for the file's contents as a string, which is what you want when the CI secret store holds strings rather than files. `upload_to_app_store` still offers `username` for an Apple ID, and that is the path to avoid: a human account drags a two-factor challenge and an expiring session into the pipeline. `upload_to_play_store` has no username option at all.

code

ruby · 16 lines
ruby
lane :android_release do
  upload_to_play_store(
    package_name: 'com.llamatrek.booking',
    json_key_data: ENV['PLAY_SERVICE_ACCOUNT_JSON'],
    aab: './build/llamatrek.aab',
    track: 'production'
  )
end

lane :ios_release do
  upload_to_app_store(
    app_identifier: 'com.llamatrek.booking',
    api_key_path: ENV['ASC_API_KEY_PATH'],
    ipa: './build/LlamaTrek.ipa'
  )
end

go deeper

for a junior

Recall the two names cleanly: App Store Connect API key for the Apple upload lane, Google service-account JSON key for the Play upload lane. Being able to say they are unrelated already answers the question.

for a middle

Explain the option pairs and when each is right: api_key_path versus api_key on Apple's side, json_key versus json_key_data on Google's, and why the in-memory form suits a string-only secret store.

for a senior

Demonstrate how you keep those secrets out of the repository and the logs, how you scope each identity, and how a rotation is planned rather than discovered during a release night.

for a principal

Own the estate view: who may issue store credentials, how they are scoped and rotated, and what the blast radius is when one leaks or is revoked across every pipeline that publishes.

## Why a human login cannot drive an unattended release A store upload is an authenticated API call, and the only real question is whose identity it carries. If the release lane authenticates as a person, then everything that protects that person also blocks the pipeline: an interactive two-factor challenge, a session that expires, a rotated password, and eventually the fact that the person leaves the team. `upload_to_app_store` still accepts an Apple ID through `username`, and that is exactly the path that strands a llama-trekking booking app release overnight, waiting for a code nobody is awake to type. The fix on both stores is the same idea and two completely different implementations: issue a credential that belongs to the **automation** rather than to a human. There is **no shared credential model** — the formats, the option names and the issuing systems all differ. ## Apple: the App Store Connect API key `upload_to_app_store` (`deliver`) reads Apple credentials from these options: - `api_key_path` — a path to a JSON file that carries the key's identifiers together with the private key text. This is the usual CI form. - `api_key` — the same material as a hash already prepared inside the lane, so nothing has to be written to disk. - `username` — an Apple ID. The interactive path, kept for humans running the lane locally. - `team_id` and `team_name` — which team the upload belongs to, when the identity can see more than one. The key is a secret with a scope: it is issued with a role, so an upload can fail on permission rather than on authentication, and revoking or rotating it breaks every lane that uses it at once. ## Google: the service-account JSON key `upload_to_play_store` (`supply`) reads Google credentials from these options: - `json_key` — a path to the service-account JSON key file. - `json_key_data` — the contents of that file as a string, ideal when the credential arrives from an environment variable. - `package_name` — the Android application id the service account is acting on. Note the asymmetry that trips people up: the action has **no `username` option**. There is no human-account path on this side at all; a service account is the only identity Google Play's upload lane will use. ## The two credentials side by side | | iOS — `upload_to_app_store` | Android — `upload_to_play_store` | |---|---|---| | Credential kind | App Store Connect API key | Google service-account JSON key | | File option | `api_key_path` | `json_key` | | In-memory option | `api_key` | `json_key_data` | | Human fallback | `username` (an Apple ID) | none | | Scoping option | `team_id`, `team_name` | `package_name` | The same split shows up wherever fastlane talks to a store: Apple's tools take `api_key`/`api_key_path`, Google's take `json_key`/`json_key_data`. Neither key is convertible into the other, and a lane that ships both platforms needs both secrets. ## Handling the keys in CI 1. Keep both keys out of the repository. A committed service-account key or Apple private key is a store-publishing credential sitting in everyone's clone. 2. Prefer the in-memory option where one exists: `json_key_data` from an environment variable means the Play key never touches the job's disk. 3. When a file is unavoidable — Apple's `api_key_path` — write it from the secret at the start of the job and delete it at the end, inside the job's own workspace. 4. Rotate deliberately. Because one key serves every lane, plan the swap rather than discovering it when a release night fails. 5. Give the credential the narrowest role that still lets the upload succeed. ## Failure modes worth recognising - The lane hangs instead of failing: almost always an Apple ID `username` path asking for a two-factor code. - Authentication succeeds but the upload is refused: the identity is real but has not been granted access to that app, or its role is too narrow. - A key pasted into a single-line environment variable loses the line breaks of its private key text and no longer parses. - Someone points the Apple key at `json_key`, or the Google key at `api_key_path`, and gets an unhelpful parse error rather than a clear message. ## What a machine credential does not buy you It removes the human from the **login**, not from the release. Store review still happens on the stores' terms, and the declarations a submission requires still have to come from somewhere — either from options set on the action or from a person. Machine credentials make an unattended release possible; they do not make it automatic.

  • Your CI secret store holds only strings, not files. How do you give `upload_to_play_store` its Google service-account key?
    Pass the whole JSON blob as `json_key_data`, read from the environment, so the credential never touches disk. If some step genuinely needs a path, write the secret to a file inside the job workspace and delete it in a cleanup step. Never commit the key or its contents, and never echo it into build logs.
  • What does an App Store Connect API key not solve compared with an Apple ID login?
    It is still a scoped secret. The key is issued with a role, so an upload can fail on permission rather than on credentials, and revoking or rotating it breaks every lane that uses it at once. It also does not remove store review; it only removes the interactive login from the lane.
  • Can the same credential serve the iOS and the Android upload lane?
    No. An App Store Connect API key authenticates only to Apple through `api_key`/`api_key_path`, and a Google service-account JSON key only to Google Play through `json_key`/`json_key_data`. A pipeline that ships both platforms carries two independent secrets with two independent rotation stories.

An App Store Connect API key is a badge issued to the build machine itself. An Apple ID is a person's badge lent to the machine, and it is still the person who gets the two-factor prompt at three in the morning.

saying these in an interview costs you the question

  • Putting an Apple ID password into the CI job configuration
  • Thinking one credential covers App Store Connect and Google Play
  • Believing upload_to_play_store accepts a username and password
  • Committing the service-account JSON key into the repository
  • Saying two-factor prompts are fine because CI can retry
  • Assuming a machine credential removes store review