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

# Send a custom event

> Record something the player never saw (a lead form, a quiz completion, an upsell) and land it on the viewer's session.

A custom event is not a conversion. `value` is reported on its own and never feeds revenue, ROAS, EPC, or RPV. Send money through [Record a conversion](/api-reference/conversions/create) instead.

Requires a token with the `events:write` scope. The legacy `tp_` workspace key is rejected here.

Server: `https://e.trackplay.io`



## OpenAPI

````yaml /api-reference/openapi.json post /v1/event
openapi: 3.1.0
info:
  title: TrackPlay API
  description: >-
    Send conversions and events to TrackPlay, and read them back. Authenticated
    with scoped tokens you mint in the dashboard.
  version: 1.0.0
servers:
  - url: https://app.trackplay.io/api/v1
    description: App API. Conversions, videos, webhooks.
  - url: https://e.trackplay.io
    description: Events API. High-volume event ingest.
security:
  - bearerAuth: []
paths:
  /v1/event:
    post:
      tags:
        - Events
      summary: Send a custom event
      description: >-
        Record something the player never saw (a lead form, a quiz completion,
        an upsell) and land it on the viewer's session.


        A custom event is not a conversion. `value` is reported on its own and
        never feeds revenue, ROAS, EPC, or RPV. Send money through [Record a
        conversion](/api-reference/conversions/create) instead.


        Requires a token with the `events:write` scope. The legacy `tp_`
        workspace key is rejected here.


        Server: `https://e.trackplay.io`
      operationId: createEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomEventRequest'
            examples:
              attributed:
                summary: Event tied to a known session
                value:
                  event: quiz_completed
                  session_id: 3b1e5c90-77aa-4f2d-8c31-6de0a9b41c22
                  value: 10
                  event_id: quiz_10482
                  properties:
                    plan: pro
                    score: '8'
              identity:
                summary: No session. Resolve the viewer by email
                value:
                  event: lead_captured
                  email: buyer@example.com
                  event_id: lead_88213
      responses:
        '200':
          description: Recorded and attributed to a session.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomEventResponse'
              examples:
                attributed:
                  value:
                    ok: true
                    event: quiz_completed
                    event_id: quiz_10482
                    attributed: true
                    matched_by: session_id
        '202':
          description: >-
            Recorded, but no session matched. The event is kept in the
            Unattributed bucket. It is never dropped, and never guessed at. If
            you see this, the identifier you sent did not resolve to a viewer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomEventResponse'
              examples:
                unattributed:
                  value:
                    ok: true
                    event: quiz_completed
                    event_id: quiz_10482
                    attributed: false
                    matched_by: null
        '400':
          description: >-
            `INVALID_PAYLOAD`: a field failed validation, or an unknown field
            was sent.


            `INVALID_EVENT_NAME`: `event` contained no usable characters after
            normalization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
        '401':
          description: |-
            `AUTH_REQUIRED`: no token sent.

            `INVALID_TOKEN`: the token is invalid, expired, or revoked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
        '403':
          description: '`INSUFFICIENT_SCOPE`: the token does not carry `events:write`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
        '422':
          description: >-
            `TOO_MANY_EVENT_NAMES`: this workspace has used its 100 distinct
            event names. Event names must be a small fixed set. Put the varying
            part in `properties`, not in the name.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
        '429':
          description: >-
            `RATE_LIMITED`: 600 events per minute per workspace, or 120 per
            minute per IP.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
        '500':
          description: >-
            `WRITE_FAILED`: the event could not be written. Retry with the same
            `event_id`. The retry will not double count.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
        '503':
          description: >-
            `AUTH_UNAVAILABLE`: token verification is temporarily unavailable.
            This is never a rejection of your token. Retry shortly.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
      servers:
        - url: https://e.trackplay.io
components:
  schemas:
    CustomEventRequest:
      type: object
      required:
        - event
      additionalProperties: false
      properties:
        event:
          type: string
          maxLength: 120
          description: >-
            The event name, e.g. `quiz_completed`. Lowercased and reduced to
            `[a-z0-9_-]`, then truncated to 64 characters. A workspace may use
            100 distinct names, so keep the set small and fixed.
          examples:
            - quiz_completed
        session_id:
          type: string
          maxLength: 100
          description: >-
            The viewer's session. This is the best identifier to send. Read it
            from the player with `window.trackplay.getSessionId()`.
        device_id:
          type: string
          maxLength: 100
          description: Fallback if you have no session.
        profile_id:
          type: string
          maxLength: 100
          description: Fallback if you have no session or device.
        email:
          type: string
          maxLength: 255
          description: Identity fallback. Hashed on arrival and never stored raw.
        external_id:
          type: string
          maxLength: 120
          description: >-
            Identity fallback. Your own customer ID, if you have already linked
            it.
        value:
          type: number
          description: >-
            A number to report on this event. **Not revenue.** It never feeds
            revenue, ROAS, EPC, or RPV. Clamped to plus or minus 1,000,000,000.
        video_code:
          type: string
          maxLength: 100
          description: >-
            Credit the event to a specific video. Wins over the session's last
            video.
        event_id:
          type: string
          maxLength: 120
          description: >-
            Your idempotency key. Send one. A retry with the same `event_id`
            collapses into the same row instead of double counting. Omit it and
            delivery is at-least-once, so duplicates are possible.
        properties:
          type: object
          additionalProperties: true
          description: >-
            Up to 20 extra key/value pairs. Keys are capped at 40 characters,
            values at 100. This is where varying data belongs. Never in the
            event name.
        timestamp:
          type: string
          format: date-time
          description: When the event happened. Defaults to arrival time.
    CustomEventResponse:
      type: object
      properties:
        ok:
          type: boolean
        event:
          type: string
          description: The normalized event name that was stored.
        event_id:
          type: string
          description: Your `event_id`, or the one generated for you.
        attributed:
          type: boolean
          description: Whether the event landed on a viewer's session.
        matched_by:
          type:
            - string
            - 'null'
          enum:
            - session_id
            - device_id
            - profile_id
            - identity
            - null
          description: Which identifier resolved the viewer. `null` when nothing matched.
    EventError:
      type: object
      properties:
        error:
          type: string
          description: A stable error code, e.g. `INSUFFICIENT_SCOPE`.
        message:
          type: string
          description: What went wrong, in plain English.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        A scoped token minted in **Settings → API tokens**. Send it as
        `Authorization: Bearer tplt_…` or `X-API-Key: tplt_…`.

````