{"openapi":"3.1.0","info":{"title":"CHC Public API","description":"Public no-auth API — pre-login brand bar resolution.\n\n**Tenant resolve cho FE:** gắn header `X-T-Slug: <tenant-slug>` (cross-origin FE) hoặc fallback subdomain `Host` header (wildcard DNS). Silent fallback default platform branding khi cả hai miss.","version":"0.1.0"},"paths":{"/api/public/_ping":{"get":{"tags":["public"],"summary":"Public namespace ping","operationId":"public_ping_api_public__ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Public Ping Api Public  Ping Get"}}}}},"parameters":[{"name":"X-T-Slug","in":"header","required":false,"description":"**Tenant slug** (vd `acme-corp`). FE **phải** gắn header này khi BE và FE khác origin (vd FE deploy `app.chc-service.shop` call BE `api.chc-service.shop`) — `Host` header lúc đó là `api.*` (reserved) không resolve được tenant.\n\n**Priority resolve tenant phía BE:**\n1. `X-T-Slug` header (cross-origin, dev local)\n2. `Host` header subdomain extraction (wildcard DNS `*.chc-service.shop` prod)\n\n**Format:** `^[a-z0-9][a-z0-9-]*$`, max 63 ký tự. Malformed → silent fallback Host. Header value không có DB row → tenant=None → endpoint trả 404 `tenant_not_found` (user/uploads namespace) hoặc default platform branding (public namespace).\n\n**Optional khi** FE và BE cùng wildcard subdomain (`acme.chc-service.shop` call `acme.chc-service.shop/api/...`) — Host tự work.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","maxLength":63,"example":"acme-corp"}}]}},"/api/public/tenant-config":{"get":{"tags":["public"],"summary":"Resolve tenant branding (no-auth) — `X-T-Slug` header or Host","description":"Resolve tenant theo thứ tự: (1) `X-T-Slug` header (cross-origin FE), (2) `Host` header subdomain (wildcard DNS). Trả 10 field branding cho FE render brand bar, theme, footer pre-login. Slug không tồn tại / Host bare / reserved → default platform. Tenant disabled → branding + `status='disabled'` để FE render maintenance page giữ logo/footer. Cache Redis 5 phút + invalidate tự động khi admin update / disable / enable tenant.\n\nEndpoint này bypass `TenantResolverMiddleware` (middleware filter out disabled tenant) — đọc header `X-T-Slug` trực tiếp để hỗ trợ brand bar maintenance page cho tenant `status='disabled'`.","operationId":"public_tenant_config_api_public_tenant_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantConfigResponse"}}}},"422":{"description":"Error response — codes: invalid_subdomain","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"}]}}}}}},"parameters":[{"name":"X-T-Slug","in":"header","required":false,"description":"**Tenant slug** (vd `acme-corp`). FE **phải** gắn header này khi BE và FE khác origin (vd FE deploy `app.chc-service.shop` call BE `api.chc-service.shop`) — `Host` header lúc đó là `api.*` (reserved) không resolve được tenant.\n\n**Priority resolve tenant phía BE:**\n1. `X-T-Slug` header (cross-origin, dev local)\n2. `Host` header subdomain extraction (wildcard DNS `*.chc-service.shop` prod)\n\n**Format:** `^[a-z0-9][a-z0-9-]*$`, max 63 ký tự. Malformed → silent fallback Host. Header value không có DB row → tenant=None → endpoint trả 404 `tenant_not_found` (user/uploads namespace) hoặc default platform branding (public namespace).\n\n**Optional khi** FE và BE cùng wildcard subdomain (`acme.chc-service.shop` call `acme.chc-service.shop/api/...`) — Host tự work.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","maxLength":63,"example":"acme-corp"}}]}},"/api/public/error-catalog":{"get":{"tags":["public"],"summary":"Localized error catalog for FE (vi/en/ko/zh).","operationId":"public_error_catalog_api_public_error_catalog_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional single-lang flat dict. One of vi/en/ko/zh. Omit to get the full multi-lang dict.","examples":["vi"],"title":"Lang"},"description":"Optional single-lang flat dict. One of vi/en/ko/zh. Omit to get the full multi-lang dict."},{"name":"X-T-Slug","in":"header","required":false,"description":"**Tenant slug** (vd `acme-corp`). FE **phải** gắn header này khi BE và FE khác origin (vd FE deploy `app.chc-service.shop` call BE `api.chc-service.shop`) — `Host` header lúc đó là `api.*` (reserved) không resolve được tenant.\n\n**Priority resolve tenant phía BE:**\n1. `X-T-Slug` header (cross-origin, dev local)\n2. `Host` header subdomain extraction (wildcard DNS `*.chc-service.shop` prod)\n\n**Format:** `^[a-z0-9][a-z0-9-]*$`, max 63 ký tự. Malformed → silent fallback Host. Header value không có DB row → tenant=None → endpoint trả 404 `tenant_not_found` (user/uploads namespace) hoặc default platform branding (public namespace).\n\n**Optional khi** FE và BE cùng wildcard subdomain (`acme.chc-service.shop` call `acme.chc-service.shop/api/...`) — Host tự work.","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","maxLength":63,"example":"acme-corp"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Public Error Catalog Api Public Error Catalog Get"}}}},"422":{"description":"Error response — codes: status_invalid","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"}]}}}}}}}}},"components":{"schemas":{"TenantConfigResponse":{"properties":{"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"name":{"type":"string","title":"Name"},"display_name":{"type":"string","title":"Display Name"},"tagline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tagline"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"favicon_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Favicon Url"},"primary_color":{"type":"string","title":"Primary Color"},"footer_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer Text"},"email_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Domain"},"status":{"$ref":"#/components/schemas/TenantStatus"},"is_default_platform":{"type":"boolean","title":"Is Default Platform"}},"type":"object","required":["slug","name","display_name","tagline","logo_url","favicon_url","primary_color","footer_text","email_domain","status","is_default_platform"],"title":"TenantConfigResponse","description":"Public branding config — render brand bar / theme / footer pre-login.\n\nResolved từ subdomain qua extract_subdomain helper. ``slug=None`` +\n``is_default_platform=true`` cho subdomain unknown / reserved / bare\ndomain (E1-E3). Tenant disabled (E4) trả branding + ``status=\"disabled\"``\nđể FE render maintenance page giữ logo/footer.","example":{"display_name":"Acme E-Tariff Portal","email_domain":"acme.example.com","favicon_url":"https://chc-public-assets.s3.ap-southeast-2.amazonaws.com/branding/acme-corp/favicon-xyz789.ico","footer_text":"© 2026 Acme Corp. All rights reserved.","is_default_platform":false,"logo_url":"https://chc-public-assets.s3.ap-southeast-2.amazonaws.com/branding/acme-corp/logo-abc123.png","name":"Acme Corporation Vietnam","primary_color":"#1E40AF","slug":"acme-corp","status":"active","tagline":"Tư vấn hải quan chuyên nghiệp"}},"TenantStatus":{"type":"string","enum":["active","disabled"],"title":"TenantStatus"},"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"}]}}}}}}