> ## 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.

# Get business report summary

> The business-level performance rollup for a period: signups, new
subscribers, attributed revenue and orders, engagement rates, and
business-wide order and revenue totals for context. Pass
`compare=previous_period` to add previous-period values and typed
change deltas to every metric.

`attributed_revenue` and `attributed_orders` credit the email and SMS
messages Privy sent, under the same last-touch rule as the rest of
Privy's reporting: an order counts when the contact's most recent
engagement in the 7 days before it was an email open, an email click, or
an SMS click. Orders credited to a display signup are excluded, and
every figure here is dated by when the crediting message was **sent**,
not by when the order or the engagement happened. A period's attributed
revenue is therefore the revenue driven by messages sent in that period,
and it includes orders placed after the period closed, whenever the
contact engaged later — so the newest days keep filling in for a while
after the fact. The engagement counts and rates use the same send
window: an open in this period on a message sent last month lands in
last month.

The rates each divide by something different. `open_rate` is opens
counted once per contact per campaign, over email deliveries — SMS
messages have no opens. `click_rate` counts clicks the same way but per
channel, over email and SMS deliveries combined. `unsubscribe_rate` is
unsubscribe and opt-out events, not distinct contacts, over those same
combined deliveries. `order_rate` is attributed orders over delivered
messages. `revenue_per_recipient` divides attributed revenue by
**email** sends only. `signup_rate` is display form submissions over
display views, so it measures on-site capture, not messaging. `signups`
counts form submissions on your displays, while `new_subscribers` counts
contacts becoming subscribed to marketing on either channel from any
source, so the two answer different questions and neither is
de-duplicated by contact.

Periods reaching back before the account's reporting boundary
(2026-07-01 for most accounts, earlier where more history was
backfilled) are served whole — comparison period included — from
Privy's historical rollups instead of the live reporting store. The
rate formulas above still hold, but the basis differs: new
subscribers, every by_channel/by_type split, and `totals_context` are
null; attributed orders and revenue include display-credited orders
and are dated by when the attribution was recorded, not by send time —
so `order_rate`'s numerator counts orders its message denominator
never sent, and reads a little high for accounts with display-credited
orders; and days are bucketed in US Eastern time regardless of the
account's time zone. Treat comparisons that straddle the boundary as
approximate, and say so.

**Required scope:** `reports_read`




## OpenAPI

````yaml openapi/privy-api.yaml GET /reports/summary
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/summary:
    get:
      tags:
        - Reports
      summary: Get business report summary
      description: |
        The business-level performance rollup for a period: signups, new
        subscribers, attributed revenue and orders, engagement rates, and
        business-wide order and revenue totals for context. Pass
        `compare=previous_period` to add previous-period values and typed
        change deltas to every metric.

        `attributed_revenue` and `attributed_orders` credit the email and SMS
        messages Privy sent, under the same last-touch rule as the rest of
        Privy's reporting: an order counts when the contact's most recent
        engagement in the 7 days before it was an email open, an email click, or
        an SMS click. Orders credited to a display signup are excluded, and
        every figure here is dated by when the crediting message was **sent**,
        not by when the order or the engagement happened. A period's attributed
        revenue is therefore the revenue driven by messages sent in that period,
        and it includes orders placed after the period closed, whenever the
        contact engaged later — so the newest days keep filling in for a while
        after the fact. The engagement counts and rates use the same send
        window: an open in this period on a message sent last month lands in
        last month.

        The rates each divide by something different. `open_rate` is opens
        counted once per contact per campaign, over email deliveries — SMS
        messages have no opens. `click_rate` counts clicks the same way but per
        channel, over email and SMS deliveries combined. `unsubscribe_rate` is
        unsubscribe and opt-out events, not distinct contacts, over those same
        combined deliveries. `order_rate` is attributed orders over delivered
        messages. `revenue_per_recipient` divides attributed revenue by
        **email** sends only. `signup_rate` is display form submissions over
        display views, so it measures on-site capture, not messaging. `signups`
        counts form submissions on your displays, while `new_subscribers` counts
        contacts becoming subscribed to marketing on either channel from any
        source, so the two answer different questions and neither is
        de-duplicated by contact.

        Periods reaching back before the account's reporting boundary
        (2026-07-01 for most accounts, earlier where more history was
        backfilled) are served whole — comparison period included — from
        Privy's historical rollups instead of the live reporting store. The
        rate formulas above still hold, but the basis differs: new
        subscribers, every by_channel/by_type split, and `totals_context` are
        null; attributed orders and revenue include display-credited orders
        and are dated by when the attribution was recorded, not by send time —
        so `order_rate`'s numerator counts orders its message denominator
        never sent, and reads a little high for accounts with display-credited
        orders; and days are bucketed in US Eastern time regardless of the
        account's time zone. Treat comparisons that straddle the boundary as
        approximate, and say so.

        **Required scope:** `reports_read`
      operationId: getReportSummary
      parameters:
        - $ref: '#/components/parameters/AccountIdParam'
        - $ref: '#/components/parameters/ReportPeriodParam'
        - $ref: '#/components/parameters/ReportStartDateParam'
        - $ref: '#/components/parameters/ReportEndDateParam'
        - name: compare
          in: query
          description: >-
            Pass `previous_period` to include `previous_value`, `change` and
            `change_type` on every metric and a `comparison_period` block. Any
            other value is rejected.
          schema:
            type: string
            enum:
              - previous_period
          example: previous_period
      responses:
        '200':
          description: The business's performance rollup over the resolved period.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      period:
                        $ref: '#/components/schemas/ReportPeriod'
                      comparison_period:
                        type:
                          - object
                          - 'null'
                        description: >-
                          The previous period of the same length, immediately
                          before the report period. Null unless
                          `compare=previous_period`.
                        properties:
                          start_date:
                            type: string
                            format: date
                            example: '2026-05-22'
                          end_date:
                            type: string
                            format: date
                            example: '2026-06-20'
                      signups:
                        allOf:
                          - $ref: '#/components/schemas/ReportSummaryMetric'
                          - type: object
                            properties:
                              by_channel:
                                $ref: '#/components/schemas/ReportChannelBreakdown'
                      new_subscribers:
                        allOf:
                          - $ref: '#/components/schemas/ReportSummaryMetric'
                          - type: object
                            properties:
                              by_source:
                                type:
                                  - object
                                  - 'null'
                                additionalProperties:
                                  type: integer
                                description: >
                                  New subscribers grouped by signup source
                                  (`display`, `import`,

                                  `api`, `shopify`, …; `unknown` for
                                  unattributed). Current period

                                  only. Null means unavailable — the period
                                  starts before the

                                  account's reporting boundary (2026-07-01 for
                                  most accounts,

                                  earlier where more history was backfilled) or
                                  the breakdown

                                  could not be computed; an empty object means
                                  genuinely zero.

                                  Never treat null as zero.
                              by_channel:
                                type:
                                  - object
                                  - 'null'
                                additionalProperties:
                                  type: integer
                                description: >
                                  New subscribers by channel (`email`, `sms`).
                                  Same availability

                                  semantics as `by_source`: null means
                                  unavailable — never treat

                                  null as zero.
                      signup_rate:
                        $ref: '#/components/schemas/ReportSummaryMetric'
                      attributed_revenue:
                        allOf:
                          - $ref: '#/components/schemas/ReportSummaryMetric'
                          - type: object
                            properties:
                              by_channel:
                                $ref: '#/components/schemas/ReportChannelBreakdown'
                              by_type:
                                $ref: '#/components/schemas/ReportTypeBreakdown'
                      attributed_orders:
                        allOf:
                          - $ref: '#/components/schemas/ReportSummaryMetric'
                          - type: object
                            properties:
                              by_channel:
                                $ref: '#/components/schemas/ReportChannelBreakdown'
                              by_type:
                                $ref: '#/components/schemas/ReportTypeBreakdown'
                      order_rate:
                        $ref: '#/components/schemas/ReportSummaryMetric'
                      average_order_value:
                        $ref: '#/components/schemas/ReportSummaryMetric'
                      revenue_per_recipient:
                        $ref: '#/components/schemas/ReportSummaryMetric'
                      open_rate:
                        $ref: '#/components/schemas/ReportSummaryMetric'
                      click_rate:
                        $ref: '#/components/schemas/ReportSummaryMetric'
                      unsubscribe_rate:
                        $ref: '#/components/schemas/ReportSummaryMetric'
                      totals_context:
                        type:
                          - object
                          - 'null'
                        description: >
                          Null when the period reaches before the account's
                          reporting boundary; never treat null as zero.
                          Business-wide (not just Privy-attributed) totals over
                          the period, dated by when each order was placed. They
                          are on a different basis from `attributed_revenue`:
                          `total_revenue` is the order total the store charged,
                          tax included, while attributed revenue excludes tax.
                          An attributed share divided out of these will
                          therefore read a little low, and the two are dated
                          differently — treat it as approximate and say so.
                        properties:
                          total_orders:
                            type: integer
                            example: 890
                          total_revenue:
                            type: number
                            example: 52300
              example:
                data:
                  period:
                    label: last_30_days
                    start_date: '2026-06-21'
                    end_date: '2026-07-20'
                  comparison_period:
                    start_date: '2026-05-22'
                    end_date: '2026-06-20'
                  signups:
                    value: 1240
                    previous_value: 980
                    change: 26.5
                    change_type: relative
                    unit: count
                    by_channel:
                      email: 1050
                      sms: 190
                  new_subscribers:
                    value: 1130
                    previous_value: 950
                    change: 18.9
                    change_type: relative
                    unit: count
                    by_source:
                      display: 980
                      import: 120
                      shopify: 30
                    by_channel:
                      email: 1000
                      sms: 130
                  signup_rate:
                    value: 3.2
                    previous_value: 2.9
                    change: 0.3
                    change_type: percent_point
                    unit: percent
                  attributed_revenue:
                    value: 18450.2
                    previous_value: 15200
                    change: 21.4
                    change_type: relative
                    unit: usd
                    by_channel:
                      email: 15200
                      sms: 3250.2
                    by_type:
                      campaign: 11400
                      flow: 7050.2
                  attributed_orders:
                    value: 312
                    previous_value: 261
                    change: 19.5
                    change_type: relative
                    unit: count
                    by_channel:
                      email: 262
                      sms: 50
                    by_type:
                      campaign: 190
                      flow: 122
                  order_rate:
                    value: 0.6
                    previous_value: 0.5
                    change: 0.1
                    change_type: percent_point
                    unit: percent
                  average_order_value:
                    value: 59.13
                    previous_value: 58.24
                    change: 1.5
                    change_type: relative
                    unit: usd
                  revenue_per_recipient:
                    value: 0.34
                    previous_value: 0.3
                    change: 13.3
                    change_type: relative
                    unit: usd
                  open_rate:
                    value: 41.2
                    previous_value: 39.8
                    change: 1.4
                    change_type: percent_point
                    unit: percent
                  click_rate:
                    value: 3.1
                    previous_value: 2.9
                    change: 0.2
                    change_type: percent_point
                    unit: percent
                  unsubscribe_rate:
                    value: 0.2
                    previous_value: 0.2
                    change: 0
                    change_type: percent_point
                    unit: percent
                  totals_context:
                    total_orders: 890
                    total_revenue: 52300
        '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'
  schemas:
    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'
    ReportSummaryMetric:
      type: object
      description: |
        A report metric with previous-period comparison fields.
        `previous_value`, `change` and `change_type` are null unless the
        request passed `compare=previous_period`. `change_type` says how to
        read `change`, and the four values are not interchangeable:
        `relative` is the percent change (one decimal, e.g. 26.5 means
        +26.5%) and is used for count and money metrics whenever the previous
        period was non-zero; `percent_point` is the absolute percentage-point
        delta and is used for every rate metric (never a relative percent, so
        a small rate move cannot read as a huge swing); `absolute` is the raw
        delta in the metric's unit, used when the previous period was zero and
        this one was not; and `not_comparable` means no delta could be
        computed, with `change` null. Units follow the `ReportMetric`
        conventions. A null `value` on a rate metric means the period had no
        volume to divide by, which is how "no data" is distinguished from a
        real 0% — though not every rate nulls, so a 0 can still mean either.
      properties:
        value:
          type:
            - number
            - 'null'
          example: 1240
        previous_value:
          type:
            - number
            - 'null'
          example: 980
        change:
          type:
            - number
            - 'null'
          example: 26.5
        change_type:
          type:
            - string
            - 'null'
          enum:
            - relative
            - percent_point
            - absolute
            - not_comparable
            - null
          example: relative
        unit:
          type: string
          description: '`count`, `percent`, or a lowercased ISO 4217 currency code.'
          example: count
    ReportChannelBreakdown:
      type:
        - object
        - 'null'
      description: |
        Current-period split by channel, in the parent metric's unit. Null when
        the period reaches before the account's reporting boundary — the
        historical source cannot credit an order to a single message channel;
        never treat null as zero. Never
        carries comparison fields, even when the request asked for one — only
        the parent metric does. On attributed orders and revenue the two legs
        never double count, because each order is credited to a single message
        and so to a single channel. On signups they can: the split is built
        from what each form captured, so a form that took both an email address
        and a phone number counts in both legs, and the legs need not add up to
        the signup total.
      properties:
        email:
          type: number
          example: 1050
        sms:
          type: number
          example: 190
    ReportTypeBreakdown:
      type:
        - object
        - 'null'
      description: |
        Current-period split by message type, in the parent metric's unit. Null
        when the period reaches before the account's reporting boundary — the
        historical source does not carry the split; never treat null as zero.
        Never carries comparison fields, even when the request asked for one —
        only the parent metric does. `campaign` counts newsletters, the one-time
        email and SMS sends. `flow` is broader than the flows in `/v1/flows`:
        it also covers the account's older automations and the follow-up email
        or SMS a display sends after a signup. The two legs cover every
        attributed message, so they add up to the parent metric's value.
      properties:
        campaign:
          type: number
          example: 190
        flow:
          type: number
          example: 122
    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
    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.

````