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

# Export Custom Events

> Bulk export of custom events from a half-open `occurredAt` range, ordered by `occurredAt` ascending. Each event's own payload is returned as a JSON object under `data`. The contact's current email and user ID are resolved per row on a best-effort basis (`contactEmail` and `userId` may be `null` if the contact no longer exists).

Send `occurredAt` on the first call, then pass the opaque cursor returned as `pagination.nextCursor` back as `cursor` on every later call. A `null` cursor means there are no more results. Persist the last non-null cursor between syncs to resume. The range may be arbitrarily long and is walked in bounded segments internally, so an empty page with a non-null cursor means there are more results to come rather than the end of the export.

> **Request access.** This endpoint is not enabled by default, contact the Conversion team to request access. Calls from accounts without it return `403 export_not_enabled`.



## OpenAPI

````yaml /openapi.json post /v2/exports/custom-events
openapi: 3.1.0
info:
  title: Conversion API
  version: 2.0.0
  description: >-
    The Conversion API lets you programmatically manage contacts, track custom
    events, and manage their associated data.
servers:
  - url: https://pub-api.conversion.ai/api
security:
  - apiKey: []
paths:
  /v2/exports/custom-events:
    post:
      summary: Export Custom Events
      description: >-
        Bulk export of custom events from a half-open `occurredAt` range,
        ordered by `occurredAt` ascending. Each event's own payload is returned
        as a JSON object under `data`. The contact's current email and user ID
        are resolved per row on a best-effort basis (`contactEmail` and `userId`
        may be `null` if the contact no longer exists).


        Send `occurredAt` on the first call, then pass the opaque cursor
        returned as `pagination.nextCursor` back as `cursor` on every later
        call. A `null` cursor means there are no more results. Persist the last
        non-null cursor between syncs to resume. The range may be arbitrarily
        long and is walked in bounded segments internally, so an empty page with
        a non-null cursor means there are more results to come rather than the
        end of the export.


        > **Request access.** This endpoint is not enabled by default, contact
        the Conversion team to request access. Calls from accounts without it
        return `403 export_not_enabled`.
      operationId: exportCustomEvents
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                occurredAt:
                  type: object
                  description: >-
                    The half-open `occurredAt` range to export, from `start` up
                    to but not including `end`. Required on the first call, and
                    omitted on later calls because the cursor carries the range.
                    Supplying both `occurredAt` and `cursor` returns an
                    `invalid_window` error.
                  required:
                    - start
                  properties:
                    start:
                      type: string
                      format: date-time
                      description: >-
                        The start of the range. Events that occurred at exactly
                        this time are included. Omitting it on a call without a
                        `cursor` returns an `invalid_window` error.
                      examples:
                        - '2026-08-01T00:00:00Z'
                    end:
                      type: string
                      format: date-time
                      description: >-
                        The end of the range. Events that occurred at exactly
                        this time are excluded, so consecutive ranges can be
                        exported without repeating a boundary event. Omit it to
                        export everything up to the time each page is served,
                        which lets a stored cursor keep returning newly arrived
                        events. Supply it to stop the export at a fixed point,
                        for example to re-export a single historical range. Must
                        be after `start`.
                      examples:
                        - '2026-09-01T00:00:00Z'
                cursor:
                  type: string
                  description: >-
                    The opaque pagination cursor returned as
                    `pagination.nextCursor` by a previous call. Omit (or pass an
                    empty string) to fetch the first page. Send it on its own,
                    with no `occurredAt`. An invalid or malformed cursor returns
                    an `invalid_cursor` error.
                  examples:
                    - eyJ2IjoxLCJjIjoiMjAyNi0wOC0wMVQwMDowMDowMFoifQ==
                limit:
                  type: integer
                  minimum: 1
                  maximum: 1000
                  default: 1000
                  description: >-
                    The maximum number of events to return per page. Defaults to
                    1000. Values are clamped to the range [1, 1000] rather than
                    rejected; values above 1000 return 1000 results.
            examples:
              first-page:
                summary: First page of a range
                value:
                  occurredAt:
                    start: '2026-08-01T00:00:00Z'
                  limit: 1000
              next-page:
                summary: Resume from a cursor
                value:
                  cursor: eyJ2IjoxLCJjIjoiMjAyNi0wOC0wMVQwMDowMDowMFoifQ==
                  limit: 1000
              fixed-range:
                summary: Re-export one fixed range
                value:
                  occurredAt:
                    start: '2026-08-01T00:00:00Z'
                    end: '2026-09-01T00:00:00Z'
                  limit: 1000
      responses:
        '200':
          description: A page of custom events retrieved successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    required:
                      - events
                    properties:
                      events:
                        type: array
                        description: The current page of exported custom events.
                        items:
                          type: object
                          required:
                            - eventId
                            - contactId
                            - occurredAt
                            - createdAt
                            - eventName
                            - source
                          properties:
                            eventId:
                              type: string
                              format: uuid
                              description: >-
                                The Conversion ID for this event. Stable primary
                                key for warehouse upserts.
                            contactId:
                              type: string
                              format: uuid
                              description: >-
                                The Conversion contact ID the event belongs to
                                (joins `contacts.id`).
                            occurredAt:
                              type: string
                              format: date-time
                              description: >-
                                When the event occurred. This is the value
                                supplied when the event was tracked, and it is
                                the only timestamp this endpoint filters on.
                            createdAt:
                              type: string
                              format: date-time
                              description: >-
                                When Conversion ingested the event. Compare it
                                with `occurredAt` to identify events that were
                                backdated, for example by a warehouse sync or a
                                CSV import.
                            eventName:
                              type: string
                              description: The name the event was tracked under.
                              examples:
                                - order_completed
                            source:
                              type: string
                              enum:
                                - CONVERSION
                                - PUBLIC_API
                                - CONVERSION_FORM
                                - CSV_UPLOAD
                                - SALESFORCE
                                - SNOWFLAKE
                                - BIGQUERY
                                - META
                                - LINKEDIN
                                - ZOOM
                                - SEQUEL
                                - GOLDCAST
                                - ATTENTIVE
                              description: The channel the event was ingested through.
                              examples:
                                - PUBLIC_API
                            contactEmail:
                              type:
                                - string
                                - 'null'
                              description: >-
                                The contact's current email address.
                                Best-effort: `null` if the contact no longer
                                exists. This is the value at export time, not
                                the value when the event occurred.
                            userId:
                              type:
                                - string
                                - 'null'
                              description: >-
                                The `userId` the contact is identified by in
                                your own system. Best-effort: `null` if the
                                contact no longer exists or has no `userId`.
                            clientEventId:
                              type:
                                - string
                                - 'null'
                              description: >-
                                The `eventId` supplied when the event was
                                tracked, or `null` if none was sent. Use it to
                                join back to the event in your own system.
                            data:
                              type:
                                - object
                                - 'null'
                              description: >-
                                The event's own payload, exactly as it was
                                tracked, or `null` if none was sent. Keys are
                                lowercased on ingest. Because the keys differ
                                per event name, this is returned as a single
                                JSON object rather than flattened into columns.
                  pagination:
                    type: object
                    required:
                      - nextCursor
                    properties:
                      nextCursor:
                        type:
                          - string
                          - 'null'
                        description: >-
                          Opaque cursor for the next page, or `null` when there
                          are no more results. This is the single source of
                          truth for whether more results exist; an empty page
                          can still carry a cursor.
              examples:
                with-more-pages:
                  summary: More pages available
                  value:
                    data:
                      events:
                        - eventId: f1e2d3c4-b5a6-7890-fedc-ba0987654321
                          contactId: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                          occurredAt: '2026-08-19T17:01:37Z'
                          createdAt: '2026-08-19T18:55:05.903Z'
                          eventName: order_completed
                          source: PUBLIC_API
                          contactEmail: jane@example.com
                          userId: user_8813
                          clientEventId: order_12345
                          data:
                            currency: usd
                            items: 3
                            order_total: 149.99
                    pagination:
                      nextCursor: eyJ2IjoxLCJjIjoiMjAyNi0wOC0wMVQwMDowMDowMFoifQ==
                last-page:
                  summary: Last page
                  value:
                    data:
                      events: []
                    pagination:
                      nextCursor: null
        '400':
          description: The request was malformed or contained invalid data.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing-range:
                  summary: No range and no cursor
                  value:
                    error:
                      code: invalid_window
                      message: occurredAt.start is required when no cursor is supplied
                conflicting-range:
                  summary: Range sent alongside a cursor
                  value:
                    error:
                      code: invalid_window
                      message: >-
                        cursor and occurredAt are mutually exclusive; the cursor
                        carries the range
                inverted-range:
                  summary: End before start
                  value:
                    error:
                      code: invalid_window
                      message: occurredAt.end must be after occurredAt.start
                invalid-cursor:
                  summary: Invalid cursor
                  value:
                    error:
                      code: invalid_cursor
                      message: invalid pagination cursor
        '401':
          description: Authentication failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                unauthorized:
                  summary: Missing or invalid API key
                  value:
                    error:
                      code: unauthorized
                      message: API key is required
        '403':
          description: The export API is not enabled for this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                export-not-enabled:
                  summary: Export not enabled
                  value:
                    error:
                      code: export_not_enabled
                      message: business is not authorized to use the export API
        '500':
          description: An unexpected server error occurred.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                internal:
                  summary: Internal error
                  value:
                    error:
                      code: internal_error
                      message: failed to list custom events
components:
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: A machine-readable error code.
              examples:
                - invalid_email
            message:
              type: string
              description: A human-readable error description.
              examples:
                - 'invalid email format: not-an-email'
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        Your Conversion API key. Found in **Settings > Integrations** in the
        dashboard. Format: `sk_live_<key_id>_<secret>`.

````