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

# Track engagement events (public, publishable tracking key)

> Public CORS-enabled receptor for browser engagement events. Authenticates with a publishable `ek_live_…` tracking key sent as `?key=` or the body `key` field - partner bearer keys are not accepted here. Accepts a batch (`events: [...]`, max 20) or a single event spread at the top level. Events are accepted per-item: the 202 envelope reports `accepted`/`duplicate`/`rejected` per event.



## OpenAPI

````yaml https://api.develop.fourdos.dev/openapi/partner.json post /v1/track
openapi: 3.1.0
info:
  title: 4D OS Partner API
  version: 0.1.0
  description: >-
    REST API for 4D OS partners. Authenticate with your organization API key:
    `Authorization: Bearer <key>`.


    Requests and responses are JSON with `snake_case` field names. Errors share
    one envelope: `{ "error": { "code", "status", "message", "details"? } }`.


    Mutating endpoints that declare it accept an `Idempotency-Key` header so
    retries are safe.


    Bulk-migrating members? See the [Migrating to 4D
    guide](https://docs.fourdos.dev/walkthroughs/migrate-existing-members) for
    the canonical import schema, a worked example, and the `/v1/imports`
    sequence.
servers: []
security:
  - partnerApiKey: []
tags:
  - name: Users
    description: Create, read, and manage users and their org membership
  - name: Imports
    description: Bulk member import jobs (staging → validate → commit)
  - name: Engagement
    description: Browser event tracking (publishable keys) and tracking-key management
paths:
  /v1/track:
    post:
      tags:
        - Engagement
      summary: Track engagement events (public, publishable tracking key)
      description: >-
        Public CORS-enabled receptor for browser engagement events.
        Authenticates with a publishable `ek_live_…` tracking key sent as
        `?key=` or the body `key` field - partner bearer keys are not accepted
        here. Accepts a batch (`events: [...]`, max 20) or a single event spread
        at the top level. Events are accepted per-item: the 202 envelope reports
        `accepted`/`duplicate`/`rejected` per event.
      operationId: trackEngagementEvents
      parameters:
        - name: key
          in: query
          required: false
          description: Publishable tracking key (alternative to the body `key` field)
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TrackBody'
      responses:
        '202':
          description: Per-event outcomes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackResponse'
        '401':
          description: Missing, unknown, or revoked tracking key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The request Origin is not on the key's allowlist
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Track rate limit exceeded for this key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - trackingKey: []
components:
  schemas:
    TrackBody:
      anyOf:
        - type: object
          properties:
            key:
              type: string
            events:
              minItems: 1
              maxItems: 20
              type: array
              items:
                type: object
                properties:
                  member_external_id:
                    type: string
                    minLength: 1
                  user_id:
                    type: string
                    format: uuid
                    pattern: >-
                      ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
                  action:
                    type: string
                    minLength: 1
                    maxLength: 64
                  tags:
                    minItems: 1
                    maxItems: 10
                    type: array
                    items:
                      type: string
                  event_id:
                    type: string
                    minLength: 1
                    maxLength: 128
                  occurred_at:
                    type: string
                    format: date-time
                    pattern: >-
                      ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
                  metadata:
                    type: object
                    propertyNames:
                      type: string
                    additionalProperties: {}
                required:
                  - tags
          required:
            - events
        - type: object
          properties:
            member_external_id:
              type: string
              minLength: 1
            user_id:
              type: string
              format: uuid
              pattern: >-
                ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
            action:
              type: string
              minLength: 1
              maxLength: 64
            tags:
              minItems: 1
              maxItems: 10
              type: array
              items:
                type: string
            event_id:
              type: string
              minLength: 1
              maxLength: 128
            occurred_at:
              type: string
              format: date-time
              pattern: >-
                ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
            metadata:
              type: object
              propertyNames:
                type: string
              additionalProperties: {}
            key:
              type: string
          required:
            - tags
    TrackResponse:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              status:
                type: string
                enum:
                  - accepted
                  - duplicate
                  - rejected
              id:
                type: string
                description: Stored engagement event id (status `accepted`)
              event_id:
                type: string
                description: Client-supplied dedupe id, echoed back
              reason:
                type: string
                description: Why the event was rejected (status `rejected`)
            required:
              - status
      required:
        - results
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
            status:
              type: integer
            message:
              type: string
            details:
              type: object
              additionalProperties: true
          required:
            - code
            - status
            - message
      required:
        - error
  securitySchemes:
    partnerApiKey:
      type: http
      scheme: bearer
      description: >-
        Organization API key (`org_…`), sent as `Authorization: Bearer <key>`.
        Keys are scoped to one organization; every request operates within that
        tenant.
    trackingKey:
      type: apiKey
      in: query
      name: key
      description: >-
        Publishable engagement tracking key (`ek_live_…`), sent as `?key=` or
        the body `key` field. Safe to embed in client code - abuse is bounded by
        the per-key origin allowlist and rate limit.

````