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

# Identify a user

> Registers or updates a user (customer) in your organization. Call this when a user signs up or when their profile changes. You must identify a user before tracking events for them.



## OpenAPI

````yaml /api-reference/openapi.json post /identify
openapi: 3.1.0
info:
  title: Retain API
  description: >-
    Retain is a churn prevention and customer analytics platform. Use the API to
    identify users and track events from your application.
  version: 1.0.0
servers:
  - url: https://api.retain.so
    description: Production
security:
  - writeKey: []
paths:
  /identify:
    post:
      tags:
        - identify
      summary: Identify a user
      description: >-
        Registers or updates a user (customer) in your organization. Call this
        when a user signs up or when their profile changes. You must identify a
        user before tracking events for them.
      operationId: identify
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IdentifyBody'
            example:
              userId: user_123
              email: jane@example.com
              properties:
                name: Jane Doe
                avatar: https://example.com/avatar.jpg
      responses:
        '201':
          description: 'User identified successfully. Response body: `{ "success": true }`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: >-
            **`validation_error`** (HTTP 400) — Message varies; describes which
            field failed validation and why. Common causes: missing `userId` or
            `email`, invalid email format, invalid URL in `avatar`, or related
            payload issues. See [Error
            codes](/api-reference/errors#error-codes).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: >-
            **`invalid_write_key`** (HTTP 401) — Missing or invalid Project
            Token. Send `Authorization: Basic rk-…` with a valid token. See
            [Error codes](/api-reference/errors#error-codes).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvalidWriteKeyErrorResponse'
        '429':
          description: >-
            **`rate_limit_exceeded`** (HTTP 429) — Too many requests; slow down
            or batch traffic. See [Error
            codes](/api-reference/errors#error-codes).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitErrorResponse'
        '500':
          description: >-
            **`application_error`** (HTTP 500) — Unexpected server error; retry
            later. See [Error codes](/api-reference/errors#error-codes).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApplicationErrorResponse'
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl -X POST https://api.retain.so/identify \
              -H "Content-Type: application/json" \
              -H "Authorization: Basic $RETAIN_WRITE_KEY" \
              -d '{
                "userId": "user_123",
                "email": "jane@example.com",
                "properties": {
                  "name": "Jane Doe",
                  "avatar": "https://example.com/avatar.jpg"
                }
              }'
        - lang: javascript
          label: Node.js
          source: |-
            import { Retain } from '@retain-so/sdk';

            const retain = new Retain(process.env.RETAIN_WRITE_KEY);

            retain.identify({
              userId: 'user_123',
              email: 'jane@example.com',
              properties: {
                name: 'Jane Doe',
                avatar: 'https://example.com/avatar.jpg',
                // Add anything else about the user here
              },
            });
        - lang: python
          label: Python
          source: |-
            import os
            import requests

            requests.post(
                'https://api.retain.so/identify',
                headers={
                    'Content-Type': 'application/json',
                    'Authorization': f'Basic {os.environ["RETAIN_WRITE_KEY"]}',
                },
                json={
                    'userId': 'user_123',
                    'email': 'jane@example.com',
                    'properties': {
                        'name': 'Jane Doe',
                        'avatar': 'https://example.com/avatar.jpg',
                    },
                },
            )
        - lang: php
          label: PHP
          source: |-
            <?php
            $payload = json_encode([
                'userId' => 'user_123',
                'email' => 'jane@example.com',
                'properties' => [
                    'name' => 'Jane Doe',
                    'avatar' => 'https://example.com/avatar.jpg',
                ],
            ]);
            $ch = curl_init('https://api.retain.so/identify');
            curl_setopt_array($ch, [
                CURLOPT_POST => true,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HTTPHEADER => [
                    'Content-Type: application/json',
                    'Authorization: Basic ' . getenv('RETAIN_WRITE_KEY'),
                ],
                CURLOPT_POSTFIELDS => $payload,
            ]);
            curl_exec($ch);
            curl_close($ch);
        - lang: ruby
          label: Ruby
          source: >-
            require 'net/http'

            require 'json'


            uri = URI('https://api.retain.so/identify')

            req = Net::HTTP::Post.new(uri)

            req['Content-Type'] = 'application/json'

            req['Authorization'] = "Basic #{ENV.fetch('RETAIN_WRITE_KEY')}"

            req.body = {
              userId: 'user_123',
              email: 'jane@example.com',
              properties: {
                name: 'Jane Doe',
                avatar: 'https://example.com/avatar.jpg',
              },
            }.to_json


            Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
            http.request(req) }
        - lang: java
          label: Java
          source: >-
            import java.net.URI;

            import java.net.http.HttpClient;

            import java.net.http.HttpRequest;

            import java.net.http.HttpResponse;


            String json = """
              {
                "userId": "user_123",
                "email": "jane@example.com",
                "properties": {
                  "name": "Jane Doe",
                  "avatar": "https://example.com/avatar.jpg"
                }
              }
              """;

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.retain.so/identify"))
                .header("Content-Type", "application/json")
                .header("Authorization", "Basic " + System.getenv("RETAIN_WRITE_KEY"))
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();

            HttpClient.newHttpClient().send(request,
            HttpResponse.BodyHandlers.ofString());
components:
  schemas:
    IdentifyBody:
      type: object
      required:
        - userId
        - email
      properties:
        userId:
          type: string
          description: Unique identifier for the user in your system (string or number)
        email:
          type: string
          format: email
          description: User's email address
        properties:
          type: object
          additionalProperties: true
          description: >-
            Customer traits, shallow-merged into existing ones. Reserved keys
            power dashboard features; any other key is stored as a custom trait.
          properties:
            name:
              type: string
              description: User's display name
            avatar:
              type: string
              format: uri
              description: URL to the user's avatar image
            plan:
              type: string
              description: >-
                Pricing plan (e.g. 'starter', 'pro'). Enables retention cohorts
                by plan
            acquisitionChannel:
              type: string
              description: >-
                Where the customer came from (e.g. 'organic', 'referral').
                Enables retention cohorts by channel
            companySize:
              type: string
              description: >-
                Company size bucket (e.g. '1-10', '11-50'). Enables retention
                cohorts by company size
            segment:
              type: string
              description: >-
                ICP segment of the company (e.g. 'agency', 'creator'). Enables
                retention cohorts by segment
        isAccountOwner:
          type: boolean
          description: Whether this user is the account owner
        account:
          type: object
          description: Account/workspace this user belongs to
          required:
            - id
            - name
          properties:
            id:
              type: string
              description: Unique identifier for the account
            name:
              type: string
              description: Account display name
            avatar:
              type: string
              format: uri
              description: URL to the account avatar image
    SuccessResponse:
      type: object
      required:
        - success
      properties:
        success:
          type: boolean
          const: true
          description: Indicates the request was successful
      example:
        success: true
    ValidationErrorResponse:
      type: object
      required:
        - message
        - statusCode
        - name
      properties:
        message:
          type: string
          description: >-
            Describes which field failed validation and why (varies per
            request).
        statusCode:
          type: integer
          const: 400
        name:
          type: string
          const: validation_error
          description: Always `validation_error` for HTTP 400.
      example:
        message: email must be a valid email address
        statusCode: 400
        name: validation_error
    InvalidWriteKeyErrorResponse:
      type: object
      required:
        - message
        - statusCode
        - name
      properties:
        message:
          type: string
          description: Missing or invalid Project Token.
        statusCode:
          type: integer
          const: 401
        name:
          type: string
          const: invalid_write_key
          description: Always `invalid_write_key` for HTTP 401.
      example:
        message: Missing or invalid Project Token.
        statusCode: 401
        name: invalid_write_key
    RateLimitErrorResponse:
      type: object
      required:
        - message
        - statusCode
        - name
      properties:
        message:
          type: string
          description: Rate limit exceeded.
        statusCode:
          type: integer
          const: 429
        name:
          type: string
          const: rate_limit_exceeded
          description: Always `rate_limit_exceeded` for HTTP 429.
      example:
        message: Too many requests. Please limit the number of requests per second.
        statusCode: 429
        name: rate_limit_exceeded
    ApplicationErrorResponse:
      type: object
      required:
        - message
        - statusCode
        - name
      properties:
        message:
          type: string
          description: Unexpected server error.
        statusCode:
          type: integer
          const: 500
        name:
          type: string
          const: application_error
          description: Always `application_error` for HTTP 500.
      example:
        message: An unexpected error occurred, please try again later.
        statusCode: 500
        name: application_error
  securitySchemes:
    writeKey:
      type: http
      scheme: bearer
      bearerFormat: Write Key
      description: >-
        Your organization's Project Token (starts with rk-). Get it from
        Settings.

````