IntGuard API v1

API REST pública para automatizar escaneos de seguridad desde tu CI/CD, scripts o dashboards. Disponible a partir del plan Pro.

Tabla de contenidos 1. Quick start 2. Autenticación 3. Rate limits 4. Endpoints 5. Códigos de error 6. Versionado 7. Webhooks
→ Crear mi primera API key Swagger UI interactivo ↗ OpenAPI spec (JSON) ↗

1. Quick start

En 3 pasos lanzas tu primer escaneo automatizado:

  1. Crea una API key en tu panel (necesitas plan Pro o superior).
  2. Cópiala — solo se muestra una vez. Empieza por sks_live_.
  3. Llama al endpoint con la key como Bearer token.
# Lanza un escaneo asíncrono. Devuelve 202 con scan_id.
curl -X POST https://intguard.co/api/v1/scans \
     -H "Authorization: Bearer sks_live_..." \
     -H "Content-Type: application/json" \
     -d '{"target": "https://example.com"}'

# Polling del estado
curl https://intguard.co/api/v1/scans/42 \
     -H "Authorization: Bearer sks_live_..."
import requests

API_KEY = "sks_live_..."
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Lanzar escaneo
r = requests.post(
    "https://intguard.co/api/v1/scans",
    headers=HEADERS,
    json={"target": "https://example.com"},
    timeout=10,
)
assert r.status_code == 202
scan_id = r.json()["scan_id"]

# Consultar resultado (después de ~30-60s)
res = requests.get(
    f"https://intguard.co/api/v1/scans/{scan_id}",
    headers=HEADERS, timeout=10,
)
print(res.json()["score"])
# {"numeric": 87, "letter": "B"}
const API_KEY = "sks_live_...";
const headers = {
    "Authorization": `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
};

// Lanzar escaneo
const resp = await fetch("https://intguard.co/api/v1/scans", {
    method: "POST",
    headers,
    body: JSON.stringify({ target: "https://example.com" }),
});
const { scan_id } = await resp.json();

// Polling
const result = await fetch(
    `https://intguard.co/api/v1/scans/${scan_id}`,
    { headers }).then(r => r.json());
console.log(result.score);

2. Autenticación

Todas las peticiones a /api/v1/* requieren el header:

Authorization: Bearer sks_live_<32 chars>

Formato de las keys:

⚠️ Seguridad: trata tu API key como una contraseña. No la subas a Git, no la pegues en issues públicos, no la hardcodees en frontend. Si crees que se ha comprometido, revócala desde tu panel.

3. Rate limits

El rate limit se aplica por API key, no por IP:

PlanRequests/hora
Free / StandardNo disponible
Basic (legacy)10 req/h
Pro100 req/h
Business / Agency1000 req/h

Cada respuesta incluye estos headers:

X-RateLimit-Limit:     100
X-RateLimit-Remaining: 87
X-RateLimit-Reset:     1762000000   # Unix timestamp del reset

Al exceder el límite recibes 429 Too Many Requests con el cuerpo de error estandarizado.

4. Endpoints

Lista resumida. Para ejemplos completos usa el Swagger UI:

MétodoRutaDescripción
POST /api/v1/scans Lanza un escaneo nuevo (202)
GET /api/v1/scans Lista paginada de escaneos del usuario
GET /api/v1/scans/{id} Detalle de un escaneo
DELETE/api/v1/scans/{id} Borra un escaneo
GET /api/v1/scans/{id}/findingsHallazgos del escaneo
POST /api/v1/domains Genera token de verificación TXT
GET /api/v1/domains Lista dominios + estado verificación
GET /api/v1/account Info de cuenta y rate limits

5. Códigos de error

Formato estandarizado en todas las respuestas de error:

{
  "error": {
    "code":       "RATE_LIMIT_EXCEEDED",
    "message":    "Rate limit exceeded for plan basic...",
    "details":    { ... },
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
StatusCodeSignificado
400INVALID_REQUESTBody o parámetros inválidos
401UNAUTHORIZEDFalta header Authorization
401INVALID_API_KEYKey no existe
401API_KEY_REVOKEDKey revocada
403FORBIDDEN_PLANTu plan no incluye API
403INSUFFICIENT_SCOPELa key no tiene el scope requerido
404NOT_FOUNDRecurso no encontrado o no propio
429RATE_LIMIT_EXCEEDEDHas superado tu cuota
429QUOTA_EXCEEDEDHas agotado tu cuota mensual de escaneos
500INTERNALError inesperado del servidor
503INTERNALBackend temporalmente no disponible

6. Versionado

La versión va en la ruta: /api/v1/. Cuando saquemos v2 anunciaremos un periodo de deprecación de al menos 6 meses con el header HTTP Sunset en cada respuesta.

Cambios no breaking (campos nuevos en respuestas, endpoints nuevos) se hacen dentro de v1 sin previo aviso.

7. Webhooks

Registra una URL HTTPS desde tu panel de webhooks y recibirás un POST en JSON cuando ocurran los eventos a los que te hayas suscrito.

Eventos disponibles

EventoCuándo se dispara
scan.completed Acaba un escaneo (siempre, haya findings o no)
finding.critical Hay al menos 1 finding critical/high en el escaneo
scan.failed Un escaneo falla y no llega a generar findings
test Evento manual disparado desde el botón Test

Estructura del payload

{
  "id":         "evt_a1b2c3...",
  "event":      "scan.completed",
  "version":    1,
  "created_at": 1730000000,
  "data": {
    "scan_id":       42,
    "domain":        "tudominio.com",
    "score":         { "numeric": 78, "letter": "B" },
    "findings_count":{ "critical": 0, "high": 2, "medium": 5,
                       "low": 1, "info": 0 },
    "dashboard_url": "https://intguard.co/dashboard?dominio=tudominio.com"
  }
}

Verificar la firma HMAC (importante)

Cada petición incluye un header X-IntGuard-Signature de la forma t=<unix-ts>,v1=<hmac-sha256-hex>. Antes de procesar el evento reconstruye signed_payload = f"{t}.{body}", calcula HMAC-SHA256 con tu secret y compara en tiempo constante. Rechaza eventos con timestamp mayor a 5 minutos (replay).

# Python (Flask receiver)
import hmac, hashlib, time
from flask import request, abort

SECRET = "el_secret_que_te_dimos_al_crear_el_webhook"

def verificar(body, header):
    t, v1 = [p.split("=", 1)[1] for p in header.split(",")]
    if abs(int(time.time()) - int(t)) > 300:
        return False                       # replay
    sig = hmac.new(SECRET.encode(),
                   f"{t}.".encode() + body,
                   hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig, v1)

@app.route("/webhook", methods=["POST"])
def webhook():
    if not verificar(request.data,
                     request.headers.get("X-IntGuard-Signature", "")):
        abort(401)
    evt = request.get_json()
    # ... procesar evt["event"] / evt["data"] ...
    return "ok", 200

Reglas de entrega

Nota: la entrega es best-effort. Si tu endpoint no está disponible en el momento del evento, la entrega se registra como fallida pero no se reintenta automáticamente (cola de reintentos planificada para Q3 2026). Recomendamos consultar periódicamente el estado de tus escaneos vía API como complemento.

¿Necesitas ayuda? scannerwebsaas@gmail.com