Enterprise API v1(REST API for Job CRUD)PRD(給後端 API 規格產生器使用)
本 PRD 對應 Coda backlog
i-uL6dSrzo8o(原名「資料匯出 API」,正名為「Enterprise API(v1:REST API for Job CRUD)」)。 v1 範圍:REST API + wport 後台人工核發;v2(前端 Setting 自助申請 + 企業端 UI 整合)與 v3(CLI)各自獨立 PRD。 此 PRD 為純 API 功能,v1 不含前端 UI、不含 Storybook;UI 區塊以 ops 後台與 API 行為描述替代。
1. 文件資訊
- 文件類型:API 功能需求規格書(v1:後端 + ops 後台)
- 適用對象:後端開發、ops、產品、QA、企業端 API 使用者
- 最後更新:2026/05/13
- 版本:1.0.0
- 對應 Storybook:N/A(v1 無前端 UI,無 Storybook story)
- Storybook 連結:N/A
- 建立日期:2026/05/13
- PRD 代號:pm_37
2. 開發進度與設計來源
開發進度
- 前端 PR #:N/A(v1 無前端)
- 後端 PR #:[TBD]
- Ops 後台 PR #:[TBD]
設計來源
- Figma 連結:N/A(v1 無 UI 設計稿)
- 競品對標(SaaS):Stripe API(key 命名、idempotency、live/test 分離)、GitHub REST API(PAT scope、rate limit header)、Anthropic API(usage 透明)、Shopify Admin API(partial success batch)
3. 功能摘要(Feature Summary)
3.1 功能概述
- 功能名稱:Enterprise API v1(REST API for Job CRUD)
- 主要使用者角色:企業端 Owner / Admin(透過程式呼叫 API)、wport ops(後台核發/管理 key)
- 核心目標:
- 讓企業 HR 跳脫 wport 前端 UI 限制,透過程式化方式 batch 操作職缺(建立、編輯、上下架)
- 為後續 v2(前端自助申請 + 企業端整合 UI)、v3(CLI 工具)、未來 MCP server 打基礎
- 緊跟 AI 工具普及趨勢(Cursor / Claude)的整合需求
3.2 範圍界定
-
包含範圍(v1):
- REST API 端點:職缺 CRUD(含 batch)、上架/下架/刪除/關閉、列表查詢、單筆查詢
- 認證機制:Bearer API Key(company-scoped、hash 儲存)
- Quota:月度固定額度 + 分鐘級 rate limit + Idempotency-Key 必填(寫操作)
- Audit log:全量記錄(caller_company_id、acting_user_id、endpoint、ip、result、latency)
- Wport 後台 ops 介面:核發 / 撤銷 / Rotate key、查 usage、設企業專屬 quota override
- OpenAPI Spec:維護(為將來 SDK、MCP server 自動產出)
-
排除範圍(明確 v1 不做):
- 前端 Setting 頁面 API key 自助申請 UI(v2)
- 企業端 UI 內的 API 整合區塊(v2)
- CLI 工具產出(v3)
- 付費升級流程 / 計費整合(架構預留,UI 不做)
- 候選人 / 履歷相關 API(v1 收斂為職缺 CRUD;其他端點視 RD 時間納為 good-to-have)
- 全平台人才搜尋 API
- 應徵者列表 / 應徵狀態更新 API
- Webhook 推送
- IP allowlist
- Key 過期機制 / user 自助 rotate
- Test / Live key 分離
- MCP server 實作(v1 僅維護 OpenAPI Spec 為基礎)
3.3 成功條件(KPI,3 個月內)
- 申請 API key 的企業 ≥ 10 家
- 月 API call 量 ≥ 50,000 次
- 透過 API 建立的職缺 ≥ 500 筆
- API 5xx 錯誤率 < 0.5%
- API p95 latency < 500ms
3.4 設計決策依據(Design Rationale)
此節記錄 v1 主要設計選擇的「為什麼」,包含:選項對比、借鏡的主流 SaaS、以及 wport(人力銀行 / B2B / HR 用戶)情境下的判斷。 目的:讓 RD、未來 PM、QA、稽核人員讀 PRD 時不只看見規則,也理解規則的權衡來由。
DR-1:認證機制 → Bearer API Key(非 OAuth)
- 選項:A. Bearer API Key(簡單 token) / B. OAuth 2.0 Client Credentials / C. mTLS
- 採用:A
- SaaS 對標:Stripe(
Authorization: Bearer sk_live_...)、Anthropic API、OpenAI API、Shopify Admin API - wport context:v1 用戶是 HR + AI 工具(Cursor / Claude),不是企業 IT 工程師。Bearer key 在 curl / Python requests / Cursor agent 中最直觀;OAuth 需要 token refresh 邏輯,對非工程師用戶過度複雜。v2 高級企業客戶若需 SSO 整合,再升 OAuth。
DR-2:Key 綁定層級 → (user_id, company_id) 雙綁定
- 選項:A. company-scoped(一公司一把) / B. user-scoped(個人 token) / C. (user, company) 雙綁定 / D. token-per-app(OAuth 風)
- 採用:C
- SaaS 對標:
- GitHub PAT(最直接對標):每個 user 在 organization context 下產 token,org admin 可看到組織內所有人的 token policy 並撤銷
- Linear Personal API Key:user-level 但 workspace admin 可見
- Slack User Token vs Workspace Token:個人 token 仍歸屬於 workspace
- wport context:統一帳號制度下一個 user 可能屬於多家公司;單純 user-scoped 無法表達「我用 A 公司身份操作」;單純 company-scoped 失去個人課責(key 外洩追不到人)。雙綁定同時滿足:個人歸屬(audit)+ 企業課責(usage 共享、Admin 可撤銷)。
DR-3:每 user 每公司 1 把 key(經 Eric 確認)
- 選項:A. 1 把 / B. 3 把(dev/prod/CI 分流) / C. 5 把 / D. 無上限
- 採用:A
- SaaS 對標:
- Stripe restricted keys 允許多把(但用戶是工程師)
- GitHub PAT 預設無上限(用戶是工程師)
- Linear、Notion personal token 預設 1 把(用戶是 PM/設計師等非工程師)
- wport context:v1 主要用戶為 HR(非工程師),每人一個工作流(接 Cursor / Claude / 自家 ATS),不需要 dev mindset 的多 key 分流。
- 重要對話記錄(2026/05/13,Eric & PM Claude):
「就一家公司的 hr 而言,不是應該是一家公司給予自己的就一把 key 就好?」
- 原 PRD 提案「3 把/user」沿用工程師預設,被指出不符合 HR 用戶心智。
- 結論:v1 採 1 把/user/company,需要重發者用 Rotate(舊 key 失效、發新 key)。
- 延伸路徑:若 v2 後實際使用發現 HR 需區分 AI 工具 / ATS 整合 / 個人腳本,再放寬上限。
DR-4:申請角色 → Owner / Admin / Manager 皆可(Q1 = B,經 Eric 確認)
- 選項:A. 僅 Owner/Admin / B. Owner/Admin/Manager 皆可 / C. 所有成員(含 Member)
- 採用:B
- SaaS 對標:
- GitHub:org member 都能建 PAT,org owner 用 policy 限制 scope,不限制建立行為
- Linear:workspace member 都能建 personal API key
- wport context:BR-016 規定 Manager 可建立 / 編輯 / 發布職缺;既然能用 UI 發職缺,沒有理由禁止透過 API 做同樣的事。Member 仍排除(無職缺操作權)。
- 未來延伸(v2):可在公司 settings 加
allow_manager_applyswitch 讓嚴格管控的企業關閉 Manager 自助申請;v1 預設全開(簡化)。
DR-5:Key 上限以 user×company 計算(取代「每公司硬上限」)
- 舊設計問題:原 PRD「單公司 5 把」對 10 人 HR 團隊不夠(10 × 1 = 10 > 5)
- 新規則:每 (user, company) 1 把 → 公司總 key 數隨成員自然成長,無硬上限
- Admin 控制權:Owner / Admin 可(v1 透過 ops、v2 透過前端)看到公司下所有人的 key 並一鍵撤銷 → 此即 governance 控制點
- SaaS 對標:GitHub Organization PAT Policy:org 不限總數,但 admin 可審批 / 撤銷
DR-6:Key 命名前綴 wpk_live_
- 採用:
wpk_live_<32 chars> - SaaS 對標:Stripe
sk_live_/pk_live_/rk_live_ - wport context:前綴 = secret scanning 入口(GitHub secret scanning、GitGuardian 可加 wport 規則自動偵測 push 到 git 的 key);明確區隔 live / test(v2 加 test)。
DR-7:Idempotency-Key 強制必填於寫操作
- 採用:必填於 POST / PATCH / DELETE,TTL 24 小時
- SaaS 對標:Stripe(同設計,TTL 24h)
- wport context:HR batch 上傳 100 筆職缺,網路抖動 / 程式 bug 重試最常見;不強制必填會造成重複建職缺,事後清理痛苦。
DR-8:Quota 共享於 company 層級(不分 user)
- 採用:5000 calls/month per company(usage 跨該公司所有 user × key 加總)
- SaaS 對標:Stripe(org-level pricing)、Linear(workspace-level seat & API limit)
- wport context:付費未來會接 company-level plan(企業付費),usage 必須 company-level 才能對齊計費單位。
DR-9:v1 排除 candidate / 履歷 API
- 排除理由:v1 收斂為「職缺 CRUD」可在 1 個 sprint 完成 MVP;候選人 API 涉及大量 BR-017 細節(目前 BR-017 標「待確認」),先別開
- SaaS 對標:OpenAI 初期僅開 completion,再逐步加 fine-tune / embeddings — 漸進開放
- 後續:v1.1 補完 BR-017 細節後可加 candidate read API
DR-10:v1 不做前端 UI,ops 人工核發
- 採用:v1 申請流程 = user email 給 wport → ops 後台核發 → 寄信
- SaaS 對標:
- OpenAI GPT-4 API 初期:waitlist + 人工核發
- Anthropic API 初期:design partner 邀請制
- wport context:v1 預期 < 50 把 key,人工成本可接受;早期需要 ops vet 用戶、收集反饋。v2 才上自助 UI。
DR-11:Batch 採 Partial Success
- SaaS 對標:Shopify Admin API、Slack
chat.scheduleMessagebatch、HubSpot batch endpoints - wport context:HR batch 100 筆,1 筆格式錯不該整批 rollback(重試成本、quota 浪費)
DR-12:維護 OpenAPI Spec(v1 即做)
- 採用:v1 必須維護
openapi.yaml - SaaS 對標:Stripe、Twilio、GitHub 都以 OpenAPI 為 SDK / 文件 / 自動化基礎
- wport context:v2 SDK、v3 CLI、未來 MCP server 全依賴此 spec;若 v1 不做,技術債在 v2 爆發
4. 商業規則對齊(Business Rules Alignment)
4.1 本功能使用到的業務規則
| 規則 ID | 規則摘要 | 是否關鍵 | 在本 PRD 的對應 |
|---|---|---|---|
| BR-001 | email 驗證後才能登入 | ✅ 是 | 申請者 email 必須已驗證 |
| BR-005 | 公司未驗證可建職缺但無法上架 | ✅ 是 | API 可建 draft,publish endpoint 強制檢查公司驗證狀態 |
| BR-008 | 職缺發布必填欄位 | ✅ 是 | API publish 強制檢查同一組必填欄位 |
| BR-009 | 職缺狀態機(含終態不可逆) | ✅ 是 | API 操作對齊狀態機;deleted/closed 為終態 |
| BR-016 | 角色權限矩陣(Owner/Admin/Manager/Member) | ✅ 是 | 僅 Owner/Admin 可申請 + 使用 API |
| BR-017 | 資料存取控制 | ✅ 是 | API caller 僅可操作所屬公司資源 |
| BR-018 | 電子郵件發送規則 | ⚠️ 補充 | 本 PRD 提案新增通知類型:API key 核發 / 撤銷 / Rotate 通知信 |
| BR-022 | 每帳號最多建立 10 間公司 | ➖ 相關 | API key 為 company-scoped,多公司需各自申請 key |
| 公司職缺上限 800 | (BR-005 區塊註記) | ✅ 是 | API 達 800 上限 → 429 jobs_quota_exceeded |
4.2 補充說明 / 新規則提案
- BR-018 提案補充:新增以下 email 通知類型(待落地後正式回寫
business-rules.md):enterprise_api_key_issued:核發 key(含明文 key 一次性顯示,CTA:開發者文件連結)enterprise_api_key_revoked:撤銷 key 通知enterprise_api_key_rotated:Rotate key 通知(含新 key 明文一次性顯示)enterprise_api_quota_warning:[v2] 達 80% quota 通知(v1 不做)
- BR-020「資料匯出規則」釐清:BR-020 為「使用者個資匯出(GDPR 等)」用途,與本 Enterprise API 無關。Coda 原命名「資料匯出 API」屬語意混淆,已於 Stage 9 改名。
- 企業職缺 quota 透通:v1 沿用既有 800 上限,不在 API quota 中重新計算。
- 角色限制(新):v1 開放 Owner / Admin / Manager 申請與使用;Member 不可申請。
- 理由(見 DR-4):BR-016 規定 Manager 可建立 / 編輯 / 發布職缺;既然 Manager 能用 UI 發職缺,沒有理由禁止透過 API 做同樣事。Member 無職缺操作權,故排除。
- v2 延伸:未來可加
allow_manager_applyper-company policy switch,讓嚴格管控企業關閉 Manager 自助申請。
- Key 綁定模型(新,見 DR-2、DR-3、DR-5):API key 為 (user_id, company_id) 雙綁定;每 user 在每家公司最多 1 把 active key;公司總 key 數無硬上限(自然隨成員數成長);Owner / Admin 對公司下所有人的 key 有撤銷權。
5. 資料實體與欄位(Data Entities & Fields)
注意:本節描述的是「需要追蹤 / 儲存的資訊」,不是 API payload schema。
API 的具體欄位名稱、JSON 結構、optional/required 等由 OpenAPI Spec 決定(RD 責任)。
5.1 實體列表與需求
| 實體 | 用途 | 必須追蹤的資訊 |
|---|---|---|
| ApiKey | 企業端認證憑證 | 綁定關係(user + company)、金鑰內容、狀態、時間戳記、使用記錄 |
| ApiUsage | 月度 quota 管理 | 公司、月度週期、已用額度、設定上限、重置時間 |
| ApiAuditLog | 稽核與監控 | 金鑰 ID、公司、呼叫者(user)、HTTP method、endpoint、IP、status code、錯誤碼、延遲、時間戳記 |
| IdempotencyRecord | 寫操作去重 | idempotency key、請求指紋、回應、過期時間(24h) |
| Job | 職缺(既有) | API 操作時須檢查:company_id、status(狀態機)、updated_at(樂觀鎖)、必填欄位集合 |
5.2 實體資料需求說明
ApiKey
為什麼需要:認證 + 課責
必須儲存:
- (user_id, company_id) 雙綁定(DR-2):支持個人課責 + 企業管理
- Key 狀態:pending → active → revoked / rejected(狀態機,§10.3)
- 時間軸:建立時間、啟用時間、撤銷時間
- 使用履歷:最後使用時間(用於監控活躍度)
- 唯一性約束:每 (user, company) 最多 1 把 active key(DR-3)
- Rotate 行為:舊 key → revoked;新 key → active
注意:
- 金鑰本身只需儲存 hash(+ salt;演算法由 RD 決定,見 §15)
- 明文僅核發信中一次性顯示
- 前綴
wpk_live_用於識別(不需特別儲存字首,生成時包含)
ApiUsage
為什麼需要:計費 + 限流
必須追蹤:
- Quota 聚合層級:公司(不分 user),跨該公司所有 key 加總(DR-8)
- 月度週期:YYYY-MM 格式(UTC+8 時區)
- 已用額度:本月累計 API call 次數
- 設定上限:預設 5000;ops 可 override per-company(§8.5、DR-8)
- 重置時間:下月 1 日起算
注意:
- 與既有公司職缺上限(800)分開追蹤;不衝突
ApiAuditLog
為什麼需要:稽核 + 監控 + 故障排查
必須記錄:
- 身份:api_key_id、company_id、acting_user_id(key 申請者)
- 請求:HTTP method、endpoint、請求 ID、idempotency key
- 網路:caller IP、user agent
- 回應:HTTP status code、錯誤碼、端到端延遲(ms)
- 時間:API 呼叫時間戳記
注意:
- 異步寫入(不阻擋 API 回應)
- 保留 365 天(§8.5)
- 不在 log 中明文記錄 key(§12.3)
IdempotencyRecord
為什麼需要:寫操作去重
必須儲存:
- Idempotency-Key:client 提供的去重識別符
- 請求指紋:method + endpoint + payload hash(識別相同請求)
- 快取內容:原始回應(status code + body)
- 過期策略:24 小時後自動清理(DR-7)
注意:
- Scope:per api_key_id(不同 key 的相同 key 字串視為不同)
- 儲存位置自由(Redis 或 DB;由 RD 決定)
Job(既有,API 操作涉及的欄位)
API 需要檢查的:
- 所有權:company_id 必須 = caller key 的 company_id(§7.1 ACT-5~9)
- 必填欄位:發布時需檢查對齊 BR-008 規定的集合
- 樂觀鎖:更新時需對比 updated_at / version(§10.5 並發更新)
- 狀態機:操作需對齊 BR-009(§10.4)
注意:
- Job 的欄位結構由既有 schema 決定,API 不改變;本 PRD 只列出 API 操作時關鍵的檢查點
6. 畫面區塊與資料需求(UI Sections & Data Needs)
v1 無 wport 企業端 UI;本節改為描述 wport ops 後台介面 與 API caller 視角的「行為輸入輸出」。
6.1 區塊列表
| 區塊 ID | 區塊名稱 | 說明 | 屬於 |
|---|---|---|---|
| OPS-1 | API Key 申請佇列 | 顯示 pending 申請列表 | ops 後台 |
| OPS-2 | API Key 管理 | 列出所有 key、可核發 / 撤銷 / Rotate / 改名 | ops 後台 |
| OPS-3 | Usage 監控 | 每企業月度用量、超限警示 | ops 後台 |
| OPS-4 | Quota 設定 | 設定企業專屬 quota override | ops 後台 |
| OPS-5 | Audit Log 檢視 | 查詢稽核日誌(依 company / 時間 / endpoint) | ops 後台 |
6.2 區塊資料需求
OPS-1:申請佇列
- 顯示實體:
ApiKey[]wherestatus='pending' - 篩選:公司名稱、申請時間
- 操作:核發(自動觸發郵件)、駁回(含理由)
OPS-2:Key 管理
- 顯示:
ApiKey[]全狀態 - 列表欄位:company_name、user_email(key 申請者)、key_prefix、status、created_at、last_used_at
- 篩選:依公司、依 user、依狀態
- 操作:撤銷、Rotate(產生新 key 並使舊 key 失效)、改名
- Admin 視角預備(v2):v2 前端 UI 啟用時,Owner / Admin 可在公司 settings 看到此公司下所有 user 的 key 並撤銷;v1 透過 ops 代為操作
OPS-3:Usage 監控
- 顯示:當月
ApiUsage[]排行 - 警示:使用率 ≥ 80% / 100%
- 排序:依使用率 DESC
OPS-4:Quota 設定
- 預設值:5000 calls/month
- override 機制:per-company 設定,後續可 per-key
OPS-5:Audit Log
- 篩選:company、api_key、endpoint、time range、status_code、error_code
- 分頁:每頁 100 筆
- 匯出:CSV(ops 內部使用,不對外)
7. 使用者動作與後端需求(User Actions & Backend Needs)
7.1 企業端(API caller)
| 動作 ID | 動作 | 類型 | 需要後端 | 涉及實體 | 說明 |
|---|---|---|---|---|---|
| ACT-1 | 建立單筆職缺 | Write | ✅ | Job | 必填驗證、寫入 |
| ACT-2 | Batch 建立職缺 | Write | ✅ | Job | 單次 ≤ 100 筆、partial success |
| ACT-3 | 列出自家職缺 | Read | ✅ | Job | pagination + 篩選 |
| ACT-4 | 取得單筆職缺 | Read | ✅ | Job | by enc_id |
| ACT-5 | 更新職缺 | Write | ✅ | Job | optimistic lock |
| ACT-6 | 上架職缺 | Write | ✅ | Job | 強制驗證 BR-005、BR-008 |
| ACT-7 | 下架職缺 | Write | ✅ | Job | 狀態轉換 |
| ACT-8 | 刪除職缺 | Write | ✅ | Job | 進入終態 |
| ACT-9 | 關閉職缺 | Write | ✅ | Job | 進入終態 |
7.2 Ops 後台
| 動作 ID | 動作 | 類型 | 需要後端 | 涉及實體 |
|---|---|---|---|---|
| OPS-ACT-1 | 核發 key | Write | ✅ | ApiKey |
| OPS-ACT-2 | 駁回申請 | Write | ✅ | ApiKey |
| OPS-ACT-3 | 撤銷 key | Write | ✅ | ApiKey |
| OPS-ACT-4 | Rotate key | Write | ✅ | ApiKey |
| OPS-ACT-5 | 設定 quota override | Write | ✅ | ApiUsage |
| OPS-ACT-6 | 查 audit log | Read | ✅ | ApiAuditLog |
8. API Hints(提示用,非最終 API 設計)
URL path、HTTP status code、payload schema 由後端最終決定。
8.1 認證
Authorization: Bearer wpk_live_<32 chars>- 必填 headers:
Authorization、Idempotency-Key(POST/PATCH/DELETE 必填)、X-Request-ID(選填,建議 client 提供 UUID) - 回應 headers:
X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset、X-Request-ID
8.2 統一錯誤格式(建議參考 RFC 7807 簡化版)
{
"error": {
"code": "company_not_verified",
"message": "Company verification is required before publishing jobs.",
"details": { "company_id": 123, "verification_status": "pending" },
"request_id": "req_abc123"
}
}
8.3 資料讀取需求(Read)
- HINT-GET-1:列出自家職缺
- 輸入:
page/cursor、status?、updated_after?、limit?(預設 20,max 100) - 對應動作:ACT-3
- 輸入:
- HINT-GET-2:取得單筆職缺
- 輸入:
enc_id - 對應動作:ACT-4
- 輸入:
8.4 資料寫入需求(Write)
- HINT-MUTATION-1:建立職缺(單筆 / batch)
- 輸入:Job payload(必填欄位對齊 BR-008)
- Idempotency-Key 必填
- 行為:建立為 draft;公司已驗證且帶
publish=true參數可直接上架(或拆開 endpoint,[RD 決定])
- HINT-MUTATION-2:更新職缺
- 輸入:
enc_id、partial payload、If-Match: <updated_at>或 version
- 輸入:
- HINT-MUTATION-3:狀態轉換(publish / unpublish / close / delete)
- 輸入:
enc_id - 行為:對齊 BR-009 狀態機
- 輸入:
- HINT-MUTATION-4:Batch 建立
- 輸入:
{ jobs: Job[] }單次 ≤ 100 - 回應:partial success(succeeded[] + failed[])
- 輸入:
8.5 Quota / Rate Limit 設定(建議值,[RD 決定])
| 維度 | 預設值 | 可 override |
|---|---|---|
| 月度 quota | 5000 calls / month per company(usage 跨該公司所有 user 加總,DR-8) | per-company override |
| 分鐘級 rate limit | 60 calls / minute | per-key |
| Batch 單次上限 | 100 筆 | 全域 |
| Idempotency TTL | 24 小時 | 全域 |
| Audit log 保留 | 365 天 | 全域 |
| 每 (user, company) active key | 1 把(DR-3) | 無 override(v1) |
| 公司總 key 數 | 無硬上限(自然隨成員增長,DR-5) | — |
8.6 OpenAPI Spec 維護要求
- v1 必須維護
openapi.yaml,作為:- 文件來源(自動產 reference docs)
- 將來 SDK 生成(TypeScript / Python)
- 將來 MCP server 自動橋接基礎
- 路徑:
[RD 決定],建議/api/v1/enterprise/openapi.yaml公開
9. 導航 / Storybook Map
- Storybook 標題:N/A(v1 無前端 UI)
- Stories 路徑:N/A
- Storybook 連結:N/A
- 情境控制:N/A
- 對應 production 路由:
- 企業 API:
/api/v1/enterprise/*(namespace 獨立) - Ops 後台:既有後台路由擴充([RD/ops 決定])
- 企業 API:
10. Flowcharts(User / System / State Machines)
10.1 User Flow(含 ops 流程)
flowchart TD
Start([HR / 企業成員想用 API]) --> Apply[user 個人 email 申請<br/>註明:以 X 公司 Y 角色申請]
Apply --> OpsRecv{ops 收件並檢核}
OpsRecv -- 公司未驗證 --> Reject1[寄駁回信<br/>引導完成公司驗證]
OpsRecv -- 申請者角色非 O/A/M --> Reject2[寄駁回信<br/>需 Owner/Admin/Manager 申請]
OpsRecv -- email 未驗證 --> Reject3[寄駁回信<br/>需先完成 email 驗證]
OpsRecv -- 該 user×company 已有 active key --> Reject4[寄信提示已有 key<br/>建議聯繫 Rotate]
OpsRecv -- 全部符合 --> Issue[ops 後台核發 key<br/>系統寄發明文 key 信件給該 user]
Reject1 --> End1([結束])
Reject2 --> End1
Reject3 --> End1
Reject4 --> End1
Issue --> Use[user 使用 key 呼叫 API]
Use --> Op{API 操作類型}
Op -- 建立職缺 --> CreateFlow[必填驗證 → 寫入 DB]
Op -- 上架職缺 --> PublishFlow{公司已驗證?}
PublishFlow -- 否 --> PublishFail[403 company_not_verified]
PublishFlow -- 是 --> PublishOk[狀態 → published]
Op -- 其他 CRUD --> OtherFlow[依資源狀態決定]
CreateFlow --> Done([API 回應])
PublishOk --> Done
PublishFail --> Done
OtherFlow --> Done
subgraph 異常路徑
Quota[公司達月度 quota<br/>跨該公司所有 user 加總] --> Q429[429 quota_exceeded]
Revoke[ops / Admin 撤銷 key] --> R401[後續 call 401]
end
10.2 System Flow(API call lifecycle)
sequenceDiagram
participant C as Client (企業)
participant GW as API Gateway
participant Auth as Auth Service
participant RL as Rate Limiter (Redis)
participant Idem as Idempotency Store
participant H as Handler (Job Service)
participant DB as DB
participant Audit as Audit Log (async)
C->>GW: POST /api/v1/enterprise/jobs<br/>Authorization: Bearer wpk_live_xxx<br/>Idempotency-Key: uuid
GW->>Auth: 驗證 key (hash 比對)
alt key invalid / revoked
Auth-->>GW: 401
GW-->>C: 401 invalid_api_key
GW->>Audit: log (async)
else valid
Auth-->>GW: company_id + scopes
end
GW->>RL: 檢查 quota & rate limit
alt 超限
RL-->>GW: 429 + reset_at
GW-->>C: 429 quota_exceeded
GW->>Audit: log (async)
else 通過
RL-->>GW: ok
end
GW->>Idem: 查 Idempotency-Key
alt 已存在 (重放)
Idem-->>GW: cached response
GW-->>C: 200 (原回應)
else 不存在
Idem-->>GW: not found
GW->>H: 執行業務邏輯
H->>DB: 寫入 (transaction)
DB-->>H: ok
H-->>GW: result
GW->>Idem: cache response (TTL 24h)
GW->>RL: usage += 1 (atomic)
GW-->>C: 200 / 201
GW->>Audit: log (async)
end
10.3 API Key 狀態機
stateDiagram-v2
[*] --> Pending: 企業申請
Pending --> Active: ops 核發
Pending --> Rejected: ops 駁回
Active --> Revoked: ops 撤銷 / Rotate
Rejected --> [*]
Revoked --> [*]
note right of Active: 永久有效直到 revoke<br/>v1 無 user-side rotate
note right of Revoked: 終態
10.4 職缺狀態機(API 視角,對齊 BR-009)
stateDiagram-v2
[*] --> Draft: POST jobs
Draft --> Published: PATCH publish<br/>(必填齊 + 公司驗證)
Published --> Unpublished: PATCH unpublish
Unpublished --> Published: PATCH publish
Draft --> Deleted: DELETE
Published --> Deleted: DELETE
Unpublished --> Deleted: DELETE
Published --> Closed: PATCH close
Unpublished --> Closed: PATCH close
Deleted --> [*]: 終態
Closed --> [*]: 終態
10.5 Edge Case Coverage(必填)
| 類別 | 情境 | 系統行為 | API 回應 | 可重試 | 資料一致性策略 | 備註 |
|---|---|---|---|---|---|---|
| Network & Performance | API call 逾時 > 30s | gateway 切斷、不寫入 | 504 gateway_timeout | Yes(搭 Idempotency-Key) | 寫操作未提交 → 0 副作用 | |
| Network & Performance | 高延遲 batch | 同步至完成或 60s 上限 | 200 partial / 504 | Yes | 成功筆數已落地,失敗可重送 | |
| Network & Performance | 重複提交(race) | Idempotency-Key 去重 | 重放回原 response | N/A | 同 fingerprint 視為同一筆 | |
| Data & Input | 必填欄位缺漏 | BE 驗證拒絕 | 400 missing_required_field + field | N/A | 不寫入 | |
| Data & Input | 欄位超長 / 格式錯 | BE 驗證拒絕 | 422 invalid_value + field + reason | N/A | 不寫入 | |
| Data & Input | Batch 超過 100 筆 | gateway 拒絕 | 413 payload_too_large | N/A | 不寫入 | |
| Data & Input | JSON 解析失敗 | gateway 拒絕 | 400 invalid_json | N/A | 不寫入 | |
| User Interruption | Client 中途斷線 | BE 可能已提交;client 重連用 Idempotency-Key 安全重送 | — | Yes | Idempotency 確保 0 副作用 | |
| User Interruption | 瀏覽器關閉 | N/A | — | — | — | 純 API 場景無瀏覽器 |
| User Interruption | 重新整理 | N/A | — | — | — | 純 API 場景 |
| Auth & Lifecycle | Key 過期 / 撤銷中途 | 進行中 call 完成、下一個 401 | 401 revoked_api_key | No(需重新申請) | 已完成 call 結果保留 | |
| Auth & Lifecycle | 公司被停用 | 立即拒絕後續 call | 403 company_suspended | No | — | |
| Auth & Lifecycle | 並發更新同職缺 | optimistic lock (updated_at / version) | 409 conflict + 最新資料 | Yes(client 解決後) | 後到失敗、不覆寫 | |
| Logical Inconsistency | Batch partial success | 部分成功部分失敗 | 200 + succeeded[] + failed[] | 失敗項 Yes | 成功項已落地、失敗項可重送 | |
| Logical Inconsistency | 公司未驗證卻 publish | 拒絕;草稿保留 | 403 company_not_verified | Yes(驗證後) | 草稿仍存在 | |
| Logical Inconsistency | 終態職缺被改 / 上下架 | 拒絕 | 409 invalid_state_transition | No | — | BR-009 |
| Logical Inconsistency | Idempotency-Key 衝突 | 同 key 不同 payload | 409 idempotency_conflict | No(換 key 或修 payload) | — | |
| Logical Inconsistency | 越權操作 | resource.company_id ≠ key.company_id | 403 forbidden(write)/ 404(read) | No | — | 不洩漏資源存在性(read) |
11. Mock Data Schema
- 資料來源:v1 無前端 UI,無 mock;後端直接對接既有 Job service + 新增 ApiKey / ApiUsage / ApiAuditLog 表
- CRUD 行為:透過真實 API 操作真實 DB;ops 後台亦操作真實資料
- 情境切換:N/A
12. 實作備註(Implementation Notes)
12.1 後端必做事項
- API namespace 獨立:
/api/v1/enterprise/* - 重用既有 Job service / 公司驗證 service,不雙寫
- API key 一律 hash + per-key salt 儲存;明文僅核發信中一次性顯示
- Rate limiter 建議 Redis sliding window;DB counter 為退路(注意競態)
- Audit log 異步寫入(建議 queue),不阻塞 API 回應;保留 365 天
- OpenAPI Spec 必須維護(為 v2/v3、SDK、未來 MCP 鋪路)
- 預留付費擴充:DB schema 設計時保留
plan_id、quota_override_reason、paid_quota_addon等欄位(不實作 UI)
12.2 後端可決定事項(標註 [RD 決定])
- 具體 endpoint URL 命名(如
POST /api/v1/enterprise/jobsvsPOST /api/v1/enterprise/job/create) - Token 編碼(opaque random vs JWT);hash 演算法(SHA-256 建議)
- Rate limiter 實作(Redis vs DB)
- Pagination 機制(cursor 建議 vs offset 可接受)
- Publish 是否拆獨立 endpoint(
PATCH /jobs/{id}/publishvsPOST /jobs?publish=true) - v1 是否一併支援 good-to-have endpoint(應徵者列表、人才搜尋等)
- Idempotency cache 後端選擇(Redis vs DB)
12.3 禁止事項
- ❌ 不直接修改
business-rules.md;新規則先於本 PRD 描述 - ❌ 不對外洩漏 candidate PII 超出既有 UI 範圍
- ❌ 不在 v1 做付費 UI / 自助申請 UI / CLI
- ❌ 不在 logs 中明文記錄 API key
12.4 對齊 SaaS 業界實踐
- Key 命名
wpk_live_<32>:借鏡 Stripesk_live_,前綴利於 secret scanning(如 GitHub secret scanning 對接) - Idempotency-Key + 24h TTL:借鏡 Stripe
- Partial success batch:借鏡 Shopify / Slack
- Rate limit header(
X-RateLimit-Remaining等):借鏡 GitHub - OpenAPI Spec 維護:借鏡 Stripe / Twilio(SDK 自動產出基礎)
13. 四大一致性表格(v1 凍結)
13.1 欄位對照表
| 欄位 | 型別 | 用途 | SoT | 版本 |
|---|---|---|---|---|
api_key.enc_id | string | 對外 ID | PRD(本 PRD) | v1 |
api_key.company_id | int | 綁定公司(雙綁定) | PRD §5.2、DR-2 | v1 |
api_key.user_id | int | 綁定 user(雙綁定) | PRD §5.2、DR-2 | v1 |
api_key.key_prefix | string wpk_live_ | 識別 + scanning | PRD、DR-6 | v1 |
api_key.status | enum | 狀態 | PRD(本 PRD §5.2) | v1 |
api_usage.period | string YYYY-MM | 月度聚合 | PRD | v1 |
api_usage.limit | int(預設 5000) | 月額度(company-level,DR-8) | PRD(可由 ops override) | v1 |
job.status | enum | 職缺狀態 | business-rules.md BR-009 | 既有 |
job.* 必填集 | — | 上架條件 | business-rules.md BR-008 | 既有 |
13.2 常數表
| 常數 | 值 | 來源 | 版本 |
|---|---|---|---|
| 月度預設 quota(per company) | 5000 calls/month | PRD §8.5、DR-8 | v1 |
| 分鐘 rate limit | 60 calls/min | PRD §8.5 | v1 |
| Batch 上限 | 100 筆 | PRD §8.5、DR-11 | v1 |
| Idempotency TTL | 24 小時 | PRD §8.5、DR-7 | v1 |
| Audit log 保留 | 365 天 | PRD §8.5 | v1 |
| Key 命名前綴 | wpk_live_ | PRD §12.4、DR-6 | v1 |
| 每 (user, company) active key | 1 把 | PRD §8.5、DR-3 | v1 |
| 公司總 key 數 | 無硬上限 | PRD §8.5、DR-5 | v1 |
| 申請角色 | Owner / Admin / Manager | PRD §4.2、DR-4 | v1 |
| 公司職缺上限 | 800 | business-rules.md BR-005 區塊 | 既有 |
13.3 事件字典
| 事件名 | 觸發時機 | Payload 摘要 | 訂閱者 |
|---|---|---|---|
enterprise_api_key_issued | ops 核發 key | { company_id, api_key_id, plain_key(明文一次性)} | Email service |
enterprise_api_key_revoked | ops 撤銷 | { company_id, api_key_id, reason } | Email service |
enterprise_api_key_rotated | ops Rotate | { company_id, api_key_id, new_plain_key } | Email service |
enterprise_api_call_completed | API 完成(成功/失敗) | { company_id, api_key_id, endpoint, status, latency } | Audit log (async) |
enterprise_api_quota_warning | [v2] 達 80% | — | — |
13.4 術語字典
| 詞 | 定義 | 用法統一 |
|---|---|---|
| API Key | 企業端認證憑證;(user, company) 雙綁定(DR-2) | 不稱「Token」「Secret」「Credential」 |
| Quota | 月度可用 API call 額度 | 不稱「Limit」(避免與 rate limit 混淆) |
| Rate Limit | 分鐘級限流 | 不稱「Quota」 |
| Idempotency-Key | 重試去重識別 | 統一 header 命名 |
| Revoke | 撤銷 key | 不稱「Disable」「Delete」 |
| Rotate | 產生新 key 並使舊 key 失效 | 不稱「Refresh」「Regenerate」 |
| Enterprise API | 本功能對外名稱 | 不再稱「資料匯出 API」 |
14. 版本凍結表
| 項目 | v1 / v2 | Blocking? | 理由 | 決策者 | 日期 |
|---|---|---|---|---|---|
| REST API(職缺 CRUD) | v1 | ✅ Blocking | 核心 | Eric | 2026/05/13 |
| API Key 認證(Bearer) | v1 | ✅ Blocking | 核心 | Eric | 2026/05/13 |
| 後台核發 / 撤銷 / Rotate | v1 | ✅ Blocking | 無 UI 必要替代 | Eric | 2026/05/13 |
| Quota / Rate limit + 429 | v1 | ✅ Blocking | 防濫用 | Eric | 2026/05/13 |
| Idempotency-Key | v1 | ✅ Blocking | 防重複寫入 | Eric | 2026/05/13 |
| Audit log | v1 | ✅ Blocking | 法遵 / 監控 | Eric | 2026/05/13 |
| OpenAPI Spec 維護 | v1 | ✅ Blocking | 為 v2/v3 / MCP 鋪路 | Eric | 2026/05/13 |
| 應徵者 / 人才搜尋 API | v1(good-to-have) | ❌ Non-blocking | 後端視時間決定 | Eric + RD | 2026/05/13 |
| 前端 Setting 自助申請 | v2 | ❌ Non-blocking | 獨立 PRD | Eric | 2026/05/13 |
| 企業 UI 整合區塊 | v2 | ❌ Non-blocking | 獨立 PRD | Eric | 2026/05/13 |
| CLI 工具 | v3 | ❌ Non-blocking | 獨立 PRD | Eric | 2026/05/13 |
| MCP server 完整實作 | 未定 | ❌ Non-blocking | v1 僅維護 OpenAPI 為基礎 | Eric | 2026/05/13 |
| 付費 UI / 計費 | 未定 | ❌ Non-blocking | 架構預留 | Eric | 2026/05/13 |
| Webhook / IP allowlist / Test key | 未定 | ❌ Non-blocking | v2+ | Eric | 2026/05/13 |
15. Token / API 契約完整性(Hard Gate)
| 項目 | 規範 |
|---|---|
| TTL(key 有效期) | 永久有效直到主動 revoke(v1);v2 可加 expiry option |
| Revoke 政策 | ops 後台立即執行;下一個 call 401;進行中 call 不中斷 |
| Expiration 行為 | N/A(v1 無自動過期) |
| Idempotency | 必填於所有寫操作;TTL 24h;同 key 不同 payload → 409 conflict |
| 即時權限重檢 | 每次 call 重新驗證 key.status + company.status(從快取或 DB) |
| Hash 演算法 | SHA-256 + per-key salt(建議,[RD 最終決定]) |
| 明文顯示 | 僅核發信中一次性顯示;DB 不存明文 |
16. Sync-Fix List(跨產出物同步動作)
| 動作 | 物件 | Owner | Blocking? | 預定完成 |
|---|---|---|---|---|
更新 Coda backlog i-uL6dSrzo8o 名稱為「Enterprise API(v1:REST API for Job CRUD)」並回寫 PRD 欄位 | coda/coda_backlog.json | Eric | ✅ Blocking 上線 | Stage 9(本次 commit) |
| 提案補充 BR-018 通知類型(key 核發 / 撤銷 / Rotate) | business-rules.md | Eric | ⚠️ 本案落地後正式回寫 | 上線後 |
| 維護 OpenAPI Spec 並建立 reference docs site | RD | RD | ✅ Blocking v1 上線 | RD spec 階段 |
| 後台 ops 介面擴充(OPS-1 ~ OPS-5) | wport 後台 | ops + RD | ✅ Blocking v1 上線 | RD spec 階段 |
| Email template 建立(3 種新 type) | 通知服務 | RD | ✅ Blocking v1 上線 | RD spec 階段 |
Secret scanning rule(wpk_live_ 前綴) | GitHub / GitGuardian | DevOps | ❌ Non-blocking(建議) | 上線前 |
17. Email Template(必填)
17.1 Key 核發信
- 收件人:申請的 user(個人 email,非公司公用信箱)
- Subject:[wport] 您的 Enterprise API Key 已核發({{company_name}})
- Body 摘要:
- 招呼 + 您於 {{company_name}}(角色:{{role_name}})申請的 API Key 已核發
- 明文 API Key(僅此一次顯示):
wpk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - 安全提醒:請立即複製保存;wport 不會再次寄送;切勿提交至 git;外洩請立即聯繫客服 Revoke
- 提醒:此 key 綁定您個人 + {{company_name}} 身份;若離開公司,請主動聯繫 wport 撤銷
- CTA Label:「查看開發者文件」
- CTA URL:
https://docs.wport.me/enterprise-api/v1/getting-started([RD 決定最終 URL])
- Query/Token 替換:
{{user_name}}、{{company_name}}、{{role_name}}、{{plain_key}}、{{docs_url}}、{{contact_url}}
17.2 Key 撤銷信
- Subject:[wport] 您的 Enterprise API Key 已被撤銷
- Body 摘要:
- 招呼 + key name + 撤銷時間 + ops 填寫的撤銷理由
- 引導:如需繼續使用,請聯繫 wport 重新申請
- CTA Label:「聯繫客服」
- CTA URL:
https://wport.me/contact
- Query/Token 替換:
{{company_name}}、{{key_name}}、{{revoked_at}}、{{revoke_reason}}、{{contact_url}}
17.3 Key Rotate 信
- Subject:[wport] 您的 Enterprise API Key 已 Rotate
- Body 摘要:
- 招呼 + 舊 key name + 新 key 明文(一次性)
- 提醒:舊 key 已立即失效,請更新所有整合
- CTA Label:「查看開發者文件」
- CTA URL:同 17.1
- Query/Token 替換:同 17.1
18. PRD 問題清單
| # | PRD section / rule | Issue | Suggested fix | Category | Impact | SoT |
|---|---|---|---|---|---|---|
| 1 | §4.2 BR-018 提案 | 新增 3 種 email 通知類型尚未正式回寫 business-rules.md | 上線後由 PM 發起 BR 更新 PR | 規則漂移 | 中 | business-rules.md |
| 2 | §8.5 Quota 預設值 | 5000/月為建議值,需 RD 與 ops 確認生產環境承載 | RD spec 階段拍板,必要時於 ops 後台允許 override | 容量規劃 | 中 | PRD §8.5 |
| 3 | §7.1 ACT 範圍 | 應徵者 / 人才搜尋 API 列為 good-to-have,未明確 in/out | RD 在 spec 階段決定並回寫此 PRD「3.2 排除範圍」 | 範圍模糊 | 中 | PRD |
| 4 | §12.2 [RD 決定] 項 | 多個項目留給 RD 決定(URL 命名、Token 編碼等) | RD 在 spec 階段補齊;本 PRD 不阻擋 | 細節未拍板 | 低 | RD spec |
| 5 | §4.1 BR-022 | 多公司情境下 user 在多家公司分別申請 key 的 onboarding 文件未定義 | 文件側補充;不影響後端實作 | 文件 | 低 | docs.wport.me |
| 6 | §3.2 排除範圍 | 候選人 PII 範圍對齊「既有 UI 範圍」,但 BR-017 對 API 存取控制細節為「待確認」 | 後續開放應徵者 / 人才搜尋 API 時,須先補完 BR-017 細節並提案 | 規則缺口 | 中(未來 v1.x) | business-rules.md |
| 7 | §3.4 DR-3 | v1 預設「1 把/user/company」,若 HR 實際反饋需多把(如分流 AI 工具 / ATS / CI),需 v2 放寬上限 | 上線 3 個月後檢視;於 v2 PRD 重新討論 | 用戶體驗 | 低 | PRD v2 |
| 8 | §3.4 DR-4 | v1 預設開放 Manager 申請;若實際發現濫用,需在 v2 加 allow_manager_apply policy switch 給 Admin 收緊 | v2 PRD 一併設計 | 治理彈性 | 低 | PRD v2 |
19. 下一步(Next Steps)
- RD 接手撰寫 OpenAPI Spec + 後端實作規格
- Ops 規劃後台 OPS-1 ~ OPS-5 介面實作
- DevOps 設定 secret scanning rule(
wpk_live_前綴) - 上線前 PM 發起 BR-018 補充 PR(3 種新 email 通知類型)
- 上線後 3 個月檢視 KPI;達標後啟動 v2(前端自助申請 UI)PRD
**此次prd大部分由opus 4.7 hight所完成 文件結束