Apple Search Ads Campaign Management API (v3) を使ってレポートを自動取得したい



ASAのAPIは初めて触ったのですが、個人的にドキュメントが少し取っ付きにくかったので備忘録として残します。
レポート取得を自動化したかったので、この記事では「日別 × キャンペーン別」のレポートを取得する方法を簡単に載せています。


## 事前準備

ASAはクライアント証明書を格納したp12ファイルを送る形でAPIにリクエストを送る必要があるので、管理画面から証明書(有効期限は24ヶ月)をダウンロードして、コマンドでp12ファイルを作成する必要があります。
これに関してはドキュメントを読めば問題ないと思うので説明は割愛します。
キャンペーン情報やレポートを読み取るだけであればどの権限で発行しても問題ありません。

自分は「p12ファイル変換 + s3にアップロード」コマンドを叩いてs3にアップロードし、Lambdaを定期実行することでレポートの自動取得(スプシに反映)を行っています。

ASAの認証方法について: [ドキュメント](https://developer.apple.com/documentation/apple_search_ads/authenticating_with_the_apple_search_ads_api)

## リクエスト

granularityにDAILYを入れることで日別レポートを取得できます。

また orderBy は必須なのですが、具体的に何を指定できるのかはよく分かってません...mm
（selector.orderBy.fieldのフィールド名を指定してねと言われるがドキュメントで見つからず...）

以下、Python3で書いたコードの一部です。


キャンペーン別レポート: [ドキュメント](https://developer.apple.com/documentation/apple_search_ads/get_campaign_level_reports) 

```python:リクエスト
    cert_file_path = "p12ファイルへのパス"
    pkcs12_password = "p12ファイルのパスワード"
    url = config.ASA_ENDPOINTS["get_campaign_report"] # "https://api.searchads.apple.com/api/v3/reports/campaigns"
    headers = {
      'Content-Type': 'application/json',
      "Authorization": "orgId={}".format(org_id),  # orgIdを渡さないと空っぽのデータが返ってくる
    }
    payload = {
      "startTime": "2021-01-01",
      "endTime": "2021-01-31",
      "granularity": "DAILY",
      "returnRecordsWithNoMetrics": True,
      "returnRowTotals": False,
      "returnGrandTotals": False,
      "selector": {
        "orderBy": [
          {
            "field": "impressions",
            "sortOrder": "ASCENDING"
          }
        ],
        "pagination": {
          "offset": 0,
          "limit": 1000
        }
      },
    }
    response = post(url, headers=headers, verify=True, pkcs12_filename=cert_file_path, pkcs12_password=pkcs12_password, data=json.dumps(payload))
    res_body = response.json()
    print(res_body)
```

## レスポンス

縦に長くなってしまうので見づらいのですが、`row`の中にキャンペーンごとにまとまったデータが入っており、`granularity` に日別(granularityで指定した粒度の塊)でデータがまとまっています。

granularity内のデータ型: [ドキュメント](https://developer.apple.com/documentation/apple_search_ads/extendedspendrow)

```shell:レスポンス
{
  'data': {
    'reportingDataResponse': {
      'row': [                           # キャンペーンごとにデータが集計されたデータの配列
        {
          'other': False,
          'granularity': [                    # 日別で集計されたデータの配列
            {
              'impressions': 100,                  # インプ数
              'taps': 20,                          # タップ数(クリック数に同じ）
              'installs': 10,                      # インストール数（newDownloads + redownloads）
              'newDownloads': 8,                   # 新規インストール数
              'redownloads': 2,                    # 再インストール数
              'latOnInstalls': 0,                  # LATがONのデバイスのインストール数
              'latOffInstalls': 1,                 # LATがOFFのデバイスのインストール数
              'ttr': 0.2,                          # CTR
              'avgCPA': {                          # 平均CPA(localSpend.amount/installs)
                'amount': '1000',
                'currency': 'JPY'
              },
              'avgCPT': {                          # 平均CPC(localSpend.amount/taps)
                'amount': '500',
                'currency': 'JPY'
              },
              'localSpend': {                      # コスト
                'amount': '10000',
                'currency': 'JPY'
              },
              'conversionRate': 0.5,               # CVR(installs/taps)
              'date': '2021-01-01'                 # 集計日
            },

　　　　　　　　　　・
　　　　　　　　　　・
　　　　　　　　　　・
            {
              'impressions': 100,
              'taps': 20,
              'installs': 10,
              'newDownloads': 8,
              'redownloads': 2,
              'latOnInstalls': 0,
              'latOffInstalls': 1,
              'ttr': 0.2,
              'avgCPA': {
                'amount': '1000',
                'currency': 'JPY'
              },
              'avgCPT': {
                'amount': '500',
                'currency': 'JPY'
              },
              'localSpend': {
                'amount': '10000',
                'currency': 'JPY'
              },
              'conversionRate': 0.5,
              'date': '2021-01-30'
            },
            {
              'date': '2021-01-31'              # もしデータがない場合はdateだけ返ってくる
            }
          ],
          'metadata': {
            'campaignId': 012345678,
            'campaignName': キャンペーン名,
            'deleted': False,
            'campaignStatus': 'ENABLED',
            'app': {
              'appName': アプリ名,
              'adamId': 9876543210
            },
            'servingStatus': 'RUNNING',
            'servingStateReasons': None,
            'countriesOrRegions': ['JP'],
            'modificationTime': '2021-01-08T19:02:46.263',
            'totalBudget': {
              'amount': '1000000',
              'currency': 'JPY'
            },
            'dailyBudget': {
              'amount': '10000',
              'currency': 'JPY'
            },
            'displayStatus': 'RUNNING',
            'supplySources': ['APPSTORE_SEARCH_RESULTS'],
            'adChannelType': 'SEARCH',
            'orgId': 0000000,
            'countryOrRegionServingStateReasons': {}
          }
        },
        {
          'other': False,
　　　　　　　・
　　　　　　　・
　　　　　　　・
        },
      ],
    }
  }
}
```