eva.integration.report.v1 GET 200 / 304

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

PasoMétodoRutaCupo
1GET/api/v1/integrations/reports/index1 hit
2aGET/api/v1/integrations/reports/extract1 hit
2bPOST/api/v1/integrations/reports/extract-batchN 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ámetroDescripción
updated_since / updated_untilISO datetime sobre project_scenarios.updated_at. Metadata del escenario, no un delta de hallazgos. Rango invertido o fecha inválida → 422.
start_from / end_toISO date. Filas con fecha nula no entran si se usa el filtro.
project_codeMatch exacto normalizado (trim + mayúsculas).
client_codeAND extra. Service/client manager se intersecta con sus códigos.
qTexto laxo (código, nombre, título, scenario_key).
limit / offsetDefault 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ámetroObligatorioDescripción
project_idUUID del proyecto
scenario_idUUID del escenario de catálogo
report_typeEtiqueta 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):

CampoContenido
contracteva.integration.report.v1
profilereport_core
generated_atInstantánea UTC ISO-8601
report_typeEtiqueta solicitada (no filtra filas)
projectid, name, code
clientcode, name (sin emails). Puede ser null
scenarioid, 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

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

HTTPcodeCuándo
401MISSING_BEARER_TOKEN / INVALID_TOKENFalta Bearer, o el PAT es inválido, caducado o revocado
401TOKEN_TYPE_NOT_ALLOWEDJWT de sesión en vez de PAT
403TENANT_MISMATCHX-Tenant-Id distinto al tenant del PAT
403INTEGRATION_SCOPE_DENIEDEl PAT no tiene scope extract
403INTEGRATION_IP_DENIEDLa IP no está en allowed_cidrs del PAT
404NOT_FOUNDSolo el GET de un escenario: proyecto/escenario inexistente o sin permiso de reportes
422validaciónQuery/body inválido (fechas del índice, más de 10 ítems, report_type ausente, pares duplicados)
429INTEGRATION_RATE_LIMITEDCupo 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.