Pinterest APIの使い方と広告実績の取得|GASで始める日次レポート

この記事でわかること

Pinterest APIの利用申請、OAuth認証、GASでの広告実績取得を解説。実際の運用をもとに、データの遅延や集計期間、レート制限への対応も図解します。

生成AIによる要約

目次開く閉じる

Pinterest APIで数値を取得できても、そのまま日次レポートに使えるとは限りません。実際にオーガニック指標を取得した際、管理画面に数字があるのに、APIから直近の指標が0で返ることがありました。

接続には成功しています。それでも、取得した数値の扱いには判断が必要でした。過去分を取り直す仕組みや、目的に合った取得範囲まで整えて、日々のレポートに使えるようになりました。

こうした実践を踏まえ、Pinterest APIの利用開始から広告実績の取得までを整理します。実践例はオーガニック分析、広告の取得例は公式仕様に基づくサンプルです。

Pinterest APIでできること

Pinterest APIは、広告レポートの取得以外にも利用できます。扱うデータによって、呼び出すURLや必要な権限が変わります。

用途主な対象
ピンとボードの管理投稿の作成、取得、整理
オーガニック分析アカウントやピンの表示、保存、クリック
広告の管理と分析広告アカウント、キャンペーン、配信実績
商品カタログの連携商品情報や商品グループ
コンバージョン計測購入などのイベントをサーバーから送信

本番APIのベースURLは https://api.pinterest.com/v5 です。たとえば、広告アカウントの日次実績は GET /ad_accounts/{ad_account_id}/analytics で取得します。Pinterest公式API仕様

Conversions APIは、サイトで発生した購入などをPinterestへ送る機能です。広告実績を取得するAPIとは用途が異なります。計測基盤を整えたい場合は、Pinterest広告の計測ガイドも参照してください。

利用申請とOAuth認証を準備する

最初に必要なのは、アプリ管理用のPinterestビジネスアカウントです。メールアドレスの確認を済ませ、開発者サイトの「My apps」からアプリを登録します。

利用開始は次の順序です。

  1. 「Connect app」からアプリ情報を入力する。
  2. Trialアクセスを申請し、承認を受ける。
  3. App IDとApp secretを確認し、認可後の転送先となるRedirect URIを登録する。
  4. 必要な権限を指定して認可を受け、アクセストークンを取得する。

Trialにも審査があります。広告データの読み取りはTrialの対象に含まれますが、アカウントへの権限や利用上限は別途確認が必要です。Standardへの移行条件とあわせて、公式のアクセスレベルを確認してください。アプリ登録、アクセスレベル

広告実績の読み取りに必要な権限は ads:read です。これは、アプリに許可する操作の範囲を表す「スコープ」です。認可する利用者自身にも、対象広告アカウントへのアクセス権が必要です。広告アカウントAPI

ここでつまずきやすいのが、管理画面で生成できるテスト用トークンです。「Production limited」のトークンには ads:read が含まれません。ピンの取得に成功しても、同じトークンで広告レポートを読めるとは限りません。公式Quickstart tools

OAuthの認可コード方式では、利用者がPinterestの認可画面で承認します。受け取ったコードを、アプリ側でトークンに交換します。Redirect URIは、登録した値と一致させます。実装には公式Quickstartと認証ガイドを利用できます。

実際の連携では、トークンの更新処理を日次の取得処理に組み込みました。有効期限はトークン交換時の応答から保存し、期限前に更新します。初回の接続確認と、自動運用を続けるための設定は、両方必要です。

認可コードをトークンに交換し、GASで広告実績を取得してシートで照合する流れ

図1:OAuthの認可から広告実績の取得まで。広告の読み取りには、スコープとアカウントへのアクセス権の両方が必要です。

広告実績をGASで取得する

最初の取得対象は、数日前の1日分で十分です。広告費、広告表示回数、外部クリックの3つに絞ると、管理画面と照合しやすくなります。

同期レポートでは、次の列を指定します。

取得する値APIの列名
広告費SPEND_IN_MICRO_DOLLAR
広告表示回数PAID_IMPRESSION
広告への直接アクションによる外部クリックOUTBOUND_CLICK_1

広告費は、広告アカウント通貨の100万分の1の単位で返ります。列名に「DOLLAR」とありますが、USD固定ではありません。アカウントの通貨がJPYなら、100万で割った値が円です。公式広告レポートガイド

スプレッドシート側の設定

スプレッドシートを新規作成し、「拡張機能」からApps Scriptを開きます。下のコードを貼り付け、プロジェクト設定の「スクリプトプロパティ」に次の4項目を登録します。Google公式の設定手順

  • PINTEREST_ACCESS_TOKEN:ads:readを付与して取得したトークン
  • PINTEREST_AD_ACCOUNT_ID:対象の広告アカウントID
  • PINTEREST_START_DATE:開始日。YYYY-MM-DD形式
  • PINTEREST_END_DATE:終了日。最初は開始日と同じ日付

広告アカウントIDは、広告マネージャーや GET /v5/ad_accounts で確認できます。スクリプトの実行関数には importPinterestAdsReport を選びます。初回は、Google側でも外部通信とスプレッドシート操作の承認が必要です。

トークンはコードに直接記載せず、スクリプトプロパティに保存します。ただし、プロジェクトの編集者はこの値へアクセスできます。編集権限を与える相手も確認してください。Google公式のプロパティ仕様

この例は、毎回新しいシートに結果を出す単発の確認用です。公式仕様に沿った処理を、疑似レスポンスで検証しています。実広告アカウントでの疎通確認は未実施です。

取得期間は、このサンプルでは最大7日間に絞っています。日付はUTCの昨日まで、過去89日以内で指定してください。APIから値が返らない指標は空欄とし、実際の0と区別します。

GASの取得コードを開く
/**
 * Pinterest広告の初回取得用サンプル(単発実行)。
 * スプレッドシートに紐づくGASで使用します。
 * スクリプトプロパティ:PINTEREST_ACCESS_TOKEN、PINTEREST_AD_ACCOUNT_ID、
 * PINTEREST_START_DATE、PINTEREST_END_DATE。
 * ads:readを付与したOAuthトークンが必要です。
 * 公式仕様に基づく例。実広告アカウントでの疎通は未実施です。
 */
function importPinterestAdsReport() {
  const properties = PropertiesService.getScriptProperties();
  function requiredProperty(key) {
    const value = (properties.getProperty(key) || '').trim();
    if (!value) throw new Error(key + 'を設定してください。');
    return value;
  }

  const token = requiredProperty('PINTEREST_ACCESS_TOKEN');
  const accountId = requiredProperty('PINTEREST_AD_ACCOUNT_ID');
  const start = requiredProperty('PINTEREST_START_DATE');
  const end = requiredProperty('PINTEREST_END_DATE');
  if (!/^\d+$/.test(accountId)) {
    throw new Error('広告アカウントIDは数字で指定してください。');
  }

  const startMs = pinterestReportDate_(start);
  const endMs = pinterestReportDate_(end);
  const dayMs = 86400000;
  const todayUtc = Math.floor(Date.now() / dayMs) * dayMs;
  if (startMs > endMs || endMs - startMs > 6 * dayMs) {
    throw new Error('この初回取得例では、開始日から終了日までを7日以内にしてください。');
  }
  if (startMs < todayUtc - 89 * dayMs || endMs >= todayUtc) {
    throw new Error('日付はUTCの昨日まで、過去89日以内で指定してください。');
  }

  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  if (!spreadsheet) throw new Error('スプレッドシートからGASを開いてください。');
  const base = 'https://api.pinterest.com/v5/ad_accounts/' + accountId;
  const account = pinterestReportGetJson_(base, token);
  if (!account || typeof account.currency !== 'string' || !/^[A-Z]{3}$/.test(account.currency)) {
    throw new Error('広告アカウントの通貨を確認できませんでした。');
  }

  const parameters = {
    start_date: start,
    end_date: end,
    columns: 'SPEND_IN_MICRO_DOLLAR,PAID_IMPRESSION,OUTBOUND_CLICK_1',
    granularity: 'DAY'
  };
  const query = Object.keys(parameters).map(function (key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(parameters[key]);
  }).join('&');
  const data = pinterestReportGetJson_(base + '/analytics?' + query, token);
  if (!Array.isArray(data)) throw new Error('広告レポートの応答形式を確認してください。');
  if (data.length === 0) throw new Error('対象期間のデータがありません。期間と配信状況を確認してください。');

  const seenDates = new Set();
  const rows = data.map(function (row) {
    if (!row || String(row.AD_ACCOUNT_ID) !== accountId) {
      throw new Error('応答の広告アカウントIDが一致しません。');
    }
    const dateMs = pinterestReportDate_(row.DATE);
    if (dateMs < startMs || dateMs > endMs || seenDates.has(row.DATE)) {
      throw new Error('応答の日付に範囲外または重複があります。');
    }
    seenDates.add(row.DATE);
    const spend = pinterestReportMetric_(row, 'SPEND_IN_MICRO_DOLLAR');
    return [
      row.DATE, accountId, account.currency,
      spend === '' ? '' : spend / 1000000,
      pinterestReportMetric_(row, 'PAID_IMPRESSION'),
      pinterestReportMetric_(row, 'OUTBOUND_CLICK_1')
    ];
  }).sort(function (a, b) { return a[0].localeCompare(b[0]); });

  // 初回確認用に毎回新しいシートを作ります。既存のシートは変更しません。
  const suffix = Utilities.formatDate(new Date(), 'UTC', 'yyyyMMdd_HHmmss')
    + '_' + Utilities.getUuid().slice(0, 8);
  const sheet = spreadsheet.insertSheet('Pinterest_' + suffix);
  const headers = ['日付(UTC)', '広告アカウントID', '通貨', '広告費', '広告表示回数', '外部クリック(直接)'];
  sheet.getRange(2, 1, rows.length, 3).setNumberFormat('@');
  sheet.getRange(1, 1, rows.length + 1, headers.length).setValues([headers].concat(rows));
  sheet.getRange(2, 4, rows.length, 1).setNumberFormat('#,##0.######');
  sheet.setFrozenRows(1);
  sheet.autoResizeColumns(1, headers.length);
  spreadsheet.toast(rows.length + '日分を新しいシートに出力しました。', 'Pinterest API');
}

function pinterestReportDate_(value) {
  if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
    throw new Error('日付はYYYY-MM-DDで指定してください。');
  }
  const milliseconds = Date.parse(value + 'T00:00:00Z');
  if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString().slice(0, 10) !== value) {
    throw new Error('日付が存在しません。');
  }
  return milliseconds;
}

function pinterestReportMetric_(row, key) {
  const value = row[key];
  if (value === undefined || value === null || value === '') return '';
  if ((typeof value !== 'number' && typeof value !== 'string') || String(value).trim() === '') {
    throw new Error('指標の形式を確認してください:' + key);
  }
  const number = Number(value);
  if (!Number.isFinite(number)) throw new Error('数値として読めない指標があります:' + key);
  return number;
}

function pinterestReportGetJson_(url, token) {
  let response;
  try {
    response = UrlFetchApp.fetch(url, {
      method: 'get',
      headers: { Authorization: 'Bearer ' + token },
      muteHttpExceptions: true,
      followRedirects: false
    });
  } catch (error) {
    throw new Error('Pinterest APIに接続できませんでした。接続状況を確認してください。');
  }
  const status = response.getResponseCode();
  if (status < 200 || status >= 300) {
    const explanations = {
      401: 'トークンの有効性を確認してください。',
      403: 'ads:readと広告アカウントへのアクセス権を確認してください。',
      429: '呼び出し上限です。レート制限ヘッダーを確認し、時間をおいて再実行してください。'
    };
    throw new Error('Pinterest API HTTP ' + status + '。'
      + (explanations[status] || '応答状況を確認してから再実行してください。'));
  }
  try {
    return JSON.parse(response.getContentText());
  } catch (error) {
    throw new Error('Pinterest APIの応答をJSONとして読み取れませんでした。');
  }
}

取得後に照合する条件

この例の日付はUTC基準です。日本時間で表示した管理画面と、同じ日付のまま比較すると範囲がずれます。日付の境界に加え、通貨、選択した指標、データの更新状況をそろえてください。公式API仕様

外部クリックの _1 は広告への直接アクションを表します。保存された広告から生じるアクションなどを含む値とは、集計範囲が異なります。コンバージョン数を追加する場合は、計測期間や集計日の基準も合わせます。公式広告レポートガイド

コードは、APIがエラーを返した場合に処理を止めます。401ならトークン、403ならスコープとアクセス権を確認します。429は呼び出し上限のため、レート制限ヘッダーを確認して待機します。公式レート制限

日次運用を始めて分かったこと

日次レポートの運用では、GASでPinterestのオーガニック指標を取得し、BigQueryへ蓄積しました。次の3点は、その運用記録と実装から確認した経験です。

直近の0を、すぐに活動停止と判断しない

導入後、直近2日ほどの指標が0で返り、後日数値が入るケースがありました。そこで、毎日直近7日分を取り直し、同じ日付の値を更新する設計にしました。

毎日直近7日分を取得し、前日と重なる6日分を更新、新しい1日分を追加する例

図2:過去分を再取得して更新する仕組み。7日間は、この運用で採用した取得範囲です。

確認には、管理画面の日付とオーガニックの集計範囲をそろえました。取得経路や集計のタイミングが異なる数値を、そのまま比べないためです。

これは当時のオーガニック分析で観測した挙動です。広告データ全般が48時間で確定するという意味ではありません。広告側には、/resources/metrics_ready_state で指標の準備状況を確認する方法もあります。公式広告レポートガイド

30日間の値と、その日の値を分ける

ピン別の指標は、直近30日間を指定して毎日保存していました。この場合、保存した日付が日次でも、数値は30日間の集計です。

昨日との差分には、新しく増えた分に加え、集計期間から外れた日の分も影響します。日次の活動量には日別データを使い、30日間の値は期間を明記したスナップショットとして扱いました。

30日間の集計範囲が1日ずれると、新しい1日分が加わり、最も古い1日分が外れる

図3:30日集計の増減には、期間に加わる日と外れる日の両方が影響します。集計済みの数値の更新も差分に含まれます。

保存する際は、「取得日」「集計開始日」「集計終了日」を区別すると、後から意味を追えます。

全ピンを個別に取る前に、レポートの目的を決める

月次レポートでは、全ピンの実績を1件ずつ取得する方法が、レート制限で進まなくなりました。必要だったのは、表示や保存、送客につながった上位ピンを確認することです。

そこで、/user_account/analytics/top_pins を使う方式へ切り替えました。目的に合った集計APIを選ぶことで、個別の呼び出しを減らせました。公式API仕様

ただし、上位ピンの一覧だけで全体の合計や構成比は求められません。全体の推移にはアカウント単位の日次指標、個別の振り返りには上位ピンを使い、レポートの役割を分けました。

最初の1日分を照合してから自動化する

まずは、過去の1日分を取り出して管理画面と照合します。確認するのは、取得できたか、日付と通貨が合っているか、同じ定義の指標を比べているかの3点です。

日次運用へ進む際には、トークンの更新と過去分の再取得を組み込みます。広告アカウントIDと日付をキーに既存の値を更新し、取得失敗も通知します。再取得する期間は、対象指標の更新状況に合わせて決めます。

直近の0を見つけたときも、管理画面と条件をそろえて見直したことが改善につながりました。最初の1行を確かめる手順まで作っておくと、取得が続くようになってからも、数字を判断に使いやすくなります。

公式仕様の確認日:2026年9月27日。アクセスレベルや利用条件は変更されるため、実装時はリンク先の公式情報も確認してください。

関連記事

この記事をシェア

参照元

この記事をAIと深掘りする

要約・疑問の解消に。記事のタイトル・URL・参照元を入れた質問文が自動で入力されます。

ryottaman

ryottaman

広告代理店で17年、現在は事業会社でインハウスマーケティングを担当しています。

SIGNALZの記事はAIを活用して作成し、10年以上の運用型広告の実務経験をもとに内容を確認・監修しています。制作方針の詳細はサイトについてをご覧ください。

この記事について感想やご質問を送れます

誤りの指摘、補足情報、ご質問など、お気軽にどうぞ。

Google検索の優先ソースにSIGNALZを追加

追加すると、Google検索のトップニュースでSIGNALZの記事が見つけやすくなります。AIモードとAIによる概要でも「優先」バッジが付きます。設定はいつでも解除できます。

追加する