kintone カスタマイズスペシャリスト　復習ノート

[kintone 認定試験](https://cybozu.co.jp/kintone-certification/) のひとつ、[Customization Specialist(カスタマイズスペシャリスト)](https://cybozu.co.jp/kintone-certification/level/customization-specialist/) を受験して出題された内容から、記憶を元に出題内容を復習していきます。
あくまでこう言う分野に関する問題が出た、と言うだけであり、出題文そのものを記憶の限り再現すると言う趣旨ではありませんし、この内容をきちんと押さえとけば合格間違いなしだぜ！と言うわけではないので悪しからず。
出題範囲がすごく広いためとても雑多な感じであんまりまとまっていませんがどうぞご容赦。
また記述内容が必ずしも正確ではないかも知れません。その際はコメント等でご指摘くださるとありがたいです。

**(2019/04/03 追記)**
[kintone 認定資格 カスタマイズスペシャリスト学習ガイド](https://developer.cybozu.io/hc/ja/articles/360022670672-kintone-%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC-%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA%E3%82%B9%E3%83%9A%E3%82%B7%E3%83%A3%E3%83%AA%E3%82%B9%E3%83%88%E5%AD%A6%E7%BF%92%E3%82%AC%E3%82%A4%E3%83%89) が公開されました。
本記事よりも体系立ててしっかりまとめてあるので合わせて参考にして頂ければと思います。

# kintone Javascript API

## レコード取得時のタイムゾーン

イベントハンドラ内での `record` オブジェクトや `kintone.app.record.get()` などで得たレコードについて、ユーザー環境のタイムゾーンにかかわらず、作成日時・更新日時・時刻フィールドなど TIME 系のフィールドは全て UTC 文字列で帰る。

```json
"作成日時": {
  "type": "CREATED_TIME",
  "value": "2012-01-11T11:30:00Z"
}
```

## レコード再利用

`app.record.create.show` イベントで、 `reuse` プロパティでレコード再利用かどうかを判定できる。

```Javascript
kintone.events.on('app.record.create.show', (event) => {
  if (event.reuse) {
    console.log('再利用です');
  }

  return event;
});
```

再利用時に発生する特別なイベント（`app.record.reuse.show` のような）はない。

## ログインユーザー情報

[ログインユーザーの情報を取得する](https://developer.cybozu.io/hc/ja/articles/201942024)

`kintone.getLoginUser()` でログインユーザーの情報が取得できる。
ただしこの情報の中にはユーザーの所属する組織、優先する組織の情報は含まれない。
それらを取得するためには [User API](https://developer.cybozu.io/hc/ja/articles/202111374) を使用する。

## kintone.plugin.app.proxy()

[プラグインから外部の API を実行する](https://developer.cybozu.io/hc/ja/articles/203661160-kintone-%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3JavaScript-API-#step3)

プラグインから外部 API をコールする目的で使用する。
通常の `kintone.proxy()` と同じようなものだが、認証情報を以下の `kintone.plugin.app.setProxyConfig()` で事前に設定しておきたいなどの用途で使う（と思う）。

## kintone.plugin.app.setProxyConfig(url, method, headers, data, callback)

[外部の API の実行に必要な情報をプラグインに保存する](https://developer.cybozu.io/hc/ja/articles/203661160#list2)

> 外部の API の実行に、認証情報など秘密にする情報を使用する場合は、API を実行する前に、あらかじめ必要な情報をプラグインに保存しておきます。

とある通り、API キーや ID・psw など秘匿する情報をプラグイン実行画面で表に出ないよう、**設定画面上**で保存するためのメソッド。

`url` は、この URL の時にここで設定したヘッダとデータを付加しますと言う事。
`method` は、**GET、POST、PUT、DELETE** のいずれか。ANY、ALL、\* などは指定できない。

使い方の例）
プラグイン設定画面で

- 外部連携用アプリの API キーを入力させる
- 外部連携サービスのユーザー ID・パスワードなどを入力させる

その後、設定保存時にこのメソッドで保存する。

プラグイン実行画面では上の `kintone.plugin.app.proxy()` によって保存した認証情報がヘッダとボディに含まれて実行される。
この際 URL や method などが合致しないとリクエストには乗らない。
おかしなところに大事なデータが飛んでしまわないような安全策的なアレと言える。
[保存した情報がリクエストに加わる条件](https://developer.cybozu.io/hc/ja/articles/203661160#list3)

保存した情報は `kintone.plugin.app.getProxyConfig(url, method)` で取得可能。
設定画面で前回設定した内容を復元するために使用すると言った用途が考えられる。

## クエリの取得

### kintone.app.getQueryCondition() と kintone.app.getQuery()

一覧画面で絞り込みを実行した時にスクリプト側からクエリを取得する目的で使用。

`getQueryCondition()` は **絞り込み条件のみ** を取得する。
クエリが指定されていない時は **空文字** が帰る。

`getQuery()` は **limit や order by も含めたクエリ全体** を取得する。
クエリが取得されていない時は `order by ** limit ** offset **` が帰る。

**condition ＝ 条件節のみ** と覚えれば良い。

URL にクエリを直接使用する実装パターンもありえる。
その場合は `q=` と `query=` の違いに留意する。
細かく言えばクォートのエスケープとかも絡んで来るが試験ではそこまで言及される事はないか。

[URL 内のクエリで、表示するレコードの条件を指定](https://developer.cybozu.io/hc/ja/articles/202388664)

**これは PC でしか使えない。**

## クエリで使える関数

[レコードの一括取得（クエリで条件を指定）](https://developer.cybozu.io/hc/ja/articles/202331474-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%8F%96%E5%BE%97-GET-#step2)

| 関数                         | 意味                                              |
| :--------------------------- | :------------------------------------------------ |
| LOGINUSER()                  | API を実行したユーザー                            |
| PRIMARY_ORGANIZATION()       | API を実行したユーザーの優先する組織              |
| NOW()                        | API 実行日時                                      |
| TODAY()                      | API 実行日                                        |
| FROM_TODAY(数字, 期間の単位) | 今日から N 日前・N 日後などに使える　引数省略不可 |
| THIS_WEEK([曜日])            | 今週＊曜日　曜日を省略したら今週 1 週間のいずれか |
| LAST_WEEK([曜日])            | 先週＊曜日　曜日を省略したら先週 1 週間のいずれか |
| THIS_MONTH([日付])           | 今月＊日　日付を省略したら今月 1 日〜末日         |
| LAST_MONTH([日付])           | 先月＊日　日付を省略したら先月 1 日〜末日         |
| THIS_YEAR()                  | API 実行年                                        |

それぞれの関数に与えられる引数の種類も要確認。

これ以外の関数はない。
`NEXT_MONTH()` とかはない。 `THIS_MONTH(NEXT)` と言った表現もない。

## レコード一覧画面でどの一覧が表示されているかを特定する

[レコード一覧画面の表示後イベント](https://developer.cybozu.io/hc/ja/articles/201941964-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88#step1)
`event.viewId` と `event.viewName` で識別できる。
前者はアプリの設定で一覧を作成した時に自動で発番され、変更されることはない。
後者は自分で名前を付ける事が出来るので運用如何によっては変わる可能性がある。
カスタマイズで使用するなら `viewId` の方が適切と言う事になる。

なお、`event.viewType` でリスト表示かカレンダー表示かカスタマイズビューか判定できる。

```Javascript
kintone.events.on('app.record.index.show', (event) => {
  console.log(event.viewId);  // 例） 5519869
});
```

## 関連レコードのフィールドを参照するクエリ

[レコードの一括取得（クエリで条件を指定）](https://developer.cybozu.io/hc/ja/articles/202331474#step2)の「関連レコードに含まれるフィールドを指定する方法」参照

## テーブルの扱い

フィールドに行追加する際はフィールドタイプの指定が必要。
個々の行の個々のフィールド単位で無効にする事ができる。

```Javascript
event.record['Table'].value[0].value['文字列__1行__0'].disabled = true;
```

階層が深いのでややこしい。

一方、テーブル行内の特定フィールドを `setFieldShown()` メソッドで非表示にする事はできない。
このような場合は DOM 操作（禁じ手）をしなければならない。

## ルックアップ

### イベントハンドラ内での自動ルックアップ

`value` を設定したあと、`lookup` を `true` にする事で自動ルックアップ取得が可能。

[ルックアップの取得を自動で行う](https://developer.cybozu.io/hc/ja/articles/201941984#step8)

```Javascript
kintone.events.on('app.record.create.show', (event) => {
  let record = event.record;
  record['ルックアップ']['value'] = '0001'; // ルックアップフィールドの値
  record['ルックアップ']['lookup'] = true;
  return event;
});
```

### ルックアップの参照先アプリを確認する

[ルックアップフィールドの参照先のアプリ ID を取得する](https://developer.cybozu.io/hc/ja/articles/201942014#step8)

```Javascript
kintone.app.getLookupTargetAppId(fieldCode)
```

## ファイルのアップロード

[ファイルアップロード](https://developer.cybozu.io/hc/ja/articles/201941824)

> リクエストは multipart/form-data 形式で送信します。 詳細は RFC1867、RFC2388 を参照してください。
> Content-Disposition 内の name には「file」、filename にはファイル名を指定してください。
> ※日本語ファイル名の場合は UTF8 でエンコードした内容を指定して下さい。

リクエストは `multipart/form-data` 形式。
Javascript では `FormData` 型になる。

```Javascript
const blob = new Blob(["テストファイルです"], {
    type: "text/plain"
});
// FormDataにファイルを格納
let formData = new FormData();
formData.append("__REQUEST_TOKEN__", kintone.getRequestToken());
formData.append("file", blob, "test.txt");
```

リクエスト実行は以下のような感じ。

```Javascript
let xmlHttp = new XMLHttpRequest();
xmlHttp.open("POST", encodeURI('/k/v1/file.json'), false);
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(formData);
let key = JSON.parse(xmlHttp.responseText).fileKey;
```

レスポンスとして `fileKey` が返却される。

```json
{
  "fileKey": "590279cd-6657-419f-8fac-d760a4a6caaa"
}
```

この値を使ってレコードの添付ファイルフィールドに割り当てる。

```Javascript
const json = {
  app: kintone.app.getId(),
  id: kintone.app.record.getId(),
  record: {
    file: {
      value: [{
        fileKey: key
      }]
    }
  }
};
kintone.api('/k/v1/record', 'PUT', json);
```

この `fileKey` は 1 回使ったらもう使えない。
アプリに複数の添付ファイルフィールドがあったとして、それぞれのフィールドに同じファイルキーのファイルを添付することは出来ない。
（実体が同じファイルを 2 カ所のフィールドに置けないと言う事）

## フィールドのエラー、レコードのエラー

[フィールドにエラーを表示する](https://developer.cybozu.io/hc/ja/articles/202166270#step6)

```Javascript
kintone.events.on('app.record.edit.change.ドロップダウン_0', (event) => {
  let record = event.record;
  record['文字列_0']['error'] = 'このエラーメッセージを表示';
  record['Table']['value'][0]['value']['文字列_1']['error'] = 'テーブルの1レコード目にエラーを表示';
  return event;
});
```

`field.error = null` でエラーを消すことが出来る。

[レコードにエラーを表示する](https://developer.cybozu.io/hc/ja/articles/202166270#step7)

```Javascript
kintone.events.on('app.record.edit.change.ドロップダウン_0', (event) => {
  event.error = 'エラーです！';
  return event;
});
```

フィールドのエラーとは異なり、 `event.error = null` でエラーを消すことは**出来ない**。

レコードエラー、フィールドエラーともに文字列として処理される。
**HTML タグを書いてもタグのまま出力されるだけ。**

`app.record.edit.submit` イベントでレコードエラー・フィールドエラーをセットして `return event` すると保存がキャンセルされる。

# kintone REST API

## ユーザー認証

[ユーザー認証](https://developer.cybozu.io/hc/ja/articles/201941754#step7)

リクエストヘッダに格納する。

優先度の高い順に以下の通り。

- パスワード認証（`X-Cybozu-Authorization:「ログイン名:パスワード」をBASE64エンコードしたもの`）
- API トークン認証（`X-Cybozu-API-Token:トークン文字列`）
- セッション認証（詳細不明）

API トークン認証は使えない場面がある。（ルックアップの登録・更新など）

## Basic 認証

cybozu.com の設定で Basic 認証を有効にしている場合はユーザー認証に加えて Basic 認証をリクエストヘッダに格納しなくてはならない。

`Authorization:Basic 「ログイン名:パスワード」をBASE64エンコードしたもの`

ここで言うログイン名・パスワードは当然だが kintone のアカウントとは無関係。
Basic 認証はアクセス制御の文脈であって、ユーザー識別が目的ではない。

## レコード取得時のタイムゾーン

ユーザーのタイムゾーンにかかわらず、作成日時・更新日時・時刻フィールドなど TIME 系のフィールドは**全て UTC 文字列**で帰る。
kintone Javascript API と同様。

```json
"作成日時": {
  "type": "CREATED_TIME",
  "value": "2012-01-11T11:30:00Z"
}
```

## IP アドレス制限との関連

cybozu.com 共通管理で IP アドレスによるアクセス制限をかけている場合、以下の条件でリクエスト可能。

- アクセス許可された IP アドレスがアクセス元となる API リクエスト
- アクセス許可されたクライアント証明書を利用した API リクエスト

[注意事項](https://developer.cybozu.io/hc/ja/articles/201941754#step11)

## updateKey

[レコードの更新（PUT）](https://developer.cybozu.io/hc/ja/articles/201941784)

レコード ID の代わりに、**重複禁止フィールドのフィールドコードと値の組合せ**でレコード更新を行う技。
例えば`customer_id` と言う重複禁止フィールドを持つアプリで、その値が `aapl` であるレコードの `customer_name` を `Apple Inc.` に更新したいとしたら以下のようなリクエストになる。

```json
{
  "app": (アプリのID),
  "updateKey" {
    "field": "customer_id",
    "value": "aapl"
  },
  "record": {
    "customer_name": {
      "value": "Apple Inc."
    }
  }
}
```

## X-HTTP-Method-Override

[リクエストヘッダ](https://developer.cybozu.io/hc/ja/articles/201941754-kintone-REST-API%E3%81%AE%E5%85%B1%E9%80%9A%E4%BB%95%E6%A7%98#step9)

> X-HTTP-Method-Override に HTTP メソッドを指定して POST リクエストを送信すると、指定した HTTP メソッドに対応する API が実行されます。
> ・X-HTTP-Method-Override ヘッダは POST リクエストの時のみ有効になります。
> ・X-HTTP-Method-Override を使った API 呼び出しは全ての kintone REST API で利用できます。
> ・X-HTTP-Method-Override に指定する HTTP メソッド名は大文字のみ使用可能です。

GET したいがパラメータが異常に長すぎて `Request URI Too Large` が出てしまう時、それを回避するためにこのヘッダを付けて POST を投げるとあたかも GET したかのようにレスポンスが得られる的な。

> ※リクエスト URI が 8KB を超えると発生するエラー(Request URI Too Large)を回避する目的で使用します。
> ※kintone.api()で URI の長さが 4KB を超える GET リクエストが送信された場合、X-HTTP-Method-Override ヘッダが自動的に付与され、POST リクエストとして送信されます。
> ※kintone.proxy()については動作保証外となります。

**4KB 以上の URL で GET すると自動的にこのヘッダが付く**と言うのがポイント。

## レコード更新時のテーブル操作

- レコード更新時にリクエストにテーブルのデータを含まない場合には、テーブルのデータは保持される
- レコード更新時にテーブルのデータを含む場合には、リクエストに含まれないデータは更新されない
- 行追加であっても、すでにテーブルにあった値をリクエストに含める必要がある
- 挿入の場合にも、テーブルの行追加と同様テーブルに表示したい順番でリクエストする
- 更新する行の ID を指定することで、その行の特定のフィールドのみを更新
- 同じ行の他のフィールドを更新しない場合にはそのフィールドを省ける
- [] を指定してやる事でテーブルをクリアできる

[レコード更新におけるテーブル操作のテクニック](https://developer.cybozu.io/hc/ja/articles/200752984-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E6%9B%B4%E6%96%B0%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E6%93%8D%E4%BD%9C%E3%81%AE%E3%83%86%E3%82%AF%E3%83%8B%E3%83%83%E3%82%AF)

# その他

## レコード番号とレコード ID

### レコード番号

フィールドコードで取得する
他の番号と重複しない数値
ただしアプリコード指定時はアプリコードとの組合せになる
→ レコード番号は必ずしも Number ではない！

```json
"レコード番号": {
  "type": "RECORD_NUMBER",
  "value": "APPCODE-1"
}
```

### レコード ID

`$id` で取得する
こちらは必ず数値になる

```json
"$id": {
  "type": "__ID__",
  "value": "1"
}
```

## 関連レコード

関連レコードを引くために指定できるフィールドは

- 文字列（1 行）
- 数値
- リンク
- 計算
- レコード番号

のみ。
日付は引くのに使えない。
なので計算フィールドを利用したりする。

また関連レコードは event.record オブジェクトに情報が載ってくる事はない。

## 計算フィールド

日時を取り扱う場合、参照元の値は UTC で扱われる。
例えば作成日時（画面上は JST）を計算フィールドで「日付」として出力させると、09:00 以前なら前日の日付になって現れる。（JST で暮らす人たちの話）
文字列（1 行）の「自動計算」を使い、DATE_FORMAT() 関数でタイムゾーンを指定できる。

[自動計算を設定する](https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/set_autocalc.html)  
[日時の形式やタイムゾーンを変える（DATE_FORMAT 関数）](https://jp.cybozu.help/k/ja/user/app_settings/form/autocalc/date_format.html)

## ルックアップ

**REST API では、API トークンを使ってルックアップの登録・更新はできない。**
**通常のユーザー認証では実行できる。**

## 全体 JS/CSS

[JavaScript や CSS で kintone 全体をカスタマイズする](https://jp.cybozu.help/k/ja/admin/system_customization/javascript_fullcustomize.html)

> 取り込んだプログラムは、次の画面を除いた、すべての画面で読み込まれます。
> ・システム管理画面
> ・アプリの管理画面
> ・個人設定画面
> ・埋め込み用タグを使用して外部サイトに埋め込んだグラフ

設定画面とか管理画面を除いた全ての画面で有効。
当然各アプリの一覧、詳細、編集、印刷、グラフ各画面でも有効。
レコード読み込みの画面でも有効。

## 通知とステータス

[通知が送信されるタイミングと宛先](https://jp.cybozu.help/k/ja/user/notifications/notification_type.html)

CSV でのレコード一括読み込みや kintone CLI でレコード登録をしても、レコード通知は発生しない。
通知が出るのはレコード読み込みを実行したユーザーにのみ。

作業者更新 API で更新すれば、当然当該ユーザーに通知が飛ぶ。
API トークンで作業者更新を実行すると、通知元は Administrator になる。
[レコードの作業者を更新する](https://developer.cybozu.io/hc/ja/articles/208243696-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%BD%9C%E6%A5%AD%E8%80%85%E3%81%AE%E6%9B%B4%E6%96%B0)

[レコードのステータスの更新](https://developer.cybozu.io/hc/ja/articles/204791550)

1 回のリクエストで複数のレコードのステータスを一括更新することも可能。
API トークンを使用する場合とユーザー認証を使用する場合では実行条件が異なる。

> ユーザー認証で API を実行する場合、次の 2 つの条件にあてはまるレコードで API を実行できます。
> ・API の実行ユーザーがレコードの閲覧権限を持つ
> ・レコードに作業者が指定されていない、または、API の実行ユーザーがレコードの作業者に指定されている

> API トークンで API を実行する場合、次の 2 つの条件にあてはまる場合に API を実行できます。
> ・レコードの閲覧を許可した API トークンを使用する
> ・レコードに作業者が指定されていない、または、「Administrator」ユーザーがレコードの作業者に指定されている

## 検索

[データを検索する](https://jp.cybozu.help/k/ja/user/search/search_details.html)

単漢字の検索は出来ない。
アルファベットは単語単位の検索で部分一致はしない。

`kintone株式会社`

○： kintone 株式 会社 式会 株式会 式会社 株式会社
×： kin ton tone 株 式 会 社　 etc.

クエリでの検索も同じ基準。

## Webhook

[Webhook を設定する](https://jp.cybozu.help/k/ja/user/app_settings/set_webhook/webhook.html)

以下の時に Webhook が実行される。

- レコードの追加
- レコードの編集
- レコードの削除
- コメントの書き込み
- レコードのステータスの変更

以下の時は通知されない。

- Excel/CSV ファイルを読み込んでレコードを操作する
- 複数のレコードを一括削除する
- 複数のレコードを一括操作する REST API を使用してレコードを操作する

> 組織間のアクセス権の設定を有効にすると、Webhook は利用できなくなります。
> Webhook の通知は、1 分間に 60 回まで送信されます。

レコード登録・変更時に送信される内容の例

```json
{
  "id":"01234567-0123-0123-0123-0123456789ab",
  "type":"ADD_RECORD",
  "app":{
    "id":"1",
    "name":"案件管理"
  },
  "record":{
    "レコード番号":{
      "type":"RECORD_NUMBER",
      "value":"2"
    },
    ～～
    "$revision":{
      "type":"__REVISION__",
      "value":"3"
    },
    "$id":{
      "type":"__ID__",
      "value":"2"
    }
  },
  "recordTitle":"往訪：サイボウズ株式会社",
  "url":"https://example.cybozu.com/k/1/show#record=2"
}
```

レコード削除時に送信される内容の例

```json
{
  "app":{
    "id":"1",
    "name":"案件管理"
  },
  "id":"01234567-0123-0123-0123-0123456789ab",
  "recordId":"2",
  "deletedBy":{
    "code":"sato",
    "name":"佐藤 昇"
  },
  "deletedAt":"2017-07-03T09:38:09Z"
  "type":"DELETE_RECORD"
}
```

レコードにコメントを書き込んだ時に送信される内容の例

```json
{
  "app": {
    "id": "1",
    "name": "案件管理"
  },
  "comment": {
    "createdAt": "2012-02-03T09:38:09Z",
    "creator": {
      "code": "kato",
      "name": "加藤 美咲"
    },
    "id": "11",
    "mentions": [
      {
        "code": "kato",
        "type": "USER"
      },
      {
        "code": "org1",
        "type": "ORGANIZATION"
      },
      {
        "code": "group1",
        "type": "GROUP"
      }
    ],
    "text": "サイボウズ株式会社に往訪してきました。"
  },
  "id": "01234567-0123-0123-0123-0123456789ab",
  "recordId": "2",
  "type": "ADD_RECORD_COMMENT",
  "url": "https://example.cybozu.com/k/1/show#record=2&comment=11"
}
```

# 参考

[kintone 認定資格 カスタマイズスペシャリスト学習ガイド](https://developer.cybozu.io/hc/ja/articles/360022670672-kintone-%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC-%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA%E3%82%B9%E3%83%9A%E3%82%B7%E3%83%A3%E3%83%AA%E3%82%B9%E3%83%88%E5%AD%A6%E7%BF%92%E3%82%AC%E3%82%A4%E3%83%89)
[kintone API](https://developer.cybozu.io/hc/ja/articles/201941754)
[User API](https://developer.cybozu.io/hc/ja/articles/202111374)
[kintone ヘルプ](https://jp.cybozu.help/k/ja/)
[kintone コマンドラインツール](https://developer.cybozu.io/hc/ja/articles/202708214-kintone-%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3%E3%83%84%E3%83%BC%E3%83%AB)
[kintone CERTIFIED Customization Specialist](https://cybozu.co.jp/kintone-certification/level/customization-specialist/)
[kintone カスタマイズスペシャリスト 受験レポート](https://qiita.com/iShinkai/items/e79f3a15065034a4985a)