# identify

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

```ts
await cow.identify({
  identifiers: { userId: "user_1" },
  traits: { email: "ada@acme-demo.io", plan: "pro" },
});
```

| Field | Required | Notes |
| --- | --- | --- |
| `identifiers` | yes | Who the write is about. At least one; two in one call link them, within the app you sent through. |
| `traits` | no | Any JSON object. Merged, not replaced. |
| `consent` | no | Consent per purpose, keyed by path: `marketing`, `transactional`, or a name your repo declares, such as `marketing.newsletter`. See [Consent](#consent). |
| `timestamp` | no | ISO 8601 UTC, for a historical write. Never in the future. |
| `sourceId` | on the wire | The client's, unless the call names one. |

## Identifiers

`identifiers` is a map from kind to value: `userId`, `anonymousId`, `email`, `clerkId`. Cowliss owns the profile id (`usr_…`) and resolves your map to it, so you never invent one and never send one back.

```ts
// An anonymous visitor.
await cow.identify({ identifiers: { anonymousId: "anon_9f3" } });

// They sign up: naming both links them, history included. No alias call.
await cow.identify({
  identifiers: { anonymousId: "anon_9f3", userId: "user_1" },
  traits: { email: "ada@acme-demo.io" },
});
```

Linking stops at the app. The `sourceId` on the call says which app the write belongs to, and a profile belongs to one app, so an identifier only ever joins identifiers under that app. Send the same `userId` to two of your products and you have two profiles, one per product, each with its own traits, consent and history.

Traits **merge**: keys you send are written, keys you leave out are untouched, and a `null` deletes the key. Writes are synchronous, so once the call has answered, segment membership is current and any automation it triggered has started. The full resolution rules, including what happens when two known people collide, are in [Identity](/tracking/identity/).

## Consent

`consent` is a merge patch over the profile's answers, keyed by purpose path. A key you leave out keeps its answer, and a name your app's `cow.json` does not declare is a 422, with nothing written.

```ts
// The signup form's newsletter box, ticked.
await cow.identify({
  identifiers: { userId: "user_1" },
  consent: { "marketing.newsletter": true },
});
```

An unanswered purpose reads as its root decides: a marketing one is off, a transactional one is on. A marketing email reaches someone only when both its name and `marketing` are on, and granting a name turns its root on, so a signup that means yes to the newsletter sends one key. Turning `marketing` or `transactional` off turns off every name under it that your app declares, so a later grant of one name brings back that name and nothing else. Receipts and other mail sent as bare `transactional` are never asked. See [Email templates](/emails/#purposes).

## Language

Store a person's language as the `locale` trait, a BCP 47 tag such as `fr` or `fr-CA`:

```ts
await cow.identify({
  identifiers: { userId: "user_1" },
  traits: { locale: "fr" },
});
```

It is a naming convention, not a setting: Cowliss neither checks nor reads it. Your automations read it to choose a template, as in [Sending in several languages](/emails/#sending-in-several-languages).

## Reserved traits

`emailVerifiedAt` and `emailVerifiedAddress` are Cowliss's to write: sending them is a 422. See [Email templates](/emails/#verifylink).
