# 📡 Sibilla — Documentazione API REST > **Versione:** 0.2.0 > **Ultimo aggiornamento:** 18 Luglio 2026 --- ## Indice 1. [Panoramica](#panoramica) 2. [Health Check](#health-check) 3. [API Ask (Web)](#api-ask) 4. [API v1 Query (Programmatica)](#api-v1-query) 5. [Codici di errore](#codici-di-errore) 6. [Esempi curl](#esempi-curl) 7. [Integrazione Alexa Skill](#integrazione-alexa-skill) --- ## Panoramica Tutti gli endpoint sono accessibili su `http://localhost:5000` (o l'host/porta configurati). | Metodo | Endpoint | Descrizione | Formato | |--------|----------|-------------|---------| | `GET` | `/` | Interfaccia web HTML | `text/html` | | `GET` | `/api/health` | Health check | `application/json` | | `POST` | `/api/ask` | Ricerca + sintesi (web UI) | `application/json` | | `GET` | `/api/v1/query` | Ricerca + sintesi (API, query string) | `application/json` | | `POST` | `/api/v1/query` | Ricerca + sintesi (API, JSON body) | `application/json` | **CORS:** Tutti gli endpoint includono header CORS permissivi (`Access-Control-Allow-Origin: *`). --- ## Health Check ### `GET /api/health` Verifica lo stato del server e la configurazione. **Request:** ```bash curl http://localhost:5000/api/health ``` **Response `200 OK`:** ```json { "status": "ok", "version": "0.2.0", "gemini_configured": true, "degoog_url": "http://localhost:4444", "searxng_url": "http://localhost:8080" } ``` | Campo | Tipo | Descrizione | |-------|------|-------------| | `status` | `string` | Sempre `"ok"` se il server risponde | | `version` | `string` | Versione di Sibilla | | `gemini_configured` | `boolean` | `true` se `GOOGLE_API_KEY` è configurata | | `degoog_url` | `string` | URL dell'istanza Degoog | | `searxng_url` | `string` | URL dell'istanza SearXNG | --- ## API Ask ### `POST /api/ask` Endpoint principale per la ricerca con sintesi AI. Utilizzato dall'interfaccia web. **Request:** ```json { "query": "Qual è stato l'ultimo film sull'Odissea?", "num_sources": 5 } ``` | Parametro | Tipo | Default | Range | Descrizione | |-----------|------|---------|-------|-------------| | `query` | `string` | *richiesto* | — | La domanda da cercare | | `num_sources` | `integer` | `5` | `1–10` | Numero massimo di fonti da usare | **Response `200 OK`:** ```json { "answer": "L'ultimo adattamento cinematografico dell'**Odissea** è *The Return* (2024)...", "html_answer": "

L'ultimo adattamento cinematografico dell'Odissea è The Return (2024)...

", "speakable_text": "L'ultimo adattamento cinematografico dell'Odissea è The Return del 2024...", "sources": [ { "index": 1, "title": "The Return - Wikipedia", "url": "https://it.wikipedia.org/wiki/The_Return_(film_2024)", "snippet": "The Return è un film del 2024 diretto da Uberto Pasolini..." } ], "model": "gemini-2.5-flash", "search_engine": "degoog", "tokens_used": 1847, "error": null } ``` | Campo | Tipo | Descrizione | |-------|------|-------------| | `answer` | `string` | Risposta in formato **Markdown** | | `html_answer` | `string` | Risposta in **HTML** sicuro (sanitizzato) | | `speakable_text` | `string` | Risposta in **plain text**, ottimizzata per TTS | | `sources` | `array` | Lista delle fonti utilizzate | | `sources[].index` | `integer` | Numero della fonte (1-based) | | `sources[].title` | `string` | Titolo della pagina | | `sources[].url` | `string` | URL della fonte | | `sources[].snippet` | `string` | Estratto della pagina (max 200 caratteri) | | `model` | `string` | Modello LLM utilizzato | | `search_engine` | `string` | Motore di ricerca usato (`degoog`, `searxng`, `duckduckgo`, `none`) | | `tokens_used` | `integer` | Token totali consumati | | `error` | `string` or `null` | Eventuale messaggio di errore | **Response `400 Bad Request`:** ```json { "error": "La query non può essere vuota." } ``` **Response `502 Bad Gateway`:** ```json { "error": "Ricerca web fallita: Connection refused", "answer": "", "sources": [] } ``` --- ## API v1 Query ### `GET /api/v1/query` Endpoint semplificato per consumo programmatico. Accetta la query via query string. **Request:** ```bash curl "http://localhost:5000/api/v1/query?q=ultimo+film+sull%27Odissea" ``` ### `POST /api/v1/query` Stesso endpoint, con query nel body JSON. **Request:** ```json { "query": "Qual è stato l'ultimo film sull'Odissea?" } ``` | Parametro | Tipo | Default | Descrizione | |-----------|------|---------|-------------| | `q` (GET) | `string` | *richiesto* | La query di ricerca (URL-encoded) | | `query` (POST) | `string` | *richiesto* | La query di ricerca | **Response `200 OK`:** ```json { "query": "Qual è stato l'ultimo film sull'Odissea?", "answer": "L'ultimo adattamento è **The Return** (2024), diretto da Uberto Pasolini...", "speakable_text": "L'ultimo adattamento è The Return del 2024, diretto da Uberto Pasolini...", "sources": [ { "index": 1, "title": "The Return - Wikipedia", "url": "https://it.wikipedia.org/wiki/The_Return_(film_2024)" } ], "model": "gemini-2.5-flash", "search_engine": "degoog", "tokens_used": 1847 } ``` | Campo | Tipo | Descrizione | |-------|------|-------------| | `query` | `string` | Echo della query originale | | `answer` | `string` | Risposta in formato **Markdown** | | `speakable_text` | `string` | **Plain text** ottimizzato per sintesi vocale (TTS) | | `sources` | `array` | Fonti semplificate (solo `index`, `title`, `url`) | | `model` | `string` | Modello LLM utilizzato | | `search_engine` | `string` | Motore di ricerca usato | | `tokens_used` | `integer` | Token totali consumati | > **Nota:** Il formato è più compatto di `/api/ask` — pensato per API consumer come skill Alexa, Lambda function, o mobile app. **Response `400 Bad Request`:** ```json { "error": "Missing query parameter 'q'." } ``` **Response `502 Bad Gateway`:** ```json { "error": "Ricerca web fallita: Connection refused", "answer": "", "speakable_text": "" } ``` --- ## Codici di errore | HTTP Status | Significato | Quando | |-------------|-------------|--------| | `200` | OK | Richiesta completata con successo | | `400` | Bad Request | Query mancante o vuota | | `502` | Bad Gateway | Tutti i motori di ricerca sono down | **Errori interni gestiti:** - Se tutti i motori falliscono ma Gemini è disponibile → fallback a risposta solo LLM (200 OK) - Se Gemini non è configurato → `answer` vuoto con `error` nel JSON - Se Gemini restituisce errore → messaggio di errore inline in `answer` --- ## Esempi curl ### Health check ```bash curl -s http://localhost:5000/api/health | python -m json.tool ``` ### Ricerca via API v1 (GET) ```bash curl -s "http://localhost:5000/api/v1/query?q=cosa+sono+i+raggi+cosmici" | python -m json.tool ``` ### Ricerca via API v1 (POST) ```bash curl -s -X POST http://localhost:5000/api/v1/query \ -H "Content-Type: application/json" \ -d '{"query": "cosa sono i raggi cosmici"}' | python -m json.tool ``` ### Estrai solo la risposta speakable (per TTS) ```bash curl -s "http://localhost:5000/api/v1/query?q=raggi+cosmici" | python -c "import sys,json; print(json.load(sys.stdin)['speakable_text'])" ``` ### Ricerca via web UI (POST /api/ask) ```bash curl -s -X POST http://localhost:5000/api/ask \ -H "Content-Type: application/json" \ -d '{"query": "raggi cosmici", "num_sources": 3}' | python -m json.tool ``` ### Verifica errori ```bash # Query vuota → 400 curl -i "http://localhost:5000/api/v1/query?q=" # Motori down → 502 (se tutti falliscono) curl -i "http://localhost:5000/api/v1/query?q=test" ``` --- ## Integrazione Alexa Skill Ecco un esempio di come usare l'endpoint `/api/v1/query` da una Lambda Python per una skill Alexa: ```python import json import urllib.request SIBILLA_URL = "https://tuo-server.com/api/v1/query" def call_sibilla(query: str) -> dict: """Chiama Sibilla e restituisce la risposta parlata.""" params = urllib.parse.urlencode({"q": query}) url = f"{SIBILLA_URL}?{params}" with urllib.request.urlopen(url, timeout=30) as resp: data = json.loads(resp.read().decode()) return { "answer": data["answer"], "speakable_text": data["speakable_text"], "sources": data["sources"], } # In un handler Alexa: def lambda_handler(event, context): query = event["request"]["intent"]["slots"]["query"]["value"] result = call_sibilla(query) return { "version": "1.0", "response": { "outputSpeech": { "type": "SSML", "ssml": f"{result['speakable_text']}" }, "shouldEndSession": True } } ``` > **Nota:** Il campo `speakable_text` è già pulito da markup Markdown, URL, grassetti e codice — pronto per essere letto da un motore TTS.