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

# List a contact's activity

> Retrieve the contact's activity timeline — signups, email and SMS sends, opens and clicks,
orders, checkouts, tag changes — newest first. This is the same record the Privy dashboard's
contact page shows, so "why did this contact get X?" can be answered from the API alone:
find the send, the signup that triggered it, and the display or campaign each traces to.

Each entry names its `type` (the event vocabulary below, as stored), when it happened, the
display or campaign it belongs to (when the event carries one, even one since deleted), and the
event's raw `properties`, whose shape varies by type (the recipient IP and user agent recorded
on email opens and clicks, and the IP-derived location on signups, are not included).

By default the timeline covers the last 24 months; narrow it with `occurred_after` /
`occurred_before` (both bounds exclusive; a bare date means midnight at the start of that
day) or to one event type with `type`.

**Required scope:** `contacts_read`




## OpenAPI

````yaml openapi/privy-api.yaml GET /contacts/{id}/events
openapi: 3.1.0
info:
  title: Privy API
  version: '1.0'
  description: >
    The Privy API lets you programmatically manage your contact list and read
    what you have set up in Privy.

    Create, update, unsubscribe, and remove contacts; report orders;

    and read your campaigns, flows, segments, orders, store products, coupon
    pools,

    performance reports, and account setup.
servers:
  - url: https://api.privy.com/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Account
    description: Retrieve information about the authenticated Privy account.
  - name: Contacts
    description: Manage your contact list.
  - name: Imports
    description: >-
      Read your contact import history — CSV uploads, connected-provider syncs,
      and SMS consent imports.
  - name: Campaigns
    description: >-
      Read your message campaigns — email and SMS newsletters, plus the messages
      your flows send.
  - name: Displays
    description: >-
      Read your on-site displays (popups, flyouts, banners, embedded forms,
      landing pages).
  - name: Coupons
    description: Read your coupon pools, their code inventory, and individual codes.
  - name: Reports
    description: >-
      Read performance reporting — signups, views, conversion rates, and
      attributed revenue — for your business, displays, and flows.
  - name: Flows
    description: Read your automation flows and their configuration.
  - name: Segments
    description: Read your customer segments and their membership.
  - name: Orders
    description: Report orders placed in your store and read your order history.
  - name: Products
    description: Read the product catalog and collections of your connected store.
  - name: Events
    description: Ingest custom events to trigger Flows and other automations.
paths:
  /contacts/{id}/events:
    get:
      tags:
        - Contacts
      summary: List a contact's activity
      description: >
        Retrieve the contact's activity timeline — signups, email and SMS sends,
        opens and clicks,

        orders, checkouts, tag changes — newest first. This is the same record
        the Privy dashboard's

        contact page shows, so "why did this contact get X?" can be answered
        from the API alone:

        find the send, the signup that triggered it, and the display or campaign
        each traces to.


        Each entry names its `type` (the event vocabulary below, as stored),
        when it happened, the

        display or campaign it belongs to (when the event carries one, even one
        since deleted), and the

        event's raw `properties`, whose shape varies by type (the recipient IP
        and user agent recorded

        on email opens and clicks, and the IP-derived location on signups, are
        not included).


        By default the timeline covers the last 24 months; narrow it with
        `occurred_after` /

        `occurred_before` (both bounds exclusive; a bare date means midnight at
        the start of that

        day) or to one event type with `type`.


        **Required scope:** `contacts_read`
      operationId: listContactEvents
      parameters:
        - $ref: '#/components/parameters/AccountIdParam'
        - $ref: '#/components/parameters/ContactIdParam'
        - name: type
          in: query
          required: false
          description: >-
            Only return events of this type. Any `type` value a row carries is
            accepted (including names a merchant's own integration sent through
            the events ingest), so a type read off the timeline always filters.
            Common values: customer-subscribed, email-sent, email-delivered,
            email-opened, email-clicked, text-message-sent,
            text-message-delivered, view, new-session, redemption, signup,
            sms-signup, order-created, checkout-started, customer-tag-added,
            customer-tag-removed.
          schema:
            type: string
          example: email-opened
        - name: occurred_after
          in: query
          required: false
          description: >-
            Only events after this instant (exclusive). ISO 8601 datetime or
            date. Defaults to 24 months before `occurred_before` — 24 months ago
            when neither bound is given.
          schema:
            type: string
          example: '2026-06-01T00:00:00Z'
        - name: occurred_before
          in: query
          required: false
          description: >-
            Only events before this instant (exclusive). ISO 8601 datetime or
            date. Defaults to now.
          schema:
            type: string
          example: '2026-07-01'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PerPageParam'
      responses:
        '200':
          description: A paginated list of the contact's events, newest first.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ContactEvent'
                  note:
                    type: string
                    description: >-
                      Present only when the account's plan limits the timeline
                      to signup events.
                  pagination:
                    $ref: '#/components/schemas/PaginationEnvelope'
              example:
                data:
                  - type: email-opened
                    occurred_at: '2026-08-20T14:22:00Z'
                    campaign:
                      id: Nw8K2rT5xY
                      title: Welcome Series — Email 1
                      type: flow
                    properties:
                      subject: Take 10% Off
                  - type: signup
                    occurred_at: '2026-08-20T14:20:31Z'
                    campaign:
                      id: gk2P8Zw1Lq
                      title: Welcome Popup
                      type: display
                    properties:
                      redemption_code: '482910375563'
                pagination:
                  page: 1
                  per_page: 40
                  total_count: 2
                  total_pages: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
      security:
        - bearerAuth: []
components:
  parameters:
    AccountIdParam:
      name: Privy-Account-Id
      in: header
      required: false
      description: >
        Target a subaccount instead of the main connected account.

        Pass a subaccount `id` from get_account's `subaccounts` list. Only
        direct subaccounts

        of the connected account are reachable — omit this for the main account
        itself.
      schema:
        type: string
      example: S1U2B3A4C5C6T7
    ContactIdParam:
      name: id
      in: path
      required: true
      description: >-
        The contact's unique identifier (returned as `id` in contact responses).
        Ids are opaque `cus_`-prefixed strings — a numeric id from a Privy
        dashboard URL is not a valid API id and will 404; list contacts, or look
        the contact up by `?email=` or `?phone_number=`, instead.
      schema:
        type: string
      example: cus_a1b2c3d4e5f6g7h8
    PageParam:
      name: page
      in: query
      description: Page number (starts at 1).
      schema:
        type: integer
        default: 1
        minimum: 1
      example: 1
    PerPageParam:
      name: per_page
      in: query
      description: >-
        Number of results per page. Values outside 10–100 are clamped to the
        nearest bound.
      schema:
        type: integer
        default: 40
        minimum: 10
        maximum: 100
      example: 40
  schemas:
    ContactEvent:
      type: object
      description: One entry on a contact's activity timeline.
      properties:
        type:
          type: string
          description: >-
            The event name as stored. The `type` filter lists the common values;
            other stored names (e.g. `email-bounced`, `text-clicked`) appear as
            well and filter the same way.
          example: email-opened
        occurred_at:
          type: string
          format: date-time
          example: '2026-08-20T14:22:00Z'
        campaign:
          type:
            - object
            - 'null'
          description: >-
            The display or message campaign the event belongs to; `null` when
            the event carries none (e.g. a store order or a tag change).
            `display` ids round-trip through `GET /v1/displays/{id}`;
            `newsletter` and `flow` ids through `GET /v1/campaigns/{id}` —
            unless the campaign has since been deleted: it still names itself
            here but no longer resolves there. `workflow` is the legacy
            automation type, which that read does not serve.
          properties:
            id:
              type: string
              example: gk2P8Zw1Lq
            title:
              type: string
              example: Welcome Popup
            type:
              type: string
              enum:
                - display
                - newsletter
                - flow
                - workflow
              example: display
        properties:
          type: object
          description: >-
            The raw event payload. Shape varies by `type`. The recipient `ip`
            and `useragent` stamped on email opens and clicks are omitted. A
            signup's `browsing_data` carries only the page, referrer, and utm
            attribution the widget recorded; the IP-derived location is omitted.
            SMS send events carry the `message_body` that was sent.
          additionalProperties: true
    PaginationEnvelope:
      type: object
      properties:
        page:
          type: integer
          example: 1
        per_page:
          type: integer
          example: 25
        total_count:
          type: integer
          example: 142
        total_pages:
          type: integer
          example: 6
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - invalid_client
                - invalid_scope
                - unauthorized
                - insufficient_scope
                - paid_plan_required
                - api_access_required
                - not_found
                - no_connected_store
                - conflict
                - contacts_not_ready
                - validation_failed
                - rate_limited
                - store_unavailable
              example: validation_failed
            message:
              type: string
              example: One or more fields are invalid
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                    example: email
                  message:
                    type: string
                    example: is required
  responses:
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: unauthorized
              message: Bearer token is missing or invalid
    InsufficientScope:
      description: Token lacks the required scope for this endpoint.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: insufficient_scope
              message: Token does not have the required scope
    NotFound:
      description: >-
        The requested resource does not exist or does not belong to your
        account.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: not_found
              message: Not found
    ValidationFailed:
      description: One or more fields failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: validation_failed
              message: One or more fields are invalid
              details:
                - field: email
                  message: is required
    RateLimited:
      description: Rate limit exceeded. Retry after the specified time.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
          example: 42
        X-RateLimit-Limit-Minute:
          description: Maximum requests allowed per minute.
          schema:
            type: integer
          example: 60
        X-RateLimit-Remaining-Minute:
          description: Requests remaining in the current minute window.
          schema:
            type: integer
          example: 0
        X-RateLimit-Reset-Minute:
          description: Unix timestamp when the minute window resets.
          schema:
            type: integer
          example: 1711929600
        X-RateLimit-Limit-Day:
          description: Maximum requests allowed per day.
          schema:
            type: integer
          example: 10000
        X-RateLimit-Remaining-Day:
          description: Requests remaining in the current day window.
          schema:
            type: integer
          example: 9500
        X-RateLimit-Reset-Day:
          description: Unix timestamp when the day window resets.
          schema:
            type: integer
          example: 1712016000
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: rate_limited
              message: Rate limit exceeded
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API token or OAuth access token
      description: >
        Send either an API token or an OAuth access token as

        `Authorization: Bearer <token>`. See the Authentication page for
        details.

````