API と Webhook で外部システムとつなぐ
基幹システムや通知ツールと ROSTER をつなぐための REST API(API キー)と Webhook(送信)の使い方。認証・回数制限・エンドポイント・署名の検証方法をまとめています。
ROSTER の外側にあるシステム(自社の基幹システム、Slack 通知、スプレッドシートの自動更新など)とつなぐための 2 つの仕組みです。どちらも Max プランでお使いいただけ、設定 > 連携 からオーナー・管理者が自分で設定できます。
- API(REST API) … 外部のシステムから ROSTER の候補者・求人・選考を読んだり、活動記録や面談予定を登録したりします。「外から ROSTER に聞きに来る」向きです。
- Webhook(送信) … ROSTER で出来事(候補者の登録・選考のステージ変更など)が起きた瞬間に、指定した URL へお知らせを送ります。「ROSTER から外へ知らせに行く」向きです。
このページの後半は、つなぎ込みを行う技術担当の方向けの正確な仕様です。前半だけ読めば「何ができるか」は分かるようにしています。
できること・できないこと
| できること | できないこと |
|---|---|
| 候補者の検索・詳細の取得・新規登録 | 候補者の更新・削除(個人情報の上書きと消去は画面で人が行います) |
| 求人 1 件の詳細の取得 | 求人・企業の登録や更新 |
| 進行中の選考の一覧 | 選考ステージの変更・成約の登録(不可逆な操作は画面で) |
| 活動記録の追加・面談予定の登録 | 請求・返金・メール送信 |
API は、ROSTER の「AI と接続」(MCP)と同じ処理を通ります。画面・AI・API のどこから登録しても、同じ行が同じ副作用(活動記録・監査ログ・カレンダー同期)付きで作られます。
API キーを発行する
- 設定 > 連携 を開き、「API キー」の「API キーを発行」を押します(オーナー・管理者のみ)
- 連携先が分かる名前(例「基幹システム連携」)を付けて「発行する」を押します
- 表示されたキー(
rk_live_で始まる文字列)をコピーして、連携先の設定に貼り付けます
キーが表示されるのはこの 1 回だけです。ROSTER にはキーそのものは保存されず、照合用の値(ハッシュ)だけが残るため、後から再表示できません。無くしたときは「失効」させてから新しいキーを発行してください。
キーは組織単位です。失効させると、そのキーを使う連携はすぐに動かなくなります(他のキーには影響しません)。
API の使い方(技術担当の方向け)
認証
すべてのリクエストに Authorization ヘッダーでキーを載せます。
Authorization: Bearer rk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- 基点 URL は
https://roster.co.jp/api/v1。すべて JSON(Content-Type: application/json)です。 - キーが無い・失効している →
401。Max プラン以外の組織のキー →403(plan_required)。
回数制限
キー 1 本あたり 1 分間に 120 回まで。超えると 429 を返し、Retry-After ヘッダーに「あと何秒で再開できるか」が入ります。
エラーの形
エラーは常に次の形です(code は機械で判定する識別子、message は日本語の説明)。
{ "error": { "code": "not_found", "message": "指定された候補者は見つかりませんでした。" } }主な code: unauthorized(401)/ plan_required(403)/ rate_limited(429)/ validation_error(400)/ not_found(404)/ duplicate・time_conflict(409)。
エンドポイント一覧
GET /api/v1/candidates?q=&limit=
候補者をフリーワードで検索します(氏名・フリガナ・現職・スキル)。limit は最大 20。
GET /api/v1/candidates?q=山田&limit=5{ "query": "山田", "count": 1, "candidates": [ { "id": "6f1c…", "name": "山田 太郎", "url": "https://roster.co.jp/candidates?open=6f1c…" } ] }GET /api/v1/candidates/:id
候補者 1 名の詳細(プロフィール・進行中の選考・直近の活動 10 件)。活動の本文は 120 文字の抜粋だけで、メール本文の全文は返しません。
{ "found": true, "candidate": { "id": "6f1c…", "name": "山田 太郎", "email": "…", "currentCompany": "…", "skills": ["Python"], "status": "active", "url": "…" }, "applications": [ { "id": "…", "jobTitle": "…", "companyName": "…", "stageName": "一次面接", "daysInStage": 3 } ], "recentActivities": [ { "id": "…", "type": "call", "at": "2026-09-10T06:00:00Z", "excerpt": "…" } ] }POST /api/v1/candidates
候補者を 1 名登録します。name は必須。同じ人がすでに登録されている可能性があるときは登録せず `409` を返し、matches に見つかった候補者を入れます。別人だと確認できたときだけ force: true を付けて呼び直してください。
{ "name": "山田 太郎", "kana": "やまだ たろう", "email": "taro@example.com", "current_company": "株式会社サンプル", "skills": ["Python", "AWS"] }成功(200):
{ "ok": true, "id": "6f1c…", "name": "山田 太郎", "forced": false, "url": "https://roster.co.jp/candidates?open=6f1c…" }重複(409):
{ "error": { "code": "duplicate", "message": "同じ方がすでに登録されている可能性があるため…" }, "matches": [ { "id": "…", "name": "山田 太郎", "matchedBy": "email", "url": "…" } ] }GET /api/v1/jobs/:id
求人 1 件の詳細(企業・条件・進行中の選考数・適合度の高い候補者 上位 3 名)。
{ "found": true, "job": { "id": "…", "title": "バックエンドエンジニア", "companyName": "株式会社サンプル", "status": "open", "salaryMin": 600, "salaryMax": 900, "url": "…" }, "activeApplicationCount": 4, "topMatchingCandidates": [ { "id": "…", "name": "…", "url": "…" } ] }GET /api/v1/applications?candidateId=&jobId=&stage=&status=&limit=
進行中の選考の一覧。candidateId / jobId で絞り込み、stage はステージの id か名前(完全一致)。status は active(既定)/ waiting_on_us(自社が動く番)/ waiting_on_client(企業の返事待ち)/ stalled(停滞)。limit は最大 50。
{ "status": "active", "count": 2, "totalMatched": 2, "applications": [ { "id": "…", "candidateId": "…", "jobId": "…", "candidateName": "…", "jobTitle": "…", "companyName": "…", "stageId": "…", "stageName": "一次面接", "daysInStage": 3, "waitingOn": "company", "waitingOnLabel": "企業の返事待ち", "stalled": false, "url": "…" } ] }POST /api/v1/activities
活動記録を 1 件追加します。subject_type は candidate / company / application、kind は call / email / visit / meeting / note(既定)。occurred_at に未来の日時は指定できません。
{ "subject_type": "candidate", "subject_id": "6f1c…", "kind": "call", "body": "電話で現況を確認。転職意欲は高い。", "occurred_at": "2026-09-12T10:00:00+09:00" }{ "ok": true, "id": "…", "subjectType": "candidate", "subjectName": "山田 太郎", "occurredAt": "2026-09-12T01:00:00.000Z", "url": "…" }POST /api/v1/interviews
面談・面接の予定を 1 件登録します。application_id か candidate_id のどちらかが必須。type は initial(候補者面談)/ company_interview(企業面接)/ follow_up / other。担当者の予定と重なるときは登録せず 409(time_conflict)を返し、conflicts に重なる予定を入れます。それでも入れる場合は force: true。
{ "application_id": "…", "type": "company_interview", "starts_at": "2026-09-15T14:00:00+09:00", "duration_minutes": 60, "location": "先方本社" }{ "ok": true, "id": "…", "scheduledAt": "2026-09-15T05:00:00.000Z", "durationMin": 60, "type": "company_interview", "subject": "山田 太郎 × 株式会社サンプル「バックエンドエンジニア」", "calendarRegistered": 1, "url": "…" }GET /api/v1/webhooks/events
Webhook で送られる出来事の一覧(名前と日本語の説明)。
Webhook(送信)を設定する
- 設定 > 連携 の「Webhook(送信)」で「エンドポイントを追加」を押します(オーナー・管理者のみ)
- お知らせを受け取る URL(
https://から始まる公開 URL)と、送る出来事を選んで「追加する」を押します - 表示された署名シークレット(
whsec_で始まる文字列)をコピーして、受け取り側の設定に保存します - 「テスト送信」を押すと
pingという出来事が届きます。受け取り側で 2xx を返せていれば成功です
送り先ごとに「有効 / 無効」を切り替えられ、「最近の配信」で送れたかどうか(成功・失敗・再送待ち)を確認できます。
送られる出来事
| 出来事名 | いつ送られるか |
|---|---|
candidate.created | 候補者が登録された(画面・AI・API のどこからでも) |
candidate.updated | 候補者が更新された(項目名だけ。値は送りません) |
application.stage_changed | 選考のステージが変わった(一括辞退・成約登録も含む) |
placement.created | 成約が登録された(金額は送りません) |
interview.scheduled | 面談の予定が入った |
ping | 「テスト送信」を押したとき |
送る内容は id と名前・タイトル・ステージ名などの要約だけです。連絡先・メモ・給与などの詳しい情報は含みません。必要なら受け取り側が API で取りに来る、という組み合わせにしてください。
本文の形
{
"id": "配信ごとの id(再送でも同じ id)",
"event": "application.stage_changed",
"created_at": "2026-09-12T01:23:45.000Z",
"org_id": "組織の id",
"data": { "id": "選考の id", "from_stage": { "id": "…", "name": "書類選考" }, "to_stage": { "id": "…", "name": "一次面接", "is_terminal": false, "is_won": false }, "source": "app" }
}ヘッダー: X-Roster-Event(出来事名)、X-Roster-Delivery(配信 id)、X-Roster-Signature(署名)。
署名の検証(Node.js の例)
X-Roster-Signature は t=<UNIX 秒>,v1=<HMAC-SHA256 の 16 進> の形で、<t>.<本文そのまま> を署名シークレットで HMAC-SHA256 した値です。本文は JSON に解釈する前の生の文字列で検証してください。
const crypto = require("node:crypto");
function verify(secret, header, rawBody) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 分より古い署名は拒否
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return expected.length === parts.v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}再送のきまり
- 受け取り側が 2xx を 10 秒以内に返せば成功です。重い処理は 2xx を返してから非同期で行ってください。
- 失敗(タイムアウト・2xx 以外)したときは、1 分後 → 10 分後 → 60 分後の 3 回まで再送し、それでも届かなければ打ち切ります(「最近の配信」に「失敗(打ち切り)」と出ます)。
- 再送は同じ
id(X-Roster-Delivery)で届きます。二重処理を避けるには、この id で重複を排除してください。 - 配信の記録は 30 日で消えます。
困ったときは
- 401 が返る … キーの先頭が
rk_live_になっているか、失効させていないかを確認してください。 - 403(plan_required)が返る … Max プラン以外では API と Webhook は使えません。設定 > プランと請求 でご確認ください。
- Webhook が届かない … 受け取り側が
https://の公開 URL か(社内ネットワークのアドレスは指定できません)、10 秒以内に 2xx を返しているかを確認し、「テスト送信」で試してください。
解決しないときはお問い合わせからご連絡ください。
関連記事
解決しませんでしたか。
ご利用中の方は、画面右下の「AI相談」で操作方法を質問できます。それでも分からないときは お問い合わせフォーム からご連絡ください。2営業日以内(土日祝日および当社の年末年始休業日を除く)にご返信します。
