1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DifyをAPI化するとき、認証・APIキー・再試行の責任をどこに置くか

1
Last updated at Posted at 2026-10-07

API boundary diagram

2026年10月8日時点のDify公式資料をもとに、Webアプリへ下書き生成を組み込むときのAPI境界を整理します。画面から呼べるようにするだけでは、利用者のなりすまし、予算超過、通信切断後の二重実行を防げません。

この記事では、ブラウザとDifyの間にアプリ側バックエンドを置き、認証・認可・利用上限と実行結果の状態を管理する設計を扱います。DifyのWorkflowは生成に集中させ、生成物の承認と業務上の確定を分けます。

対象は、DifyのWorkflowをWebアプリへ組み込むエンジニアと、その運用を判断するテックリードです。題材は架空の「申請文の下書き生成」です。実績の範囲は、公式仕様の確認と、後掲するJavaScriptのローカルモック検証です。Dify実機でのAPI呼び出し、性能測定、本番導入、工数削減の測定は行っていません。

APIキーを隠すだけでは、利用者の権限は守れない

Dify公式のAPI導入ガイドでは、アプリのAPIキーはそのアプリに対応し、バックエンドから呼び出すよう説明されています。ブラウザへ埋め込んだキーは取り出せるため、フロントエンドへ配布しません。

もう一つの境界がuserです。公式のEnd User Identityによれば、これは呼び出し元が指定する利用者識別子であり、Difyはその値を本人認証しません。

したがって、画面から受け取ったuserをそのまま転送すると、APIキーを隠しても別利用者の識別子を名乗る余地が残ります。次の責任分担は、仕様を踏まえた設計案です。

境界 管理するもの 信頼してはいけないもの
ブラウザ → アプリAPI セッション認証、操作権限、入力形式、CSRF対策 リクエスト本文の利用者ID・組織ID
アプリAPI → Dify サーバー側APIキー、認証結果から導出したuser、入力の許可リスト ブラウザが指定した接続先・任意の入力変数
Dify → アプリAPI 実行状態、出力の形式、業務上の承認 HTTP 200だけを根拠にした完了判定

テナントをまたぐ利用者IDの衝突も避けます。例えば、認証済みの組織とアカウントに対応する不透明なIDをサーバー側で管理し、その値をuserへ渡します。メールアドレスを識別子として送る必要はありません。

直接呼び出し・バックエンド・非同期ジョブを比較する

方式 適する条件 残る責任・コスト
ブラウザからDify APIを直接呼ぶ アプリAPIキーを持つブラウザには採用しない キー流出と利用者識別子の偽装を防げない
アプリ側バックエンドから同期実行 短時間の下書き生成、完了を待てる画面 認証・認可、同時実行数、タイムアウト、結果の保存
バックエンド+永続ジョブ 長時間処理、画面を閉じても追跡したい処理 ジョブの状態管理、所有者確認、再開・照合、キュー運用

今回の設計案では、外部への送信や業務データ更新を含めない、短い下書き生成から始めます。そのため同期バックエンドを選びます。ただし、結果不明のまま再実行するUIにはしません。処理が長くなる、切断が頻発する、確定操作を含む場合は、永続ジョブへ移す判断が必要です。

DifyのWorkflow API概要では、Workflowは呼び出しごとに独立して実行されます。API化したことだけを根拠に、重複要求が自動でまとめられると考えないようにします。

公開する前に、入力と出力の契約を決める

ここでいう公開は、Dify内でWorkflowの実行対象を確定する操作です。業務システムから誰が利用できるかは、アプリ側で別に制御します。Run Workflowは公開済みWorkflowを実行するAPIです。

架空のWorkflowでは、次の契約を先に決めます。

項目 この例の契約
入力 inputs.request_textに申請の素材となる文字列
出力 data.outputs.draftに下書き文字列
認証 アプリ側セッションで確認し、バックエンドだけがDifyキーを使う
認可 認証済み利用者が下書き生成機能を使えるか確認する
完了条件 Workflow成功+出力形式が妥当で、下書きが確認待ちとして保存されたこと
確定条件 利用者が内容を確認して承認すること。生成成功だけでは申請しない

request_textとdraftはこの例で決めた変数名で、Difyが自動で用意する名前ではありません。実際のアプリ設定と合わせます。空入力、長すぎる入力、想定外の出力を公開前の評価データに入れ、出力名を変える変更もAPIの契約変更として扱います。

通常の/workflows/runは現在公開中のWorkflowを使います。公開版を固定したい場合は、公式のRun Workflow by IDで特定の公開済みworkflow_idを指定できます。Dify Cloudでは、特定の公開版を実行する機能は有料プラン向けです。Sandboxプランでは最新の公開版が実行されます(2026-10-10確認:Version Control)。これは公開版の選択であり、実行ごとのworkflow_run_idとは用途が異なります。

APIキーはサーバーの秘密管理へ保存します。検証用と本番用ではアプリとキーを分け、キーの失効、更新、漏えい時の停止手順を用意します。キー、Authorizationヘッダー、入力全文、出力全文を通常ログへ残しません。

HTTPアダプターでは、生成成功を「確認待ち」に変換する

以下はNode.js用のサーバー側モジュールdify-draft.mjsです。呼び出し側で認証・認可と利用上限を確認し、認証結果から得た識別子を渡す前提です。この関数自身は利用者を認証しません。画面が送ったIDをそのまま渡してはいけません。

APIキーはDIFY_API_KEYから読み、接続先はコード側に固定します。入力上限2,000文字、出力上限10,000文字、待機時間15秒は例として置いた値です。性能実測に基づく設定でも、Difyの上限値でもありません。独自のsignalを渡す場合は、呼び出し側で待機期限も組み込みます。

// dify-draft.mjs: サーバー側のみ。自動再試行は行わない。
export async function generateDraft(
  text,
  authenticatedUserId,
  signal = AbortSignal.timeout(15_000),
) {
  if (
    typeof text !== "string" || !text.trim() || text.length > 2_000 ||
    typeof authenticatedUserId !== "string" ||
    !/^[A-Za-z0-9_-]{1,128}$/.test(authenticatedUserId)
  ) {
    return { state: "not_started", reason: "invalid_input" };
  }
  if (signal.aborted) {
    return { state: "not_started", reason: "aborted_before_request" };
  }
  const apiKey = process.env.DIFY_API_KEY?.trim();
  if (!apiKey) {
    return { state: "not_started", reason: "missing_server_config" };
  }

  let response;
  try {
    response = await fetch("https://api.dify.ai/v1/workflows/run", {
      method: "POST",
      redirect: "error",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        inputs: { request_text: text.trim() },
        response_mode: "blocking",
        user: authenticatedUserId,
      }),
      signal,
    });
  } catch {
    // 通信切断・中断は、Dify側の未実行を証明しない。
    return { state: "unknown", reason: "transport_interrupted" };
  }
  if (!response.ok) {
    if ([400, 401, 403, 404, 413, 415, 429].includes(response.status)) {
      return { state: "rejected", httpStatus: response.status };
    }
    return { state: "unknown", reason: "upstream_http_error" };
  }

  let body;
  try {
    body = await response.json();
  } catch {
    return { state: "unknown", reason: "unreadable_response" };
  }
  const runId = body?.workflow_run_id;
  if (
    typeof runId !== "string" ||
    !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(runId)
  ) {
    return { state: "unknown", reason: "missing_run_id" };
  }
  const draft = body?.data?.outputs?.draft;
  if (body?.data?.status !== "succeeded") {
    return { state: "reconcile_required", runId, reason: "run_not_succeeded" };
  }
  if (typeof draft !== "string" || !draft.trim() || draft.length > 10_000) {
    return { state: "reconcile_required", runId, reason: "invalid_output" };
  }
  return { state: "review_pending", runId, draft };
}

このモジュールはHTTP境界の例であり、DBへの保存、Webのルート、レート制限、業務内容の正しさの評価は含みません。review_pendingを返しただけでは、保存済み・承認済みとは扱えません。

呼び出し側では、受け付けた操作のIDと所有者を保存し、上の結果で状態を更新します。review_pendingなら下書きを保存してから確認画面を出します。unknownなら再実行ボタンを有効にする前に照合へ進めます。保存に失敗した場合も、生成自体の失敗と区別します。

出力の文字列検証は内容の正しさを保証しません。事実、申請条件、誤った断定などを別の品質評価で確認し、必要ならHuman Reviewへ送ります。画面への表示もテキストとして行い、生成文を未検証のHTMLとして挿入しません。

タイムアウトと失敗を、同じ再試行ボタンへ押し込まない

アダプターの結果 残る状態 次の動作
not_started この関数から外部呼び出ししていない 入力・設定・中断理由を直して、新しい操作として開始
rejected HTTPエラーを受信した。途中の副作用がないことまでは断定しない 原因を分類。自動再試行はしない
unknown 実行の有無・結果を確定できない 操作を保留し、記録を照合。必要なら人が判断
reconcile_required 実行IDはあるが、成功または出力契約を満たさない 実行詳細を確認し、復旧または再実行を判断
review_pending 生成結果を受信した 下書きを保存し、品質評価・利用者確認へ進む

Difyのエラーガイドでは、429でもtoo_many_requestsは同時実行数、rate_limit_errorは利用枠の問題として区別されます。前者はバックオフの候補ですが、後者は待ち時間を少し増やしても解決しません。401や入力不正も、そのまま繰り返す理由がありません。

上の例はHTTP状態だけを返す最小構成です。本番では、許可リストにあるエラーcodeを内部記録へ追加し、同時実行数、利用枠、設定不備を分類します。エラー本文は利用者へ丸ごと返しません。また、Dify側の401をアプリ利用者のログイン切れとして表示すると、再ログインしても直らない障害になります。

通信失敗の再試行については、APIの一般的な再試行指針に加えて、Workflowが外部へ与える副作用を確認します。特に送信・登録を含む処理では、タイムアウトを「未実行」と決めつけられません。今回の下書き生成でも、自動再実行すれば追加の推論コストが発生し得るため、アダプターでは再試行しません。

workflow_run_idが保存できていれば、Get Workflow Run Detailで状態と出力を照合できます。ただし、切断前に実行IDを受け取れない場合、この方法だけでは解決しません。自前の操作IDがあることと、Difyが同じ操作を重複排除することも別です。

長時間化したときは、接続と実行を分けて管理する

blockingからstreamingへ変えるだけで、耐障害性が完成するわけではありません。画面の接続が切れても、開始した実行を追跡できる保存先が必要です。

長時間処理へ拡張する場合の設計案は、次のようになります。

同じ操作IDの再送は、所有者と入力の一致を確認し、既存の状態を返します。異なる入力での使い回しは拒否します。DBの一意制約と状態更新で同時実行を制御し、dispatchingのままプロセスが落ちた操作は、未実行と断定せず照合対象にします。このジョブ設計自体は未実装です。

公式資料では、task_idは実行中の制御、workflow_run_idは実行記録の参照に使います。停止はstreamingの実行を対象にします。Workflow API概要

ブラウザのAbortControllerで待機を中断しても、Dify側の処理や、既に成功した外部操作が取り消されたことにはなりません。停止APIを設けるなら、バックエンドで操作の所有者を確認してから呼び、停止後にも結果を照合します。特にWorkflowの停止APIはuserを検証しないと公式に説明されているため、所有者確認を省けません。End User Identity

ローカルで何を確認し、何が残っているか

掲載したアダプターを記事から抽出し、Node.js v24.11.1で外部通信をモックした検証を行いました。検証対象はHTTP境界の分岐であり、Dify実機の動作ではありません。

ローカル検証結果の抜粋です。構文確認と19件のテストが通りました。

tests 19
pass 19
fail 0
検証対象 期待する動作
正常な実行・妥当な下書き review_pending。送信・登録を実行しない
空入力、長すぎる入力、利用者識別子の不正 not_started。外部呼び出しゼロ
APIキー未設定、呼び出し前に中断済み not_started。外部呼び出しゼロ
401・429・500 401・429はrejected、500はunknown。自動再試行ゼロ
通信切断・要求開始後の中断 unknown。取消済みと断定しない
読めないJSON、実行ID欠落 unknown。成功として画面へ返さない
Workflow失敗、出力形式不正 reconcile_required。実行IDを保持

この検証では、認証・認可の実装、保存失敗、同時要求の重複排除、画面のCSRF対策、実環境の切断・停止挙動は未確認です。Dify実機とWebアプリを組み合わせた検証が、本番採用前に別途必要です。

本番へ進めるときの品質ゲートと費用の見方

  • 認証結果からuserを導出し、リクエスト本文の利用者IDを採用しない
  • 生成、実行詳細の参照、停止の各操作で所有者と操作権限を確認する
  • Cookie認証ではCSRF対策を用意し、ブラウザへAPIキーを配布しない
  • 利用者・組織ごとの要求数と同時実行数を制限する
  • 入力・出力契約、禁止入力、応答サイズの上限、内容評価を確認する
  • 正常・失敗・中断・結果不明・保存失敗を別の状態として残す
  • APIキー更新と緊急停止の手順を、検証環境で確認する
  • 操作ID、許可された実行ID、処理段階、所要時間、状態を記録し、全文や秘密情報を通常ログへ出さない
  • 保存・再送・切断・停止の結合検証を行い、実行済み操作を重複実行しないか確認する

費用は、推論、Difyの実行枠、バックエンド、ログ、運用対応を分けて見ます。固定の料金や削減率はここでは示しません。まず要求数、トークン使用量、結果不明件数、人による修正時間を実測します。

業務上の効果も「生成できた件数」だけでは判断できません。承認された下書き1件に必要な確認時間と、差し戻し率を計測します。再試行で生成数だけ増えても、推論費用と確認作業が増えれば改善とは言えません。工数効果を出す場合は、同じ難易度の作業について導入前後を比較する必要があります。

次に改善するのは、操作状態の永続化と結果不明時の照合です。その後、実測した待機時間と中断率を見て、同期のまま運用できるか、streamingと永続ジョブが必要かを判断します。

参考リンク

同様の仕組みの設計・構築・運用については相談可能。

この記事を書いた人✏️@YushiYamamoto
ITPRODX.com代表 / AIアーキテクト
Next.js / TypeScript / n8nを活用した自律型アーキテクチャ設計を専門としています。
日々の自動化の検証結果や、ビジネス側の視点(ROI等)に関するより深い考察は、以下の公式サイトおよびnoteで発信しています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?