{"openapi":"3.1.0","info":{"title":"TaLaem (ตาแหลม) API","description":"AI Service สำหรับวิเคราะห์ความเสี่ยงจากข้อความ ภาพหน้าจอ และเสียงภาษาไทย ก่อนเชื่อ ก่อนคลิก ก่อนโอน","version":"1.0.0"},"paths":{"/health":{"get":{"summary":"Health","description":"Liveness — แค่บอกว่า process ยังทำงานอยู่ ไม่ตรวจ dependency ใด ๆ","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}},"/ready":{"get":{"summary":"Ready","description":"Readiness — ตรวจเฉพาะ dependency ภายใน (DB, knowledge base, config, temp dir)\n\nห้ามเรียก AI provider จริงในนี้ เพราะจะเสียเงิน/quota ทุกครั้งที่ orchestrator poll","operationId":"ready_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/analyze-scam-text":{"post":{"tags":["analyze"],"summary":"Analyze Scam Text","operationId":"analyze_scam_text_v1_analyze_scam_text_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeTextRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalysisResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/analyze-scam-image":{"post":{"tags":["analyze"],"summary":"Analyze Scam Image","operationId":"analyze_scam_image_v1_analyze_scam_image_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_scam_image_v1_analyze_scam_image_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeImageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/analyze-scam-audio":{"post":{"tags":["analyze"],"summary":"Analyze Scam Audio","operationId":"analyze_scam_audio_v1_analyze_scam_audio_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_scam_audio_v1_analyze_scam_audio_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeAudioResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/report-feedback":{"post":{"tags":["analyze"],"summary":"Report Feedback","description":"รายงานว่าผลวิเคราะห์ผิด — ต้องแนบ feedback_token ที่ได้จากผลวิเคราะห์นั้น (issue #2 P0-5)\n\nโทเคนใช้ได้ครั้งเดียวและมีอายุจำกัด จึงทำหน้าที่เป็นทั้งการยืนยันว่า \"ผู้ส่งเคยเรียกวิเคราะห์\nจริง\" และเป็นโควตาไปในตัว: หนึ่งการวิเคราะห์ = หนึ่ง feedback ดูเหตุผลเต็มที่ feedback_token.py\n\nตอบ 401 เหมือนกันทุกกรณีที่โทเคนไม่ผ่าน (ไม่ได้แนบมา / ลายเซ็นผิด / หมดอายุ / ถูกใช้แล้ว)\n— การแยกให้ละเอียดคือการบอกผู้โจมตีว่าเดาถูกไปกี่ขั้นแล้ว เหตุผลจริงอยู่ใน log ฝั่งเรา","operationId":"report_feedback_v1_report_feedback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Report Feedback V1 Report Feedback Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AnalysisResult":{"properties":{"risk_level":{"$ref":"#/components/schemas/RiskLevel"},"risk_score":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Risk Score","description":"เท่ากับ score_breakdown.final_score"},"score_breakdown":{"$ref":"#/components/schemas/ScoreBreakdown"},"matched_scam_categories":{"items":{"type":"string"},"type":"array","title":"Matched Scam Categories"},"red_flags":{"items":{"type":"string"},"type":"array","title":"Red Flags"},"explanation":{"type":"string","title":"Explanation"},"recommended_actions":{"items":{"type":"string"},"type":"array","title":"Recommended Actions"},"extracted_entities":{"items":{"$ref":"#/components/schemas/ExtractedEntity"},"type":"array","title":"Extracted Entities"},"family_summary":{"type":"string","title":"Family Summary"},"official_contacts":{"items":{"$ref":"#/components/schemas/OfficialContactOut"},"type":"array","title":"Official Contacts"},"advisory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Advisory"},"disclaimer":{"type":"string","title":"Disclaimer","default":"ตาแหลมช่วยดูให้เบื้องต้น ไม่ใช่คำตัดสิน ถ้าไม่แน่ใจ ให้ถามธนาคารหรือหน่วยงานโดยตรง"},"feedback_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback Token"}},"type":"object","required":["risk_level","risk_score","score_breakdown","explanation","family_summary"],"title":"AnalysisResult"},"AnalyzeAudioResponse":{"properties":{"risk_level":{"$ref":"#/components/schemas/RiskLevel"},"risk_score":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Risk Score","description":"เท่ากับ score_breakdown.final_score"},"score_breakdown":{"$ref":"#/components/schemas/ScoreBreakdown"},"matched_scam_categories":{"items":{"type":"string"},"type":"array","title":"Matched Scam Categories"},"red_flags":{"items":{"type":"string"},"type":"array","title":"Red Flags"},"explanation":{"type":"string","title":"Explanation"},"recommended_actions":{"items":{"type":"string"},"type":"array","title":"Recommended Actions"},"extracted_entities":{"items":{"$ref":"#/components/schemas/ExtractedEntity"},"type":"array","title":"Extracted Entities"},"family_summary":{"type":"string","title":"Family Summary"},"official_contacts":{"items":{"$ref":"#/components/schemas/OfficialContactOut"},"type":"array","title":"Official Contacts"},"advisory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Advisory"},"disclaimer":{"type":"string","title":"Disclaimer","default":"ตาแหลมช่วยดูให้เบื้องต้น ไม่ใช่คำตัดสิน ถ้าไม่แน่ใจ ให้ถามธนาคารหรือหน่วยงานโดยตรง"},"feedback_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback Token"},"transcript":{"type":"string","title":"Transcript"}},"type":"object","required":["risk_level","risk_score","score_breakdown","explanation","family_summary","transcript"],"title":"AnalyzeAudioResponse"},"AnalyzeImageResponse":{"properties":{"risk_level":{"$ref":"#/components/schemas/RiskLevel"},"risk_score":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Risk Score","description":"เท่ากับ score_breakdown.final_score"},"score_breakdown":{"$ref":"#/components/schemas/ScoreBreakdown"},"matched_scam_categories":{"items":{"type":"string"},"type":"array","title":"Matched Scam Categories"},"red_flags":{"items":{"type":"string"},"type":"array","title":"Red Flags"},"explanation":{"type":"string","title":"Explanation"},"recommended_actions":{"items":{"type":"string"},"type":"array","title":"Recommended Actions"},"extracted_entities":{"items":{"$ref":"#/components/schemas/ExtractedEntity"},"type":"array","title":"Extracted Entities"},"family_summary":{"type":"string","title":"Family Summary"},"official_contacts":{"items":{"$ref":"#/components/schemas/OfficialContactOut"},"type":"array","title":"Official Contacts"},"advisory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Advisory"},"disclaimer":{"type":"string","title":"Disclaimer","default":"ตาแหลมช่วยดูให้เบื้องต้น ไม่ใช่คำตัดสิน ถ้าไม่แน่ใจ ให้ถามธนาคารหรือหน่วยงานโดยตรง"},"feedback_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback Token"},"extracted_text":{"type":"string","title":"Extracted Text"}},"type":"object","required":["risk_level","risk_score","score_breakdown","explanation","family_summary","extracted_text"],"title":"AnalyzeImageResponse"},"AnalyzeTextRequest":{"properties":{"text":{"type":"string","maxLength":8000,"minLength":1,"title":"Text","description":"ข้อความแชทที่ต้องการวิเคราะห์"},"source_channel":{"type":"string","title":"Source Channel","description":"line_oa หรือ web","default":"web"}},"type":"object","required":["text"],"title":"AnalyzeTextRequest"},"Body_analyze_scam_audio_v1_analyze_scam_audio_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_analyze_scam_audio_v1_analyze_scam_audio_post"},"Body_analyze_scam_image_v1_analyze_scam_image_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_analyze_scam_image_v1_analyze_scam_image_post"},"ExtractedEntity":{"properties":{"type":{"type":"string","title":"Type"},"value":{"type":"string","title":"Value"}},"type":"object","required":["type","value"],"title":"ExtractedEntity"},"FeedbackRequest":{"properties":{"feedback_token":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Feedback Token"},"analysis_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Analysis Id"},"original_text":{"anyOf":[{"type":"string","maxLength":8000},{"type":"null"}],"title":"Original Text"},"reported_risk_level":{"$ref":"#/components/schemas/RiskLevel"},"correct_risk_level":{"$ref":"#/components/schemas/RiskLevel"},"comment":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Comment"}},"type":"object","required":["reported_risk_level","correct_risk_level"],"title":"FeedbackRequest","description":"รายงานจากผู้ใช้ว่าระบบตัดสินผิด — เป็น endpoint ที่เปิดรับจากภายนอกโดยไม่ต้องยืนยันตัวตน\n\nทุก field ที่เป็นข้อความต้องมีเพดานความยาว (issue #2 P0-5): เดิมเป็น str เปล่า ๆ ซึ่งรับ\nค่าเท่าไหร่ก็ได้ ผู้โจมตีจึงยิง comment ขนาด 100 MB ซ้ำ ๆ ให้ฐานข้อมูล SQLite โตจนดิสก์เต็ม\nได้โดยไม่ต้องมีสิทธิ์อะไรเลย — และดิสก์เต็มแปลว่าทั้งระบบหยุดให้บริการ ไม่ใช่แค่ feedback พัง\n\n8000 ตัวอักษรเท่ากับเพดานของ AnalyzeTextRequest.text พอดี เพราะ original_text ที่ส่งมา\nก็คือข้อความเดียวกับที่เคยส่งเข้าวิเคราะห์ ไม่มีเหตุผลที่จะยาวกว่านั้น"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"OfficialContactOut":{"properties":{"name_th":{"type":"string","title":"Name Th"},"phone":{"type":"string","title":"Phone"},"purpose":{"type":"string","title":"Purpose"}},"type":"object","required":["name_th","phone","purpose"],"title":"OfficialContactOut","description":"เบอร์ติดต่อทางการที่แนบไปกับผลวิเคราะห์ (ดู org_directory.OfficialContact)"},"RiskLevel":{"type":"string","enum":["green","yellow","red"],"title":"RiskLevel"},"ScoreBreakdown":{"properties":{"llm":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Llm","description":"คะแนนจาก AI provider (Pathumma หรือ mock)"},"rules":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Rules","description":"คะแนนจากกฎ rule-based ที่ทำงานอิสระจาก provider"},"knowledge_base":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Knowledge Base","description":"คะแนน similarity สูงสุดจากฐานความรู้ ไม่ใช่ความน่าจะเป็นจาก LLM"},"consistency":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Consistency","description":"ความสอดคล้องระหว่างผล LLM กับผล rule-based"},"final_score":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Final Score"},"rag":{"type":"integer","title":"Rag","description":"ชื่อเดิมของ knowledge_base; คงไว้ชั่วคราวเพื่อ compatibility","readOnly":true}},"type":"object","required":["llm","rules","knowledge_base","consistency","final_score","rag"],"title":"ScoreBreakdown","description":"คะแนนแยกตามองค์ประกอบ เพื่อให้ตรวจสอบย้อนกลับได้ (Explainable AI) — ดู\ndocs/ARCHITECTURE.md สำหรับสถาปัตยกรรมของ scoring และที่มาของแต่ละองค์ประกอบ"},"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"}}},"servers":[{"url":"/api"}]}