本記事のポイント
「API を使えばもっと自由にデータを触れるのは分かる。でも認証とか制限とか、どこから手を付けていいか分からなくて止まっている」。HubSpot を使う現場で、よく聞く悩みです。原因はスキル不足ではありません。最初の 1 リクエストが返ってくる成功体験の手前で、認証とレート制限という 2 つの壁に情報が散らばっているからです。本記事では、HubSpot の Contacts を全件取得する「最初の 1 本」を、Private App の Token 発行から実行まで再現できる手順に落として公開します。特別なスキルは不要で、スクリプト本体は巻末の一括実行プロンプトで生成できます。
完成形から見せます。下図の 4 STEP をなぞると、HubSpot の Contacts が Token 認証つきの API 経由で取得され、手元の端末に JSON として保存される状態になります。ここまで動けば、あとは取得対象を Companies や Deals に広げるだけで、既存の同期シリーズにそのまま接続できます。
| 項目 | 内容 |
|---|---|
| 所要時間の目安 | 約 30 分 (API 未経験 + 一括実行プロンプト利用の場合) |
| 前提条件 | HubSpot Starter 以上 / HubSpot の super admin 権限 / Claude Code (ブラウザ版 Claude でもコードは生成できます) |
| 追加ランニングコスト | $0 (Private App も API 利用も無料枠内) |
| この記事で動かす範囲 | Contacts を Token 認証で全件取得し、JSON に保存するまで |
ノーコードの iPaaS (Zapier や Make.com) にも、監視 UI やリトライを画面で組める価値はあります。決まった連携を非エンジニアが日常的に編集する体制ならそちらも選択肢です。マーケ担当 1 人が Claude Code でスクリプトを持てるなら、この STEP を進めるほうが自由度とコストの両面で有利です。以下、STEP 1 から順に進めます。
「API キーってどこで発行するんですか」。入門で最初に出る質問です。HubSpot は以前の API キー方式を廃止しており、今は Private App を 1 つ作って、そこで発行される access token を使います。所要は 5 分ほどです。
やることは 3 つです。
crm.objects.contacts.read の 1 つでまず十分です)つまずきポイントは 2 つあります。ひとつは権限で、Private App の作成には HubSpot の super admin 権限が必要です。メニューに Private Apps が出てこないときは、自分のユーザーが super admin かを管理者に確認してください。もうひとつは Scopes の付け忘れで、読み取りに必要な scope が 1 つでも欠けていると API は 403 を返します。今回は読み取りだけなので .read で足りますが、あとで値を書き戻す連携に広げるときは .write も追加します。
なお 2026 年時点で、HubSpot は UI で作る Private App を段階的に legacy 扱いへ移し、データ連携用途には service key というクレデンシャルを推奨し始めています。既存の Private App Token は引き続き有効なので、入門としてはこのまま進めて問題ありません。【要確認: 2026-09 公開時点で管理画面の表記が "Private Apps" のままか "Legacy apps" 配下に移っているか。実アカウント Hub 22817028 の UI で最終確認して本文を合わせる】
Token を手に入れたら、スクリプトを書く前に「本当に通るか」を 1 リクエストだけで確かめます。ここを飛ばして全件取得のコードから書き始めると、認証エラーなのかコードのバグなのか切り分けられず、時間を溶かします。まず疎通、それからコード、の順です。
Token を環境変数に入れて、Contacts を 1 件だけ取得してみます。ターミナルで実行できます。
export HUBSPOT_TOKEN="pat-na1-xxxxxxxx" # STEP 1 でコピーした Token
curl --request GET \
--url 'https://api.hubapi.com/crm/v3/objects/contacts?limit=1' \
--header "Authorization: Bearer $HUBSPOT_TOKEN"
ポイントは 2 点です。API のベース URL は https://api.hubapi.com で、認証は Authorization: Bearer {Token} ヘッダーで渡すこと。そして limit=1 を付けて、まず 1 件だけ返させて疎通を確かめることです。Contacts が 1 件ぶん JSON で返ってくれば、認証は成功しています。
つまずきポイントは、エラーが返ったときの切り分けです。HTTP ステータスで原因がほぼ特定できます。
| ステータス | 意味 | 対処 |
|---|---|---|
| 200 OK | 認証成功。Contacts が返っている | 次の STEP へ |
| 401 Unauthorized | Token が間違い / コピーミス / 期限切れ | STEP 1 の Auth タブで Token を再表示してコピーし直す |
| 403 Forbidden | 認証は通ったが scope が足りない | Private App の Scopes に crm.objects.contacts.read を追加する |
401 と 403 は名前が似ていますが、原因は正反対です。401 は「あなたが誰だか分からない (Token の問題)」、403 は「誰かは分かるがその操作は許可していない (scope の問題)」です。この 2 つを切り分けられれば、認証まわりのトラブルはほぼ自己解決できます。
疎通が取れたら、いよいよ「全件取得」に進みます。「1 件は取れたけど、何千件もあるデータを全部取るコードなんて書けない」。ここで多くの人が止まります。手で書く必要はありません。Claude Code に HubSpot の公式ドキュメントの URL を渡して、スクリプトを生成させます。
Contacts のような件数の多いデータを検索・全件取得するときは、POST /crm/v3/objects/contacts/search を使い、paging.next.after というカーソルを次のリクエストに渡して続きを取得します。この「ページ送り (pagination)」を Claude Code に実装させます。指示は次のようなものです。
https://developers.hubspot.com/docs/guides/api/crm/search を読んで、
HubSpot の Contacts を Private App Token で全件取得する Node.js スクリプトを書いてください。
- Token は環境変数 HUBSPOT_TOKEN から読む
- Search API を POST で叩き、paging.next.after が無くなるまでループして全件取得する
- 取得した配列を contacts.json に保存する
すると、次のような骨格が生成されます。カーソルが空になるまで while で回すのが全件取得の肝です。
const TOKEN = process.env.HUBSPOT_TOKEN;
const BASE = 'https://api.hubapi.com';
async function fetchAllContacts() {
const results = [];
let after = undefined;
do {
const res = await fetch(`${BASE}/crm/v3/objects/contacts/search`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
limit: 100,
properties: ['email', 'firstname', 'lastname', 'createdate'],
after,
}),
});
const body = await res.json();
results.push(...body.results);
after = body.paging?.next?.after;
} while (after);
return results;
}
つまずきポイントは、Search API の「同一 query で 10,000 件まで」という上限です。1 リクエストで返るのは最大 100 件で、それをカーソルで繋いでも 1 つの query では 1 万件が天井になります。Contacts が 1 万件を超える場合は、createdate で期間を区切って複数 query に分けるよう Claude Code に追記させます。もうひとつ、生成されたコードが if で 1 回だけカーソルを見て終わっていないかは確認してください。while (after) になっていないと、最初の 100 件しか取れません。
全件取得のコードが動くと、次は件数が増えたときの壁に当たります。「ローカルでは動いたのに、本番データで回したら途中で止まった」。原因の多くはレート制限です。HubSpot の API には秒間の上限があり、超えると HTTP 429 が返ってリクエストが弾かれます。
Free/Starter プランの Private App は、10 秒あたり 100 リクエスト (burst) と 1 日あたり 250,000 リクエスト (daily) が上限です (HubSpot API usage guidelines, 取得日: 2026-07)。burst 上限はアプリ単位、daily 上限はアカウント内の全アプリで共有されます。Professional 以上でも burst は同じ 100/10s が基準で、必要なら API Limit Increase add-on で 10 秒あたり 250 リクエスト + 1 日 100 万リクエストまで引き上げられます。
入門段階では、429 が返ったら少し待って同じリクエストを送り直す「リトライ」を 1 つ入れておけば十分です。Claude Code には「429 が返ったら Retry-After ヘッダーの秒数だけ待って再送する処理を追加して」と指示します。加えて、100 件ずつのループの合間に短い待機 (たとえば 200 ミリ秒) を挟むと、そもそも burst 上限に当たりにくくなります。
async function fetchWithRetry(url, options, retries = 3) {
const res = await fetch(url, options);
if (res.status === 429 && retries > 0) {
const wait = Number(res.headers.get('Retry-After') || 10) * 1000;
await new Promise(r => setTimeout(r, wait));
return fetchWithRetry(url, options, retries - 1);
}
return res;
}
つまずきポイントは、リトライを入れずに件数を増やしてしまうことです。429 を無視すると、途中まで取れたデータで処理が止まり、原因も分かりにくくなります。最初のスクリプトのうちに、上のリトライ関数を通してから本番データに向けるのが安全です。
ここまでの部品が揃ったら、実行して手元にデータを落とします。生成されたスクリプトに、取得した配列をファイルに保存する処理を足して走らせます。
import { writeFileSync } from 'node:fs';
const contacts = await fetchAllContacts();
writeFileSync('contacts.json', JSON.stringify(contacts, null, 2));
console.log(`取得件数: ${contacts.length}`);
ターミナルで node fetch-contacts.js のように実行し、取得件数: 1234 のような出力が出て contacts.json が生成されれば成功です。中を開いて、email や firstname が想定どおり入っているかを目視で確かめます。ここまでが「最初の 1 本」です。
次の 3 点が揃えば、API 連携の入り口は突破できています。
contacts.json が生成され、取得件数 が HubSpot 画面の Contacts 総数とおおむね一致している3 点が揃えば、取得対象を Companies (crm.objects.companies.read) や Deals (crm.objects.deals.read) に広げる、値を書き戻す (.write scope を追加) といった応用は、同じ型の繰り返しです。
HubSpot API の入門でやることは、Token を 1 つ発行し、認証 (401 / 403) とレート制限 (100req/10s と 429) の 2 点を押さえ、あとは Claude Code に公式ドキュメントを読ませてスクリプトを生成させる、これだけです。手でコードを書く必要はなく、必要なのは「どのデータを、どこまで取りたいか」を決める判断です。
この「最初の 1 本」が動けば、次は用途を広げる番です。取得したデータを毎朝 Google Sheets に同期する全体像は HubSpot を Apps Script で Sheets に同期 で、データ量が増えて全件取得が重くなったときの 増分同期の設計 で解説しています。Claude Code をまだ導入していなければ マーケター向け Claude Code 入門 から、コードを書かずに HubSpot を操作したい場合は HubSpot MCP の接続手順 から辿れます。
humbulls では、こうした API 連携の設計から運用までを一気通貫で伴走しています。自社のデータで何をどこまで自動化できるかは 連携開発の相談窓口 からご相談ください。
本文の STEP 1〜5 を、そのまま Claude Code に丸ごと実行させるためのキットです。プロンプトの記入例を自社の値に置き換えて渡してください。
種別: 実装キット 使うもの: Claude Code (ブラウザ版 Claude でもコードは生成できますが、実行は手元のターミナルで行います) 事前に用意するもの: HubSpot の Private App Token (STEP 1 で発行) / Node.js が動く環境 / 取得したいオブジェクトと列名のメモ
プロンプト:
HubSpot の Contacts を Private App Token で全件取得する Node.js スクリプトを
一式書いてください。まず下記の公式ドキュメントを読んでから実装してください。
- https://developers.hubspot.com/docs/guides/api/crm/search
- https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines
【私の環境】
- 取得するオブジェクト: contacts(記入例。companies / deals に置き換え可)
- 取得するプロパティ: email, firstname, lastname, createdate(記入例。自社の必要列に)
- Token は環境変数 HUBSPOT_TOKEN から読む
- 保存先: contacts.json
【必ず守る実装条件(本文の対策を転写)】
1. ベース URL は https://api.hubapi.com、認証は Authorization: Bearer ヘッダー
2. Search API を POST で叩き、paging.next.after が無くなるまで while でループして全件取得する
(if の 1 回判定にしない。最初の 100 件で止まるため)
3. 1 query 10,000 件上限に備え、超える場合は createdate で期間分割する分岐を入れる
4. HTTP 429 が返ったら Retry-After 秒だけ待って再送するリトライ関数を通す
(Free/Starter は 10 秒あたり 100 リクエスト上限のため)
5. 100 件ループの合間に 200ms の待機を挟む
【出力】
- fetchAllContacts() + fetchWithRetry() + ファイル保存 + 件数ログ
- 実行コマンドと、401/403 が出たときの切り分け手順を最後にコメントで併記
出力の確認ポイント:
while (after) で書かれ、if の 1 回判定になっていないかfetch していないか)うまくいかないとき:
.read を追加してくださいよくあるつまずきの早見表:
| 症状 | 原因 | 対処 |
|---|---|---|
| 401 Unauthorized | Token 不正 / 期限切れ | Auth タブで Token を再取得 |
| 403 Forbidden | scope 不足 | Private App に .read scope を追加 |
| 最初の 100 件しか取れない | pagination 漏れ | while (after) でカーソル判定 |
| 途中で 429 で止まる | レート制限超過 | Retry-After で待って再送 + 待機を挟む |
| 1 万件で頭打ち | Search API の query 上限 | createdate で期間分割 |