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

# Upsert user

> POST /api/v1/users &mdash; identify or update a user.

Upserts a user. Same call works for first-touch and updates — the backend deduplicates by `external_user_id`.

<Snippet file="auth-headers.mdx" />

## Request

```http theme={null}
POST /api/v1/users HTTP/1.1
Host: api.vibefollow.com
Authorization: Bearer sk_live_••••
Content-Type: application/json
Idempotency-Key: 7b6c7e9e-2c2d-4a8d-9d3a-4f3d2c1b0a99
```

### Body

```json theme={null}
{
  "external_user_id": "usr_42",
  "email": "jane@acme.io",
  "traits": {
    "name": "Jane Doe",
    "plan": "trial",
    "signupDate": "2026-05-17T14:00:00Z",
    "company": "Acme Inc.",
    "role": "engineering_lead"
  }
}
```

<ParamField body="external_user_id" type="string" required>
  Your primary key for the user. Stable across re-identifies.
</ParamField>

<ParamField body="email" type="string">
  Email address. Stored in a dedicated column for fast lookup and PII access control.
</ParamField>

<ParamField body="traits" type="object">
  Trait bag. Known keys (`name`, `plan`, `signupDate`, `company`, `role`) are stored in dedicated columns; any other key flows into the `traits` JSON column.
</ParamField>

## Response

```http theme={null}
HTTP/1.1 202 Accepted
Content-Type: application/json
```

```json theme={null}
{ "data": { "accepted": true } }
```

<ResponseField name="data.accepted" type="boolean">
  Always `true` on success. The upsert is enqueued and processed asynchronously — downstream views (audiences, etc.) reflect the new state within a few hundred milliseconds.
</ResponseField>

## Examples

<CodeGroup>
  ```ts Node (SDK) theme={null}
  import { VibeFollow } from '@vibefollow/sdk';

  const vf = new VibeFollow({ apiKey: process.env.VIBEFOLLOW_API_KEY! });

  await vf.users.identify('usr_42', {
    email: 'jane@acme.io',
    name: 'Jane Doe',
    plan: 'trial',
  });
  ```

  ```bash cURL theme={null}
  curl https://api.vibefollow.com/api/v1/users \
    -X POST \
    -H "Authorization: Bearer $VIBEFOLLOW_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
      "external_user_id": "usr_42",
      "email": "jane@acme.io",
      "traits": { "name": "Jane Doe", "plan": "trial" }
    }'
  ```
</CodeGroup>

## Common errors

<AccordionGroup>
  <Accordion title="401 auth_required" icon="key">
    Missing or malformed `Authorization` header.
  </Accordion>

  <Accordion title="403 forbidden" icon="shield-halved">
    API key valid but not authorised for this resource.
  </Accordion>

  <Accordion title="422 validation_failed" icon="circle-exclamation">
    Body failed schema validation; `errors[0].field` indicates the offending field.
  </Accordion>

  <Accordion title="429 rate_limited" icon="gauge-high">
    Wait `Retry-After` seconds.
  </Accordion>

  <Accordion title="5xx server_error" icon="server">
    Retry — the `Idempotency-Key` prevents duplicates.
  </Accordion>
</AccordionGroup>
