{"openapi":"3.1.0","info":{"title":"CHC Expert API","description":"Expert Portal API — cross-tenant pool (expert không thuộc 1 tenant cụ thể). Tenant_id resolve từ JWT claims hoặc request body (vd `request_id`). KHÔNG cần `X-T-Slug` header.","version":"0.1.0"},"paths":{"/api/expert/_ping":{"get":{"tags":["expert"],"summary":"Expert namespace ping","operationId":"expert_ping_api_expert__ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Expert Ping Api Expert  Ping Get"}}}}}}},"/api/expert/requests/{request_id}/upload-result":{"post":{"tags":["expert-requests"],"summary":"Confirm 3-step presigned upload of WP Final + Report","operationId":"expert_request_upload_result_confirm_api_expert_requests__request_id__upload_result_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Request Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpertUploadConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpertUploadResultResponse"}}}},"401":{"description":"Error response — codes: missing_bearer, token_revoked, expert_inactive_or_missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}},"examples":{"missing_bearer":{"summary":"missing_bearer","value":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}}},"token_revoked":{"summary":"token_revoked","value":{"error":{"code":"token_revoked","message":"Phiên đăng nhập đã hết hạn, vui lòng đăng nhập lại"}}},"expert_inactive_or_missing":{"summary":"expert_inactive_or_missing","value":{"error":{"code":"expert_inactive_or_missing","message":"Tài khoản expert không tồn tại hoặc đã bị vô hiệu hoá"}}}}}}},"403":{"description":"Error response — codes: insufficient_role, role_not_expert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}},"examples":{"insufficient_role":{"summary":"insufficient_role","value":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}}},"role_not_expert":{"summary":"role_not_expert","value":{"error":{"code":"role_not_expert","message":"Vai trò không phải expert"}}}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"error":{"code":"validation_error","message":"Dữ liệu không hợp lệ","details":[{"field":"body.email","message":"Field required","type":"missing"}]}}}}}}}},"/api/expert/requests/{request_id}/files/{kind}":{"get":{"tags":["expert-requests"],"summary":"Issue presigned GET URL for an assigned-request file","operationId":"expert_request_file_download_api_expert_requests__request_id__files__kind__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Request Id"}},{"name":"kind","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ExpertDownloadKind"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpertFileDownloadResponse"}}}},"401":{"description":"Error response — codes: missing_bearer, token_revoked, expert_inactive_or_missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}},"examples":{"missing_bearer":{"summary":"missing_bearer","value":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}}},"token_revoked":{"summary":"token_revoked","value":{"error":{"code":"token_revoked","message":"Phiên đăng nhập đã hết hạn, vui lòng đăng nhập lại"}}},"expert_inactive_or_missing":{"summary":"expert_inactive_or_missing","value":{"error":{"code":"expert_inactive_or_missing","message":"Tài khoản expert không tồn tại hoặc đã bị vô hiệu hoá"}}}}}}},"403":{"description":"Error response — codes: insufficient_role, role_not_expert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}},"examples":{"insufficient_role":{"summary":"insufficient_role","value":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}}},"role_not_expert":{"summary":"role_not_expert","value":{"error":{"code":"role_not_expert","message":"Vai trò không phải expert"}}}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"error":{"code":"validation_error","message":"Dữ liệu không hợp lệ","details":[{"field":"body.email","message":"Field required","type":"missing"}]}}}}}}}},"/api/expert/profile":{"get":{"tags":["expert"],"summary":"Expert Profile Read","operationId":"expert_profile_read_api_expert_profile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpertProfileResponse"}}}},"401":{"description":"Error response — codes: missing_bearer, token_revoked, expert_inactive_or_missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}},"examples":{"missing_bearer":{"summary":"missing_bearer","value":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}}},"token_revoked":{"summary":"token_revoked","value":{"error":{"code":"token_revoked","message":"Phiên đăng nhập đã hết hạn, vui lòng đăng nhập lại"}}},"expert_inactive_or_missing":{"summary":"expert_inactive_or_missing","value":{"error":{"code":"expert_inactive_or_missing","message":"Tài khoản expert không tồn tại hoặc đã bị vô hiệu hoá"}}}}}}},"403":{"description":"Error response — codes: insufficient_role, role_not_expert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}},"examples":{"insufficient_role":{"summary":"insufficient_role","value":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}}},"role_not_expert":{"summary":"role_not_expert","value":{"error":{"code":"role_not_expert","message":"Vai trò không phải expert"}}}}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["expert"],"summary":"Expert Profile Update","operationId":"expert_profile_update_api_expert_profile_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpertProfileUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpertProfileResponse"}}}},"401":{"description":"Error response — codes: missing_bearer, token_revoked, expert_inactive_or_missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}},"examples":{"missing_bearer":{"summary":"missing_bearer","value":{"error":{"code":"missing_bearer","message":"Thiếu thông tin xác thực"}}},"token_revoked":{"summary":"token_revoked","value":{"error":{"code":"token_revoked","message":"Phiên đăng nhập đã hết hạn, vui lòng đăng nhập lại"}}},"expert_inactive_or_missing":{"summary":"expert_inactive_or_missing","value":{"error":{"code":"expert_inactive_or_missing","message":"Tài khoản expert không tồn tại hoặc đã bị vô hiệu hoá"}}}}}}},"403":{"description":"Error response — codes: insufficient_role, role_not_expert","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}},"examples":{"insufficient_role":{"summary":"insufficient_role","value":{"error":{"code":"insufficient_role","message":"Bạn không có quyền truy cập"}}},"role_not_expert":{"summary":"role_not_expert","value":{"error":{"code":"role_not_expert","message":"Vai trò không phải expert"}}}}}}},"409":{"description":"Error response — codes: profile_update_conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"profile_update_conflict","message":"Không thể cập nhật hồ sơ, dữ liệu xung đột"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"error":{"code":"validation_error","message":"Dữ liệu không hợp lệ","details":[{"field":"body.email","message":"Field required","type":"missing"}]}}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"ChcModule":{"type":"string","enum":["item_code_generator","tariff_classification","customs_valuation","non_tariff_measures","exim_statistics"],"title":"ChcModule","description":"5 CHC service modules — single source of truth (BR-009).\n\nPhase 16 S3.3 consolidates two duplicate frozensets that previously\nlived in `app/admin/schemas/experts.py` (`_CHC_MODULES`) and\n`app/user/schemas/request.py` (`MODULES_5SET`). Pydantic v2 enum\nvalidation replaces the manual `model_post_init` checks."},"ExpertDownloadKind":{"type":"string","enum":["ecus","wp_draft","wp_final","report"],"title":"ExpertDownloadKind","description":"4 file kinds an assigned expert may download (FSD §5.6 row Expert)."},"ExpertFileDownloadResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["url","expires_at"],"title":"ExpertFileDownloadResponse","example":{"expires_at":"2026-05-09T22:30:00+07:00","url":"https://chc-private.s3.amazonaws.com/wp_draft/770e8400.../wp-draft-2026-05-09.xlsx?X-Amz-Algorithm=..."}},"ExpertProfileResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"language":{"$ref":"#/components/schemas/LanguageCode"},"expertise_modules":{"items":{"$ref":"#/components/schemas/ChcModule"},"type":"array","title":"Expertise Modules"},"status":{"$ref":"#/components/schemas/ExpertStatus"},"rating_avg":{"type":"number","title":"Rating Avg"},"completed_count":{"type":"integer","title":"Completed Count"},"force_password_change":{"type":"boolean","title":"Force Password Change"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","email","name","phone","language","expertise_modules","status","rating_avg","completed_count","force_password_change","created_at"],"title":"ExpertProfileResponse","description":"Full expert profile (self view).\n\nRead-only fields (``email``, ``expertise_modules``, ``status``,\n``rating_avg``, ``completed_count``, ``force_password_change``,\n``created_at``) are surfaced for display but cannot be updated via\n``PUT /profile`` — admin manages them through\n``/api/admin/experts/*``.","example":{"completed_count":156,"created_at":"2026-05-09T10:15:00+07:00","email":"expert@chc.com","expertise_modules":["item_code_generator","tariff_classification"],"force_password_change":false,"id":"880e8400-e29b-41d4-a716-446655440003","language":"vi","name":"Lê Quang Phi","phone":"+84 912 345 678","rating_avg":4.8,"status":"active"}},"ExpertProfileUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"Display name.","examples":["Lê Quang Phi"]},"phone":{"anyOf":[{"type":"string","maxLength":30},{"type":"null"}],"title":"Phone","description":"Contact phone. Empty string clears the field.","examples":["+84 912 345 678"]},"language":{"anyOf":[{"$ref":"#/components/schemas/LanguageCode"},{"type":"null"}],"description":"Preferred email language (vi / en / ko / zh).","examples":["vi"]}},"additionalProperties":false,"type":"object","title":"ExpertProfileUpdate","description":"Editable expert profile fields. ``extra='forbid'`` blocks attempts\nto PUT read-only fields (``email``, ``expertise_modules``, …) with a\n422 ``validation_error`` so the FE catches the contract drift early."},"ExpertStatus":{"type":"string","enum":["active","inactive"],"title":"ExpertStatus"},"ExpertUploadConfirmRequest":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note","description":"Optional admin-facing note attached at upload-result confirm time. The 2 s3_keys (wp_final + report) live in Redis drafts keyed by request_id.","examples":["Đã hoàn tất phân loại 12 dòng hàng — vui lòng review trước 17h."]}},"additionalProperties":false,"type":"object","title":"ExpertUploadConfirmRequest","description":"Confirm body — optional admin-facing note only.\n\nThe 2 s3_keys (wp_final + report) live in Redis drafts keyed by\n``request_id`` (URL path id). Server reads BOTH drafts atomically\nbefore any DB write per the multi-file 2-URL hand-off pattern."},"ExpertUploadResultFile":{"properties":{"kind":{"type":"string","title":"Kind"},"filename":{"type":"string","title":"Filename"},"size_bytes":{"type":"integer","title":"Size Bytes"},"uploaded_at":{"type":"string","format":"date-time","title":"Uploaded At"}},"type":"object","required":["kind","filename","size_bytes","uploaded_at"],"title":"ExpertUploadResultFile"},"ExpertUploadResultResponse":{"properties":{"request_id":{"type":"string","format":"uuid","title":"Request Id"},"status":{"type":"string","title":"Status"},"files":{"items":{"$ref":"#/components/schemas/ExpertUploadResultFile"},"type":"array","title":"Files"}},"type":"object","required":["request_id","status","files"],"title":"ExpertUploadResultResponse","example":{"files":[{"filename":"wp-final-2026-05-09.xlsx","kind":"wp_final","size_bytes":102400,"uploaded_at":"2026-05-09T16:30:00+07:00"},{"filename":"report-2026-05-09.pdf","kind":"report","size_bytes":204800,"uploaded_at":"2026-05-09T16:30:00+07:00"}],"request_id":"770e8400-e29b-41d4-a716-446655440002","status":"completed"}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LanguageCode":{"type":"string","enum":["vi","en","ko","zh"],"title":"LanguageCode"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ErrorBody":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable error key (see GET /api/public/error-catalog). FE switches logic / shows icon based on this value.","example":"request_not_found"},"message":{"type":"string","description":"Localized human-readable text. Language picked from Accept-Language header (vi/en/ko/zh); /api/admin/* always returns vi.","example":"Không tìm thấy đơn hàng"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}},"example":{"error":{"code":"request_not_found","message":"Không tìm thấy đơn hàng"}}},"ValidationErrorDetail":{"type":"object","properties":{"field":{"type":"string","example":"body.email"},"message":{"type":"string","example":"Field required"},"type":{"type":"string","example":"missing"}}},"ValidationErrorBody":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"validation_error"},"message":{"type":"string","example":"Dữ liệu không hợp lệ"},"details":{"type":"array","items":{"$ref":"#/components/schemas/ValidationErrorDetail"}}}},"ValidationErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ValidationErrorBody"}},"example":{"error":{"code":"validation_error","message":"Dữ liệu không hợp lệ","details":[{"field":"body.email","message":"Field required","type":"missing"}]}}}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}