iOS · Android · SDK

# Identity and data

How the SDK identifies app users, connects them to your accounts, and what it stores and sends.

## Anonymous by default

On first configuration, the SDK creates an anonymous identity and stores it on the device. Later launches reuse it. Apps without their own accounts need nothing else and should not call `identify`. Feature plan votes belong to the identity; FAQs and alerts are the same for every identity.

## Sign in

Pass your account ID during configuration when the user is already signed in at launch, or identify the user after sign-in:

| Platform | At configuration | After sign-in |
| --- | --- | --- |
| iOS | `try Joe.configure(withAPIKey:appUserID:)` | `try Joe.identify(appUserID:)` |
| Android | `Joe.configure(applicationContext, apiKey, appUserId = …)` | `Joe.identify(appUserId)` |

The first identification keeps the anonymous ID, so votes cast before sign-in carry over to the account. Identifying the same account again changes nothing, and configuring without an app user ID keeps the stored identity, including a signed-in account. Devices identified with the same app user ID share their votes.

## Choose an app user ID

- **Stable and unique:** an ID that belongs to exactly one account and never changes, such as your account's primary key.
- **Not personal data:** no email address or phone number.
- **Not temporary:** no placeholders, receipt IDs, or purchase SDK IDs that change after a restore, such as anonymous RevenueCat IDs.
- **Not empty:** a blank ID throws `MobileJoeError.invalidAppUserID` on iOS and `IllegalArgumentException` on Android.

## Sign out and switch accounts

After sign-out, call `Joe.logout()`. The SDK continues with new anonymous and device IDs; the account keeps its votes. Identifying a different account logs out first, so you do not need to call `logout()` before switching.

## Identify requests

The SDK sends the identity to Mobile Joe in the background, once per app start and once per identity change. If that fails, the SDK stays usable and retries on the next app start.

## Order of calls

Configure once at app start; further `configure` calls change nothing until the app relaunches. `identify` and `logout()` take effect immediately, in call order, and throw when the identity cannot be stored. `identify` also throws before configuration; `logout()` does too on Android, while on iOS it replaces the stored identity locally.

## Identifiers

| Identifier | Format | Purpose |
| --- | --- | --- |
| Anonymous ID | `$MBJAnonymousID:` followed by a random UUID as 32 hex characters | Identifies the app user, for example for votes. Created without an account. |
| Device ID | `$MBJDeviceID:` followed by a random UUID as 32 hex characters | Identifies the installation of an app user. It is generated by the SDK, not read from the hardware. |
| External ID | `$MBJExternalID:` followed by the first 32 hex characters of the unsalted SHA-256 hash of your app user ID | Optional. Associates the app user with an account in your app across devices. Only set after configuring or identifying with an app user ID. |

The SDK never sends the app user ID itself. The hash is unsalted, so use an opaque account ID.

## Local storage

Both SDKs store exactly one identity: the anonymous ID, the device ID, and the external ID if set.

| Platform | Location | Format |
| --- | --- | --- |
| iOS | `Application Support/MobileJoe/identity.mbj` in the app container | JSON file, written without a Data Protection class |
| Android | Private SharedPreferences file `dev.mobilejoe.identity` | Keys `anonymous_id`, `device_id`, and `external_id` |

The SDKs store nothing else, apart from the iOS [response cache](https://docs.mobilejoe.dev/sdk/identity.md#response-cache).

## Backups

The SDKs do not exclude the identity from backups. Platform rules apply:

- **iOS:** the Application Support directory is part of iCloud and computer backups. To exclude the identity, set `isExcludedFromBackup` on the `Application Support/MobileJoe` directory.
- **Android:** SharedPreferences are part of Auto Backup unless your app disables or limits it. To exclude the identity, add `dev.mobilejoe.identity.xml` in the `sharedpref` domain to your backup rules.

```xml
<exclude domain="sharedpref" path="dev.mobilejoe.identity.xml" />
```

On Android 12 and higher, add the rule to `cloud-backup` and `device-transfer` in your data extraction rules. On earlier versions, add it to your full backup content rules.

## Request metadata

Each request to the API contains these headers:

| Header | iOS | Android |
| --- | --- | --- |
| `Authorization` | Public SDK API key | Public SDK API key |
| `Identity-Anonymous-ID` | Anonymous ID | Anonymous ID |
| `Identity-Device-ID` | Device ID | Device ID |
| `Identity-External-ID` | External ID, only when set | External ID, only when set |
| `Framework-Version` | SDK version | SDK version |
| `App-Version` | `CFBundleShortVersionString` | `versionName` |
| `App-Build-Version` | `CFBundleVersion` | `versionCode` |
| `OS-Name` | `iOS` | `Android` |
| `OS-Version` | System version, for example `18.2.0` | API level, for example `35` |
| `Device-Model` | Model identifier, for example `iPhone16,2` | `Build.MODEL`, for example `Pixel 8` |
| `Device-Brand` | Not sent | `Build.BRAND`, for example `google` |
| `Preferred-Languages` | Up to three languages of `Locale.preferredLanguages` at the first request | Up to three app locales at configuration |
| `Debug-Mode` | `debugMode` passed to `Joe.configure` | `isDebugMode` passed to `Joe.configure` |

Requests also contain the parameters of the operation, such as a search term or a feature plan ID. See the [REST API reference](https://docs.mobilejoe.dev/api.md).

## Response cache

The iOS SDK uses `URLSession.shared`, whose `URLCache` stores API responses in the Caches directory. The system can clear it at any time, and it is not backed up. The Android SDK does not cache responses.
