Đề Xuất Kỹ Thuật API

API Tuyển Dụng
RECR
Phiên bản1.4
Base URLhttps://api.example.com/v1
Hệ thốngHệ thống Quản lý Nhân sự
ModuleRECR - Tuyển dụng
Ngày2026-08-17
Tác giảNam Nguyen
SRS liên quanSRS Tuyển dụng

Mục lục

1. Tổng quan

1.1 Các yêu cầu được đề cập

Yêu cầu SRSEndpoint API
FR-003 Duyệt tin tuyển dụngGET /recr/jobs
FR-004 Nộp đơn ứng tuyểnPOST /recr/applications
FR-007 Tải lên hồ sơPOST /recr/candidate/resume
FR-008 Xem đơn ứng tuyểnGET /recr/hr/applications
FR-017 Đăng tin tuyển dụngPOST /recr/employer/jobs
FR-037 Quản lý hồ sơ ứng viênGET /recr/candidate/resumes, POST /recr/candidate/resume, DELETE /recr/candidate/resumes/:id
FR-038 Liên kết hồ sơ với đơn ứng tuyểnPOST /recr/applications, GET /recr/candidate/applications/:id/resume

1.2 Nguyên tắc API

1.3 Xác thực

Tất cả các endpoint đều yêu cầu JWT Bearer token trừ khi được đánh dấu là Công khai. JWT chứa vai trò cấp hệ thống của người dùng.

Authorization: Bearer <access_token>

// JWT Payload includes:
{
  "sub": "user_id",
  "email": "[email protected]",
  "role": "user" | "super_admin"
}

// Note: Organization-level roles (HR, Candidate, Employer)
// are managed within organizations, not in the system JWT.

2. Định dạng Phản hồi

2.1 Phản hồi thành công

{
  "success": true,
  "data": { ... },
  "meta": {
    "requestId": "req-abc-123",
    "timestamp": "2026-07-31T10:30:00Z"
  }
}

2.2 Phản hồi lỗi

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      { "field": "email", "message": "Invalid email format" }
    ]
  }
}

3. Mã lỗi

Mã HTTPMã lỗiMô tả
400VALIDATION_ERRORKiểm tra yêu cầu không thành công
400INVALID_ROLEVai trò không hợp lệ cho tự đăng ký (super_admin phải được tạo bởi quản trị viên)
401UNAUTHORIZEDThiếu hoặc token không hợp lệ
403FORBIDDENKhông đủ quyền cho vai trò
404NOT_FOUNDKhông tìm thấy tài nguyên
409EMAIL_EXISTSEmail đã được đăng ký (từ module AUTH)
409APPLICATION_EXISTSỨng viên đã nộp đơn cho vị trí này
409RESUME_LINKEDKhông thể xóa hồ sơ đã liên kết với đơn ứng tuyển đã nộp
400INVALID_FILEKhông tìm thấy tệp trong module FILE hoặc tệp không thuộc danh mục RESUME
400INVALID_RESUME_FILEfile_id của hồ sơ không tồn tại hoặc không thuộc về ứng viên
400INVALID_FILE_CATEGORYChỉ các tệp danh mục RESUME mới có thể liên kết với đơn ứng tuyển
400FILE_TOO_LARGEKích thước tệp vượt quá giới hạn 10MB
422INVALID_STATUS_TRANSITIONThay đổi trạng thái không hợp lệ
429RATE_LIMITEDQuá nhiều yêu cầu
500INTERNAL_ERRORLỗi máy chủ
503SERVICE_UNAVAILABLEKhông thể truy cập module AUTH hoặc dịch vụ downstream
409INVITE_EXISTSLời mời đang chờ xử lý đã tồn tại cho email+nhà tuyển dụng
400INVITE_EXPIREDToken lời mời đã hết hạn
400INVITE_ALREADY_USEDLời mời đã được chấp nhận
400INVITE_REVOKEDLời mời đã bị thu hồi
400CANNOT_INVITE_SELFKhông thể gửi lời mời cho chính mình
400INVITE_NOT_PENDINGChỉ các lời mời đang CHỜ XỬ LÝ mới có thể bị thu hồi
400INVITE_NOT_RESENDABLEChỉ các lời mời ĐÃ HẾT HẠN hoặc ĐÃ THU HỒI mới có thể gửi lại
400EMAIL_MISMATCHEmail người dùng đăng nhập không khớp với lời mời

4. Giới hạn tốc độ yêu cầu

Loại EndpointYêu cầu/phútBurst
Endpoint xác thực105
Endpoint ứng viên (vai trò tổ chức)6020
Endpoint HR (vai trò tổ chức)10030
Endpoint nhà tuyển dụng (vai trò tổ chức)6020

5. Các Endpoint

5.2 Endpoint cho Ứng viên

Lưu ý: Các endpoint này yêu cầu vai trò tổ chức Ứng viên trong ngữ cảnh tổ chức đang hoạt động. Vai trò Ứng viên được gán bởi chủ/quản trị viên tổ chức, không phải khi đăng ký hệ thống.

GET /recr/jobs FR-003

Duyệt danh sách tin tuyển dụng. Endpoint công khai (không yêu cầu xác thực).

Tham số truy vấn: search, location, salary_min, employment_type, page, limit

POST /recr/applications FR-004, FR-038

Nộp đơn ứng tuyển. Yêu cầu vai trò candidate.

Phụ thuộc liên module: ApplicationService gọi FileService (FILE) để xác minh resume_file_id tồn tại và là tệp danh mục RESUME. resume_file_id được đóng băng (sao chép) vào bản ghi đơn ứng tuyển tại thời điểm nộp.

Tóm tắt luồng

  1. Frontend gửi đơn ứng tuyển với job_id, cover_letter và resume_file_id (hoặc resume_id từ danh sách hồ sơ của ứng viên)
  2. ApplicationService kiểm tra ứng viên tồn tại và chưa nộp đơn cho vị trí này
  3. ApplicationService gọi FileService (FILE) để xác minh resume_file_id tồn tại và có thể truy cập bởi ứng viên
  4. ApplicationService tạo bản ghi đơn ứng tuyển với resume_file_id đã đóng băng
  5. ApplicationService phát sự kiện application.submitted
  6. ApplicationService trả về 201 Created với chi tiết đơn ứng tuyển

Request Body

TrườngKiểuBắt buộcKiểm traMô tả
job_idintegerID vị trí hợp lệ, vị trí phải đang mởVị trí tuyển dụng mục tiêu
cover_letterstringKhôngTối đa 5000 ký tựNội dung thư xin việc
resume_file_idstring (UUID)Phải tồn tại trong module FILE, phải là danh mục RESUME, phải thuộc về ứng viênID tệp từ module FILE (được đóng băng khi nộp)
{
  "job_id": 1,
  "cover_letter": "I am interested in this position...",
  "resume_file_id": "550e8400-e29b-41d4-a716-446655440000"
}

Phản hồi 201

{
  "success": true,
  "data": {
    "id": 1,
    "job_id": 1,
    "candidate_id": 101,
    "resume_file_id": "550e8400-e29b-41d4-a716-446655440000",
    "cover_letter": "I am interested in this position...",
    "status": "submitted",
    "applied_at": "2026-08-17T10:00:00Z"
  }
}

Phản hồi lỗi

409 Xung đột -- Đã nộp đơn:

{
  "success": false,
  "error": {
    "code": "APPLICATION_EXISTS",
    "message": "Candidate already applied to this job"
  }
}

400 Yêu cầu không hợp lệ -- Tệp hồ sơ không hợp lệ:

{
  "success": false,
  "error": {
    "code": "INVALID_RESUME_FILE",
    "message": "Resume file not found or does not belong to candidate"
  }
}

400 Yêu cầu không hợp lệ -- Sai danh mục tệp:

{
  "success": false,
  "error": {
    "code": "INVALID_FILE_CATEGORY",
    "message": "Only RESUME category files can be linked to applications"
  }
}

Quy tắc kinh doanh

Quy tắcMô tả
BR-RECR-08Đơn ứng tuyển đóng băng hồ sơ khi nộp: resume_file_id không thể thay đổi sau khi tạo đơn ứng tuyển
BR-RECR-09Chỉ các tệp danh mục RESUME (từ module FILE) mới có thể liên kết với đơn ứng tuyển

GET /recr/candidate/applications FR-005

Xem đơn ứng tuyển của tôi. Yêu cầu vai trò candidate.

PATCH /recr/candidate/profile FR-006

Cập nhật hồ sơ. Yêu cầu vai trò candidate.

POST /recr/candidate/resume FR-007, FR-037

Tải lên tệp hồ sơ vào danh sách hồ sơ của ứng viên. Yêu cầu vai trò candidate. Tệp được lưu trữ thông qua module FILE.

Phụ thuộc liên module: ProfileService gọi FileService (FILE) để lấy URL tải lên đã ký, sau đó tệp được tải lên module FILE. ProfileService lưu file_id được trả về trong bảng recr_candidate_resumes.

Tóm tắt luồng

  1. Frontend yêu cầu URL tải lên đã ký từ RECR (hoặc gọi trực tiếp module FILE)
  2. Frontend tải tệp lên module FILE thông qua URL đã ký
  3. Frontend gọi POST /recr/candidate/resume với file_id từ module FILE
  4. ProfileService kiểm tra file_id tồn tại trong module FILE và là danh mục RESUME
  5. ProfileService tạo bản ghi trong recr_candidate_resumes với tham chiếu file_id
  6. ProfileService trả về 201 Created với metadata hồ sơ

Request Body

TrườngKiểuBắt buộcKiểm traMô tả
file_idstring (UUID)Phải tồn tại trong module FILE, phải là danh mục RESUMEID tệp được module FILE trả về sau khi tải lên
{
  "file_id": "550e8400-e29b-41d4-a716-446655440000"
}

Phản hồi 201

{
  "success": true,
  "data": {
    "resume_id": 1,
    "file_id": "550e8400-e29b-41d4-a716-446655440000",
    "file_name": "john_doe_resume.pdf",
    "mime_type": "application/pdf",
    "file_size": 245760,
    "is_primary": false,
    "uploaded_at": "2026-08-17T10:00:00Z"
  }
}

Phản hồi lỗi

400 Yêu cầu không hợp lệ -- Tệp không hợp lệ:

{
  "success": false,
  "error": {
    "code": "INVALID_FILE",
    "message": "File not found in FILE module or is not a RESUME category file"
  }
}

400 Yêu cầu không hợp lệ -- Tệp quá lớn:

{
  "success": false,
  "error": {
    "code": "FILE_TOO_LARGE",
    "message": "File size must not exceed 10MB"
  }
}

Quy tắc kinh doanh

Quy tắcMô tả
BR-RECR-07Các tệp hồ sơ phải được lưu trữ thông qua module FILE; không cho phép tải tệp trực tiếp lên RECR
BR-RECR-09Chỉ các tệp danh mục RESUME mới có thể được tải lên làm hồ sơ ứng viên

GET /recr/candidate/resumes FR-037

Liệt kê tất cả hồ sơ của ứng viên hiện tại. Yêu cầu vai trò candidate.

Phản hồi 200

{
  "success": true,
  "data": {
    "resumes": [
      {
        "id": 1,
        "file_id": "550e8400-e29b-41d4-a716-446655440000",
        "file_name": "john_doe_resume.pdf",
        "mime_type": "application/pdf",
        "file_size": 245760,
        "is_primary": true,
        "created_at": "2026-08-17T10:00:00Z"
      },
      {
        "id": 2,
        "file_id": "660e8400-e29b-41d4-a716-446655440001",
        "file_name": "john_doe_resume_v2.docx",
        "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "file_size": 189440,
        "is_primary": false,
        "created_at": "2026-08-15T14:30:00Z"
      }
    ]
  }
}

DELETE /recr/candidate/resumes/:id FR-037

Xóa hồ sơ khỏi danh sách của ứng viên. Yêu cầu vai trò candidate. Chỉ được phép khi hồ sơ chưa liên kết với đơn ứng tuyển nào đã nộp.

Phản hồi 200

{
  "success": true,
  "data": {
    "message": "Resume deleted successfully"
  }
}

Phản hồi lỗi

409 Xung đột -- Hồ sơ liên kết với đơn ứng tuyển:

{
  "success": false,
  "error": {
    "code": "RESUME_LINKED",
    "message": "Cannot delete resume that is linked to a submitted application"
  }
}

GET /recr/candidate/applications/:id/resume FR-038

Lấy URL tải xuống đã ký cho hồ sơ đã đóng băng của đơn ứng tuyển. Yêu cầu vai trò candidate. Ứng viên chỉ có thể xem hồ sơ từ đơn ứng tuyển của chính mình.

Phụ thuộc liên module: ApplicationController gọi FileService (FILE) để tạo URL tải xuống đã ký có thời hạn cho resume_file_id đã đóng băng.

Phản hồi 200

{
  "success": true,
  "data": {
    "download_url": "https://file-storage.example.com/presigned/...",
    "expires_at": "2026-08-17T11:00:00Z",
    "file_name": "john_doe_resume.pdf"
  }
}

5.3 Endpoint cho HR

Lưu ý: Các endpoint này yêu cầu vai trò tổ chức HR trong ngữ cảnh tổ chức đang hoạt động. Vai trò HR được gán bởi chủ/quản trị viên tổ chức.

GET /recr/hr/applications FR-008

Xem đơn ứng tuyển. Yêu cầu vai trò hr.

Tham số truy vấn: job_id, status, page, limit

PATCH /recr/hr/applications/:id/status FR-009

Cập nhật trạng thái đơn ứng tuyển. Yêu cầu vai trò hr.

POST /recr/hr/interviews FR-010

Lên lịch phỏng vấn. Yêu cầu vai trò hr.

Yêu cầu:

{
  "application_id": 1,
  "interviewer_id": 201,
  "scheduled_at": "2026-08-05T14:00:00Z",
  "duration_minutes": 60,
  "location": "Meeting Room A",
  "meeting_link": "https://meet.google.com/abc-defg-hij"
}

POST /recr/hr/evaluations FR-011

Gửi đánh giá. Yêu cầu vai trò hr.

POST /recr/hr/offers FR-012

Tạo đề nghị tuyển dụng. Yêu cầu vai trò hr.

GET /recr/hr/pipeline FR-014

Xem quy trình tuyển dụng. Yêu cầu vai trò hr.

GET /recr/hr/reports FR-015

Tạo báo cáo. Yêu cầu vai trò hr.

5.4 Endpoint cho Nhà tuyển dụng

Lưu ý: Các endpoint này yêu cầu vai trò tổ chức Nhà tuyển dụng trong ngữ cảnh tổ chức đang hoạt động. Vai trò Nhà tuyển dụng thường được gán cho chủ/quản trị viên tổ chức tạo tin tuyển dụng.

POST /recr/employer/jobs FR-017

Tạo tin tuyển dụng. Yêu cầu vai trò employer.

Yêu cầu:

{
  "title": "Senior Software Engineer",
  "description": "We are looking for a senior engineer...",
  "department": "Engineering",
  "location": "Ho Chi Minh City",
  "salary_min": 20000000,
  "salary_max": 40000000,
  "employment_type": "full_time",
  "requirements": [
    { "type": "skill", "description": "5+ years Node.js", "is_mandatory": true }
  ]
}

GET /recr/employer/jobs FR-018

Liệt kê tin tuyển dụng của tôi. Yêu cầu vai trò employer.

PATCH /recr/employer/jobs/:id FR-018

Cập nhật tin tuyển dụng. Yêu cầu vai trò employer.

GET /recr/employer/jobs/:id/applicants FR-019

Xem người nộp đơn. Yêu cầu vai trò employer.

PATCH /recr/employer/jobs/:id/submit FR-020

Gửi để HR phê duyệt. Yêu cầu vai trò employer.

GET /recr/employer/analytics FR-021

Xem phân tích. Yêu cầu vai trò employer.

6. Phân trang

Tất cả các endpoint danh sách hỗ trợ phân trang offset.

GET /recr/jobs?page=2&limit=20

7. Sự kiện Webhook

Sự kiệnHướngGiao thứcMô tả
user.registeredĐiRabbitMQNgười dùng mới đăng ký với vai trò hệ thống user
application.submittedĐiRabbitMQỨng viên nộp đơn ứng tuyển
interview.scheduledĐiRabbitMQHR lên lịch phỏng vấn
offer.sentĐiRabbitMQHR gửi thư đề nghị tuyển dụng
candidate.hiredĐiRabbitMQỨng viên chấp nhận đề nghị
job.submitted_for_approvalĐiRabbitMQNhà tuyển dụng gửi tin tuyển dụng để phê duyệt
file.deletedĐếnRabbitMQModule FILE thông báo cho RECR khi tệp bị xóa; RECR dọn dẹp các tham chiếu trong recr_candidate_resumes và recr_applications
invitation.createdĐiRabbitMQLời mời được tạo, gửi thông báo email
invitation.acceptedĐiRabbitMQLời mời được chấp nhận, kích hoạt module AUTH

8. Nhật ký thay đổi

NgàyPhiên bảnThay đổi
2026-07-311.0.0Phiên bản đầu tiên với 3 vai trò: Ứng viên, HR, Nhà tuyển dụng
2026-08-121.1.0API đăng ký chi tiết: request/response riêng biệt cho Ứng viên và Nhà tuyển dụng, quy tắc kiểm tra, phản hồi lỗi, quy tắc kinh doanh, luồng liên module
2026-08-171.2.0Hệ thống vai trò hai cấp mới: vai trò hệ thống (user, super_admin) và vai trò tổ chức (HR, Candidate, Employer). Đăng ký tạo người dùng với vai trò user. JWT chứa vai trò hệ thống. Vai trò tổ chức được quản lý trong tổ chức.
2026-08-171.3.0Tích hợp module FILE: Tải lên hồ sơ thông qua module FILE với tham chiếu file_id. Các endpoint mới: GET /recr/candidate/resumes, DELETE /recr/candidate/resumes/:id, GET /recr/candidate/applications/:id/resume. POST /recr/applications hiện yêu cầu resume_file_id. Thêm quy tắc kinh doanh BR-RECR-07, BR-RECR-08, BR-03-RECR-09. Xử lý sự kiện file.deleted đến.
2026-08-171.4.0Tính năng INVITE được tích hợp như tính năng module RECR. Các endpoint mới: POST /recr/invitations/send, GET /recr/invitations/verify, POST /recr/invitations/accept, GET /recr/invitations, DELETE /recr/invitations/:id, POST /recr/invitations/:id/resend. Bảng: recr_invitations. Sự kiện: invitation.created, invitation.accepted.