> ## 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.

# Track an event

> Records an event for a user. The user must have been identified first using the /identify endpoint. Events are used to calculate engagement, health scores, and churn risk.



## OpenAPI

````yaml /api-reference/openapi.json post /track
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:
  /track:
    post:
      tags:
        - track
      summary: Track an event
      description: >-
        Records an event for a user. The user must have been identified first
        using the /identify endpoint. Events are used to calculate engagement,
        health scores, and churn risk.
      operationId: track
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TrackBody'
            example:
              userId: user_123
              event: report_exported
              properties:
                format: csv
              timestamp: '2024-01-01T00:00:00Z'
      responses:
        '201':
          description: 'Event tracked 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
            `event`, invalid event name (e.g. spaces), or invalid payload. See
            [Error codes](/api-reference/errors#error-codes).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackValidationErrorResponse'
        '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'
        '404':
          description: >-
            **`not_found`** (HTTP 404) — User not found; the `userId` was never
            identified. Call [POST
            /identify](/api-reference/identify/identify-a-user) before tracking.
            See [Error codes](/api-reference/errors#error-codes).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '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/track \
              -H "Content-Type: application/json" \
              -H "Authorization: Basic $RETAIN_WRITE_KEY" \
              -d '{
                "userId": "user_123",
                "event": "report_exported",
                "properties": { "format": "csv" }
              }'
        - lang: javascript
          label: Node.js
          source: |-
            import { Retain } from '@retain-so/sdk';

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

            retain.track({
              userId: 'user_123',
              event: 'report_exported',
              properties: { format: 'csv' },
            });
        - lang: python
          label: Python
          source: |-
            import os
            import requests

            requests.post(
                'https://api.retain.so/track',
                headers={
                    'Content-Type': 'application/json',
                    'Authorization': f'Basic {os.environ["RETAIN_WRITE_KEY"]}',
                },
                json={
                    'userId': 'user_123',
                    'event': 'report_exported',
                    'properties': {'format': 'csv'},
                },
            )
        - lang: php
          label: PHP
          source: |-
            <?php
            $payload = json_encode([
                'userId' => 'user_123',
                'event' => 'report_exported',
                'properties' => ['format' => 'csv'],
            ]);
            $ch = curl_init('https://api.retain.so/track');
            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/track')

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

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

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

            req.body = {
              userId: 'user_123',
              event: 'report_exported',
              properties: { format: 'csv' },
            }.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",
                "event": "report_exported",
                "properties": { "format": "csv" }
              }
              """;

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.retain.so/track"))
                .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:
    TrackBody:
      type: object
      required:
        - userId
        - event
      properties:
        userId:
          type: string
          description: User ID (must match the userId used in /identify)
        event:
          type: string
          description: >-
            Event name (e.g. report_exported, video_watched, meeting_scheduled,
            post_created, etc)
        properties:
          type: object
          additionalProperties: true
          description: Optional event metadata
        timestamp:
          type: string
          format: date-time
          description: >-
            Optional. When to record the event in ISO 8601 format (e.g.,
            2024-01-01T00:00:00Z or 2024-01-01). Use for backfilling past
            events. Defaults to now in the organizion timezone when omitted.
    SuccessResponse:
      type: object
      required:
        - success
      properties:
        success:
          type: boolean
          const: true
          description: Indicates the request was successful
      example:
        success: true
    TrackValidationErrorResponse:
      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: event is required
        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
    NotFoundErrorResponse:
      type: object
      required:
        - message
        - statusCode
        - name
      properties:
        message:
          type: string
          description: User not found; identify the user before tracking.
        statusCode:
          type: integer
          const: 404
        name:
          type: string
          const: not_found
          description: Always `not_found` for HTTP 404 on /track.
      example:
        message: >-
          User not found, please identify the user first using the /identify
          endpoint.
        statusCode: 404
        name: not_found
    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.

````