Manual de Uso — Sherlock API REST¶
Framework: FastAPI 0.135+
Entry point: uvicorn sherlock_docs.interfaces.api.main:app
Puerto por defecto: 8000
Swagger UI: http://localhost:8000/docs
OpenAPI spec: http://localhost:8000/openapi.json
Requisito: pip install -e ".[api]"
Endpoints: 22 (7 Foundation S24 + 15 Completa S25)
Autenticacion: JWT Bearer (HS256)
Inicio Rapido¶
Arrancar la API¶
# Desarrollo (auth deshabilitado, auto-reload)
SHERLOCK_AUTH_ENABLED=0 uvicorn sherlock_docs.interfaces.api.main:app --port 8000 --reload
# Produccion
SHERLOCK_JWT_SECRET=clave-segura-produccion \
SHERLOCK_DATABASE_PATH=data/database/sherlock.db \
SHERLOCK_AUTH_ENABLED=1 \
uvicorn sherlock_docs.interfaces.api.main:app --host 0.0.0.0 --port 8000
Verificar que funciona¶
{
"status": "healthy",
"version": "1.0.0",
"components": [
{"name": "database", "status": "ok", "detail": "42 documents"},
{"name": "ocr", "status": "ok", "detail": null},
{"name": "ner", "status": "ok", "detail": null},
{"name": "dedup", "status": "ok", "detail": "42 indexed"}
]
}
Configuracion¶
Todas las variables de entorno usan el prefijo SHERLOCK_:
| Variable | Descripcion | Default |
|---|---|---|
SHERLOCK_DATABASE_PATH |
Ruta a la base de datos SQLite | data/database/sherlock.db |
SHERLOCK_JWT_SECRET |
Clave secreta para firmar tokens JWT | dev-secret-change-in-production |
SHERLOCK_JWT_ALGORITHM |
Algoritmo JWT | HS256 |
SHERLOCK_JWT_EXPIRY_HOURS |
Horas de validez del token | 8 |
SHERLOCK_JWT_MAX_REFRESHES |
Maximo de refreshes por sesion | 5 |
SHERLOCK_MAX_UPLOAD_SIZE_MB |
Tamano maximo de archivo (MB) | 50 |
SHERLOCK_AUTH_ENABLED |
Activar autenticacion (true/false) |
true |
SHERLOCK_AUTH_CONFIG_PATH |
Ruta al YAML con credenciales bcrypt | .streamlit/auth_config.yaml |
SHERLOCK_CORS_ORIGINS |
Origenes permitidos CORS (JSON array) | ["http://localhost:3000","http://localhost:8501"] |
SHERLOCK_API_PREFIX |
Prefijo de ruta para todos los endpoints | /api/v1 |
SHERLOCK_PREWARM_MODELS |
Pre-cargar modelos ML al iniciar | false |
Tambien se puede usar un archivo .env en la raiz del proyecto:
Autenticacion (JWT Bearer)¶
La API usa tokens JWT con el esquema Bearer. Todos los endpoints excepto /health y /auth/login requieren autenticacion.
Obtener token¶
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "mi-password"}'
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 28800
}
Usar token en peticiones¶
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
curl http://localhost:8000/api/v1/documents \
-H "Authorization: Bearer $TOKEN"
Renovar token¶
Limite: Maximo 5 refreshes por sesion (configurable via
SHERLOCK_JWT_MAX_REFRESHES). Despues se requiere nuevo login.
Ver usuario actual¶
Credenciales: La API reutiliza las mismas credenciales bcrypt de la GUI Streamlit. Se leen del archivo
.streamlit/auth_config.yamlo de la variable de entornoAUTH_USERS(JSON con hashes bcrypt).
Formato de Respuestas¶
Respuesta exitosa¶
Para listas paginadas:
Respuesta de error¶
Codigos HTTP¶
| Codigo | Significado | Cuando |
|---|---|---|
| 200 | OK | Operacion exitosa |
| 201 | Created | Documento creado (manual, import) |
| 202 | Accepted | OCR encolado (process) |
| 204 | No Content | Eliminacion exitosa |
| 401 | Unauthorized | Token invalido o faltante |
| 404 | Not Found | Documento/task no existe |
| 413 | Payload Too Large | Archivo excede limite de tamano |
| 422 | Unprocessable Entity | Datos de request invalidos |
| 500 | Internal Server Error | Error inesperado del servidor |
| 503 | Service Unavailable | ServiceContainer no inicializado |
Endpoints (22 totales)¶
1. GET /api/v1/health — Estado del sistema¶
Verifica la disponibilidad de todos los componentes: base de datos, OCR, NER y deteccion de duplicados.
Autenticacion: No requerida
| Campo | Tipo | Descripcion |
|---|---|---|
status |
string | healthy, degraded o unhealthy |
version |
string | Version de la API |
components |
array | Lista de componentes con su estado |
Uso recomendado: Configurar como health check en Docker, EasyPanel, o balanceador de carga.
2. POST /api/v1/auth/login — Iniciar sesion¶
Autenticacion: No requerida Rate limit: 5 intentos por minuto por IP (S26 H-02)
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password123"}'
| Parametro | Tipo | Requerido | Descripcion |
|---|---|---|---|
username |
string | Si (min 1 char) | Nombre de usuario |
password |
string | Si (min 1 char) | Contrasena en texto plano |
Respuesta exitosa (200):
Errores: 401 (credenciales invalidas), 422 (campos vacios), 429 (rate limit excedido)
3. POST /api/v1/auth/refresh — Renovar token¶
Genera un nuevo JWT con la misma identidad. Maximo 5 refreshes por sesion.
Error (401): Limite de refreshes alcanzado
4. GET /api/v1/auth/me — Usuario actual¶
5. GET /api/v1/documents — Listar documentos¶
Lista paginada de documentos ordenados por fecha de creacion (mas recientes primero).
curl "http://localhost:8000/api/v1/documents?page=1&per_page=20" \
-H "Authorization: Bearer $TOKEN"
| Parametro | Tipo | Default | Descripcion |
|---|---|---|---|
page |
int | 1 | Numero de pagina (>=1) |
per_page |
int | 20 | Documentos por pagina (1-100) |
Respuesta (200):
{
"data": [
{
"id": "abc-123",
"file_name": "tutela_2024.pdf",
"document_type": "tutela",
"status": "processed",
"demandante": "Juan Garcia",
"demandado": "EPS Sanitas",
"radicado": "05001-02-03-000-2024-00001-00",
"created_at": "2024-03-20 14:30:00"
}
],
"meta": {"total": 142, "page": 1, "per_page": 20, "total_pages": 8}
}
Equivalente CLI:
sherlock list --limit 20
6. GET /api/v1/documents/{id} — Detalle de documento¶
Retorna toda la informacion de un documento incluyendo entidades extraidas.
Respuesta (200):
{
"data": {
"id": "abc-123",
"file_name": "tutela_2024.pdf",
"document_type": "tutela",
"status": "processed",
"demandante": "Juan Garcia Rodriguez",
"demandado": "EPS Sanitas",
"radicado": "05001-02-03-000-2024-00001-00",
"juzgado": "Juzgado Primero Civil Municipal de Bello",
"cedula": "1234567890",
"correo": "juan.garcia@email.com",
"ocr_confidence": 0.87,
"is_manual_entry": false,
"created_at": "2024-03-20 14:30:00"
}
}
Error (404): Documento no encontrado
Equivalente CLI:
sherlock detail abc-123
7. GET /api/v1/documents/stats/dashboard — Estadisticas¶
Retorna las estadisticas del dashboard (totales, por tipo, por estado).
Equivalente CLI:
sherlock stats --format json
8. POST /api/v1/documents/process — Procesar PDF (async)¶
Sube un PDF y encola procesamiento asincrono OCR+NER+dedup. Devuelve 202 con task_id para polling.
curl -X POST http://localhost:8000/api/v1/documents/process \
-H "Authorization: Bearer $TOKEN" \
-F "file=@tutela_001.pdf" \
-F "doc_type=tutela"
| Parametro | Tipo | Requerido | Default | Descripcion |
|---|---|---|---|---|
file |
UploadFile | Si | — | Archivo PDF a procesar |
doc_type |
string | No | tutela |
Tipo de documento |
Respuesta (202 Accepted):
Errores: 413 (archivo > 50MB), 422 (no es PDF)
Equivalente CLI:
sherlock process archivo.pdf
9. GET /api/v1/tasks/{task_id} — Estado de tarea async¶
Consulta el estado de una tarea asincrona (polling para procesamiento OCR).
Respuesta (200) — Tarea completada:
{
"task_id": "abc123def456",
"status": "completed",
"progress": 1.0,
"result": {
"id": "doc-uuid",
"file_name": "tutela_001.pdf",
"demandante": "Juan Perez",
"demandado": "EPS Salud",
"ocr_confidence": 0.87,
"processing_time_ms": 45000
},
"error": null,
"created_at": "2026-03-25 10:00:00+00:00",
"completed_at": "2026-03-25 10:00:45+00:00"
}
Valores de status: pending, processing, completed, failed
Error (404): Task no encontrado o expirado
10. PUT /api/v1/documents/{id}/validate — Guardar correcciones NER¶
Guarda las correcciones de entidades validadas por el operador humano.
curl -X PUT http://localhost:8000/api/v1/documents/abc-123/validate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"validated_fields": {"demandante": "Juan Alberto Perez", "cedula": "1234567890"},
"original_fields": {"demandante": "Juan A. Perez", "cedula": "123456789O"},
"corrected_by": "operator"
}'
Respuesta (200):
Errores: 404 (documento no existe), 500 (error guardando)
Nota: Los campos string se sanitizan contra XSS automaticamente.
Equivalente CLI:
sherlock update <doc_id> <campo> <valor>
11. POST /api/v1/documents/{id}/recurring — Entidades recurrentes¶
Verifica si las entidades (cedula, correo, demandante) aparecen en otros documentos.
curl -X POST http://localhost:8000/api/v1/documents/abc-123/recurring \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"cedula": "1234567890", "correo": "juan@mail.com", "demandante": "Juan Perez"}'
Respuesta (200):
{
"data": {
"cedula_matches": [
{"document_id": "other-doc", "file_name": "tutela_005.pdf", "demandante": "Juan Perez"}
],
"correo_matches": [],
"demandante_matches": [],
"total_matches": 1
}
}
Equivalente CLI:
sherlock find --correo <correo>/sherlock find --accionante <nombre>
12. POST /api/v1/documents/manual — Entrada manual¶
Crea un documento manualmente sin procesamiento OCR.
curl -X POST http://localhost:8000/api/v1/documents/manual \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"numero_acta": "2026-0001",
"demandante": "Maria Lopez",
"demandado": "EPS Salud Total",
"doc_type": "tutela",
"juzgado": "Juzgado 1 Civil Municipal de Bello",
"cedula": "9876543210"
}'
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
numero_acta |
string | Si | Numero de acta de reparto |
demandante |
string | Si | Nombre del accionante |
demandado |
string | Si | Nombre del demandado |
doc_type |
string | No | Tipo de documento (default: tutela) |
radicado |
string | No | Numero de radicado |
juzgado |
string | No | Juzgado asignado |
cedula |
string | No | Cedula del demandante |
correo |
string | No | Correo del demandante |
direccion_demandante |
string | No | Direccion del demandante |
direccion_demandado |
string | No | Direccion del demandado |
observaciones |
string | No | Notas u observaciones |
etiquetas |
string | No | Etiquetas separadas por coma |
Respuesta (201 Created):
{
"data": {
"document_id": "new-doc-uuid",
"numero_acta": "2026-0001",
"status": "manual_entry",
"is_manual_entry": true
}
}
Errores: 422 (campos requeridos faltantes)
Nota: Todos los campos string se sanitizan contra XSS automaticamente.
13. GET /api/v1/documents/search — Busqueda full-text¶
Busqueda full-text usando SQLite FTS5 con paginacion.
curl "http://localhost:8000/api/v1/documents/search?q=tutela+salud&page=1&page_size=50" \
-H "Authorization: Bearer $TOKEN"
| Parametro | Tipo | Default | Descripcion |
|---|---|---|---|
q |
string | "" |
Termino de busqueda FTS5 |
page |
int | 1 | Numero de pagina |
page_size |
int | 50 | Items por pagina (1-200) |
sort_by |
string | Relevancia |
Campo de ordenamiento |
Respuesta (200):
{
"data": {
"documents": [
{
"id": "doc-uuid",
"file_name": "tutela_001.pdf",
"demandante": "Juan Perez",
"demandado": "EPS Salud",
"document_type": "tutela",
"status": "validated"
}
],
"total": 150,
"page": 1,
"page_size": 50
}
}
Equivalente CLI:
sherlock search "termino" --limit N
14. POST /api/v1/documents/{id}/duplicates — Detectar duplicados¶
Detecta documentos duplicados usando el ensemble hibrido (MinHash + TF-IDF + Entity matcher).
curl -X POST http://localhost:8000/api/v1/documents/abc-123/duplicates \
-H "Authorization: Bearer $TOKEN"
Respuesta (200):
{
"data": {
"candidates": [
{
"document_id": "other-doc",
"similarity": 0.85,
"file_name": "tutela_005.pdf",
"details": {
"minhash_score": 0.80,
"tfidf_score": 0.88,
"entity_score": 0.90,
"ensemble_score": 0.85
}
}
],
"total": 1
}
}
Errores: 404 (documento no encontrado), 500 (error de deteccion)
15. POST /api/v1/duplicates/verify — Verificar texto contra corpus¶
Verifica un fragmento de texto libre contra el corpus existente.
curl -X POST http://localhost:8000/api/v1/duplicates/verify \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"text": "El accionante interpone accion de tutela contra EPS...",
"document_type": "tutela"
}'
| Campo | Tipo | Requerido | Default | Descripcion |
|---|---|---|---|---|
text |
string | Si | — | Texto a verificar |
document_type |
string | No | tutela |
Tipo para umbrales |
Nota (S26 D-08): El campo
thresholdfue eliminado. Los umbrales se determinan internamente segundocument_typey son configurables viaPUT /config/dedup.
Respuesta (200):
{
"data": {
"candidates": [
{
"document_id": "existing-doc",
"similarity": 0.78,
"file_name": "tutela_032.pdf"
}
],
"total": 1
}
}
Errores: 422 (texto vacio)
16. POST /api/v1/import/excel — Importar desde Excel¶
Importa documentos desde archivo Excel (.xlsx/.xls).
curl -X POST http://localhost:8000/api/v1/import/excel \
-H "Authorization: Bearer $TOKEN" \
-F "file=@documentos.xlsx" \
-F "update_existing=false"
| Parametro | Tipo | Requerido | Default | Descripcion |
|---|---|---|---|---|
file |
UploadFile | Si | — | Archivo Excel |
update_existing |
bool | No | false |
Actualizar existentes |
Respuesta (201 Created):
Errores: 413 (> 50MB), 422 (no es Excel)
Equivalente CLI:
sherlock import archivo.xlsx
17. GET /api/v1/export/excel — Exportar a Excel¶
Exporta documentos a archivo Excel (descarga directa .xlsx).
curl "http://localhost:8000/api/v1/export/excel?filename=indice_marzo&date_from=2026-03-01&date_to=2026-03-25" \
-H "Authorization: Bearer $TOKEN" \
-o indice_marzo.xlsx
| Parametro | Tipo | Default | Descripcion |
|---|---|---|---|
filename |
string | indice_documentos |
Nombre del archivo (sin extension) |
date_from |
string | "" |
Fecha inicio filtro |
date_to |
string | "" |
Fecha fin filtro |
Respuesta (200): Descarga directa del archivo .xlsx
Errores: 404 (sin documentos para exportar)
Equivalente CLI:
sherlock export --filename nombre
18. GET /api/v1/config/dedup — Configuracion de duplicados¶
Obtiene la configuracion actual del ensemble de deteccion de duplicados.
Respuesta (200):
{
"data": {
"minhash_weight": 0.25,
"tfidf_weight": 0.40,
"entity_weight": 0.35,
"tutela_threshold": 0.70,
"habeas_threshold": 0.65,
"default_threshold": 0.70
}
}
19. PUT /api/v1/config/dedup — Actualizar config duplicados¶
Actualiza pesos y umbrales del ensemble. Los 3 pesos deben sumar 1.0.
Requiere rol admin (S26 H-01 RBAC)
curl -X PUT http://localhost:8000/api/v1/config/dedup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"minhash_weight": 0.30,
"tfidf_weight": 0.35,
"entity_weight": 0.35
}'
Errores: 403 (no es admin), 422 (pesos no suman 1.0)
20. GET /api/v1/config/ner — Configuracion NER¶
Obtiene pesos del extractor NER (ensemble de 5 extractores).
Respuesta (200):
21. DELETE /api/v1/documents/{id} — Eliminar documento¶
Elimina un documento por su ID.
Requiere rol admin (S26 H-01 RBAC)
Respuesta (204): Sin cuerpo de respuesta
Errores: 403 (no es admin), 404 (documento no encontrado)
Equivalente CLI:
sherlock delete <doc_id>
22. GET /api/v1/stats/processing — Metricas de procesamiento¶
Obtiene metricas de tiempos de procesamiento (promedios OCR, NER, dedup).
Respuesta (200):
{
"data": {
"total_documents": 150,
"total_processed": 120,
"total_manual": 30,
"avg_processing_time_ms": 35000,
"documents_today": 5,
"documents_this_week": 25
}
}
Equivalente CLI:
sherlock stats/sherlock stats-daily --days N
Tabla Resumen — 22 Endpoints¶
| # | Metodo | Endpoint | Auth | Sprint | Descripcion |
|---|---|---|---|---|---|
| 1 | GET | /health |
No | S24 | Estado del sistema |
| 2 | POST | /auth/login |
No | S24 | Iniciar sesion |
| 3 | POST | /auth/refresh |
Si | S24 | Renovar token (max 5) |
| 4 | GET | /auth/me |
Si | S24 | Usuario actual |
| 5 | GET | /documents |
Si | S24 | Listar documentos (paginado) |
| 6 | GET | /documents/{id} |
Si | S24 | Detalle de documento |
| 7 | GET | /documents/stats/dashboard |
Si | S24 | Estadisticas dashboard |
| 8 | POST | /documents/process |
Si | S25 | Procesar PDF async (202) |
| 9 | GET | /tasks/{id} |
Si | S25 | Estado tarea async |
| 10 | PUT | /documents/{id}/validate |
Si | S25 | Correcciones NER |
| 11 | POST | /documents/{id}/recurring |
Si | S25 | Entidades recurrentes |
| 12 | POST | /documents/manual |
Si | S25 | Entrada manual |
| 13 | GET | /documents/search |
Si | S25 | Busqueda FTS5 |
| 14 | POST | /documents/{id}/duplicates |
Si | S25 | Detectar duplicados |
| 15 | POST | /duplicates/verify |
Si | S25 | Verificar texto vs corpus |
| 16 | POST | /import/excel |
Si | S25 | Importar Excel |
| 17 | GET | /export/excel |
Si | S25 | Exportar Excel |
| 18 | GET | /config/dedup |
Si | S25 | Config duplicados |
| 19 | PUT | /config/dedup |
Si | S25 | Actualizar config dedup |
| 20 | GET | /config/ner |
Si | S25 | Config NER |
| 21 | DELETE | /documents/{id} |
Si | S25 | Eliminar documento |
| 22 | GET | /stats/processing |
Si | S25 | Metricas procesamiento |
Mapeo CLI a API¶
| Accion | CLI | API |
|---|---|---|
| Ver estado del sistema | sherlock health |
GET /health |
| Listar documentos | sherlock list |
GET /documents |
| Ver detalle | sherlock detail <id> |
GET /documents/{id} |
| Estadisticas | sherlock stats |
GET /documents/stats/dashboard |
| Buscar | sherlock search "texto" |
GET /documents/search?q=texto |
| Procesar PDF | sherlock process archivo.pdf |
POST /documents/process |
| Validar campos | sherlock update <id> <campo> <val> |
PUT /documents/{id}/validate |
| Entrada manual | (GUI ManualEntry) | POST /documents/manual |
| Buscar recurrentes | sherlock find --correo <correo> |
POST /documents/{id}/recurring |
| Detectar duplicados | (auto en process) | POST /documents/{id}/duplicates |
| Verificar texto | (no disponible) | POST /duplicates/verify |
| Importar Excel | sherlock import archivo.xlsx |
POST /import/excel |
| Exportar Excel | sherlock export |
GET /export/excel |
| Config duplicados | (no disponible) | GET/PUT /config/dedup |
| Config NER | (no disponible) | GET /config/ner |
| Eliminar documento | sherlock delete <id> |
DELETE /documents/{id} |
| Metricas procesamiento | sherlock stats-daily |
GET /stats/processing |
Seguridad¶
| Aspecto | Implementacion |
|---|---|
| Autenticacion | JWT Bearer tokens (HS256, 8h expiry) |
| Credenciales | Hashes bcrypt (reutiliza GOB-05) |
| Token refresh | Max 5 por sesion (limitacion conocida H-07: client-side count) |
| RBAC | require_admin en DELETE y PUT /config/dedup (S26 H-01) |
| Rate limiting | 5 intentos/min por IP en login (S26 H-02) |
| Security headers | X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, Permissions-Policy, HSTS en prod (S26 M-02) |
| CORS | Restrictivo: solo GET/POST/PUT/DELETE/OPTIONS, headers Authorization/Content-Type/Accept (S26 M-03) |
| Swagger | Deshabilitado en produccion (ENVIRONMENT=production) (S26 M-06) |
| File upload | Max 50MB, extension validada, path traversal prevenido, filename sanitizado (S26 M-08) |
| Temp files | Cleanup automatico en finally blocks |
| XSS | Sanitizacion HTML + protocolos URI peligrosos + null bytes (S26 HALL-05) |
| SQL Injection | Queries parametrizadas (nunca string format) |
| Error leaking | Global exception handler, sin stack traces al cliente |
| Audit trail | Todas las operaciones registradas via get_logger |
Nota: En desarrollo, se puede desactivar la autenticacion con
SHERLOCK_AUTH_ENABLED=0. Nunca usar esto en produccion.
Swagger UI¶
La documentacion interactiva esta disponible en http://localhost:8000/docs:
- Prueba cualquier endpoint directamente desde el navegador
- Haz click en "Authorize" para ingresar tu JWT token
- Los schemas de request/response se muestran automaticamente
- Descarga la especificacion OpenAPI en
/openapi.json
La especificacion OpenAPI es consumible por NSwag/Refitter (C# .NET), openapi-generator, o Postman.
Arquitectura Interna¶
Cliente HTTP (.NET, curl, Postman)
|
v
FastAPI (uvicorn :8000)
|
+-- routes/health.py -> Sin auth
+-- routes/auth.py -> JWT login/refresh/me
+-- routes/search.py -> Busqueda FTS5
+-- routes/process.py -> Upload + async OCR (TaskManager)
+-- routes/manual.py -> Entrada manual
+-- routes/documents.py -> List, detail, stats
+-- routes/validate.py -> Correcciones NER + recurring
+-- routes/duplicates.py -> Dedup detect + verify
+-- routes/import_export.py -> Excel import/export
+-- routes/config_routes.py -> Config dedup/NER + stats (delete movido a documents S26)
+-- routes/tasks.py -> Task polling
|
v
dependencies.py
|
+-- get_container() -> ServiceContainer (14 properties)
+-- get_current_user() -> JWT payload
|
v
Application Services (6 servicios + 3 use cases)
|
v
SQLite Repository (WAL mode)
La API no accede directamente a la base de datos. Toda la logica pasa por los Application Services del ServiceContainer.
Notas Tecnicas¶
- API y Streamlit pueden ejecutarse simultaneamente (puertos 8000 y 8501)
- Ambos comparten la misma base de datos SQLite en modo WAL
- El
ServiceContainerse inicializa una vez al arrancar uvicorn - Los modelos ML se pueden pre-cargar con
SHERLOCK_PREWARM_MODELS=1 - Logging via
get_logger(__name__)en todos los modulos API - OCR se ejecuta en ThreadPoolExecutor (max 2 workers) por ser CPU-intensive
Version: 2.1 — 22 endpoints completos (S24 Foundation + S25 Completa + S25.1 Security + S26 Audit) Fecha: 2026-03-25 Endpoints activos: 22/22 funcionales (HALL-01 corregido en S26) Cambios S26: RBAC (H-01), rate limiting (H-02), security headers (M-02), CORS restrictivo (M-03), Swagger condicional (M-06), threshold eliminado de verify (D-08), delete movido a documents router (M-09), datetime ISO 8601 (L-03)