カーソルAPI
kintone の REST API でレコードを複数件取得する場合、通常の records.json エンドポイントでは offset の上限が 10,000 件 に制限されています。そのため、10,000 件を超えるレコードを扱うアプリでは、通常のクエリだけではすべてのレコードを取得できません。
この問題を解決するのがカーソル API です。カーソル API を使えば、offset の制限を気にすることなく、大量のレコードを安全かつ効率的に取得できます。
この記事では、カーソル API の基本的な使い方から、全件取得の汎用関数、運用上の注意点まで解説します。
カーソル API の概要
カーソル API は、以下の 3 つのエンドポイントで構成されています。
| エンドポイント | メソッド | 説明 |
|---|---|---|
/k/v1/records/cursor.json | POST | カーソルを作成する |
/k/v1/records/cursor.json | GET | カーソルからレコードを取得する |
/k/v1/records/cursor.json | DELETE | カーソルを削除する |
処理の流れ
- カーソルを作成する — 取得条件(アプリ ID、クエリ、フィールド)を指定してカーソルを作成
- レコードを取得する — カーソル ID を指定して、500 件ずつレコードを取得(
next: trueの間は繰り返す) - カーソルを削除する — 全件取得後、カーソルを削除してリソースを解放
すべてのレコードを取得し終えた時点(next: false
が返された時点)でカーソルは自動的に削除されます。途中でカーソルの使用を中断する場合のみ、手動で削除する必要があります。
kintone から利用する場合
カーソルの作成
/**
* カーソルを作成する
* @param { Object } params
* @param { string | number } params.app - アプリID
* @param { string[] } [params.fields] - 取得するフィールドコード
* @param { string } [params.query] - クエリ条件
* @param { number } [params.size] - 1回の取得件数(最大500、デフォルト100)
* @returns { Promise<{ id: string; totalCount: string }> } カーソルIDと総件数
*/
const createCursor = (params) => {
const { app, fields, query, size } = params;
return kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'POST', {
app,
fields,
query,
size: size || 500,
});
};
size パラメータには 1〜500 の値を指定できます。パフォーマンスを最大化するために、500
を指定することをおすすめします。
カーソルからレコードを取得
/**
* カーソルからレコードを取得する
* @param { Object } params
* @param { string } params.id - カーソルID
* @returns { Promise<{ records: Record<string, any>[]; next: boolean }> } レコードと次ページ有無
*/
const getCursorRecords = (params) => {
return kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'GET', {
id: params.id,
});
};
カーソルの削除
/**
* カーソルを削除する
* @param { Object } params
* @param { string } params.id - カーソルID
* @returns { Promise<{}> }
*/
const deleteCursor = (params) => {
return kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'DELETE', {
id: params.id,
});
};
全件取得の汎用関数
カーソルの作成からレコード取得、カーソルの削除までを一貫して行う汎用関数を定義すると便利です。
/**
* カーソル API を使用して、条件に一致する全レコードを取得する
* @param { Object } params
* @param { string | number } params.app - アプリID
* @param { string[] } [params.fields] - 取得するフィールドコード
* @param { string } [params.query] - クエリ条件(order by, limit, offset は指定不要)
* @returns { Promise<Record<string, any>[]> } 全レコード
*/
const getAllRecordsWithCursor = async (params) => {
const { app, fields, query } = params;
// Step 1: カーソルを作成
const cursor = await kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'POST', {
app,
fields,
query,
size: 500,
});
const allRecords = [];
try {
// Step 2: レコードを繰り返し取得
let hasNext = true;
while (hasNext) {
const result = await kintone.api(
kintone.api.url('/k/v1/records/cursor.json', true),
'GET',
{ id: cursor.id }
);
allRecords.push(...result.records);
hasNext = result.next;
}
} catch (error) {
// Step 3: エラー時はカーソルを削除してリソースを解放
await kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'DELETE', {
id: cursor.id,
});
throw error;
}
return allRecords;
};
使用例
(() => {
'use strict';
kintone.events.on(['app.record.index.show'], async (event) => {
const appId = kintone.app.getId();
// 全件取得
const allRecords = await getAllRecordsWithCursor({ app: appId });
console.log(`全 ${allRecords.length} 件取得しました`);
// クエリ付きで全件取得
const filteredRecords = await getAllRecordsWithCursor({
app: appId,
query: '契約状況 in ("契約中")',
fields: ['会社名', '契約状況', '契約日'],
});
console.log(`条件に一致: ${filteredRecords.length} 件`);
return event;
});
})();
カーソル API の query パラメータでは、order by、limit、offset
を指定できません。これらはカーソル API
が内部的に管理するため、絞り込み条件のみを指定してください。
kintone 以外から利用する場合
Node.js を使用した場合
const BASE_URL = 'https://__your_subdomain__.cybozu.com';
const API_TOKEN = 'YOUR_API_TOKEN';
/**
* カーソル API を使用して全レコードを取得する
* @param { Object } params
* @param { string | number } params.app - アプリID
* @param { string[] } [params.fields] - 取得するフィールドコード
* @param { string } [params.query] - クエリ条件
* @returns { Promise<Record<string, any>[]> } 全レコード
*/
const getAllRecordsWithCursor = async (params) => {
const { app, fields, query } = params;
// カーソルを作成
const createRes = await fetch(`${BASE_URL}/k/v1/records/cursor.json`, {
method: 'POST',
headers: {
'X-Cybozu-API-Token': API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ app, fields, query, size: 500 }),
});
const cursor = await createRes.json();
const allRecords = [];
try {
let hasNext = true;
while (hasNext) {
const getRes = await fetch(`${BASE_URL}/k/v1/records/cursor.json?id=${cursor.id}`, {
method: 'GET',
headers: { 'X-Cybozu-API-Token': API_TOKEN },
});
const result = await getRes.json();
allRecords.push(...result.records);
hasNext = result.next;
}
} catch (error) {
// エラー時にカーソルを削除
await fetch(`${BASE_URL}/k/v1/records/cursor.json`, {
method: 'DELETE',
headers: {
'X-Cybozu-API-Token': API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: cursor.id }),
});
throw error;
}
return allRecords;
};
Python を使用した場合
import requests
BASE_URL = 'https://__your_subdomain__.cybozu.com'
API_TOKEN = 'YOUR_API_TOKEN'
HEADERS = {
'X-Cybozu-API-Token': API_TOKEN,
'Content-Type': 'application/json',
}
def get_all_records_with_cursor(app, query='', fields=None):
"""カーソル API を使用して全レコードを取得する"""
# カーソルを作成
body = {'app': app, 'size': 500}
if query:
body['query'] = query
if fields:
body['fields'] = fields
create_res = requests.post(
f'{BASE_URL}/k/v1/records/cursor.json',
headers=HEADERS,
json=body,
)
cursor = create_res.json()
all_records = []
try:
has_next = True
while has_next:
get_res = requests.get(
f'{BASE_URL}/k/v1/records/cursor.json',
headers=HEADERS,
params={'id': cursor['id']},
)
result = get_res.json()
all_records.extend(result['records'])
has_next = result['next']
except Exception as e:
# エラー時にカーソルを削除
requests.delete(
f'{BASE_URL}/k/v1/records/cursor.json',
headers=HEADERS,
json={'id': cursor['id']},
)
raise e
return all_records
カーソル API の制約と注意点
カーソル API を利用する際には、いくつかの重要な制約を把握しておく必要があります。
同時カーソル数の上限
| 項目 | 制限値 |
|---|---|
| ドメインあたりの同時カーソル数 | 10 個 |
| 1 カーソルあたりの有効期間 | 10 分 |
| 1 回の取得レコード数(最大) | 500 件 |
ドメイン全体で同時に使用できるカーソルは 10 個 までです。上限を超えると新しいカーソルを作成できなくなります。カーソルの使用が終わったら、速やかに削除するか、全件取得して自動削除させてください。
カーソルの有効期間
カーソルは作成後 10 分間 有効です。10 分以内に取得を完了できない場合、カーソルは無効になります。
大量のレコードを処理しながら取得する場合は、取得後にまとめて処理するようにしてください。
// ✅ 推奨: まず全件取得してから処理する
const allRecords = await getAllRecordsWithCursor({ app: appId });
for (const record of allRecords) {
// 各レコードに対する処理
}
// ❌ 非推奨: 取得しながら重い処理を行う(タイムアウトのリスク)
カーソル API とシーク法の比較
| 比較項目 | カーソル API | シーク法(レコード ID 昇順取得) |
|---|---|---|
| 実装の容易さ | 高い | やや複雑 |
| offset 上限の回避 | ✅ | ✅ |
| ドメイン全体の同時実行数制限 | あり(10 個) | なし |
| 有効期限 | あり(10 分) | なし |
| ソート順の指定 | query で order by 指定不可 | $id 昇順のみ |
| 途中からの再開 | できない | 最後の ID を記録すれば可能 |
同時実行数や有効期限の制約が問題になる場合は、シーク法(レコード ID を使った一括取得)の利用を検討してください。
まとめ
- カーソル API は、
records.jsonの offset 上限(10,000 件)を超えるレコード を取得するための手段 - カーソルの作成 → 繰り返し取得 → 削除(または自動削除)の 3 ステップで利用する
sizeパラメータは最大500を指定してパフォーマンスを最大化する- ドメインあたりの同時カーソル数は 10 個 まで。不要なカーソルは速やかに削除する
- カーソルの有効期間は 10 分 。取得中に重い処理を挟まないよう注意する
queryパラメータにorder by、limit、offsetは指定できない