> For the complete documentation index, see [llms.txt](https://nvsbl.gitbook.io/apphafen/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://nvsbl.gitbook.io/apphafen/apple.md).

# Apple

This app needs three values for an Apple App Store Connect connection:

* `Issuer ID`
* `Key ID`
* `Private key (.p8 contents)`

## What each value is

### Issuer ID, Key ID, and private key

These come from an App Store Connect API key and are used to sign short-lived JWTs for the App Store Connect API.

{% stepper %}
{% step %}

### Sign in to App Store Connect

Sign in to [App Store Connect](https://appstoreconnect.apple.com/).
{% endstep %}

{% step %}

### Open Users and Access

Open `Users and Access`.
{% endstep %}

{% step %}

### Open Integrations

Open `Integrations`.
{% endstep %}

{% step %}

### Open App Store Connect API

Open the `App Store Connect API` section if needed.
{% endstep %}

{% step %}

### Request API access if needed

If API access is not enabled for the account yet, request access first.
{% endstep %}

{% step %}

### Open Team Keys

Open `Team Keys`.
{% endstep %}

{% step %}

### Generate an API key

Click `Generate API Key`.
{% endstep %}

{% step %}

### Enter key details

Enter a name for the key.
{% endstep %}

{% step %}

### Choose a role

Choose a role with access to the apps you want to track.
{% endstep %}

{% step %}

### Generate the key

Generate the key.
{% endstep %}

{% step %}

### Copy the credentials

Copy the displayed `Issuer ID` and `Key ID`.
{% endstep %}

{% step %}

### Download the private key

Download the private key file and keep the `.p8` contents.
{% endstep %}
{% endstepper %}

Paste the full `.p8` file contents into this app, including:

```
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
```

{% hint style="warning" %}

* The private key can only be downloaded once. Store it securely when Apple shows it.
* The `Issuer ID`, `Key ID`, and private key must all come from the same App Store Connect API key setup.
* The key’s role must have access to the apps you want to import and sync.
* Team keys work across the account according to their assigned role. Individual keys follow the permissions of the user who created them.
  {% endhint %}

## How app discovery works

This app automatically discovers the apps available to the authenticated App Store Connect account.

You do not need to paste app IDs manually.

{% stepper %}
{% step %}

### Load the app

This app loads the app from the App Store Connect API.
{% endstep %}

{% step %}

### Load App Store versions

This app loads available App Store versions.
{% endstep %}

{% step %}

### Import product rows

This app imports one product row per supported platform.
{% endstep %}
{% endstepper %}

Right now, the Apple connector imports:

* iOS apps
* macOS apps

If an app ships on both supported platforms, this app creates one row per platform.

## Required Apple-side prerequisites

* App Store Connect API access must be enabled for the account.
* The API key must still be active and not revoked.
* The selected key role must be able to access the apps you want to track.
* The private key pasted into this app must be the original `.p8` key for that `Key ID`.
* Your server must have a correct system clock because Apple JWT authentication is time-sensitive.

## How Apple authentication works

App Store Connect API requests use a JWT in the `Authorization` header.

This app creates that JWT for you automatically from the stored `Issuer ID`, `Key ID`, and private key.

The JWT:

* uses the `ES256` signing algorithm
* sets `kid` in the header to your `Key ID`
* sets `iss` to your `Issuer ID`
* sets `aud` to `appstoreconnect-v1`
* uses a short expiration window

## Quick validation checklist

If the app shows `Apple authentication failed.` or cannot import your apps, check these in order:

1. Confirm App Store Connect API access is enabled for the account.
2. Confirm the `Issuer ID` is copied exactly from App Store Connect.
3. Confirm the `Key ID` is copied exactly from the same API key.
4. Confirm the pasted private key is the full `.p8` contents, including the `BEGIN` and `END` lines.
5. Confirm the API key has not been revoked.
6. Confirm the key role has access to the apps you expect to import.
7. Confirm your system clock is correct.

## Direct authentication test

You can verify the credentials outside the app by generating a JWT and calling the apps endpoint.

Create a token with this PHP one-liner:

```bash
php -r '
$issuerId = "YOUR_ISSUER_ID";
$keyId = "YOUR_KEY_ID";
$privateKey = <<<KEY
-----BEGIN PRIVATE KEY-----
YOUR_P8_KEY_CONTENTS
-----END PRIVATE KEY-----
KEY;
$header = rtrim(strtr(base64_encode(json_encode([
  "alg" => "ES256",
  "kid" => $keyId,
  "typ" => "JWT",
])), "+/", "-_"), "=");
$now = time();
$payload = rtrim(strtr(base64_encode(json_encode([
  "iss" => $issuerId,
  "aud" => "appstoreconnect-v1",
  "exp" => $now + 1200,
])), "+/", "-_"), "=");
$unsigned = $header . "." . $payload;
openssl_sign($unsigned, $signatureDer, $privateKey, OPENSSL_ALGO_SHA256);
function derToJose(string $der): string {
  $offset = 3;
  $rLength = ord($der[$offset]);
  $offset++;
  $r = substr($der, $offset, $rLength);
  $offset += $rLength + 1;
  $sLength = ord($der[$offset]);
  $offset++;
  $s = substr($der, $offset, $sLength);
  return str_pad(ltrim($r, "\0"), 32, "\0", STR_PAD_LEFT) . str_pad(ltrim($s, "\0"), 32, "\0", STR_PAD_LEFT);
}
$signature = rtrim(strtr(base64_encode(derToJose($signatureDer)), "+/", "-_"), "=");
echo $unsigned . "." . $signature;
'
```

Use the resulting token like this:

```bash
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  "https://api.appstoreconnect.apple.com/v1/apps?limit=5"
```

Interpretation:

* `200`: credentials are valid
* `401`: wrong issuer ID, wrong key ID, wrong private key, expired token, or system clock issue
* `403`: authenticated, but the key does not have access to the requested resources

## Direct app test

If authentication works, verify a specific app directly:

```bash
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  "https://api.appstoreconnect.apple.com/v1/apps/YOUR_APP_ID"
```

You can also inspect version state for a platform:

```bash
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  "https://api.appstoreconnect.apple.com/v1/apps/YOUR_APP_ID/appStoreVersions?filter[platform]=IOS&include=build"
```

Interpretation:

* `200`: the app exists and the key can access it
* `401`: authentication problem
* `403`: the key is authenticated but does not have access to that app or endpoint
* `404`: wrong app ID

## What the app imports from Apple

For each discovered Apple app, the app:

{% stepper %}
{% step %}

### Load the app record

Loads the app record from the App Store Connect API.
{% endstep %}

{% step %}

### Resolve supported platforms

Resolves supported platforms from App Store version data.
{% endstep %}

{% step %}

### Import product rows

Imports one product row per supported platform.
{% endstep %}

{% step %}

### Sync App Store state and version information

Syncs the current App Store state and version information for each imported row.
{% endstep %}
{% endstepper %}

The current Apple status mapping includes states such as:

* `PREPARE_FOR_SUBMISSION`
* `WAITING_FOR_REVIEW`
* `IN_REVIEW`
* `PENDING_DEVELOPER_RELEASE`
* `READY_FOR_SALE`
* `REJECTED`
* `METADATA_REJECTED`
* `INVALID_BINARY`
* `DEVELOPER_REMOVED_FROM_SALE`
* `REMOVED_FROM_SALE`

## Sources

* [App Store Connect API overview](https://developer.apple.com/documentation/appstoreconnectapi)
* [Creating API Keys for App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api)
* [Generating Tokens for API Requests](https://developer.apple.com/documentation/appstoreconnectapi/generating-tokens-for-api-requests)
* [App Store Connect API get started guide](https://developer.apple.com/help/app-store-connect/get-started/app-store-connect-api)
