LINE予約「LUCA」の機能と料金はこちら

Airtable 422 INVALID_MULTIPLE_CHOICE_OPTIONSの原因

実務ノート 実務ノート 公開

n8nからAirtableの単一選択の列へ書き込んだら、422 と INVALID_MULTIPLE_CHOICE_OPTIONS が返ってきた。そんな場面を想定して書いています。単一選択(singleSelect)は、あらかじめ決めた選択肢から1つを選ぶ種類の列です。やっかいなのは、ワークフローの作りによっては、この失敗が起きてもn8nの実行が success のまま終わることです。

この記事の結論

  • 原因は、書き込む文字列が、その列の既存の選択肢と一字一句同じでないことです。Airtableの公式ドキュメントによると、一致しないときは typecast を有効にしない限り INVALID_MULTIPLE_CHOICE_OPTIONS で失敗します。
  • 弊社では、OpenAIのモデルを差し替えたら出力の語が変わり、選択肢に無い語を書き込もうとして起きました。エラー文には Insufficient permissions to create new select option も付いていましたが、種類は権限のエラーではなく INVALID_MULTIPLE_CHOICE_OPTIONS でした。
  • n8nで success のまま落ちるのは、失敗を受け止めて先へ流す作りのときです。Airtableノードの On Error が「Continue」、HTTP Requestノードの「Never Error」がオン、Codeノードで失敗を握りつぶしている、の3つを疑います。
  • 合否は「実行が success か」では測れません。保存できた件数と、前回の実行との出力の比較で測ります。

実際に起きたこと

2026年9月6日、弊社で動かしているワークフローのOpenAIのモデルを、GPT-5.6 Luna へ移せるかを検証していました。そのうちの1本、trend-content-injector を Luna にしたところ、単一選択の列 industry に入れる値として、Luna は新しい語「中小企業SNS集客」を返しました。

この値を書き込もうとして、Airtableから次のエラーが返りました。2件とも書き込みに失敗していました。

INVALID_MULTIPLE_CHOICE_OPTIONS / Insufficient permissions to create new select option

それなのに、n8nの実行ステータスは success、ノードも success でした。実行の一覧を眺めても異常は見えません。出力の saved:0 を、過去の実行(毎回 saved:1)と並べて、初めて気づきました。

このときの記録には、モデルを替えたときの合否は「実行が success か」では測れない、前回の実行との出力の比較で測る、と残しました。

422には、別の場面でも当たっています。2026年5月14日、品質チェック(Quality Gate)が誤って不合格にした記録を戻すため、qa_status 列を空にしようとしました。空文字 '' を送ると422になり、null と typecast:true の組み合わせで空にできました。

原因

Airtable側:選択肢は完全一致でしか受け付けない

Airtableの公式ドキュメント(フィールドの型の説明)には、単一選択について次の2文があります。

  • 「When creating or updating records, if the choice string does not exactly match an existing option, the request will fail with an INVALID_MULTIPLE_CHOICE_OPTIONS error unless the typecast parameter is enabled.」
  • 「If typecast is enabled, a new choice will be created if one does not exactly match.」

送った文字列が、既存の選択肢の名前とぴったり同じでなければ失敗します。typecast を有効にしたときは、Airtableが新しい選択肢を作ります。複数選択(multipleSelects)の列の説明にも、同じ内容の文があります。

HTTPの422は、Airtableのエラーの説明では「The request data is invalid. This includes most of the base-specific validations.」です。ベース(Airtableのデータベース1つ分)ごとの決まりに合わない、という種類の失敗です。

エラー文に「permissions」があっても、権限の問題とは限らない

弊社の件のエラー文には、Insufficient permissions to create new select option という文が付いていました。言葉どおりに訳すと「新しい選択肢を作る権限が足りない」です。この文だけを見ると、トークンの権限の問題に見えます。

ただ、Airtableのエラーの説明では、権限が足りないときの失敗は 403 Forbidden に分けられています。種類も INVALID_PERMISSIONS などで、別のものです。弊社の件は422で、種類は INVALID_MULTIPLE_CHOICE_OPTIONS でした。

上の公式の説明のとおり、この種類は、送った値が既存の選択肢と完全一致しないときに出ます。新しい選択肢が作られるのは、typecast を有効にしたときです。弊社の件でも、書き込もうとしたのは選択肢に無い語でした。このエラーが出たら、トークンの権限を見直す前に、送った値が既存の選択肢と一字一句同じかを確かめます。

出力の文字列が変わると、書き込みが落ちる

弊社の件は、OpenAIのモデルを Luna にしたことで出力の語が変わり、選択肢に無い語が出た時点で、その先の単一選択への書き込みが落ちたものです。モデルを差し替えたときに起きるもう1つの失敗は、n8nでmax_tokens is not supportedが出る原因と直し方 に書きました。

n8n側:失敗を受け止めて先へ流すと success になる

ここからは n8n 2.19.2(package.json に "version": "2.19.2")の実装です。失敗が実行全体のエラーにならない作りは、次の3つです。

1. Airtableノードの On Error が「Continue」

Airtableノードは、作成(Create)も更新(Update)も、入力の1件ごとに try/catch で書き込んでいます。失敗したときに this.continueOnFail() が真なら、エラーを普通の出力の1件として次のノードへ流します。

if (this.continueOnFail()) {
    returnData.push({ json: { message: error.message, error } });
    continue;
}
throw error;

continueOnFail() が真になるのは、ノードの設定の On Error が「Continue」か「Continue (using error output)」のときです。「Continue」の画面の説明は「Pass error message as item in regular output」です。次のノードには message と error を持った1件が届き、ノードの処理はそのまま終わります。On Error を何も設定していないときは偽なので、ノードは例外を投げ、実行は失敗になります。

2. HTTP Requestノードの「Never Error」

HTTP Requestノードには「Never Error」という設定があります。説明は「Whether to succeeds also when status code is not 2xx」です。オンにすると requestOptions.simple = false が設定され、422の返事も普通の出力として流れます。

3. Codeノードで失敗を握りつぶす

Codeノードの this.helpers.httpRequest は、中で axios(HTTP通信のライブラリ)を使っています。axios の既定は2xxだけを成功とみなすので、何も指定しなければ422で例外になります。ところが ignoreHttpStatusErrors: true を付けると、n8n本体が axiosRequest.validateStatus = () => true; を設定し、どの返事でも例外になりません。try/catch で受け止めて件数だけ返す作りも、同じく実行は success で終わります。

どの作りでも、失敗はノードの中で片付き、実行全体の結果にはエラーが残りません。n8n本体は、実行の結果にエラーが入っているとき(fullRunData.data.resultData.error !== undefined)にだけ、エラー通知用のワークフロー(Error Workflow)を呼びます。そのため、通知も来ません。

「Map Each Column Manually」の検査も、「Continue」なら出力の1件になる

Airtableノードの列の割り当て方には「Map Automatically」と「Map Each Column Manually」があります。後者で Typecast をオフにしていると、n8nは送る前に値を検査します。検査に使う選択肢は、ノードの設定に保存された列の定義(schema)から取ります。選択肢に無い値なら、次のエラーになります。

Invalid input for 'industry' [item 0]
'industry' expects one of the following values: [...] but we got ...

([...] には選択肢の一覧、... には実際に来た値が入ります。)

ただし、この検査は、1件ごとの書き込みと同じ try ブロックの中で行われます。作成でも更新でも同じです。On Error が「Continue」なら、このエラーも普通の出力の1件として次へ流れ、実行は success のままです。Airtableへ送る前に実行が止まるのは、On Error を何も設定していないか、「Stop Workflow」にしているときです。

Typecast をオンにすると、この検査は飛ばされます(作成の処理では skipValidation: typecast)。「Map Automatically」では入力の項目をそのまま送るので、この検査はありません。どちらの場合も、選択肢に無い値はそのままAirtableへ送られます。

直し方

手順1:書き込みの失敗を、実行の失敗として見えるようにする

まず、書き込みが落ちたら実行も失敗になるようにします。

  • Airtableノードは、On Error を「Stop Workflow」にします。画面の説明は「Halt execution and fail workflow」です。止めずに続けたいなら「Continue (using error output)」にします。説明は「Pass item to an extra error output」で、エラー用の出力が別に出るので、その先に通知をつなぎます。
  • HTTP Requestノードは、「Never Error」をオフにします。
  • Codeノードは、失敗した件数を数え、1件でもあれば最後に例外を投げます。

Codeノードで書き込む場合の形です(モードは「Run Once for All Items」)。トークンは直接書かず、資格情報か環境変数から読んでください。

const items = $input.all();
let saved = 0;
const failed = [];
for (const item of items) {
  try {
    await this.helpers.httpRequest({
      method: 'PATCH',
      url: 'https://api.airtable.com/v0/XXXX/XXXX',
      headers: { Authorization: 'Bearer XXXX' },
      body: { records: [{ id: item.json.id, fields: { industry: item.json.industry } }] },
      json: true,
    });
    saved++;
  } catch (e) {
    failed.push(item.json.industry);
  }
}
if (failed.length > 0) {
  throw new Error(`Airtableへの書き込みが ${failed.length} 件失敗: ${failed.join(', ')}`);
}
return [{ json: { saved } }];

Codeノードの On Error を何も設定していなければ、例外を投げた時点で実行は失敗になり、実行の結果にエラーが入ります。エラー通知用のワークフローは、n8nの公式ドキュメントのとおり Workflow Settings でワークフローごとに設定します。設定していれば、ここで呼ばれます。

ただし、エディタで手動で実行したときは呼ばれません。n8n 2.19.2 では、エラー通知用のワークフローを呼ぶ処理が if (!isManualMode) の中にあります。isManualMode は、実行のモードが manual のときに真です。エディタからの実行はこのモードで動きます。テスト用のWebhookのURLで受けた実行も、同じく manual です。

Codeノードを使わない場合は、件数を見て Stop And Error ノードへつなぐ形でも同じことができます。n8nの公式ドキュメントにも、条件に合ったときに実行をわざと失敗させ、エラー通知用のワークフローを動かす方法として、このノードが載っています。

手順2:書き込む値を、列の既存の選択肢にそろえる

Airtableは完全一致でしか受け付けないので、送る値を既存の選択肢のどれかと一字一句同じにします。

選択肢の一覧は、ベースの構造を返すAPI GET https://api.airtable.com/v0/meta/bases/{baseId}/tables で読めます。トークンには schema.bases:read のスコープが要ります。n8nのAirtableノードも、列の一覧を作るときに meta/bases/${base}/tables を呼び、field.options.choices の name を選択肢にしています。

次は、選択肢を読み、書き込む前に照らし合わせる形の例です。選択肢に無い値が来たら、書き込まずに止めます。

// 「Run Once for All Items」で動かす
const meta = await this.helpers.httpRequest({
  method: 'GET',
  url: 'https://api.airtable.com/v0/meta/bases/XXXX/tables',
  headers: { Authorization: 'Bearer XXXX' },
  json: true,
});
const table = meta.tables.find((t) => t.id === 'XXXX');
const field = table.fields.find((f) => f.name === 'industry');
const choices = field.options.choices.map((c) => c.name);

const out = [];
for (const item of $input.all()) {
  const value = String(item.json.industry ?? '').trim();
  if (!choices.includes(value)) {
    throw new Error(`選択肢に無い値です: ${value}`);
  }
  out.push({ json: { ...item.json, industry: value } });
}
return out;

弊社の件のように、モデルの出力を単一選択へ書く作りについては、弊社の記録に「直すなら既存の選択肢をプロンプトに列挙して選ばせる」と書き残しています。ただ、プロンプトに並べただけでは、書き込む値が選択肢の中にあることまでは保証されません。上の照らし合わせは、その場合にも書き込みの手前で止めるためのものです。

選択肢を増やしてよい列なら、typecast を有効にする方法もあります。公式ドキュメントによると、typecast を有効にすると、完全一致しない値が来るたびに新しい選択肢が作られます。レコードの更新の説明には、typecast について「To ensure your data’s integrity, this should only be used when necessary.」という注意もあります。n8nのAirtableノードでは、この設定は Typecast という項目で、説明は「Whether the Airtable API should attempt mapping of string values for linked records & select options」です。

手順3:選択肢の列を空にするときは ” でなく null を送る

弊社の記録では、単一選択・複数選択の列を PATCH で空にするとき、空文字 '' を渡すと422になる、としています。空にするときは null を送ります。弊社では typecast も付けた次の形で、qa_status 列を空にできました。

{
  "records": [
    { "id": "recXXXX", "fields": { "qa_status": null } }
  ],
  "typecast": true
}

弊社で確かめたのは、この形で空にできたことまでです。typecast を付けずに null だけを送った場合は、試していません。

この形を写すときは、選択肢の列へ入れるほかの値を、同じ要求に混ぜないでください。手順2で見たとおり、typecast を有効にすると、選択肢と完全一致しない値は新しい選択肢として作られます。空にする要求は、空にする列だけを送る形に分けます。

送るときは PATCH を使います。Airtableのレコードの更新の説明によると、PATCH は送った項目だけを更新します。PUT は「A PUT request will perform a destructive update and clear all unincluded cell values.」とあり、送らなかった項目まで空になります。

直ったかを確かめる

  • 保存できた件数を出力に残し、過去の実行と並べます。弊社が気づけたのも、saved:0 と過去の saved:1 を並べたからです。
  • Airtableノードを「Continue」のまま使うなら、出力に message と error を持った項目が無いかを見ます。
  • わざと選択肢に無い値を1件流し、実行が失敗になることを確かめます。本番のデータを汚さないよう、試し用のレコードや試し用のベースに向けて流します。
  • エラー通知が届くかまで確かめるときは、エディタの手動実行では確かめられません。手順1のとおり、手動の実行ではエラー通知用のワークフローが呼ばれないからです。有効にしたワークフローの定期実行か、本番用のWebhookのURLで起動した実行で確かめます。本番用のWebhookのURLで受けた実行のモードは webhook です。手動で試して通知が来なくても、設定が壊れているとは限りません。
  • モデルやプロンプトを変えるときは、前回の実行と出力を並べます。2026年9月21日にほかのワークフローのモデルを移したときは、直近の成功した実行の入力でCodeノードを再生し、OpenAIへの要求だけを今のモデルと Luna の両方へ送って並べました。そのあいだ、書き込み(POST/PATCH/PUT/DELETE)は全部止めました。

比べる相手の過去の実行が残っていないと、比べられません。実行履歴が消える仕組みと残し方は、n8nの実行履歴が数日で消える原因と自動削除の設定 に書きました。

それでも直らないとき

  • 前後の空白や改行が付いていないか。選択肢は完全一致なので、見た目が同じでも空白1つで失敗します。手順2の例のように trim() してから比べます。
  • 複数選択の列ではないか。複数選択は選択肢の名前の配列で送ります。フィールドの型の説明には「this array-type field will override the current cell value when being updated.」とあり、更新すると今の値が置き換わります。今の値を残したいなら、今の値も配列に入れて送ります。
  • Airtableで選択肢を足した直後ではないか。「Map Each Column Manually」の検査は、ノードの設定に保存された列の定義を使います。Airtable側で選択肢を足したら、その選択肢がノードの列の定義にも入っているかを確かめます。
  • 422ではなく権限のエラーではないか。トークンがそのベースに触れない場合は、422ではなく 403 Forbidden の権限のエラーになります。Airtableのエラーの説明に従い、使っている人とトークンの両方がベースに触れるかを確かめます。

まとめ

  • INVALID_MULTIPLE_CHOICE_OPTIONS は、typecast を有効にしていないとき、書き込んだ文字列が単一選択・複数選択の既存の選択肢と完全一致しないと出ます。エラー文に permissions の語があっても、権限のエラー(403 Forbidden)とは別の種類です。
  • 弊社では、OpenAIのモデルの差し替えで出力の語が変わり、選択肢に無い語を書き込もうとして2件とも失敗しました。実行は success のままでした。
  • n8nでは On Error の「Continue」、HTTP Requestの「Never Error」、Codeノードの try/catch で、失敗が success に化けます。「Map Each Column Manually」の検査も、「Continue」なら出力の1件になります。
  • 直し方は、失敗を実行の失敗として見えるようにすることと、送る値を既存の選択肢にそろえることです。選択肢の列を空にするときは null を送ります。
  • 確かめるのは、保存できた件数と前回の実行との比較です。エラー通知は、手動の実行では呼ばれないので、本番の起動で確かめます。

参考にした公式ドキュメント

n8nの点検と構築のご相談

「実行は success なのに、データが入っていない」といったn8nの静かな失敗の点検や、業務の自動化の仕組みづくりをお引き受けしています。

お問い合わせはこちら

この記事を書いた人

貫名 孝夫(マルタマーケティング株式会社 代表取締役)。n8nで140本を超えるワークフローを本番で動かしながら、実際に起きた不具合と直し方を記録しています。会社概要