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

# Ad referral summary (CTWA)

> Groups the linked number's Click-to-WhatsApp traffic by ad/post, ordered by conversation count desc. `conversations` counts DISTINCT conversations and `messages` counts messages — two messages in the same conversation are 1 conversation and 2 messages.

`lastSourceUrl` and `lastHeadline` come from the newest message for that ad: the copy can be edited, and the useful one is the last Meta sent.

The list is capped at 200 ads; when it overflows, `truncated` is `true` and a `notice` says so — narrow the window with startDate/endDate.

`referral.sourceId` is Meta's **ad (or post) id**, exposed as an opaque grouping key. There is no campaign or adset name here and that is not an omission: reading the ad object (`GET /{ad_id}?fields=id,name,adset{id,name},campaign{id,name}`) requires the `ads_read` permission on the advertiser's ad account, and the WhatsApp system-user token behind a connected number carries only `whatsapp_business_management` and `whatsapp_business_messaging`. To resolve names, call the Meta Marketing API yourself with a token authorised on that ad account, using this `sourceId`.

Privacy (PII): the COUNTS stay correct on a RELAY_ONLY number (they come from the ad identity, which is not personal data); only `lastSourceUrl` and `lastHeadline` come back null, with `piiNotice: "PII_RELAY_ONLY"`.

**Requires a number-scoped key.**

The `referral` object also carries the RESOLVED names of the ad — `adName`, `adsetId`, `adsetName`, `campaignId`, `campaignName` — plus `namesResolvedAt`.

⚠️ Those six come back **null** unless the advertiser has shared that ad account with the Pilot Status business portfolio as a partner. A null name is a SUPPORTED state, not an error: the conversation is still counted and `sourceId` is still returned.

No endpoint resolves a name on demand — resolution happens asynchronously, off the request path, so querying never triggers a Marketing API call and a null can become a name minutes later with nothing done on your side. `namesResolvedAt` is what makes that observable: null means "not resolved yet", while a timestamp with null names means "we asked and Meta returned nothing" (deleted ad, or sharing not granted).



## OpenAPI

````yaml openapi.json GET /v1/referrals/summary
openapi: 3.1.0
info:
  title: Pilot Status API
  version: 1.0.0
  license:
    name: Pilot Status Terms of Service
    url: https://pilotstatus.com.br/terms
  description: >-
    Public REST API for Pilot Status. Authenticate with the `x-api-key: ps_...`
    header (or `x-api-key-id`). Base URL: https://pilotstatus.com.br
servers:
  - url: https://pilotstatus.com.br
security:
  - apiKey: []
  - apiKeyId: []
paths:
  /v1/referrals/summary:
    get:
      tags:
        - Conversations
      summary: Ad referral summary (CTWA)
      description: >-
        Groups the linked number's Click-to-WhatsApp traffic by ad/post, ordered
        by conversation count desc. `conversations` counts DISTINCT
        conversations and `messages` counts messages — two messages in the same
        conversation are 1 conversation and 2 messages.


        `lastSourceUrl` and `lastHeadline` come from the newest message for that
        ad: the copy can be edited, and the useful one is the last Meta sent.


        The list is capped at 200 ads; when it overflows, `truncated` is `true`
        and a `notice` says so — narrow the window with startDate/endDate.


        `referral.sourceId` is Meta's **ad (or post) id**, exposed as an opaque
        grouping key. There is no campaign or adset name here and that is not an
        omission: reading the ad object (`GET
        /{ad_id}?fields=id,name,adset{id,name},campaign{id,name}`) requires the
        `ads_read` permission on the advertiser's ad account, and the WhatsApp
        system-user token behind a connected number carries only
        `whatsapp_business_management` and `whatsapp_business_messaging`. To
        resolve names, call the Meta Marketing API yourself with a token
        authorised on that ad account, using this `sourceId`.


        Privacy (PII): the COUNTS stay correct on a RELAY_ONLY number (they come
        from the ad identity, which is not personal data); only `lastSourceUrl`
        and `lastHeadline` come back null, with `piiNotice: "PII_RELAY_ONLY"`.


        **Requires a number-scoped key.**


        The `referral` object also carries the RESOLVED names of the ad —
        `adName`, `adsetId`, `adsetName`, `campaignId`, `campaignName` — plus
        `namesResolvedAt`.


        ⚠️ Those six come back **null** unless the advertiser has shared that ad
        account with the Pilot Status business portfolio as a partner. A null
        name is a SUPPORTED state, not an error: the conversation is still
        counted and `sourceId` is still returned.


        No endpoint resolves a name on demand — resolution happens
        asynchronously, off the request path, so querying never triggers a
        Marketing API call and a null can become a name minutes later with
        nothing done on your side. `namesResolvedAt` is what makes that
        observable: null means "not resolved yet", while a timestamp with null
        names means "we asked and Meta returned nothing" (deleted ad, or sharing
        not granted).
      operationId: get_referrals_summary
      parameters:
        - name: startDate
          in: query
          required: false
          description: Start of the date range (over providerTimestamp). Must be ≤ endDate.
          schema:
            type: string
            description: (string (ISO 8601))
          example: '2026-08-01T00:00:00Z'
        - name: endDate
          in: query
          required: false
          description: End of the date range (over providerTimestamp).
          schema:
            type: string
            description: (string (ISO 8601))
          example: '2026-08-17T23:59:59Z'
      responses:
        '200':
          description: Ad referral summary
          content:
            application/json:
              example:
                ads:
                  - sourceId: '120249828053880703'
                    sourceType: ad
                    conversations: 12
                    messages: 31
                    firstAt: '2026-07-28T10:00:00.000Z'
                    lastAt: '2026-08-16T12:00:00.000Z'
                    lastSourceUrl: https://www.instagram.com/p/DcJhrlpA3Pl/
                    lastHeadline: SALE MM DESIGN
                    adName: Ads 01 - Vídeo dos 70%
                    adsetId: '120249828053990703'
                    adsetName: '[IG] [LL1% IG + VÍDEOS] - 35 a 50'
                    campaignId: '120249677595590703'
                    campaignName: '[CD-01] [WHATSAPP] - MM Design - Sale 70%'
                    namesResolvedAt: '2026-08-17T21:04:00.000Z'
                  - sourceId: '120249828075420703'
                    sourceType: ad
                    conversations: 4
                    messages: 9
                    firstAt: '2026-08-02T09:00:00.000Z'
                    lastAt: '2026-08-15T18:30:00.000Z'
                    lastSourceUrl: https://www.instagram.com/p/DcAbcdEfGh/
                    lastHeadline: MM DESIGN — Outlet
                    adName: null
                    adsetId: null
                    adsetName: null
                    campaignId: null
                    campaignName: null
                    namesResolvedAt: null
                total: 2
                limit: 200
                truncated: false
        '400':
          description: Invalid date range
          content:
            application/json:
              example:
                error: startDate must be before or equal to endDate
                code: INVALID_DATE_RANGE
        '401':
          description: Missing or invalid `x-api-key` / `x-api-key-id` header
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Unauthorized
        '403':
          description: Tenant-scoped key used on a number-scoped endpoint
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Tenant-scoped keys cannot call number endpoints
                code: TENANT_SCOPE_NOT_ALLOWED
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: Your ps_ API key
    apiKeyId:
      type: apiKey
      in: header
      name: x-api-key-id
      description: API key id (alternative to x-api-key)

````