> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# User identity

> Tie FlowPilot activity to your own account IDs, attach your own attributes, and see what the SDK reports about each device.

FlowPilot gives every install a stable anonymous ID and uses it to attribute flows, experiments, and revenue. Call `identify` to connect that install to your own account ID, and `setUserAttributes` to attach anything you know that FlowPilot cannot see.

Everything here shows up on the [Users page](/dashboard/users).

## identify

```swift theme={null}
FlowPilot.identify(currentUser.id)
```

Call it after `configure(...)`, as soon as you know who the person is — typically right after sign-in or when you restore a session at launch.

From that point on, events are attributed to your ID instead of the anonymous one. The anonymous ID is kept as an **alias**, so the person's history from before they signed up stays attached to them rather than appearing as a second, unrelated user.

A flow already on screen keeps the ID it started with, so a session's funnel is never split across two identities.

## reset

```swift theme={null}
FlowPilot.reset()
```

Call it on logout. Both the account ID and the anonymous ID are cleared and a fresh anonymous identity is minted, so the next person to sign in on a shared device does not inherit the previous account's history.

## setUserAttributes

```swift theme={null}
FlowPilot.setUserAttributes([
    "plan": "trial",
    "campaign": "spring_sale",
    "signup_days": 3,
])
```

Attributes appear on the person's profile in the dashboard under **Custom attributes**.

They are **merged**, not replaced — a call that sets only `plan` leaves `campaign` alone. Pass `NSNull()` as a value to clear a key:

```swift theme={null}
FlowPilot.setUserAttributes(["campaign": NSNull()])
```

Safe to call as often as you like. The SDK fingerprints the report and drops it before it reaches the network when nothing has changed, so calling this from a view body costs one request per launch, not one per frame.

This is where ad attribution belongs. FlowPilot cannot see an ad network, a referrer, or a plan tier your own backend owns — your app can, and this is the channel for it.

<Note>
  Attributes are limited to 100 keys, with each value capped at 512 characters. Nested objects are stored as JSON text.
</Note>

## What the SDK reports about the device

Once per app session, the SDK sends a device report. It is sent separately from analytics events rather than attached to them, so a person's event count is not inflated by a description of their phone.

| Reported           | Examples                                                                     |
| ------------------ | ---------------------------------------------------------------------------- |
| Platform and build | `ios`, SDK version, app version, build number, bundle ID                     |
| Device             | OS version, hardware model (`iPhone16,2`), manufacturer, interface type      |
| Identity           | Vendor device ID, install date                                               |
| Region             | Device country, locale, language, time zone and offset                       |
| Store              | Storefront country, storefront currency, environment (production or sandbox) |
| Conditions         | Interface style (light or dark), low power mode, simulator, debug build      |

### What it never collects

* **No advertising identifier.** The SDK does not read the IDFA and never triggers an App Tracking Transparency prompt. Adding FlowPilot cannot cause your app to ask for tracking permission.
* **No location.**
* **Nothing personal.** No contacts, no email, no name. If FlowPilot knows who someone is, it is because you called `identify`.

The only device identifier collected is `identifierForVendor`, which is scoped to you as a vendor, requires no permission, and resets when the app is uninstalled.

### Absent, not guessed

Anything the SDK cannot read is left out of the report entirely. It is never sent as an empty string or `"unknown"`, and an absent value never overwrites a value the server already had — so a report taken before the storefront resolves cannot blank a storefront recorded on a previous launch.

## When reports are sent

| Moment                   | What happens                                                                                  |
| ------------------------ | --------------------------------------------------------------------------------------------- |
| `configure(...)`         | One report, in the background, at utility priority. Nothing on your launch path waits for it. |
| `identify(...)`          | An immediate report, so the alias link is written at the moment the person signs up.          |
| `setUserAttributes(...)` | A report, unless nothing has changed since the last one.                                      |

Every report is fire-and-forget. A failed one is dropped silently and retried on the next launch; it never surfaces an error, blocks a flow, or affects analytics ingestion.
