{"openapi":"3.1.0","info":{"title":"CHC Uploads API","description":"Uploads API — common presigned 3-step upload (ECUS + E-Tariff Batch + branding). Tenant_id + actor role từ JWT claims (qua `get_current_actor`). KHÔNG cần `X-T-Slug` header — admin/user/expert call cùng endpoint, RBAC validate trong orchestrator.","version":"0.1.0"},"paths":{"/api/uploads/_ping":{"get":{"tags":["uploads"],"summary":"Uploads namespace ping","operationId":"uploads_ping_api_uploads__ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Uploads Ping Api Uploads  Ping Get"}}}}}}},"/api/uploads/presigned-url":{"post":{"tags":["uploads"],"summary":"Issue a presigned PUT URL for any registered upload kind","operationId":"common_uploads_presigned_url_api_uploads_presigned_url_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadKindRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"401":{"description":"Error response — codes: missing_bearer, token_revoked","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"}}}}}}},"403":{"description":"Error response — codes: forbidden, password_change_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"forbidden","message":"Bạn không có quyền thực hiện hành động này"}},"examples":{"forbidden":{"summary":"forbidden","value":{"error":{"code":"forbidden","message":"Bạn không có quyền thực hiện hành động này"}}},"password_change_required":{"summary":"password_change_required","value":{"error":{"code":"password_change_required","message":"Vui lòng đổi mật khẩu trước khi tiếp tục"}}}}}}},"404":{"description":"Error response — codes: request_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"request_not_found","message":"Không tìm thấy đơn hàng"}}}}},"409":{"description":"Error response — codes: illegal_transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"illegal_transition","message":"Trạng thái không thể chuyển đổi như vậy"}}}}},"422":{"description":"Error response — codes: size_exceeds_cap, magic_byte_mismatch","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":{"UploadExtras":{"properties":{"modules":{"anyOf":[{"items":{"$ref":"#/components/schemas/UploadModule"},"type":"array","minItems":1},{"type":"null"}],"title":"Modules","description":"Required cho kind=ecus (5 module enum). None cho kind khác.","examples":[["item_code_generator","tariff_classification"]]},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id","description":"Required cho kind=wp_final / kind=report. UUID của request đã assign cho expert đang gọi (orchestrator verify expert.id == request.expert_id + request.status ∈ {processing, completed}).","examples":["770e8400-e29b-41d4-a716-446655440002"]}},"additionalProperties":false,"type":"object","title":"UploadExtras","description":"Per-kind extras typed schema.\n\n- ``ecus``: ``modules`` required (≥1 module trong 5-set).\n- ``wp_final`` / ``report``: ``request_id`` required (UUID của\n  request đã assign cho expert đang gọi).\n- ``branding_logo`` / ``branding_favicon`` / ``etariff_batch``: bỏ\n  trống."},"UploadKind":{"type":"string","enum":["branding_logo","branding_favicon","ecus","etariff_batch","wp_final","report"],"title":"UploadKind","description":"Whitelist các kind hợp lệ cho common upload endpoint.\n\nPhải đồng bộ với ``app.shared.upload_kinds.UPLOAD_KINDS`` registry.\nPhase 04.6 ships 4 kinds; Phase 09.5 (Y2) adds expert ``wp_final`` +\n``report`` cho 2-URL multi-file hand-off."},"UploadKindRequest":{"properties":{"kind":{"$ref":"#/components/schemas/UploadKind","description":"Loại file upload — quyết định bucket / RBAC / extras schema.","examples":["ecus"]},"filename":{"type":"string","maxLength":300,"minLength":1,"title":"Filename","description":"Original filename (FE display + server logging).","examples":["ecus-export-2026-05-09.xlsx"]},"mime":{"type":"string","maxLength":150,"minLength":3,"title":"Mime","description":"MIME chuẩn (vd image/png cho branding_logo, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet cho ecus/wp_final, application/pdf cho report).","examples":["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]},"size_bytes":{"type":"integer","exclusiveMinimum":0.0,"title":"Size Bytes","description":"Byte count, server enforce per-kind cap.","examples":[1048576]},"extras":{"anyOf":[{"$ref":"#/components/schemas/UploadExtras"},{"type":"null"}],"description":"Per-kind metadata. Required cho kind=ecus (modules) và kind=wp_final / kind=report (request_id); bỏ trống cho kinds khác.","examples":[{"modules":["item_code_generator","tariff_classification"]}]}},"additionalProperties":false,"type":"object","required":["kind","filename","mime","size_bytes"],"title":"UploadKindRequest"},"UploadModule":{"type":"string","enum":["item_code_generator","tariff_classification","customs_valuation","non_tariff_measures","exim_statistics"],"title":"UploadModule","description":"5 module CHC service (BR-009) — extras.modules cho kind=ecus."},"UploadResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url","description":"Presigned PUT URL (TTL 10 phút BR-037)."},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"max_size_bytes":{"type":"integer","title":"Max Size Bytes","description":"Per-kind size cap (server-enforced)."},"public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Url","description":"URL công khai render trực tiếp (CHỈ branding kinds)."},"draft_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Draft Id","description":"Server-allocated draft id (CHỈ private kinds với confirm step)."}},"type":"object","required":["upload_url","expires_at","max_size_bytes"],"title":"UploadResponse","description":"Kind-aware response (fields conditional on per-kind config).\n\n- Public kinds (branding_logo / branding_favicon): trả ``public_url``\n  để FE embed trực tiếp ``<img src=URL>`` vào TenantCreate/Update.\n- Private kinds với confirm step (ecus / etariff_batch / wp_final /\n  report): trả ``draft_id`` để FE biết server đã allocate draft cache\n  slot. Cho ECUS / etariff_batch ``draft_id`` = server-allocated\n  UUID; cho wp_final / report ``draft_id`` = ``extras.request_id``\n  echo lại (cache key match URL path của confirm endpoint).","examples":[{"summary":"Public kind (branding_logo / branding_favicon)","value":{"expires_at":"2026-05-09T22:00:00+07:00","max_size_bytes":2097152,"public_url":"https://chc-public.s3.amazonaws.com/branding/logo/660e8400.../acme-corp-logo.png","upload_url":"https://chc-public.s3.amazonaws.com/branding/logo/660e8400.../acme-corp-logo.png?X-Amz-Algorithm=..."}},{"summary":"Private kind ECUS / etariff_batch (server-allocated draft_id)","value":{"draft_id":"770e8400-e29b-41d4-a716-446655440002","expires_at":"2026-05-09T22:00:00+07:00","max_size_bytes":26214400,"upload_url":"https://chc-private.s3.amazonaws.com/ecus/770e8400.../ecus-export.xlsx?X-Amz-Algorithm=..."}},{"summary":"Expert kind wp_final / report (draft_id echoes extras.request_id)","value":{"draft_id":"770e8400-e29b-41d4-a716-446655440002","expires_at":"2026-05-09T22:00:00+07:00","max_size_bytes":26214400,"upload_url":"https://chc-private.s3.amazonaws.com/wp_final/770e8400.../wp-final.xlsx?X-Amz-Algorithm=..."}}]},"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"}}}}