> ## 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 campaign performance

> Every message campaign **sent in the period**, ranked by attributed
revenue, each row carrying the full channel stat block (delivery,
engagement, unsubscribes, attribution) scoped to the same period — so
period comparisons need no per-campaign detail calls. Zero-revenue
sends are listed too, ranked after the revenue rows; at most 100
campaigns are ranked, and a send with no recorded activity at all in
the period does not appear.

By default the list serves your **newsletters** — email and SMS
one-time sends. Pass `type=flows` to instead rank the **flow
campaigns**: the email and SMS messages your automation flows send
(flow rows carry no `sent_date` or `audience`, and instead name the
flow that sends them in `flow`, so attributing a message to its flow
needs no follow-up request). Use
`/v1/reports/campaigns/{campaign_id}` for one campaign's
most-clicked links or its lifetime funnel.

**Required scope:** `reports_read`




## OpenAPI

````yaml openapi/privy-api.yaml GET /reports/campaigns
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:
  /reports/campaigns:
    get:
      tags:
        - Reports
      summary: List campaign performance
      description: |
        Every message campaign **sent in the period**, ranked by attributed
        revenue, each row carrying the full channel stat block (delivery,
        engagement, unsubscribes, attribution) scoped to the same period — so
        period comparisons need no per-campaign detail calls. Zero-revenue
        sends are listed too, ranked after the revenue rows; at most 100
        campaigns are ranked, and a send with no recorded activity at all in
        the period does not appear.

        By default the list serves your **newsletters** — email and SMS
        one-time sends. Pass `type=flows` to instead rank the **flow
        campaigns**: the email and SMS messages your automation flows send
        (flow rows carry no `sent_date` or `audience`, and instead name the
        flow that sends them in `flow`, so attributing a message to its flow
        needs no follow-up request). Use
        `/v1/reports/campaigns/{campaign_id}` for one campaign's
        most-clicked links or its lifetime funnel.

        **Required scope:** `reports_read`
      operationId: listCampaignReports
      parameters:
        - $ref: '#/components/parameters/AccountIdParam'
        - $ref: '#/components/parameters/ReportPeriodParam'
        - $ref: '#/components/parameters/ReportStartDateParam'
        - $ref: '#/components/parameters/ReportEndDateParam'
        - name: type
          in: query
          description: >-
            Pass `flows` to rank the messages your flows send instead of
            newsletters.
          schema:
            type: string
            enum:
              - flows
          example: flows
        - name: channel
          in: query
          description: >-
            Filter by message channel. Works for both newsletters and flow
            campaigns.
          schema:
            type: string
            enum:
              - email
              - sms
          example: email
        - $ref: '#/components/parameters/PageParam'
        - name: per_page
          in: query
          description: >-
            Campaigns per page. Values above 50 are clamped to 50; omitted or
            invalid values default to 10.
          schema:
            type: integer
            default: 10
            minimum: 1
            maximum: 50
          example: 10
      responses:
        '200':
          description: Campaigns ranked by attributed revenue over the resolved period.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/CampaignReportRow'
                  period:
                    $ref: '#/components/schemas/ReportPeriod'
                  pagination:
                    $ref: '#/components/schemas/PaginationEnvelope'
              example:
                data:
                  - campaign_id: kD4qN8wZr1
                    type: newsletter
                    title: June Restock Announcement
                    channel: email
                    sent_date: '2026-06-18'
                    audience:
                      included_segments:
                        - Engaged 90d
                      excluded_segments:
                        - Recent purchasers
                    stats:
                      sent:
                        value: 8200
                        unit: count
                      delivered:
                        value: 8020
                        unit: count
                      delivery_rate:
                        value: 97.8
                        unit: percent
                      opened_unique:
                        value: 3450
                        unit: count
                      open_rate:
                        value: 43
                        unit: percent
                      clicked_unique:
                        value: 610
                        unit: count
                      click_rate:
                        value: 7.6
                        unit: percent
                      bounced:
                        value: 74
                        unit: count
                      bounce_rate:
                        value: 0.9
                        unit: percent
                      spam_reports:
                        value: 3
                        unit: count
                      unsubscribed:
                        value: 21
                        unit: count
                      unsubscribe_rate:
                        value: 0.3
                        unit: percent
                      attributed_orders:
                        value: 45
                        unit: count
                      order_rate:
                        value: 0.6
                        unit: percent
                      attributed_revenue:
                        value: 2810
                        unit: usd
                      average_order_value:
                        value: 62.44
                        unit: usd
                period:
                  label: last_30_days
                  start_date: '2026-06-21'
                  end_date: '2026-07-20'
                pagination:
                  page: 1
                  per_page: 10
                  total_count: 12
                  total_pages: 2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '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
    ReportPeriodParam:
      name: period
      in: query
      description: >-
        The date range to report over, resolved in the account's time zone. Pass
        `custom` together with `start_date` and `end_date`.
      schema:
        type: string
        enum:
          - last_7_days
          - last_30_days
          - last_90_days
          - this_month
          - last_month
          - custom
        default: last_30_days
      example: last_30_days
    ReportStartDateParam:
      name: start_date
      in: query
      description: >-
        Inclusive start of a `custom` period (ISO 8601 date). Required when
        `period=custom`, ignored otherwise.
      schema:
        type: string
        format: date
      example: '2026-06-01'
    ReportEndDateParam:
      name: end_date
      in: query
      description: >-
        Inclusive end of a `custom` period (ISO 8601 date). Required when
        `period=custom`, ignored otherwise. Custom periods are capped at 366
        days.
      schema:
        type: string
        format: date
      example: '2026-06-30'
    PageParam:
      name: page
      in: query
      description: Page number (starts at 1).
      schema:
        type: integer
        default: 1
        minimum: 1
      example: 1
  schemas:
    CampaignReportRow:
      type: object
      properties:
        campaign_id:
          type: string
          description: >
            The campaign's unique identifier. Use this value in
            `/v1/reports/campaigns/{campaign_id}`; it matches `id` in
            `/v1/campaigns` responses.
          example: kD4qN8wZr1
        type:
          type: string
          description: >
            Discriminates the two campaign families — `newsletter` (email and
            SMS one-time sends) and `flow` (the messages automation flows send).
          enum:
            - newsletter
            - flow
          example: newsletter
        title:
          type:
            - string
            - 'null'
          example: June Restock Announcement
        channel:
          type: string
          enum:
            - email
            - sms
          example: email
        sent_date:
          type:
            - string
            - 'null'
          format: date
          description: >
            Newsletter rows only — flow campaigns send continuously, so their
            rows omit this field.
          example: '2026-06-18'
        audience:
          allOf:
            - $ref: '#/components/schemas/CampaignReportAudience'
          description: >
            Newsletter rows only — flow campaigns target no segments, so their
            rows omit this field.
        flow:
          oneOf:
            - $ref: '#/components/schemas/CampaignFlowRef'
            - type: 'null'
          description: >
            Flow rows only — the flow that sends this message, so ranking a
            flow's messages needs no follow-up request. Newsletter rows omit the
            field entirely; a flow row whose node has since been deleted carries
            `null`.
        stats:
          $ref: '#/components/schemas/CampaignReportFunnelStats'
    ReportPeriod:
      type: object
      description: |
        The resolved date range the report covers, inclusive, in the account's
        time zone. Every label except `last_month` ends today, so the last day
        of the range is still in progress: `last_7_days` is the six days before
        today plus today, `this_month` runs from the 1st through today, and
        `last_month` is the whole previous calendar month. Custom ranges cannot
        exceed 366 days.
      properties:
        label:
          type: string
          enum:
            - last_7_days
            - last_30_days
            - last_90_days
            - this_month
            - last_month
            - custom
          example: last_30_days
        start_date:
          type: string
          format: date
          example: '2026-06-21'
        end_date:
          type: string
          format: date
          example: '2026-07-20'
    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
    CampaignReportAudience:
      type: object
      description: >
        Saved segment names the send targeted and excluded. Smart-segment
        audiences carry no saved name and are omitted.
      properties:
        included_segments:
          type: array
          items:
            type: string
          example:
            - Engaged 90d
        excluded_segments:
          type: array
          items:
            type: string
          example:
            - Recent purchasers
    CampaignFlowRef:
      type: object
      description: >
        The automation flow that sends this campaign. Pass `id` to `GET
        /v1/flows/{id}` for the flow's trigger, filters, and node graph.
      properties:
        id:
          type: string
          description: The flow's identifier, as returned by `GET /v1/flows`.
          example: 0Rqw3Z8
        title:
          type:
            - string
            - 'null'
          example: Welcome Series
    CampaignReportFunnelStats:
      type: object
      description: |
        The campaign's engagement funnel over the window the response covers —
        the resolved report period on list rows and period-scoped detail reads,
        or the campaign's whole life on the default (no-period) detail read.
        Email campaigns carry `delivery_rate`, `opened_unique`, `open_rate`,
        `bounced`, `bounce_rate`, `spam_reports`, `unsubscribed`,
        `unsubscribe_rate`, and `average_order_value`; SMS campaigns carry
        `opted_out` and `failed`; both carry the rest.

        `attributed_orders` and `attributed_revenue` credit the campaign when
        one of its messages was the contact's most recent engagement — email
        open, email click, or SMS click — in the 7 days before the order was
        placed. `order_rate` is those orders over the messages the campaign
        got out: email deliveries for an email campaign, sends for an SMS one —
        so an SMS campaign's `order_rate` is not directly comparable with an
        email campaign's (undelivered messages still sit in the denominator).

        The rates do not share one denominator. `delivery_rate` and
        `bounce_rate` are over `sent`; `open_rate`, `click_rate` and
        `unsubscribe_rate` are over `delivered`. `unsubscribed`, `bounced` and
        `spam_reports` count every event, while `opened_unique` and
        `clicked_unique` de-duplicate — but only against the previous month, so
        on a campaign that has been sending for longer, which includes any
        long-running flow campaign, a contact who re-engages after that gap is
        counted again and these counters drift above the true number of
        distinct contacts.
        `average_order_value` is `attributed_revenue / attributed_orders`,
        reported as 0 when there are no attributed orders. SMS stat blocks
        carry no `average_order_value` — divide `attributed_revenue` by
        `attributed_orders` yourself.
      properties:
        sent:
          $ref: '#/components/schemas/ReportMetric'
        delivered:
          $ref: '#/components/schemas/ReportMetric'
        delivery_rate:
          $ref: '#/components/schemas/ReportMetric'
        opened_unique:
          $ref: '#/components/schemas/ReportMetric'
        open_rate:
          $ref: '#/components/schemas/ReportMetric'
        clicked_unique:
          $ref: '#/components/schemas/ReportMetric'
        click_rate:
          $ref: '#/components/schemas/ReportMetric'
        bounced:
          $ref: '#/components/schemas/ReportMetric'
        bounce_rate:
          $ref: '#/components/schemas/ReportMetric'
        spam_reports:
          $ref: '#/components/schemas/ReportMetric'
        unsubscribed:
          $ref: '#/components/schemas/ReportMetric'
        unsubscribe_rate:
          $ref: '#/components/schemas/ReportMetric'
        opted_out:
          $ref: '#/components/schemas/ReportMetric'
        failed:
          $ref: '#/components/schemas/ReportMetric'
        attributed_orders:
          $ref: '#/components/schemas/ReportMetric'
        order_rate:
          $ref: '#/components/schemas/ReportMetric'
        attributed_revenue:
          $ref: '#/components/schemas/ReportMetric'
        average_order_value:
          $ref: '#/components/schemas/ReportMetric'
    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
    ReportMetric:
      type: object
      description: |
        A single report metric with its unit. `count` values are whole
        numbers, `percent` values are 0–100 with one decimal. Money metrics
        carry the account's lowercased ISO 4217 currency code as their unit
        (e.g. `usd`, `eur`, `jpy`) and are expressed in that currency's major
        units, two decimals. A rate whose denominator was zero reports 0, not
        null — so 0 does not distinguish "no activity" from "activity that
        never converted"; read the underlying counts to tell those apart.
      properties:
        value:
          type: number
          example: 42
        unit:
          type: string
          description: '`count`, `percent`, or a lowercased ISO 4217 currency code.'
          example: count
  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
    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.

````