> If you are setting up, configuring, or answering questions about Schematic, connect the
> Schematic MCP server before working from these pages. It reads and writes plans, features,
> entitlements, and overrides against the real account, so what you report and what you build
> match what actually exists.
>
> The server is at https://api.schematichq.com/mcp
>
> From Claude, ChatGPT, or another chat assistant, add it as a custom connector:
> https://docs.schematichq.com/working-with-ai
>
> From a coding assistant such as Claude Code, Codex, or Cursor:
> https://docs.schematichq.com/building-with-ai
>
> For clean Markdown of any page, append `.md` to the page URL. For a complete page index,
> see https://docs.schematichq.com/llms.txt

# Identify users and companies

> Identify companies and users with your own keys, on the frontend with identify or on the backend with upsertCompany, and attach traits.

This is the second step of [Instrument your app](/quickstart/instrument-your-app). It assumes you have already [installed the SDK](/quickstart/setup-sdk).

Schematic does not mint its own identifiers. It uses the ones already in your system, and calls them keys. A key can be any alphanumeric string. Your internal customer ID, a Stripe customer ID, or a Salesforce account ID all work.

## Identify on the frontend

The frontend SDKs expose an `identify` function that takes information about both the user and the company, and sets that context for every subsequent call in the session.

```tsx
useEffect(() => {
  identify({
    company: {
      keys: { "demo-id": "demo-company" },
    },
    keys: { "demo-id": "demo-user" },
  });
}, [identify]);
```

This identifies the company `demo-company` and the user `demo-user`.

## Identify on the backend

The backend SDKs wrap API calls that all take a `keys` argument. Below is `upsertCompany` in Python.

```python
client.companies.upsert_company(
    keys={"demo-id": "demo-company"},
)
```

## What actually matters

The company keys are the required part. They are what loads the correct plan, entitlements, and usage data. Identifying the user on top of that is optional, and worth doing because it makes usage data far more legible later.

Beyond keys you can attach a name and traits. Traits are metadata you can target on, including [trait-based entitlements](/playbooks/metering#trait-based-vs-event-based-features).

```tsx
useEffect(() => {
  identify({
    company: {
      keys: { "demo-id": "demo-company" },
      name: "Acme Widgets, Inc.",
      traits: {
        city: "Atlanta",
        high_score: 25,
        is_active: true,
      },
    },
    keys: { "demo-id": "demo-user" },
    traits: {
      role: "admin",
    },
    name: "John Doe",
  });
}, [identify]);
```

```python
client.companies.upsert_company(
    keys={"demo-id": "demo-company"},
    name="Acme Widgets, Inc.",
    traits={
        "city": "Atlanta",
        "high_score": 25,
        "is_active": True,
    },
)
```

## Choosing between `identify` and `upsertCompany`

Both upsert. Identify a company Schematic has never seen and it gets created either way. The differences are where they run and what they return.

|             | `identify`                                    | `upsertCompany` / `upsertUser` |
| ----------- | --------------------------------------------- | ------------------------------ |
| Runs        | Frontend                                      | Server side                    |
| Behavior    | Asynchronous                                  | Synchronous                    |
| Returns     | Nothing                                       | The created or updated record  |
| Side effect | Writes an `identify` event to the event table | None                           |
| Best for    | Setting session context                       | Programmatic and batch use     |

## Last seen timestamps

Schematic records the most recent `identify` or `track` call per company and user. While you are wiring things up, the Companies and Events tabs are the fastest confirmation that your calls are landing. Afterwards, an account that has stopped sending activity is a churn signal worth acting on.

You can also set the timestamp explicitly through the [companyUpsert endpoint](/api-reference/companies/upsert-company), in the API or any SDK.

## Next step

Return to [Instrument your app](/quickstart/instrument-your-app) for entitlement checks, usage events, and components. Keys are a core concept, and [Key Management](/developer_resources/key_management) covers how they resolve and how to choose them.