Extract report_core
Snapshot en vivo de reportería. Descubra escenarios con el índice, extraiga uno con el GET o hasta 10 con el batch. El GET de un escenario no cambia.
Dos pasos
| Paso | Método | Ruta | Cupo |
|---|---|---|---|
| 1 | GET | /api/v1/integrations/reports/index | 1 hit |
| 2a | GET | /api/v1/integrations/reports/extract | 1 hit |
| 2b | POST | /api/v1/integrations/reports/extract-batch | N hits (máx. 10) |
Scope extract cubre las tres. Índice y batch no usan ETag. JWT de sesión → 401 TOKEN_TYPE_NOT_ALLOWED.
Índice
GET /api/v1/integrations/reports/index
Lista lo que el PAT puede extraer: tenant del token + client_code para service/client manager. No recorta consultores por asignación/as_of. Vacío + paginación es válido. Sin vulns ni counts. Sin ETag. Cobra 1 hit.
| Parámetro | Descripción |
|---|---|
updated_since / updated_until | ISO datetime sobre project_scenarios.updated_at. Metadata del escenario, no un delta de hallazgos. Rango invertido o fecha inválida → 422. |
start_from / end_to | ISO date. Filas con fecha nula no entran si se usa el filtro. |
project_code | Match exacto normalizado (trim + mayúsculas). |
client_code | AND extra. Service/client manager se intersecta con sus códigos. |
q | Texto laxo (código, nombre, título, scenario_key). |
limit / offset | Default 50 / 0. Máximo 50. |
200: { items, total, limit, offset }. Cada ítem: project_id, scenario_id, project_code, project_name, client_code, scenario_key, scenario_title, start_date, end_date, updated_at.
curl -sS \
-H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
-H "Accept: application/json" \
"${EVA_API_BASE_URL}/api/v1/integrations/reports/index?project_code=${EVA_PROJECT_CODE}&limit=50"
Fixture: examples/extract_index.example.json.
Ruta (un escenario)
GET /api/v1/integrations/reports/extract
Scope: extract. Directo a Flask, sin BFF.
| Parámetro | Obligatorio | Descripción |
|---|---|---|
project_id | Sí | UUID del proyecto |
scenario_id | Sí | UUID del escenario de catálogo |
report_type | Sí | Etiqueta del snapshot: technical | retest | executive | preliminary. No recorta filas. |
Grano: un escenario. Sin picker de campos y sin SQL. Query params desconocidos se ignoran (no provocan 422).
Snapshot completo: sin filtro ni página en servidor
Extract v1 no acepta page, limit, status ni severity. El JSON trae vulnerabilities[], assets[], positive_aspects[], incidents[] y counts enteros. Si el cliente necesita un subconjunto, filtra en memoria tras el 200.
report_type es una etiqueta del snapshot (el mismo campo del contrato). No recorta hallazgos: un technical y un executive sobre el mismo escenario devuelven las mismas filas; solo cambia report_type (y generated_at).
Ejemplo curl
curl -sS \
-H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
-H "Accept: application/json" \
"${EVA_API_BASE_URL}/api/v1/integrations/reports/extract?project_id=${EVA_PROJECT_ID}&scenario_id=${EVA_SCENARIO_ID}&report_type=technical"
Con comprobación opcional de tenant:
curl -sS \
-H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
-H "X-Tenant-Id: ${EVA_TENANT_ID}" \
-H "Accept: application/json" \
"${EVA_API_BASE_URL}/api/v1/integrations/reports/extract?project_id=${EVA_PROJECT_ID}&scenario_id=${EVA_SCENARIO_ID}&report_type=technical"
Variables de laboratorio:
EVA_INTEGRATION_TOKEN= EVA_API_BASE_URL=http://localhost:8002 EVA_PROJECT_ID= EVA_SCENARIO_ID= EVA_REPORT_TYPE=technical EVA_TENANT_ID=
Scope extract. Fixtures en examples/.
ETag y revalidación (304)
Toda respuesta 200 incluye ETag (fuerte: "<sha256 hex>" del cuerpo JSON) y Cache-Control: private, no-cache. El cliente puede reenviar ese valor en If-None-Match. Si coincide (lista RFC 9110 o *), EVA responde 304 con cuerpo vacío y el mismo ETag. 304 no es un error: el snapshot, last_used y la auditoría de extract se ejecutan igual; solo se ahorra red.
ETAG=$(curl -sS -D - -o /tmp/report_core.json \
-H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
-H "Accept: application/json" \
"${EVA_API_BASE_URL}/api/v1/integrations/reports/extract?project_id=${EVA_PROJECT_ID}&scenario_id=${EVA_SCENARIO_ID}&report_type=technical" \
| awk -F': ' 'tolower($1)=="etag" {print $2}' | tr -d '\r')
curl -sS -D - -o /dev/null \
-H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
-H "If-None-Match: ${ETAG}" \
-H "Accept: application/json" \
"${EVA_API_BASE_URL}/api/v1/integrations/reports/extract?project_id=${EVA_PROJECT_ID}&scenario_id=${EVA_SCENARIO_ID}&report_type=technical"
El segundo curl debe mostrar HTTP/1.1 304 si el cuerpo no cambió. generated_at forma parte del JSON: un snapshot posterior con otro instante suele llevar otro ETag. Índice y batch no revalidan con ETag.
Batch
POST /api/v1/integrations/reports/extract-batch
Hasta 10 ítems. report_type en la raíz o en el ítem (el del ítem gana). Pares duplicados, más de 10, UUID inválido o tipo ausente → 422. Sin ETag. Cobra N hits. Si no hay cupo → 429 y no se extrae nada. La respuesta es 200 con ok / not_found por ítem; un escenario ausente no tumba el lote. report es el mismo report_core que el GET.
{
"report_type": "technical",
"items": [
{ "project_id": "…", "scenario_id": "…" },
{ "project_id": "…", "scenario_id": "…", "report_type": "retest" }
]
}
curl -sS -X POST \
-H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"report_type":"technical","items":[{"project_id":"'"${EVA_PROJECT_ID}"'","scenario_id":"'"${EVA_SCENARIO_ID}"'"}]}' \
"${EVA_API_BASE_URL}/api/v1/integrations/reports/extract-batch"
Autorización
El escenario debe pertenecer al tenant del PAT y el usuario dueño del token debe poder ver reportes ahí (el mismo recorte que el catálogo de reportes). Si no existe o no hay acceso, la respuesta es 404 (no se filtra existencia hacia otros tenants).
El JSON viaja en claro por HTTPS. El PAT es el control de acceso. EVA no re-cifra el cuerpo ni lo escribe en logs.
Contrato report_core
Campos de identidad (siempre presentes en una respuesta 200):
| Campo | Contenido |
|---|---|
contract | eva.integration.report.v1 |
profile | report_core |
generated_at | Instantánea UTC ISO-8601 |
report_type | Etiqueta solicitada (no filtra filas) |
project | id, name, code |
client | code, name (sin emails). Puede ser null |
scenario | id, title, scenario_key, family_key, variant_key, status |
Hallazgos (vulnerabilities[]): id, title, severity, status, category, cvss_score, cvss_vector, cwes, description, impact, recommendation, linked_asset_ids.
Activos (assets[]): id, asset_type, value, label.
Conteos (counts): vulnerabilities, assets, evidences, positive_aspects, incidents.
Aspectos positivos: id, title, description, fechas. Incidentes: id, caption, description, fechas.
Excluido de forma explícita
storage_uri,storage_backend, checksumsclient_emails- Evidencias binarias (ni bytes, ni filenames de storage, ni mime/size)
- Actores internos (
created_by,updated_by,pm_id, confirmadores) external_storage_*- Eventos de auditoría y PII de usuario EVA
counts.evidences es un recuento; no viajan los binarios.
Ejemplo sanitizado
Descargue el fixture completo: examples/report_core.example.json.
{
"contract": "eva.integration.report.v1",
"profile": "report_core",
"generated_at": "2026-08-20T16:00:00+00:00",
"report_type": "technical",
"project": {
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example web application assessment",
"code": "EX-WEB-001"
},
"client": { "code": "ACME", "name": "Acme Corporation" },
"scenario": {
"id": "22222222-2222-4222-8222-222222222222",
"title": "External web application",
"scenario_key": "web.external.default",
"family_key": "web",
"variant_key": "external",
"status": "execution"
},
"counts": {
"vulnerabilities": 1,
"assets": 1,
"evidences": 2,
"positive_aspects": 1,
"incidents": 1
}
}
Errores de extract
| HTTP | code | Cuándo |
|---|---|---|
| 401 | MISSING_BEARER_TOKEN / INVALID_TOKEN | Falta Bearer, o el PAT es inválido, caducado o revocado |
| 401 | TOKEN_TYPE_NOT_ALLOWED | JWT de sesión en vez de PAT |
| 403 | TENANT_MISMATCH | X-Tenant-Id distinto al tenant del PAT |
| 403 | INTEGRATION_SCOPE_DENIED | El PAT no tiene scope extract |
| 403 | INTEGRATION_IP_DENIED | La IP no está en allowed_cidrs del PAT |
| 404 | NOT_FOUND | Solo el GET de un escenario: proyecto/escenario inexistente o sin permiso de reportes |
| 422 | validación | Query/body inválido (fechas del índice, más de 10 ítems, report_type ausente, pares duplicados) |
| 429 | INTEGRATION_RATE_LIMITED | Cupo extract (60/min por token). Índice/GET = 1 hit; batch = N. Cabecera Retry-After |
El batch responde 200 con not_found por ítem; no hay 404 de lote. Tras revocar el token, el mismo curl responde 401. 304 (If-None-Match) es revalidación correcta, no un error. Extract tiene un cupo de 60 peticiones/minuto por PAT: índice y GET cuentan 1; batch cuenta N (máx. 10). Retest no usa este cupo ni allowed_cidrs.