コンテンツにスキップ

候補者 API

候補者の一覧・詳細・更新、インポート(TSV / アンケート)、スコアリング、選定、ステータス管理、日程確定。共通規約は API 共通仕様 を参照。

ベースパス: /api/v1/projects/{projectId}/candidates

メソッド パス 概要 必要ロール
GET / 候補者一覧取得 viewer
GET /fields フィルタ可能なフィールド一覧取得 viewer
POST /import/upload TSV ファイルアップロード member
GET /import/{fileId} アップロード済みファイル情報取得 viewer
POST /import/{fileId}/execute TSV インポート実行(非同期) member
POST /import/survey/preview アンケートインポートプレビュー member
POST /import/survey アンケートインポート実行(非同期) member
GET /import/{importId}/status インポート状況確認 viewer
GET /{candidateId} 候補者詳細取得 viewer
PATCH /{candidateId} 候補者更新 member
DELETE /{candidateId} 候補者削除 member
POST /score スコア再計算 member
PUT /selection 候補者選定タイプ一括更新 member
PATCH /{candidateId}/status 候補者ステータス更新 member
GET /{candidateId}/history 候補者ステータス履歴取得 viewer
GET /{candidateId}/email-logs 候補者メール送信履歴取得 viewer
POST /{candidateId}/confirm-schedule 日程確定(管理者) member

全エンドポイントで Authorization: Bearer <access_token> が必要。更新系(POST / PATCH / PUT / DELETE)は member 以上。

candidateId は project_candidates.id

パスの {candidateId} は candidates.id ではなく project_candidates.id(プロジェクトへの参加レコードの ID)を指す。

ProjectCandidate オブジェクト

フィールド 型 説明
id integer プロジェクト候補者 ID
projectId integer プロジェクト ID
candidateId integer 候補者 ID
status enum 10 値のステータス
selectionType enum | null primary / reserve
segmentId string | null セグメント ID
memo string | null メモ
priority string | null 優先度(A / B / C)
score string | null スコア(decimal の文字列表現)
attributes object | null 属性
surveyResponses object | null アンケート回答
reservationId integer | null 確定予約 ID
reservation object | null 予約概要(scheduledAt / durationMinutes / interviewType / status / interviewerName)
availableDates array 候補者が送信した希望日程(id / startAt / endAt / interviewerName)
candidate object 個人情報(id / externalUserId / email / phone / name)
createdAt / updatedAt string(date-time) 作成/更新日時

候補者一覧取得

URI

GET /api/v1/projects/{projectId}/candidates

クエリパラメータ

フィールド 型 必須 既定値 ルール 説明
page integer - 1 正の整数 ページ番号
limit integer - 20 1〜100 件数
sort string - - created_at / score / status / name 並び順
status enum - - 10 値 ステータスで絞り込み
selectionType enum - - primary / reserve 選定区分で絞り込み
segmentId string - - - セグメントで絞り込み
minScore number - - - スコア下限
maxScore number - - - スコア上限
search string - - - 氏名またはメールの部分一致
responseFilter string - - JSON 文字列 回答内容による絞り込み

responseFilter の指定方法

attributes と surveyResponses のフィールドを条件に絞り込む。JSON を文字列化して渡す。

単純な条件の配列:

[
  { "field": "性別", "operator": "eq", "value": "女性" },
  { "field": "attributes.prefecture", "operator": "in", "value": ["東京", "神奈川"] }
]

論理演算子を指定する形式:

{
  "conditions": [
    { "field": "性別", "operator": "eq", "value": "女性" },
    {
      "conditions": [
        { "field": "年代", "operator": "eq", "value": "20代" },
        { "field": "年代", "operator": "eq", "value": "30代" }
      ],
      "logic": "or"
    }
  ],
  "logic": "and"
}
フィールド ルール
field 必須。attributes または surveyResponses のフィールド名
operator 必須。eq / neq / contains / in
value 必須。文字列・数値、または配列(in の場合)
logic 任意。and(既定)/ or

レスポンス(200 OK)

data が ProjectCandidate の配列、pagination 付き。

例外処理

説明 ステータスコード ステータス名
クエリまたは responseFilter の形式不正 400 Bad Request
トークンが欠落または無効 401 Unauthorized
プロジェクトが存在しない 404 Not Found
サーバ内部エラー 500 Internal Server Error

フィルタ可能なフィールド一覧取得

登録済み候補者の attributes / surveyResponses に実在するフィールドと、そのサンプル値を返す。セグメント定義や絞り込み UI の選択肢を作るために使う。

URI

GET /api/v1/projects/{projectId}/candidates/fields

レスポンス(200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "fields": [
      { "name": "prefecture", "source": "attributes", "sampleValues": ["東京", "大阪", "福岡"] },
      { "name": "性別", "source": "surveyResponses", "sampleValues": ["男性", "女性"] }
    ]
  },
  "path": "/api/v1/projects/1/candidates/fields",
  "method": "GET"
}
フィールド 型 説明
name string フィールド名
source enum attributes / surveyResponses
sampleValues string[] サンプル値

例外処理

説明 ステータスコード ステータス名
トークンが欠落または無効 401 Unauthorized
プロジェクトが存在しない 404 Not Found
サーバ内部エラー 500 Internal Server Error

TSV ファイルアップロード

URI

POST /api/v1/projects/{projectId}/candidates/import/upload

リクエストボディ

multipart/form-data。

フィールド 必須 ルール
file ◯ TSV ファイル(UTF-16LE、タブ区切り)

レスポンス(200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "file_id": "550e8400-e29b-41d4-a716-446655440000",
    "file_info": { "name": "candidates.tsv", "size": 20480, "rowCount": 120 },
    "columns": [
      { "index": 0, "name": "回答者ID", "sampleValues": ["1001", "1002"] },
      { "index": 1, "name": "お名前", "sampleValues": ["山田太郎", "佐藤花子"] }
    ],
    "suggested_mapping": { "external_user_id": { "column_index": 0 } },
    "preview": [{ "回答者ID": "1001", "お名前": "山田太郎" }],
    "expires_at": "2026-01-15T11:00:00Z"
  },
  "path": "/api/v1/projects/1/candidates/import/upload",
  "method": "POST"
}
フィールド 型 説明
file_id string(uuid) 一時ファイル ID。実行時に指定する
file_info object ファイル名・サイズ・行数
columns array 列インデックス・列名・サンプル値
suggested_mapping object 列名から推測したマッピング案
preview array 先頭数行のプレビュー
expires_at string(date-time) 一時ファイルの有効期限

例外処理

説明 ステータスコード ステータス名
ファイル未添付・パース失敗 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
プロジェクトが存在しない 404 Not Found
サーバ内部エラー 500 Internal Server Error

アップロード済みファイル情報取得

アップロード直後のレスポンスと同じ内容を再取得する。画面リロード後にマッピング作業を再開するために使う。

URI

GET /api/v1/projects/{projectId}/candidates/import/{fileId}

パスパラメータ

フィールド ルール
fileId 必須。UUID

レスポンス(200 OK)

アップロード時と同じ ImportUploadResponse。

例外処理

説明 ステータスコード ステータス名
トークンが欠落または無効 401 Unauthorized
ファイルが存在しない/期限切れ 404 Not Found
サーバ内部エラー 500 Internal Server Error

TSV インポート実行

非同期で実行され、受付時点で 201 を返す。進捗は「インポート状況確認」で追う。

URI

POST /api/v1/projects/{projectId}/candidates/import/{fileId}/execute

リクエストボディ

{
  "mapping": {
    "external_user_id": { "column_index": 0 },
    "name": { "column_index": 1 },
    "email": { "column_index": 2 },
    "phone": { "column_index": 3 },
    "attributes": {
      "prefecture": { "type": "direct", "column_index": 4 },
      "gender": { "type": "single_select", "columns": [5, 6], "mapping": "first_selected" }
    },
    "survey_responses": {
      "mode": "all_remaining",
      "exclude_columns": [2, 3]
    }
  }
}

バリデーションルール

フィールド ルール
mapping.external_user_id.column_index 必須。0 以上の整数
mapping.name.column_index 必須。0 以上の整数
mapping.email.column_index 必須。0 以上の整数
mapping.phone.column_index 任意。0 以上の整数
mapping.attributes 任意。フィールド名 → マッピング定義
mapping.survey_responses.mode 任意。all_remaining / specified
mapping.survey_responses.exclude_columns 任意。列インデックスの配列
mapping.survey_responses.include_columns 任意。列インデックスの配列

attributes のマッピング定義は 2 種類:

type フィールド 説明
direct column_index 指定列の値をそのまま使う
single_select columns / mapping 複数列の選択肢群から 1 値に畳む。mapping は値の対応表または "first_selected"

レスポンス(201 Created)

{
  "success": true,
  "status": "success",
  "statusCode": 201,
  "data": { "importLogId": 12, "status": "processing" },
  "path": "/api/v1/projects/1/candidates/import/550e.../execute",
  "method": "POST"
}

例外処理

説明 ステータスコード ステータス名
マッピングの形式不正 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
ファイルまたはプロジェクトが存在しない 404 Not Found
サーバ内部エラー 500 Internal Server Error

アンケートインポートプレビュー

実際には登録せず、マッピングとフィルタを適用した結果を試算する。

URI

POST /api/v1/projects/{projectId}/candidates/import/survey/preview

リクエストボディ

{
  "surveyId": "234567",
  "mapping": {
    "externalUserId": { "type": "direct", "sourceField": "回答者ID" },
    "name": { "type": "direct", "sourceField": "お名前をご記入ください" },
    "email": { "type": "direct", "sourceField": "メールアドレス" }
  },
  "filter": {
    "conditions": [{ "field": "回答状況", "operator": "equals", "value": "完了" }],
    "logic": "and"
  },
  "sampleSize": 10
}

バリデーションルール

フィールド ルール
surveyId 必須。1 文字以上
mapping 必須。ImportMapping(プロジェクト API を参照)
filter 任意。ImportFilter
sampleSize 任意。1〜100 の整数

レスポンス(200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "surveyId": "234567",
    "totalRespondents": 1234,
    "filteredRespondents": 320,
    "sampleData": [
      { "externalUserId": "1001", "name": "山田太郎", "email": "yamada@example.com" }
    ],
    "warnings": ["メールアドレスが空の回答が 3 件あります"]
  },
  "path": "/api/v1/projects/1/candidates/import/survey/preview",
  "method": "POST"
}
フィールド 型 説明
totalRespondents integer フィルタ前の回答者数
filteredRespondents integer フィルタ後に取り込まれる見込みの件数
sampleData array マッピング適用後のサンプル
warnings string[] 警告メッセージ

例外処理

説明 ステータスコード ステータス名
バリデーションエラー 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
プロジェクトまたはアンケートが存在しない 404 Not Found
Databricks 未設定 503 Service Unavailable

アンケートインポート実行

インポートログを先に作成し、ECS タスク(本番)またはサブプロセス(開発)へ処理を委譲して即座に返す。

URI

POST /api/v1/projects/{projectId}/candidates/import/survey

リクエストボディ

プレビューから sampleSize を除いたもの。

{
  "surveyId": "234567",
  "mapping": { "externalUserId": { "type": "direct", "sourceField": "回答者ID" } },
  "filter": { "conditions": [], "logic": "and" }
}

レスポンス(201 Created)

受付時点の値が返るため、件数はすべて 0。

{
  "success": true,
  "status": "success",
  "statusCode": 201,
  "data": {
    "importLogId": 12,
    "status": "processing",
    "importedCount": 0,
    "skippedCount": 0,
    "errorCount": 0,
    "duplicateCount": 0,
    "errors": []
  },
  "path": "/api/v1/projects/1/candidates/import/survey",
  "method": "POST"
}

例外処理

説明 ステータスコード ステータス名
バリデーションエラー 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
プロジェクトが存在しない 404 Not Found

処理フロー

sequenceDiagram
    participant FE as フロントエンド
    participant API as Backend API
    participant DB as PostgreSQL
    participant Task as ECS タスク / サブプロセス

    FE->>API: POST /candidates/import/survey
    API->>DB: import_logs を作成(pending)
    API->>Task: インポート処理を起動
    API-->>FE: 201(importLogId, processing)
    Task->>DB: Databricks から回答を読み、候補者を登録
    Task->>DB: import_logs を completed / partial / failed に更新
    loop ポーリング
        FE->>API: GET /candidates/import/{importId}/status
        API-->>FE: 進捗と件数
    end

インポート状況確認

TSV / アンケートいずれのインポートにも使える。

URI

GET /api/v1/projects/{projectId}/candidates/import/{importId}/status

パスパラメータ

フィールド ルール
importId 必須。正の整数(import_logs.id)

レスポンス(200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "importLogId": 12,
    "status": "completed",
    "importedCount": 300,
    "skippedCount": 15,
    "errorCount": 2,
    "duplicateCount": 3,
    "errors": [
      { "row": 42, "respondentId": "1042", "field": "email", "message": "メールアドレスが不正です" }
    ]
  },
  "path": "/api/v1/projects/1/candidates/import/12/status",
  "method": "GET"
}
status 意味
pending 受付済み・未開始
processing 処理中
completed 全件成功
partial 一部成功
failed 失敗

例外処理

説明 ステータスコード ステータス名
トークンが欠落または無効 401 Unauthorized
インポートログが存在しない 404 Not Found
サーバ内部エラー 500 Internal Server Error

候補者詳細取得

URI

GET /api/v1/projects/{projectId}/candidates/{candidateId}

レスポンス(200 OK)

data に ProjectCandidate。reservation と availableDates を含む。

例外処理

説明 ステータスコード ステータス名
トークンが欠落または無効 401 Unauthorized
候補者が存在しない 404 Not Found
サーバ内部エラー 500 Internal Server Error

候補者更新

URI

PATCH /api/v1/projects/{projectId}/candidates/{candidateId}

リクエストボディ

{
  "attributes": { "prefecture": "東京" },
  "surveyResponses": { "性別": "女性" },
  "memo": "要フォロー",
  "priority": "A"
}

バリデーションルール

フィールド ルール
attributes 任意。任意構造のオブジェクト
surveyResponses 任意。任意構造のオブジェクト
memo 任意。文字列または null
priority 任意。文字列または null(DB 上は 1 文字)

個人情報は更新できない

氏名・メールアドレス・電話番号は candidates テーブル側の情報であり、このエンドポイントでは変更できない。

レスポンス(200 OK)

data に更新後の ProjectCandidate。

例外処理

説明 ステータスコード ステータス名
バリデーションエラー 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
候補者が存在しない 404 Not Found

候補者削除

URI

DELETE /api/v1/projects/{projectId}/candidates/{candidateId}

レスポンス(204 No Content)

ボディなし。

例外処理

説明 ステータスコード ステータス名
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
候補者が存在しない 404 Not Found

Warning

プロジェクトへの参加(project_candidates)が削除され、メールログ・調整トークン・希望日程・予約・ステータス履歴も連鎖削除される。個人(candidates)自体は残る。


スコア再計算

プロジェクトの scoring_rules を全候補者に再適用する。

URI

POST /api/v1/projects/{projectId}/candidates/score

リクエスト

ボディなし。

レスポンス(200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": { "updated": 25 },
  "path": "/api/v1/projects/1/candidates/score",
  "method": "POST"
}
フィールド 型 説明
updated integer スコアが更新された候補者数

例外処理

説明 ステータスコード ステータス名
スコアリングルールが不正(SCORING_ERROR) 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
プロジェクトが存在しない 404 Not Found

候補者選定タイプ一括更新

STEP2 の選定結果を保存する。

URI

PUT /api/v1/projects/{projectId}/candidates/selection

リクエストボディ

{
  "selections": [
    { "candidateId": 12, "selectionType": "primary", "segmentId": "seg-1" },
    { "candidateId": 34, "selectionType": "reserve", "segmentId": "seg-1" },
    { "candidateId": 56, "selectionType": null, "segmentId": null }
  ]
}

バリデーションルール

フィールド ルール
selections 必須。配列
selections[].candidateId 必須。整数(project_candidates.id)
selections[].selectionType 必須。primary / reserve / null(選定解除)
selections[].segmentId 任意。文字列または null

レスポンス(200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": { "updated": 3 },
  "path": "/api/v1/projects/1/candidates/selection",
  "method": "PUT"
}

例外処理

説明 ステータスコード ステータス名
バリデーションエラー 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
プロジェクトが存在しない 404 Not Found

候補者ステータス更新

URI

PATCH /api/v1/projects/{projectId}/candidates/{candidateId}/status

リクエストボディ

{
  "status": "contacted",
  "note": "案内メールを送信"
}

バリデーションルール

フィールド ルール
status 必須。not_contacted / contacted / waiting_response / scheduling / scheduled / interviewed / completed / declined / bounced / cancelled
note 任意。備考

遷移ルール

現在 遷移可能な次の状態
not_contacted contacted / declined / cancelled
contacted waiting_response / bounced / declined / cancelled
waiting_response scheduling / declined / cancelled
scheduling scheduled / declined / cancelled
scheduled interviewed / cancelled
interviewed completed / cancelled
completed (なし)
declined (なし)
bounced contacted
cancelled not_contacted

レスポンス(200 OK)

data に更新後の ProjectCandidate。同時に status_histories に 1 行追加される。

例外処理

説明 ステータスコード ステータス名
許可されていない遷移(INVALID_STATUS_TRANSITION) 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
候補者が存在しない 404 Not Found

候補者ステータス履歴取得

URI

GET /api/v1/projects/{projectId}/candidates/{candidateId}/history

レスポンス(200 OK)

data が StatusHistory の配列(ページネーションなし)。

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": [
    {
      "id": 1,
      "projectCandidateId": 12,
      "oldStatus": "not_contacted",
      "newStatus": "contacted",
      "changedBy": 1,
      "note": "案内メールを送信",
      "createdAt": "2026-01-15T09:00:00Z"
    }
  ],
  "path": "/api/v1/projects/1/candidates/12/history",
  "method": "GET"
}

例外処理

説明 ステータスコード ステータス名
トークンが欠落または無効 401 Unauthorized
候補者が存在しない 404 Not Found

候補者メール送信履歴取得

URI

GET /api/v1/projects/{projectId}/candidates/{candidateId}/email-logs

レスポンス(200 OK)

data が EmailLog の配列(ページネーションなし)。フィールドは メール API を参照。

例外処理

説明 ステータスコード ステータス名
トークンが欠落または無効 401 Unauthorized
候補者が存在しない 404 Not Found

日程確定(管理者)

候補者が送信した希望日程から 1 件を選んで予約を作成する。

URI

POST /api/v1/projects/{projectId}/candidates/{candidateId}/confirm-schedule

リクエストボディ

{
  "availableDateId": 5
}

バリデーションルール

フィールド ルール
availableDateId 必須。正の整数(candidate_available_dates.id)。対象候補者のものである必要がある

レスポンス(201 Created)

{
  "success": true,
  "status": "success",
  "statusCode": 201,
  "data": {
    "message": "日程を確定しました",
    "reservationId": 7,
    "scheduledAt": "2026-02-03T01:00:00.000Z",
    "durationMinutes": 60
  },
  "path": "/api/v1/projects/1/candidates/12/confirm-schedule",
  "method": "POST"
}

例外処理

説明 ステータスコード ステータス名
バリデーションエラー 400 Bad Request
トークンが欠落または無効 401 Unauthorized
ロール不足 403 Forbidden
候補者または希望日程が存在しない 404 Not Found
既に確定済み(SLOT_NOT_AVAILABLE) 409 Conflict

処理内容

  1. candidate_available_dates から対象を取得し、候補者との一致を検証する
  2. 既に confirmed の予約があれば SLOT_NOT_AVAILABLE で中断する
  3. reservations を作成する
  4. 面接官の Google Calendar 上の仮枠を確定イベントに変換する
  5. 候補者へ確定通知メールを送る

PROTOTYPE_MODE

PROTOTYPE_MODE=true の間は 4. と 5. がスキップされ、DB の予約作成のみが行われる。