Microsoftカスタムコネクタ用kintoneのOpenAPI Spec2.0を作成する

著者名:Aiko Hanatsuka( サイボウズ株式会社 (External link) )

目次

はじめに

Microsoft Power Platformには、 公式のkintoneコネクタ が用意されています。
ただし対応しているAPI、アクションは限られているため、Microsoft製品とkintoneを連携してやりたいことができないこともあると思います。
そんなときに、OpenAPI Specをインポートするだけで、公式のkintoneコネクタでは提供されていないAPIも扱えるカスタムコネクタを簡単に作ることができます。

しかし、 kintone OpenAPI Spec はOpenAPI 3.0で、カスタムコネクタの作成に使えるのはOpenAPI 2.0のみです。
そこで、本記事では、kintoneのOpenAPI 2.0を手軽に作成できるClaude CodeのSkillsを紹介します。

information

OpenAPI Spec 2.0の作成は、必ずしも本記事の方法で行う必要はありません。
一例として参考にしてください。

使用するもの

この記事では次の2つを使用します。

ファイル名 説明
SKILL.md kintone OpenAPI Spec を参照してOpenAPI 2.0を生成するためのSkills
sample.json カスタムコネクタで利用することを想定したOpenAPI 2.0のサンプル

サンプルには、kintoneとの連携でまず利用することが多い次のAPIをあらかじめ含めています。

  • レコードを取得
  • レコードを一括登録
  • レコードを一括更新
  • アプリのフィールド情報を取得

使い方

以下のzipファイルをダウンロードして、/kintone-openapi2-generate Skillsを実行してください。
使いたいAPI、認証方式、サブドメインをAIに伝えればkintoneの仕様に沿ったOpenAPI Spec 2.0が作成されます。

kintone-openapi2-generator.zip

zipファイルの中は、次のようなディレクトリ構成になっています。

1
2
3
4
5
6
7
kintone-openapi2-generator/
└── .claude/
    └── skills/
        └── kintone-openapi2-generate/
            ├── SKILL.md
            └── references/
                └── sample.json

SKILL.mdとsample.jsonの中身は、それぞれ次のとおりです。
Claude Code以外のAIサービスでもSkillsを活用できるので、参考にしてください。

SKILL.md
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# kintone OpenAPI 2.0 Generator

kintone REST APIをMicrosoft Power PlatformのカスタムコネクタやCopilot Studioから利用するためのOpenAPI 2.0 JSONを作成・更新する。

## 1. 公式仕様を参照する

kintone REST APIの仕様は、必ずkintone OpenAPI Specを参照する。
[https://github.com/kintone/openapi-spec](https://github.com/kintone/openapi-spec)

`main`ブランチの最新仕様を使用する。
特に、各APIのdescription、parameter、request、response、制限事項は公式仕様をもとに作成する。

APIの仕様を推測で補完しない。公式仕様から判断できない場合はユーザーに伝える。

## 2. ユーザーに確認する

作業ディレクトリに`sample.json`が無くても「存在しない」と判断しない。`references/sample.json`はこのSkillに同梱されているので、ユーザーに尋ねず自分で読み込む。

OpenAPIを作成する前に、次の3点を確認する。

1. Copilotに許可したい操作
2. 認証方式
3. kintoneのサブドメイン

### Copilotに許可したい操作

「どのkintone REST APIを使いますか?」と確認する。
ユーザーはAPI名を知らない場合、「Copilotからkintoneで何をできるようにしたいですか?」と確認する。

たとえば次のような操作として確認する。

- 複数のレコードを取得する
- 複数のレコードを追加する
- 複数のレコードを更新する
- 複数のレコードを削除する
- アプリのフィールド情報を取得する
- コメントを取得・投稿する
- ファイルを取得する
- その他のkintone操作

ユーザーの目的から必要なkintone REST APIを特定し、公式仕様で確認する。
アプリのフィールド情報を取得するAPIは、レコードの追加や更新をする際に必ず追加する。

1つの操作に複数のAPIが必要な場合は必要なAPIを特定する。ただし、「あると便利」という理由だけでAPIを追加せず、Copilotに許可する操作は必要最小限にする。
目的からAPIを一意に判断できない場合だけ、ユーザーに追加で確認する。

### 認証方式

次のどちらを使用するか確認する。

- APIトークン認証
- OAuthクライアント認証

### サブドメイン

OpenAPIの`host`に使用するkintoneのサブドメインを確認する。
API名、endpoint、OAuth scope、schemaなど、公式情報から判断できる技術情報はユーザーに質問せずAI側で確認する。

## 3. OpenAPI 2.0を生成する

このSkill配下の`references/sample.json`(このSKILL.mdと同じディレクトリの`references/`内)を必ず読み込み、その設計をベースにする。
ユーザーが既存のOpenAPI 2.0ファイルを提供している場合は、そちらも併せてベースにする。

既存のoperationやdefinitionsは必要がない限り変更・削除せず、利用できるdefinitionsは再利用する。

公式提供されているkintone OpenAPI Specを単純に機械変換せず、必要なAPIとschemaだけをOpenAPI 2.0向けに再構成する。

基本的に次のルールで変換する。

- `servers` → `host` / `basePath` / `schemes`
- `components.schemas` → `definitions`
- `components.securitySchemes` → `securityDefinitions`
- `requestBody` → `in: body`のparameter
- responseの`content` → response `schema`
- `#/components/schemas/...` → `#/definitions/...`

OpenAPI 3.x固有の定義を最終結果に残さない。

### OpenAPI 2.0で直接表現できない場合

- `oneOf` / `anyOf`などを単純に削除しない。
- `sample.json`のように共通schemaとdescriptionで意味を維持できる場合は単純化する。安全に変換できない場合は推測せずユーザーに伝える。
- `nullable: true`は、必要に応じて`x-nullable: true`として表現する。
- `example` / `examples`はSwagger 2.0で有効な位置へ配置する。
- `style`、`explode`などparameter serializationの指定がある場合は、実際のAPI仕様を確認してOpenAPI 2.0で適切に表現する。安全に変換できない場合は推測しない。

### kintoneの動的フィールド

kintoneのフィールドコードはアプリごとに異なるため、存在するか分からないフィールドコードを固定で定義しない。
`sample.json`にある汎用的なRecordやFieldValueのschemaを可能な限り再利用する。

### description

descriptionは、AIエージェントがAPIを正しく利用するための重要な情報として扱う。
公式仕様のdescriptionをもとに、特に次の情報を維持する。

- parameterの意味
- queryの書き方
- field typeによる違い
- 上限値
- 権限
- API固有の制限事項

## 4. 認証方式を確認する

ユーザーが選択した認証方式を、すべてのAPIで利用できるとは仮定しない。
追加するAPIごとに、選択された認証方式が利用可能か公式仕様で確認する。
利用できないAPIがある場合は、OpenAPIを生成する前にユーザーへ伝える。

### APIトークン認証

対象APIがAPIトークン認証に対応していることを確認し、`X-Cybozu-API-Token`を使用する。
実際のAPIトークンをOpenAPIへ記載しない。

### OAuthクライアント認証

対象APIに必要なOAuth scopeを公式情報から確認する。
複数のAPIを使用する場合は、それぞれに必要なscopeを確認し、必要なscopeをまとめて定義する。
scopeを推測で設定しない。

## 5. 最終チェック

出力前に次を確認する。

- JSONとして正しい
- `"swagger": "2.0"`になっている
- OpenAPI 3.x固有の定義が残っていない
- `$ref`の参照先が存在する
- `operationId`が重複していない
- 選択した認証方式を各APIで利用できる
- OAuthの場合、必要なscopeが含まれている
- ユーザーが必要としているAPIだけが含まれている
- API利用に重要なdescriptionや制限事項が失われていない

既存ファイルを更新する場合は、依頼されていないoperationを削除しない。
完成したOpenAPI 2.0 JSON全体を返し、追加したAPIと認証方式を簡潔に説明する。
sample.json
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
{
  "swagger": "2.0",
  "info": {
    "title": "kintone REST API (Copilot Studio validation profile)",
    "version": "1.0.0",
    "description": "kintone REST APIの一部をSwagger 2.0形式に変換したもの(Power Platform / Copilot StudioはOpenAPI 3.xに未対応のため)。\n\n次の4操作を提供します。\n\n1. `GET /app/form/fields.json` — フィールドコードと種類を取得\n2. `GET /records.json` — レコードを検索\n3. `POST /records.json` — レコードを一括登録(最大100件)\n4. `PUT /records.json` — レコードを一括更新(最大100件)\n\nkintone REST API全体(OpenAPI 3.0、204操作)を簡略化したサブセットです。利用前にhostを自身のkintoneサブドメインに変更してください。",
    "license": {
      "name": "MIT No Attribution",
      "url": "https://opensource.org/license/MIT-0"
    }
  },
  "host": "sample.cybozu.com",
  "basePath": "/k/v1",
  "schemes": [
    "https"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "securityDefinitions": {
    "api_key": {
      "type": "apiKey",
      "in": "header",
      "name": "X-Cybozu-API-Token"
    }
  },
  "security": [
    {
      "api_key": []
    }
  ],
  "paths": {
    "/records.json": {
      "get": {
        "operationId": "getRecords",
        "summary": "Get records",
        "description": "Retrieves records from an App. Use the `query` parameter to filter, sort and page. Returns at most 500 records per call.",
        "parameters": [
          {
            "name": "app",
            "in": "query",
            "required": true,
            "description": "The App ID. This is the number in the App's URL, for example `31` in `https://example.cybozu.com/k/31/`.",
            "type": "integer"
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Search conditions written in kintone's own query syntax. This is **not** SQL and **not** OData.\nOmit this parameter to retrieve all records the credential can access.\n\n## Syntax rules\n\n- The field code always goes on the **left** of the operator, never on the right.\n- Wrap string values in double quotes: `Status in (\"Closed\")`. Numbers and times are unquoted: `price > 10`, `Time > 10:00`.\n- Combine conditions with `and` / `or`, and group them with parentheses.\n- Field codes are defined per App and are case-sensitive. Call `GET /app/form/fields.json` first to obtain the exact field codes and their types. Do not guess field codes.\n\n## Operators allowed per field type\n\nUsing an operator that the field type does not support returns an error, so check the field type first.\n\n| Field type | Allowed operators |\n|---|---|\n| Single-line text, Link | `=` `!=` `in` `not in` `like` `not like` |\n| Multi-line text | `like` `not like` `is empty` `is not empty` — **`=` is not allowed** |\n| Rich text | `like` `not like` |\n| Number, Calculated | `=` `!=` `>` `<` `>=` `<=` `in` `not in` |\n| Drop-down, Radio button, Check box, Multi-choice | **`in` / `not in` only — `=` is not allowed** |\n| Date, Date and time, Time | `=` `!=` `>` `<` `>=` `<=` |\n| Created datetime, Updated datetime | `=` `!=` `>` `<` `>=` `<=` |\n| User selection, Department selection, Group selection | `in` `not in` |\n| Created by, Updated by | `in` `not in` |\n| Status | `=` `!=` `in` `not in` |\n| Record number, `$id` | `=` `!=` `>` `<` `>=` `<=` `in` `not in` |\n| Attachment | `like` `not like` `is empty` `is not empty` |\n| Lookup | Same as the field type it looks up |\n| Related Records, fields inside a Table | Same as the source field type, but `=` and `!=` are **not** allowed — use `in` / `not in` |\n| Field group, Category | Cannot be used in a query |\n\nTo reference a field inside a Related Records field, use `<relatedRecordsFieldCode>.<fieldCode>`.\n\n## Functions\n\n| Function | Applies to |\n|---|---|\n| `LOGINUSER()` | User selection, Created by, Updated by |\n| `PRIMARY_ORGANIZATION()` | Department selection |\n| `NOW()` `TODAY()` `YESTERDAY()` `TOMORROW()` | Date, Date and time, Created datetime, Updated datetime |\n| `FROM_TODAY(n, \"DAYS\"\\|\"WEEKS\"\\|\"MONTHS\"\\|\"YEARS\")` | same as above |\n| `THIS_WEEK(SUNDAY..SATURDAY)` `LAST_WEEK()` `NEXT_WEEK()` | same as above |\n| `THIS_MONTH(1..31\\|LAST)` `LAST_MONTH()` `NEXT_MONTH()` | same as above |\n| `THIS_YEAR()` `LAST_YEAR()` `NEXT_YEAR()` | same as above |\n\nArguments may be omitted to mean the whole week / month / year, e.g. `THIS_MONTH()`.\n\n## Options\n\nPlace these **after all conditions, in this exact order**: `order by`, `limit`, `offset`.\n\n| Option | Meaning |\n|---|---|\n| `order by <fieldCode> asc\\|desc` | Sort order. Separate multiple fields with commas. Defaults to record ID descending. |\n| `limit <n>` | Number of records to return. Default 100, **maximum 500**. |\n| `offset <n>` | Number of records to skip. **Maximum 10000.** To read past 10000 records, use the cursor API instead. |\n\n## Escaping\n\nInside double quotes, escape `\"` and `\\` with a backslash. Required for text, rich text, check box, radio button, drop-down, multi-choice and status fields.\nExample: `Checkbox in (\"sample\\\"1\\\"\")`\n\n## Examples\n\n```\nText like \"sample\" and Text_area like \"sample\" order by Created_datetime desc\nDrop_down in (\"value1\", \"value2\") and (Multi_choice in (\"value3\") or Radio_button in (\"value4\"))\nTime > 10:00 and Time < 19:00 and Created_datetime = TODAY() order by $id asc limit 10\nUser_selection in (LOGINUSER()) and Department_selection not in (PRIMARY_ORGANIZATION())\nCompany_DB.company_name in (\"kintone\") and Company_DB.location like \"San Francisco\"\nText_area is empty\n```",
            "type": "string"
          },
          {
            "name": "totalCount",
            "in": "query",
            "required": false,
            "description": "If set to `true`, the total count of records that match the query conditions will be included in the response.\nIgnoring this parameter will return `null`.",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "The matching records.",
            "schema": {
              "$ref": "#/definitions/GetRecordsResponse"
            },
            "examples": {
              "application/json": {
                "records": [
                  {
                    "$id": {
                      "type": "__ID__",
                      "value": "12"
                    },
                    "会社名": {
                      "type": "SINGLE_LINE_TEXT",
                      "value": "サイボウズ株式会社"
                    },
                    "売上": {
                      "type": "NUMBER",
                      "value": "1200000"
                    },
                    "商談フェーズ": {
                      "type": "DROP_DOWN",
                      "value": "提案"
                    },
                    "対応種別": {
                      "type": "CHECK_BOX",
                      "value": [
                        "訪問",
                        "電話"
                      ]
                    },
                    "受注予定日": {
                      "type": "DATE",
                      "value": "2026-08-31"
                    },
                    "更新者": {
                      "type": "MODIFIER",
                      "value": {
                        "code": "sean",
                        "name": "Sean"
                      }
                    }
                  }
                ],
                "totalCount": "1"
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "401": {
            "description": "Unauthorized.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "403": {
            "description": "Forbidden.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "404": {
            "description": "NotFound.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "429": {
            "description": "Too many requests.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "495": {
            "description": "Cert Error.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "496": {
            "description": "No Cert.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "503": {
            "description": "Service Unavailable.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "default": {
            "description": "An unexpected error occurred.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      },
      "post": {
        "operationId": "addRecords",
        "summary": "Register (add) multiple records in an App",
        "description": "Registers up to 100 new records in a single call. Call `GET /app/form/fields.json` first so that field codes, required fields and choice strings (for selection fields) are known before building the request.\n\nRecords are registered in the order given in `records`. If any record in the request fails validation, **the entire batch is cancelled** and no records are registered.\n\nThe following fields cannot be set through this API: fields copied from a Lookup's source, Status, Category, Calculated, Assignee, and an auto-calculated Single-line text field. Setting Created by / Updated by / Created datetime / Updated datetime requires App Management permission on the credential; otherwise these are set automatically.\n\nRequired permissions: record-add permission on the App, and edit permission on every field being set.",
        "consumes": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "description": "The records to register.",
            "schema": {
              "$ref": "#/definitions/AddRecordsRequestBody"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The records were registered. `ids` and `revisions` are in the same order as the `records` given in the request.",
            "schema": {
              "$ref": "#/definitions/AddRecordsResponse"
            },
            "examples": {
              "application/json": {
                "ids": [
                  "100",
                  "101"
                ],
                "revisions": [
                  "1",
                  "1"
                ]
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "401": {
            "description": "Unauthorized.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "403": {
            "description": "Forbidden.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "404": {
            "description": "NotFound.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "429": {
            "description": "Too many requests.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "495": {
            "description": "Cert Error.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "496": {
            "description": "No Cert.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "503": {
            "description": "Service Unavailable.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "default": {
            "description": "An unexpected error occurred.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      },
      "put": {
        "operationId": "updateRecords",
        "summary": "Update multiple records in an App",
        "description": "Updates up to 100 existing records in a single call. Each entry in `records` identifies one record either by `id` (the record ID) or by `updateKey` (a field/value pair on a field with duplicates disallowed) — never both. Call `GET /app/form/fields.json` first so that field codes, and which field is safe to use as an `updateKey`, are known.\n\nIf `upsert` is `true`, an entry whose target record does not already exist is registered as a new record instead of failing (UPSERT mode); this also requires record-add permission on the App. `updateKey` is required to use UPSERT mode, since a not-yet-existing record has no `id` to match against.\n\nIf `records[].record` is omitted for an entry, that record's field values are left unchanged. If `records[].revision` is given, the update is rejected (and the whole batch cancelled) when it does not match the record's current revision; omit it, or pass `-1`, to skip this check.\n\nIf any entry in the request fails, **the entire batch is cancelled** and no records are updated.\n\nThe following fields cannot be updated through this API: fields copied from a Lookup's source, Status, Category, Calculated, Assignee, Created by, Created datetime, Updated by, Updated datetime, and an auto-calculated Single-line text field.\n\nRequired permissions: record-edit permission on the App, and edit permission on the records and fields being updated (plus record-add permission on the App when `upsert` is `true`).",
        "consumes": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "description": "The records to update.",
            "schema": {
              "$ref": "#/definitions/UpdateRecordsRequestBody"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The records were updated (or, in UPSERT mode, registered). `records` is in the same order as the `records` given in the request.",
            "schema": {
              "$ref": "#/definitions/UpdateRecordsResponse"
            },
            "examples": {
              "application/json": {
                "records": [
                  {
                    "id": "1",
                    "revision": "5",
                    "operation": "UPDATE"
                  },
                  {
                    "id": "2",
                    "revision": "1",
                    "operation": "INSERT"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "401": {
            "description": "Unauthorized.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "403": {
            "description": "Forbidden.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "404": {
            "description": "NotFound.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "429": {
            "description": "Too many requests.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "495": {
            "description": "Cert Error.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "496": {
            "description": "No Cert.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "503": {
            "description": "Service Unavailable.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "default": {
            "description": "An unexpected error occurred.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      }
    },
    "/app/form/fields.json": {
      "get": {
        "operationId": "getFormFields",
        "summary": "Get the field codes and types of an App",
        "description": "Returns the App's field definitions. Call this before building a `query` so that the correct operators are used for each field type, and so that exact field codes and choice strings are known.",
        "parameters": [
          {
            "name": "app",
            "in": "query",
            "required": true,
            "description": "The App ID. This is the number in the App's URL, for example `31` in `https://example.cybozu.com/k/31/`.",
            "type": "integer"
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "The localized language to retrieve the data in:\n- `default`: retrieves the default names\n- `en`: retrieves the localized English names\n- `zh`: retrieves the localized Chinese names\n- `ja`: retrieves the localized Japanese names\n- `user`: retrieves the localized names, in the same language as the language setting* set on the user used for the authentication.If ignored, the default names will be retrieved.",
            "type": "string",
            "enum": [
              "default",
              "user",
              "ja",
              "en",
              "zh"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "`properties` is keyed by field code. Each entry has at least `type`, `code` and `label`.\nUse `type` to decide which query operators are valid for that field (see the `query` parameter of `GET /records.json`).\nSelection fields additionally carry `options`, which lists the exact choice strings that a query must match.",
            "schema": {
              "$ref": "#/definitions/GetFormFieldsResponse"
            }
          },
          "400": {
            "description": "The request was rejected.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "401": {
            "description": "Unauthorized.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "403": {
            "description": "Forbidden.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "404": {
            "description": "NotFound.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "429": {
            "description": "Too many requests.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "495": {
            "description": "Cert Error.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "496": {
            "description": "No Cert.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "503": {
            "description": "Service Unavailable.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          },
          "default": {
            "description": "An unexpected error occurred.",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "GetRecordsResponse": {
      "type": "object",
      "properties": {
        "records": {
          "type": "array",
          "description": "The records that matched the query.",
          "items": {
            "$ref": "#/definitions/Record"
          }
        },
        "totalCount": {
          "type": "string",
          "x-nullable": true,
          "description": "The total number of records matching the query, as a string. `null` unless `totalCount=true` was requested."
        }
      }
    },
    "Record": {
      "type": "object",
      "description": "One record. **The keys are the App's field codes**, which differ per App, so they are not listed in this schema.\nEach value is an object with `type` (the field type) and `value` (the content).\nRead a field as `records[i].<fieldCode>.value`.\n\nThe shape of `value` depends on `type`:\n\n| type | shape of `value` |\n|---|---|\n| `SINGLE_LINE_TEXT` `MULTI_LINE_TEXT` `RICH_TEXT` `LINK` `NUMBER` `CALC` `DATE` `TIME` `DATETIME` `RECORD_NUMBER` `STATUS` `__ID__` `__REVISION__` | string |\n| `CHECK_BOX` `MULTI_SELECT` `CATEGORY` | array of strings |\n| `DROP_DOWN` `RADIO_BUTTON` | string, or `null` when nothing is selected |\n| `USER_SELECT` `ORGANIZATION_SELECT` `GROUP_SELECT` `STATUS_ASSIGNEE` | array of `{ code, name }` |\n| `CREATOR` `MODIFIER` | `{ code, name }` |\n| `FILE` | array of `{ fileKey, name, contentType, size }` |\n| `SUBTABLE` | array of `{ id, value: { <fieldCode>: { type, value } } }` |\n\nField types that hold no data of their own — `GROUP`, `REFERENCE_TABLE`, `LABEL`, `SPACER`, `HR` — are **not returned** in the response even though they exist on the form.",
      "additionalProperties": {
        "$ref": "#/definitions/FieldValue"
      }
    },
    "FieldValue": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "description": "The field type, for example `SINGLE_LINE_TEXT`, `NUMBER`, `DROP_DOWN`, `DATE`, `USER_SELECT`, `SUBTABLE`."
        },
        "value": {
          "description": "The field's content. A string, an array of strings, an object, or an array of objects depending on `type`. See the description of the parent schema."
        }
      }
    },
    "GetFormFieldsResponse": {
      "type": "object",
      "properties": {
        "properties": {
          "type": "object",
          "description": "Field definitions, keyed by field code.",
          "additionalProperties": {
            "$ref": "#/definitions/FieldProperty"
          }
        },
        "revision": {
          "type": "string",
          "description": "The App's settings revision number."
        }
      }
    },
    "FieldProperty": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "description": "The field type. Determines which query operators may be used against this field."
        },
        "code": {
          "type": "string",
          "description": "The field code, used in queries."
        },
        "label": {
          "type": "string",
          "description": "The display name shown on the form."
        },
        "required": {
          "type": "boolean",
          "description": "Whether the field is mandatory."
        },
        "options": {
          "type": "object",
          "description": "For selection fields only. Keyed by the exact choice string that a query must match.",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string"
              },
              "index": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "AddRecordsRequestBody": {
      "type": "object",
      "required": [
        "app",
        "records"
      ],
      "properties": {
        "app": {
          "type": "integer",
          "description": "The App ID. This is the number in the App's URL, for example `31` in `https://example.cybozu.com/k/31/`."
        },
        "records": {
          "type": "array",
          "maxItems": 100,
          "description": "The records to register, up to 100 per call. Records are registered in the order given here.\nAn empty object (`{}`) registers a record with every field left at its default value.\nA field code that does not exist on the App is silently ignored rather than rejected.",
          "items": {
            "$ref": "#/definitions/RecordInput"
          }
        }
      }
    },
    "RecordInput": {
      "type": "object",
      "description": "One record to write, keyed by the App's field codes (call `GET /app/form/fields.json` first to obtain these). Each value is an object of the form `{ \"value\": <content> }`.\n\nThe shape of `value` depends on the field's type:\n\n| Field type | shape of `value` |\n|---|---|\n| Single-line text, Multi-line text, Rich text, Link, Number, Date, Time, Date and time, Radio button, Drop-down | string (`\"\"` clears a Radio button/Drop-down) |\n| Check box, Multi-choice | array of strings — must exactly match the App's configured choice strings |\n| User selection, Department selection, Group selection | array of `{ \"code\": <login name or org/group code> }` |\n| Attachment | array of `{ \"fileKey\": <key from the file-upload API> }` — include existing `fileKey`s to keep already-attached files |\n| Table (Subtable) | array of `{ \"id\": <existing row id, optional>, \"value\": { <fieldCode>: { \"value\": ... } } }` |\n\nFields that cannot be set through this API — values copied from a Lookup's source, Status, Category, Calculated, Assignee, and an auto-calculated Single-line text field — are ignored if included.\nSetting Created by, Updated by, Created datetime or Updated datetime requires App Management permission on the credential.",
      "additionalProperties": {
        "$ref": "#/definitions/FieldValueInput"
      }
    },
    "FieldValueInput": {
      "type": "object",
      "properties": {
        "value": {
          "description": "The value to write. A string, an array of strings, or an array of objects depending on the field's type — see the description of the parent schema."
        }
      }
    },
    "AddRecordsResponse": {
      "type": "object",
      "properties": {
        "ids": {
          "type": "array",
          "description": "The record IDs assigned to the newly registered records, in the same order as the `records` given in the request.",
          "items": {
            "type": "string"
          }
        },
        "revisions": {
          "type": "array",
          "description": "The revision number of each newly registered record (normally `\"1\"`), in the same order as `ids`.",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "UpdateRecordsRequestBody": {
      "type": "object",
      "required": [
        "app",
        "records"
      ],
      "properties": {
        "app": {
          "type": "integer",
          "description": "The App ID. This is the number in the App's URL, for example `31` in `https://example.cybozu.com/k/31/`."
        },
        "upsert": {
          "type": "boolean",
          "description": "If `true`, run in UPSERT mode: an entry whose target record (identified by `updateKey`) does not exist is registered as a new record instead of failing. Requires every entry to use `updateKey` rather than `id`, and requires record-add permission on the App in addition to the usual record-edit permission. Defaults to `false`.",
          "default": false
        },
        "records": {
          "type": "array",
          "maxItems": 100,
          "description": "The records to update, up to 100 per call. Each entry identifies its target record with exactly one of `id` or `updateKey`.",
          "items": {
            "$ref": "#/definitions/UpdateRecordItem"
          }
        }
      }
    },
    "UpdateRecordItem": {
      "type": "object",
      "description": "One record to update. Provide exactly one of `id` or `updateKey` to identify the target record — never both.",
      "properties": {
        "id": {
          "type": "integer",
          "description": "The record ID to update. Mutually exclusive with `updateKey`. Not valid in UPSERT mode (use `updateKey` there, since a new record has no ID yet)."
        },
        "updateKey": {
          "$ref": "#/definitions/UpdateKey"
        },
        "record": {
          "$ref": "#/definitions/RecordInput",
          "description": "The field values to write. If omitted, the record's existing values are left unchanged. See `RecordInput` for the shape of each field's `value`, and which fields cannot be updated through this API."
        },
        "revision": {
          "type": "integer",
          "description": "The revision number this update expects the record to currently be at. If it does not match, the update is rejected (and the whole batch cancelled). Omit, or pass `-1`, to skip this check."
        }
      }
    },
    "UpdateKey": {
      "type": "object",
      "required": [
        "field",
        "value"
      ],
      "description": "Identifies a record by a field value instead of by record ID. The field must be a Single-line text or Number field configured with duplicates disallowed. Mutually exclusive with `id`.",
      "properties": {
        "field": {
          "type": "string",
          "description": "The field code of the unique-value field to match on."
        },
        "value": {
          "type": "string",
          "description": "The value to match against that field."
        }
      }
    },
    "UpdateRecordsResponse": {
      "type": "object",
      "properties": {
        "records": {
          "type": "array",
          "description": "One entry per updated (or, in UPSERT mode, newly registered) record, in the same order as the `records` given in the request.",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The record ID. In UPSERT mode, this is the newly assigned ID when the record was registered rather than updated."
              },
              "revision": {
                "type": "string",
                "description": "The record's revision number after this update."
              },
              "operation": {
                "type": "string",
                "enum": [
                  "UPDATE",
                  "INSERT"
                ],
                "description": "`UPDATE` if an existing record was modified, `INSERT` if UPSERT mode registered a new record."
              }
            }
          }
        }
      }
    },
    "Error": {
      "type": "object",
      "properties": {
        "message": {
          "type": "string",
          "description": "A human-readable description of the error."
        },
        "id": {
          "type": "string",
          "description": "An identifier for the individual error occurrence."
        },
        "code": {
          "type": "string",
          "description": "The error code, for example `CB_VA01`."
        }
      }
    }
  }
}
tips
補足

作成したOpenAPI Specに問題がないかは、 Swagger Editor (External link) で確認できます。
次の図のように、生成したOpenAPI Spec 2.0ファイルの中身をコピー&ペーストし、エラーや警告がなければ問題ありません。
レコードとレコードのコメントの取得のREST APIを定義している例です。

ポイント

2.0への変換について

ここで公開しているSkillsは、公式のkintone OpenAPI Specを参照し、OpenAPI 2.0へ再構成しています。

2.0への書き換えは、単純にOpenAPI 3.0の記述を2.0へ置き換えるわけではありません。
なぜなら、3.0には、oneOf/anyOf、nullable、parameter serializationなどがあり、OpenAPI 2.0でそのまま表現できないものがあるため、kintone独自の設計に合わせて調整しています。

必要なAPIだけを追加する

kintone REST APIすべてをひとつのOpenAPIファイルへ入れる必要はありません。
たとえば、Copilotとの連携目的で、レコードを検索するだけでよい場合は、レコード削除などの不要な操作まで追記しないようにしましょう。

必要なAPIだけを追加することは、次のメリットが得られます。

  • 連携時に許可する操作を限定できる。
  • OpenAPI Specがシンプルになる。
  • ファイルサイズを抑えられる。
information

MicrosoftのカスタムコネクタにOpenAPIをインポートする際のファイルサイズ上限は、1MB未満と決められています。
利用時は、Microsoftの公式ドキュメントも確認してください。

おわりに

Microsoft Power Platformのカスタムコネクタ用のOpenAPI Specをゼロから作成する場合、kintoneのAPIだけでなくOpenAPI Specの書き方についても理解する必要があります。
Skillsを使うと、その部分をAIに任せて手軽に作成できます。
ぜひ活用してみてください。

information

このTipsは、2026年10月版kintoneで動作を確認しています。

公式コミュニティ

kintone開発者同士で質問や知見を共有し、学び合うことができます。