diff --git a/api/bulk_import.yml b/api/bulk_import.yml
new file mode 100644
index 00000000..121dffe3
--- /dev/null
+++ b/api/bulk_import.yml
@@ -0,0 +1,299 @@
+openapi: 3.0.3
+info:
+ title: 一括インポートタスク登録API
+ version: "1.0"
+tags:
+ - name: 一括インポートタスク登録
+paths:
+ /api/items/import-task:
+ post:
+ tags:
+ - bulk_import
+ summary: 一括インポートタスク登録機能
+ description: |-
+ メタデータ(tsv/csv)を含むzipファイルを受け取り、非同期で一括インポートタスク(インポート可否の事前チェック、およびアイテムの登録・更新)を実行する。
+ |ロール |動作 |
+ | ---------------- | ------ |
+ |システム管理者 |使用可能|
+ |リポジトリ管理者 |使用可能|
+ |コミュニティ管理者|使用不可|
+ |登録ユーザー |使用不可|
+ |一般ユーザー |使用不可|
+ |ゲストユーザー |使用不可|
+ parameters:
+ - name: Authorization
+ in: header
+ required: true
+ description: Bearer アクセストークン
+ schema:
+ type: string
+ - name: mode
+ in: query
+ required: false
+ description: モード指定。
importの場合、非同期でインポートタスクを登録する。
checkの場合、インポート可否の確認のためのチェックタスクを登録する。
+ schema:
+ type: string
+ enum:
+ - import
+ - check
+ default: import
+ - name: is_change_identifier
+ in: query
+ required: false
+ description: 識別子変更モード可否。
trueの場合、識別子変更を行う。
falseの場合、識別子の変更を行わない。
+ schema:
+ type: boolean
+ default: false
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ file:
+ type: string
+ format: binary
+ description: メタデータ(tsv/csv)を含むzipファイル
+ responses:
+ "200":
+ description: タスクの登録に成功した場合/タスクの登録可否の確認に成功した場合
+ content:
+ application/json:
+ examples:
+ インポートモード タスク登録成功:
+ value:
+ can_import: true
+ check_status: "SUCCESS"
+ expire: "YYYY-MM-DD HH:mm:ss"
+ summary: {
+ total: <総件数>,
+ new_item: <新規件数>,
+ update_item: <更新件数>,
+ check_error: <エラー件数>,
+ warning: <警告件数>
+ }
+ tasks: [
+ {
+ item_task_id: <アイテムタスクID>,
+ task_status: "PENDING",
+ task_result: {}
+ }
+ ]
+ task_id: <タスクID>
+ チェックモード タスク登録可否確認成功:
+ value:
+ can_import: true
+ check_status: "SUCCESS"
+ expire: "YYYY-MM-DD HH:mm:ss"
+ summary: {
+ total: <総件数>,
+ new_item: <新規件数>,
+ update_item: <更新件数>,
+ check_error: <エラー件数>,
+ warning: <警告件数>
+ }
+ task_id: <タスクID>
+ "400":
+ description: リクエスト内容に不備がある場合、
+ またはzipファイル形式でない場合
+ content:
+ application/json:
+ examples:
+ リクエスト内容不備:
+ value:
+ can_import: false
+ check_status: "ERROR"
+ expire: "YYYY-MM-DD HH:mm:ss"
+ error_details: ["エラーメッセージ"]
+ summary: {
+ total: <総件数>,
+ new_item: <新規件数>,
+ update_item: <更新件数>,
+ check_error: <エラー件数>,
+ warning: <警告件数>
+ }
+ tasks: []
+ task_id: ""
+ リクエストヘッダー不備:
+ value:
+ result: "NG"
+ error: "Cannot get filename by Content-Disposition."
+ 指定ファイルが存在しない場合:
+ value:
+ result: "NG"
+ error: "Not found {filename} in request body."
+ ZIPファイル形式でない場合:
+ value:
+ result: "NG"
+ error: "Uploaded file is not a valid ZIP file."
+ チェックタスクが失敗、強制終了した場合:
+ value:
+ can_import: false
+ check_status: "ERROR"
+ error_details: ["Check task failed."]
+ summary: {}
+ tasks: []
+ task_id: ""
+ チェックタスクがタイムアウトした場合:
+ value:
+ can_import: false
+ check_status: "TIMEOUT"
+ error_details: ["Check task timeout."]
+ summary: {}
+ tasks: []
+ task_id: ""
+ アイテムにエラーがある場合:
+ value:
+ can_import: false
+ check_status: "ERROR"
+ error_details: ["<エラーメッセージ>"]
+ summary: {}
+ tasks: []
+ task_id: ""
+ "401":
+ description: リクエストでAuthorizationヘッダーが提供されていない、または無効なアクセストークンが提供された場合
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/401ErrorResponse"
+ "403":
+ description: 認証に失敗した場合、認証したOAuthトークンが必須スコープを持っていない場合
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/403ErrorResponse"
+ "500":
+ description: 例外が発生した場合
+ content:
+ application/json:
+ example:
+ result: "NG"
+ error: "Internal Server Error"
+
+ /api/items/import-task/get_bulk_import_task_status/{task_id}:
+ get:
+ tags:
+ - bulk_import
+ summary: 一括インポートステータス取得機能
+ description: |-
+ タスクIDを指定し、インポートタスクの登録状況や実行結果を取得する
+ |ロール |動作 |
+ | ---------------- | ------ |
+ |システム管理者 |使用可能|
+ |リポジトリ管理者 |使用可能|
+ |コミュニティ管理者|使用不可|
+ |登録ユーザー |使用不可|
+ |一般ユーザー |使用不可|
+ |ゲストユーザー |使用不可|
+ parameters:
+ - name: Authorization
+ in: header
+ required: true
+ description: Bearer アクセストークン
+ schema:
+ type: string
+ - name: task_id
+ in: path
+ required: true
+ description: タスクID
+ schema:
+ type: string
+ responses:
+ "200":
+ description: タスクの登録状況の取得に成功した場合
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ can_import:
+ type: boolean
+ example: true
+ check_status:
+ type: string
+ example: "SUCCESS"
+ expire:
+ type: string
+ example: "YYYY-MM-DD HH:mm:ss"
+ tasks:
+ type: array
+ items:
+ type: object
+ properties:
+ item_task_id:
+ type: string
+ task_status:
+ type: string
+ task_result:
+ type: object
+ properties:
+ recid:
+ type: string
+ start_date:
+ type: string
+ example: "YYYY-MM-DD HH:mm:ss"
+ success:
+ type: boolean
+
+ task_id:
+ type: string
+ "400":
+ description: デコード失敗や、エラーが発生した場合
+ content:
+ application/json:
+ examples:
+ デコード失敗:
+ value:
+ result: "NG"
+ error: "Task data decode error."
+ チェック失敗:
+ value:
+ result: "NG"
+ error: "Task check failed.: {error_message}"
+ "401":
+ description: リクエストでAuthorizationヘッダーが提供されていない、または無効なアクセストークンが提供された場合
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/401ErrorResponse"
+ "403":
+ description: 認証に失敗した場合、認証したOAuthトークンが必須スコープを持っていない場合、別ユーザーがタスクを参照しようとした場合
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/403ErrorResponse"
+ "404":
+ description: タスクが見つからない場合
+ content:
+ application/json:
+ example:
+ result: "NG"
+ error: "Task not found."
+
+
+components:
+ schemas:
+ 401ErrorResponse:
+ type: object
+ properties:
+ result:
+ type: string
+ default: "NG"
+ error:
+ type: array
+ items:
+ type: string
+ default: "Authentication is required."
+ 403ErrorResponse:
+ type: object
+ properties:
+ result:
+ type: string
+ default: "NG"
+ error:
+ type: array
+ items:
+ type: string
+ default: "Permission required."
+
diff --git a/docs/spec/base/api/API_20_bulk_import_api.md b/docs/spec/base/api/API_20_bulk_import_api.md
new file mode 100644
index 00000000..e95e5097
--- /dev/null
+++ b/docs/spec/base/api/API_20_bulk_import_api.md
@@ -0,0 +1,224 @@
+# 一括インポートタスク登録・ステータスチェックAPI
+
+## 目的・用途
+
+クライアントからメタデータ(tsv/csv)を含むzipファイルを受け取り、非同期で一括インポートタスク(インポート可否の事前チェック、およびアイテムの登録・更新)を実行する。
+実際の処理状況やインポート結果はタスク登録時に発行されるタスクIDを用いてステータス確認APIから取得する。
+
+## 利用方法
+
+APIの認証にはOAuth2を利用する。
+アクセストークンの発行は[API-1:OAuth2](./API_01_Oauth2.md#oauth2)を参照。
+
+### Scope:
+一括インポートタスクの登録、およびステータス確認を行うためには以下のスコープが必要となる。
+
+- item:bulkprocess
+
+#### 利用可能なロール
+
+| ロール | システム
管理者 | リポジトリ
管理者 | コミュニティ
管理者 | 登録
ユーザー | 一般
ユーザー | ゲスト
(未ログイン) |
+|:-----------:|:-----------:|:-----------:|:-----------:|:-----------:|:-----------:|:-----------:|
+| 利用可否 | 〇 | 〇 | × | × | × | × |
+
+### エンドポイント:
+
+| 項番 | HTTP request | 内容 |
+|------|--------------------------------------------------|----------------------------------------------------------------------|
+| 1 | POST /api/items/import-task | メタデータ(tsv/csv)を含むzipファイルをアップロードし、一括インポートタスクを登録する。 |
+| 2 | GET /api/items/import-task/get_bulk_import_task_status/ | task_idを指定して、インポートタスクの処理状況や実行結果を取得する。 |
+
+### POST /api/items/import-task
+
+#### リクエスト
+
+```shell
+curl -X POST {hostname}/api/items/import-task?mode=&is_change_identifier= \
+ -F "file=@;type=application/zip" \
+ -H "Authorization: Bearer " \
+ -H "Content-Disposition:attachment; filename="
+```
+
+- **クエリパラメータ**
+ - `mode`:モード指定("import" または "check") 指定しなかった場合は"import"として扱われる。
+ - `is_change_identifier`:識別子変更モード可否(true/false)指定しなかった場合はfalseとして扱われる。
+
+- **フォームデータ**
+ - `file`:アップロードするZIPファイル(MIMEタイプ: application/zip)
+ ※ ZIPファイルは[ADMIN_2_4.md](../admin/ADMIN_2_4.md)で使用するファイルと同様のものを使用する。
+ ※ TSVファイルの「.bulk_doi」に指定したDOI値
+ で[DOIを使用したメタデータ補完機能](../user/USER_4_6.md#3-web-apiによるdoiを使用したメタデータ補完機能)を行うことが出来る。
+
+- **HTTPヘッダー**
+ - `Authorization`: Bearer <アクセストークン>
+ - `Content-Disposition`: attachment; filename=〇〇.zip(添付ファイル名指定)
+
+#### レスポンス
+
+- インポートチェック処理のタイムアウト時間はデフォルトで60秒が設定されている。
+ この値は設定ファイルのWEKO_ITEMS_UI_BULK_IMPORT_TIMEOUTから変更可能。
+ ※ タイムアウト時間60秒で処理できるインポートファイル数は約250件。(実行環境による)
+- タスク結果はexpire(有効期限)まで確認することが出来、デフォルトはタスク登録から24時間後に設定されている。
+ 有効期限は設定ファイルのWEKO_ITEMS_UI_EXPIRE_TIMEから変更可能。
+
+- **成功レスポンス インポートモード**
+ ```json
+ {
+ "can_import": true,
+ "check_status": "SUCCESS",
+ "expire": "YYYY-MM-DD HH:mm:ss",
+ "summary": {
+ "total": <総件数>,
+ "new_item": <新規登録数>,
+ "update_item": <更新数>,
+ "check_error": <エラー数>,
+ "warning": <警告数>
+ },
+ "tasks": [
+ {
+ "item_task_id": <インポートタスクID>,
+ "task_status": "PENDING",
+ "task_result": {}
+ },
+ ...
+ ],
+ "task_id": <タスクID>,
+ }
+ ```
+
+- **成功レスポンス チェックモード**
+ ```json
+ {
+ "can_import": true,
+ "check_status": "SUCCESS",
+ "expire": "YYYY-MM-DD HH:mm:ss",
+ "summary": {
+ "total": <総件数>,
+ "new_item": <新規登録数>,
+ "update_item": <更新数>,
+ "check_error": <エラー数>,
+ "warning": <警告数>
+ },
+ "task_id": <タスクID>,
+ }
+ ```
+
+- **エラーレスポンス**
+ ```json
+ {
+ "result": "NG",
+ "error": [<エラーメッセージ>]
+ }
+ ```
+
+ ```json
+ {
+ "can_import": false,
+ "check_status": "ERROR",
+ "expire": "YYYY-MM-DD HH:mm:ss",
+ "error_details": [
+ <エラーメッセージ>
+ ],
+ "summary": {},
+ "tasks": [],
+ "task_id": <タスクID>
+ }
+ ```
+
+#### レスポンスコード
+
+| コード | 内容 |
+|--------------|--------------|
+| 200 | 正常レスポンス |
+| 400 | Content-Dispositionヘッダが不正、またはファイル名が取得できない場合 |
+| 400 | リクエストボディに指定されたファイル名のファイルが存在しない場合 |
+| 400 | アップロードされたファイルがZIP形式でない場合 |
+| 400 | Celeryによるチェックタスクが失敗または強制終了した場合 |
+| 400 | チェックタスクがタイムアウトした場合 |
+| 401 | アクセストークンが無効 |
+| 403 | ロールがシステム管理者、もしくはリポジトリ管理者ではない場合 |
+| 500 | その他例外発生時 |
+
+---
+
+### GET /api/items/import-task/get_bulk_import_task_status/
+
+#### リクエスト
+
+```shell
+curl -X GET {hostname}/api/items/import-task/get_bulk_import_task_status/ \
+ -H "Authorization: Bearer "
+```
+
+- **パスパラメータ**
+ - `task_id`:タスクID
+
+- **HTTPヘッダー**
+ - `Authorization`: Bearer <アクセストークン>
+
+#### レスポンス
+
+- **成功レスポンス**
+ ```json
+ {
+ "can_import": true,
+ "check_status": "SUCCESS",
+ "expire": "YYYY-MM-DD HH:mm:ss",
+ "tasks": [
+ {
+ "item_task_id": <インポートタスクID>,
+ "task_status": <状態>,
+ "task_result": {
+ "recid": <レコードID>,
+ "start_date": <インポート開始時間>,
+ "success": true
+ }
+ },
+ ...
+ ],
+ "task_id": <タスクID>
+ }
+ ```
+
+- **エラーレスポンス**
+ ```json
+ {
+ "can_import": false,
+ "check_status": "ERROR",
+ "expire": "YYYY-MM-DD HH:mm:ss",
+ "error_details": <エラー内容>,
+ "tasks": [],
+ "task_id": <タスクID>
+ }
+ ```
+
+ ```json
+ {
+ "result": "NG",
+ "error": [<エラーメッセージ>]
+ }
+ ```
+
+#### レスポンスコード
+
+| レスポンス | 内容 |
+|--------------|--------------|
+| 200 | 正常レスポンス |
+| 400 | タスク情報のデコード失敗 |
+| 400 | 上記以外のエラー発生時 |
+| 401 | アクセストークンが無効 |
+| 403 | ユーザーID不一致(登録ユーザー以外によるアクセス) |
+| 404 | タスク情報が存在しない |
+
+
+### 関連モジュール
+
+> weko\_items\_ui
+
+### 更新履歴
+
+| 日付 | GitHubコミットID | 更新内容 |
+|--------------|--------------|--------------|
+| 3/31 | | 新規作成 |
+
+