> ## 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 referrals (CTWA)

> Lists the linked number's inbound messages that arrived from a Click-to-WhatsApp ad or post, newest first. In practice only official API (Meta) numbers carry an ad origin: the `referral` block is a Cloud API webhook field.

Meta sends the origin on the **first message of the conversation only** and never repeats it, so each item here amounts to one conversation that started from that ad.

`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): on a redacted row the ad identity survives (`sourceId`, `sourceType`) while `ctwaClid`, `headline` and `body` come back null and `redacted: true` is set — an ad id identifies a creative, never a person, so attribution keeps working on a RELAY_ONLY number instead of returning nothing.

**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
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:
    get:
      tags:
        - Conversations
      summary: List ad referrals (CTWA)
      description: >-
        Lists the linked number's inbound messages that arrived from a
        Click-to-WhatsApp ad or post, newest first. In practice only official
        API (Meta) numbers carry an ad origin: the `referral` block is a Cloud
        API webhook field.


        Meta sends the origin on the **first message of the conversation only**
        and never repeats it, so each item here amounts to one conversation that
        started from that ad.


        `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): on a redacted row the ad identity survives (`sourceId`,
        `sourceType`) while `ctwaClid`, `headline` and `body` come back null and
        `redacted: true` is set — an ad id identifies a creative, never a
        person, so attribution keeps working on a RELAY_ONLY number instead of
        returning nothing.


        **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
      parameters:
        - name: sourceId
          in: query
          required: false
          description: Filter to a single ad/post id.
          schema:
            type: string
            description: (string)
          example: '120249828053880703'
        - name: sourceType
          in: query
          required: false
          description: 'Filter by origin type: "ad" or "post".'
          schema:
            type: string
            description: (string ("ad" | "post"))
          example: ad
        - 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'
        - name: page
          in: query
          required: false
          description: Page (default 1).
          schema:
            type: string
            description: (integer (≥1))
          example: '1'
        - name: pageSize
          in: query
          required: false
          description: Items per page (default 30, max 100).
          schema:
            type: string
            description: (integer (1–100))
          example: '30'
      responses:
        '200':
          description: List ad referrals
          content:
            application/json:
              example:
                referrals:
                  - id: cm_01HZX...
                    conversationId: conv_01HZX...
                    direction: INBOUND
                    providerKind: META
                    externalMessageId: wamid.HBgNNTU0Mj...
                    messageType: text
                    text: Oi, vi o anúncio
                    providerTimestamp: '2026-08-16T12:00:00.000Z'
                    referral:
                      sourceId: '120249828053880703'
                      sourceType: ad
                      sourceUrl: https://www.instagram.com/p/DcJhrlpA3Pl/
                      headline: SALE MM DESIGN
                      body: Seu ambiente pode ficar ainda mais incrível
                      mediaType: video
                      imageUrl: null
                      videoUrl: https://video.example/v.mp4
                      thumbnailUrl: https://thumb.example/t.jpg
                      ctwaClid: ARAaZ1x...
                      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'
                  - id: cm_def456
                    conversationId: conv_01HZX...
                    direction: INBOUND
                    providerKind: META
                    externalMessageId: wamid.HBgNNTU0Mj...2
                    messageType: text
                    text: quero saber o preço
                    providerTimestamp: '2026-08-16T12:00:00.000Z'
                    referral:
                      sourceId: '120249828075420703'
                      sourceType: ad
                      sourceUrl: https://www.instagram.com/p/DcJhrlpA3Pl/
                      headline: MM DESIGN — Outlet
                      body: Seu ambiente pode ficar ainda mais incrível
                      mediaType: video
                      imageUrl: null
                      videoUrl: https://video.example/v.mp4
                      thumbnailUrl: https://thumb.example/t.jpg
                      ctwaClid: ARAaZ1x...
                      adName: null
                      adsetId: null
                      adsetName: null
                      campaignId: null
                      campaignName: null
                      namesResolvedAt: null
                total: 2
                page: 1
                pageSize: 30
                totalPages: 1
        '400':
          description: Invalid source type
          content:
            application/json:
              example:
                error: sourceType must be "ad" or "post"
                code: INVALID_SOURCE_TYPE
        '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)

````