# API Reference Toate endpoint-urile, parametrii, status codes, exemple curl. ## Base URL ``` http://localhost:8085 ``` > `8085` este portul publicat pe host. În interiorul containerului serviciul > ascultă pe `8080` (vezi `docker-compose.yml`: `127.0.0.1:${API_PORT:-8085}:8080`). ## Endpoints ### `POST /api/forensic-evidence` **Singurul endpoint care contează în practică.** Upload un video/imagine, primește înapoi evidence formatat pentru LLM. #### Request Content-Type: `multipart/form-data` | Field | Type | Required | Default | Description | |-----------------|----------|----------|---------|-------------| | `video` | file | DA | — | Fișier video sau imagine (mp4, mov, avi, mkv, webm, jpg, jpeg, png). Max 2 GB. | | `modules` | string | NU | toate | CSV de module IDs ("m25,m27,m28"). Default rulează toate cele 5. | | `encode_images` | string | NU | "1" | "1" → atașează data URLs base64; "0" → doar paths absolute pe disk | | `every_n_frames`| int | NU | adaptiv | Pas extracție cadre. None → orchestrator alege bazat pe durata video | | `async_mode` | string | NU | "0" | "1" → returnează 202 cu job_id; "0" → blochează până la rezultat | #### Response **Sync mode (default) — 200 OK** — JSON cu schema completă (vezi [CONTRACT.md](CONTRACT.md)): ```jsonc { "video_path": "video.mp4", "n_frames_extracted": 64, "every_n_frames_used": 3, "auto_skipped": ["m26 (no audio track)"], "modules_run": ["m25", "m27", "m28", "m29"], "execution_time_ms": 48432.0, "fusion": { "score": 0.67, "label": "FAKE", "confidence": 0.78, "n_contributing": 4, "n_no_signal": 0, "disagreement": 0.005, "contributions": {"m25": 0.09, "m27": 0.23, "m28": 0.24, "m29": 0.11} }, "explanation": [ "Verdict forensic: FAKE (score=0.67, confidence=0.78, din 4 detectoare active)", "Top contributors: m28 INCERT, m27 FAKE, m25 INCERT" ], "modules": { "m25": { ... schema completă CONTRACT.md ... }, "m27": { ... }, "m28": { ... }, "m29": { ... } }, "evidence_text": "FORENSIC EVIDENCE (objective measurements...)\n\n[m25 ...] score=...\n...", "images": [ { "name": "m25_pulse_signal.png", "tool_id": "m25", "abs_path": "/app/data/results/{job_id}/images/m25_pulse_signal.png", "size_bytes": 69384, "data_url": "data:image/png;base64,iVBORw0KG..." // dacă encode_images=1 }, // ... 8-15 imagini total ], "summary": { "overall_score": 0.67, "overall_label": "FAKE", "overall_confidence": 0.78, "n_modules_run": 4, "n_modules_signal": 4, "disagreement": 0.005 }, "instruction_for_llm": "HOW TO USE FORENSIC EVIDENCE ABOVE:\n...", "errors": [] } ``` **Async mode (`async_mode=1`) — 202 Accepted**: ```json { "job_id": "abc123def456789a", "status": "queued", "modules": ["m25", "m26", "m27", "m28", "m29"] } ``` Apoi polling pe `/api/status/{job_id}` și preluare cu `/api/result/{job_id}`. #### Error codes | Code | Cauza | |------|-------| | 400 | Câmp `video` lipsește, tip fișier nesuportat, sau modul necunoscut | | 500 | Eroare în pipeline (vezi mesaj eroare în body) | #### Examples **Sync, toate modulele**: ```bash curl -X POST http://localhost:8085/api/forensic-evidence \ -F "video=@suspicious_video.mp4" ``` **Sync, doar 2 module + fără base64 (mai rapid)**: ```bash curl -X POST http://localhost:8085/api/forensic-evidence \ -F "video=@image.jpg" \ -F "modules=m27,m28" \ -F "encode_images=0" ``` **Async, polling**: ```bash # Submit JOB=$(curl -s -X POST http://localhost:8085/api/forensic-evidence \ -F "video=@long_video.mp4" -F "async_mode=1" \ | python -c "import sys,json; print(json.load(sys.stdin)['job_id'])") # Wait while [ "$(curl -s http://localhost:8085/api/status/$JOB \ | python -c "import sys,json; print(json.load(sys.stdin)['status'])")" != "done" ]; do sleep 5 done # Get result curl http://localhost:8085/api/result/$JOB | jq .summary ``` --- ### `GET /api/forensic-modules` Listează modulele disponibile, pentru introspection. ```bash curl http://localhost:8085/api/forensic-modules ``` ```json { "available_modules": [ {"id": "m25", "input_type": "overview_frames", "import_path": "tools.m25_physiology.physiology", "function": "run"}, {"id": "m26", "input_type": "video_path", "import_path": "tools.m26_audio.audio", "function": "run"}, {"id": "m27", "input_type": "overview_frames", "import_path": "tools.m27_ai_detector.ai_detector", "function": "run"}, {"id": "m28", "input_type": "overview_frames", "import_path": "tools.m28_forgery_heatmap.forgery_heatmap", "function": "run"}, {"id": "m29", "input_type": "overview_frames", "import_path": "tools.m29_lighting.lighting", "function": "run"} ], "default": ["m25", "m26", "m27", "m28", "m29"] } ``` --- ### `GET /api/status/{job_id}` Polling pentru cereri async. Returns 200 cu status string. ```bash curl http://localhost:8085/api/status/abc123def456789a ``` ```json { "job_id": "abc123def456789a", "status": "running", "progress": "Running forensic modules m25-m29..." } ``` Status values: `queued`, `running`, `done`, `error`. Error code 404 dacă job_id nu există. --- ### `GET /api/result/{job_id}` Preluare rezultat job async. Comportament: | Job status | Răspuns | |------------|--------------------------------------------| | `queued` sau `running` | 202 cu status + progress | | `done` | 200 cu rezultatul complet (același ca sync)| | `error` | 500 cu mesajul de eroare | ```bash curl http://localhost:8085/api/result/abc123def456789a ``` Error code 404 dacă job_id nu există. --- ### `GET /health` Healthcheck pentru Docker / load balancer / monitoring. ```bash curl http://localhost:8085/health ``` ```json { "status": "ok", "service": "forensic-features", "modules": ["m25", "m26", "m27", "m28", "m29"] } ``` --- ### `GET /metrics` Expune metrici Prometheus (contor + histogramă durată per rută) pentru monitoring. Format text Prometheus. ```bash curl http://localhost:8085/metrics ``` Returnează `503` cu `prometheus_client not installed` dacă dependența opțională `prometheus_client` nu e instalată. ## Notes ### Limita upload `client_max_size=2 * 1024**3` = **2 GB** per request. Pe video >2GB, preprocesezi cu ffmpeg înainte de upload. ### Concurența Job store este **in-memory** (Python dict). Asta înseamnă: - Joburile se pierd la restart container - Multi-replica fără sticky sessions = nu funcționează Pentru producție serioasă, înlocuiește `_jobs` din `api.py:46` cu un store persistent (Redis recomandat). ### CORS Activat default cu `*` pentru orice origine. Pentru producție public, restrictionează în `api.py:build_app()`. ### Rate limiting **NU există**. Pentru producție public, pune un reverse proxy (nginx, Traefik) în față cu rate limiting. ### Auth **NU există**. Endpoint-urile sunt deschise. Pentru producție, integrează cu sistemul tău de auth via reverse proxy sau adaugă middleware.