# Identity

> Part of the Cowliss docs. Markdown for every page: append `index.md` to its URL. Site map: /llms.txt

Neither you nor your auth provider owns a profile's id: Cowliss generates it (`usr_…`). What you send instead is **who the write is about**, as named identifiers, on every single call. That is what lets your app code and a Clerk source converge on one person without either side knowing the other's ids.

## Identifiers on every call

`identify` and `track` take an `identifiers` map with at least one entry:

```sh
curl -X POST https://api.cowliss.com/v1/track \
  -H "Authorization: Bearer $COW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "sourceId": "'"$COW_SOURCE_ID"'",
      "event": "viewed_pricing",
      "identifiers": { "anonymousId": "anon_9f3…" },
      "messageId": "msg-…"
    }
  }'
```

The kinds are a fixed enum, grown by code:

| Kind          | What it is |
| ------------- | ---------- |
| `anonymousId` | A pre-signup handle you mint (cookie, device id). |
| `userId`      | Your own id for the user. |
| `clerkId`     | The Clerk user id; this is what a [Clerk source](/tracking/apps/#connecting-clerk) sends. |
| `email`       | Match by address: you are opting into "this mailbox is this person". Addresses are lowercased before matching, so `Ada@Acme-Demo.io` and `ada@acme-demo.io` are one identifier. Mail goes to the `email` trait, and to the newest `email` identifier when the profile has no trait. |

If a Clerk source feeds this app, signing up and logging in are already events (`system.signed_up`, `system.logged_in`), so do not send your own alongside them.

A call with zero identifiers is a 422: nothing could ever reference that profile again. A profile whose identifiers are all `anonymousId` is labelled **anonymous** in the dashboard.

## Resolution: create, claim, merge

Before any write, Cowliss looks up every identifier in the call:

- **None known**: create a profile, claim them all. Anonymous-only traffic therefore accumulates on one anonymous profile per `anonymousId`.
- **Exactly one profile known**: claim the new identifiers for it and write. This is how linking happens: there is no alias call. When your visitor signs up, one `identify` carrying `{ "userId": "user_1", "anonymousId": "anon_9f3…" }` claims the `userId` onto the profile the anonymous history already belongs to, and the whole history is identified from then on.
- **More than one profile known**: the profiles merge. The call names two different people's known identifiers, so Cowliss merges the profiles into one, then writes. Every profile a call can resolve belongs to the same app as the call, so a merge never spans apps.

Linking never rewrites or replays history: events are immutable, and claiming an identifier does not fire automations at past events. The live funnel reacts to live events; the linked history informs segments, timelines, and exports.

## Merging

When a write's identifiers resolve to more than one profile, Cowliss merges them automatically instead of refusing the write. The oldest profile, by `createdAt`, survives: the others get `mergedInto` set, and their identifiers move to the survivor. The survivor keeps its own traits, gains any key it lacked from the merged profiles (oldest merged first), then the incoming write applies on top. Consent is granted after the merge only where every merged profile had granted it, so an unsubscribe on either side survives. Events are never rewritten, the survivor's reads union its merged ids, the same read-time resolution the alias map had. Memberships of the merged profiles are dropped, and the survivor is recomputed. Running automation instances of a merged profile keep running: their activities resolve the profile through `mergedInto`, so sends land on the survivor. A `system.profiles_merged` system event lands on the survivor's timeline, carrying the merged profile ids.

Merging never retro-triggers automations: a merge changes reads and the one write that caused it, nothing else. There is no un-merge in v1. A request naming a merged-away profile id gets a 307 redirect to `/v1/users/:survivorId`.

## Identifiers are unique per app

Identifiers are unique per `(orgId, appId, kind, value)`: the same `clerkId` always resolves to the same profile, whichever of that app's sources it arrives through. The app comes from the `sourceId` on the call, so your calls never name it.

A profile belongs to one app, and so identity stops at the app's edge. The same email address signing up on two of your products is two profiles with two `usr_` ids, and no call, no identifier kind and no merge ever joins them. If you want your own products to share an identity, join them at your own edge and send the id you chose as a `userId` to both.

Identity never crosses organizations either, so a second organization is where to send test traffic that would otherwise claim a real person's identifiers. See [Your repo](/guides/repo/).

## Erasure covers the identifiers

`DELETE /v1/users/:profileId` removes the profile, its identifiers, traits, suppression state, and every one of its events, and stops any automation it is in. Erasing a survivor also removes every merged-away profile id and every identifier row that ever pointed at them, so a merge never leaves an erased person's data behind under another id. See the [users reference](/reference/api/users/) for erasure and export.
