株式会社etika(エティカ)CRMサポートセンターです。
「Zoho CRMのデータを自前のスクリプトから読み書きしたい」「バッチ処理でまとめて項目を更新したい」——このとき最初の関門になるのがAPI認証です。この記事では、リフレッシュトークンの管理が不要で、サーバー間連携に向いた**Client Credentials方式(Self Client)**でアクセストークンを取得する手順を、実際に動くNode.jsコードで解説します。
結論(先に要点)
- Zoho API Console で Self Client を作り、Client ID / Client Secret を取得する
grant_type=client_credentialsとsoid=ZohoCRM.<組織ID>を付けてトークンを取得する- データセンター(dc)ごとに
accountsとapi_domainのURLが異なる点に注意する
認証方式の選び方
Zoho APIの認証にはいくつか方式がありますが、用途で選びます。
| 方式 | 向き | リフレッシュトークン |
|---|---|---|
| Authorization Code | ユーザー操作を伴うアプリ | 必要 |
| Client Credentials(Self Client) | サーバー間・バッチ処理 | 不要 |
社内バッチや管理用スクリプトのように「特定の組織のデータを、ユーザーのログインなしに操作する」ケースでは、Client Credentials方式がシンプルで運用も楽です。

手順1:Self Clientを作成する
- Zoho API Console(日本DCなら
api-console.zoho.jp)へアクセス - 「Self Client」を選択して作成
- 発行された Client ID と Client Secret を控える
あわせて、対象組織の**組織ID(zgid)**を用意します(CRMの「設定 > 会社情報」等で確認できます)。
手順2:必要なscopeを決める
やりたい操作に対応するscopeだけを付けます。例:
- レコードの読み書き:
ZohoCRM.modules.ALL - 項目・レイアウトなどの設定:
ZohoCRM.settings.ALL - 項目定義の読み書きに絞る:
ZohoCRM.settings.fields.ALL
必要最小限にするのがセキュリティ上の基本です。
手順3:トークンを取得する(Node.js)
Client Credentials方式のポイントは、soidパラメータにZohoCRM.<組織ID>を渡すことです。これで「どの組織を操作するか」を指定します。
function accountsBaseUrl(dc) {
switch ((dc || "").toLowerCase()) {
case "jp": return "https://accounts.zoho.jp";
case "eu": return "https://accounts.zoho.eu";
case "in": return "https://accounts.zoho.in";
case "au": return "https://accounts.zoho.com.au";
default: return "https://accounts.zoho.com";
}
}
async function getToken({ dc, clientId, clientSecret, orgId, scope }) {
const body = new URLSearchParams();
body.set("client_id", clientId);
body.set("client_secret", clientSecret);
body.set("grant_type", "client_credentials");
body.set("scope", scope);
body.set("soid", `ZohoCRM.${orgId}`); // ← 操作対象の組織を指定
const res = await fetch(`${accountsBaseUrl(dc)}/oauth/v2/token`, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body,
});
const json = await res.json();
if (!res.ok || json.error || !json.access_token) {
throw new Error(`トークン取得に失敗: ${JSON.stringify(json)}`);
}
return {
accessToken: json.access_token,
apiDomain: (json.api_domain || "").replace(/\/+$/, ""),
};
}
手順4:取得したトークンでAPIを叩く
レスポンスに含まれるapi_domainを必ず使うのがコツです。DCによってAPIのドメインが異なるため、ハードコードせずレスポンス値を使います。
async function crmFetch(token, path, { method = "GET", body } = {}) {
const res = await fetch(`${token.apiDomain}${path}`, {
method,
headers: {
Authorization: `Zoho-oauthtoken ${token.accessToken}`,
...(body ? { "content-type": "application/json" } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
});
if (res.status === 204) return null;
const json = await res.json();
if (!res.ok) throw new Error(`API ${method} ${path} failed: ${JSON.stringify(json)}`);
return json;
}
// 使用例:商談モジュールの項目一覧を取得
const token = await getToken({
dc: "jp",
clientId: process.env.ZOHO_CLIENT_ID,
clientSecret: process.env.ZOHO_CLIENT_SECRET,
orgId: process.env.ZOHO_ORG_ID,
scope: "ZohoCRM.settings.fields.ALL",
});
const fields = await crmFetch(token, "/crm/v8/settings/fields?module=Deals");
console.log(fields.fields.map((f) => `${f.field_label} = ${f.api_name}`));

つまずきやすいポイント
missing_org_infoエラー:soid=ZohoCRM.<組織ID>の付け忘れ、または組織IDの誤りが原因です。- DCの取り違え:日本DCの組織なら
accounts.zoho.jp。.comに投げると認証が通りません。api_domainも同様にレスポンス値を使います。 - scope不足:
INVALID_SCOPEや権限エラーが出たら、操作に対応するscopeが付いているか確認します。 - Client SecretはGitに載せない:
.envに置き、環境変数から読み込みます。
まとめ
Client Credentials方式(Self Client)なら、リフレッシュトークンの更新運用に悩まされることなく、スクリプトからZoho APIを叩けます。ポイントは**soidで組織を指定することとDCごとのURL・api_domainを正しく使うこと**の2点。ここさえ押さえれば、項目一覧の取得からレコードの一括更新まで自動化の土台が整います。



