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.
1. Quick start
En 3 pasos lanzas tu primer escaneo automatizado:
- Crea una API key en tu panel (necesitas plan Pro o superior).
- Cópiala — solo se muestra una vez. Empieza por
sks_live_. - 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:
sks_live_...— entorno de producciónsks_test_...— entorno de pruebas (sandbox)
3. Rate limits
El rate limit se aplica por API key, no por IP:
| Plan | Requests/hora |
|---|---|
| Free / Standard | No disponible |
| Basic (legacy) | 10 req/h |
| Pro | 100 req/h |
| Business / Agency | 1000 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étodo | Ruta | Descripció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}/findings | Hallazgos 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"
}
}
| Status | Code | Significado |
|---|---|---|
| 400 | INVALID_REQUEST | Body o parámetros inválidos |
| 401 | UNAUTHORIZED | Falta header Authorization |
| 401 | INVALID_API_KEY | Key no existe |
| 401 | API_KEY_REVOKED | Key revocada |
| 403 | FORBIDDEN_PLAN | Tu plan no incluye API |
| 403 | INSUFFICIENT_SCOPE | La key no tiene el scope requerido |
| 404 | NOT_FOUND | Recurso no encontrado o no propio |
| 429 | RATE_LIMIT_EXCEEDED | Has superado tu cuota |
| 429 | QUOTA_EXCEEDED | Has agotado tu cuota mensual de escaneos |
| 500 | INTERNAL | Error inesperado del servidor |
| 503 | INTERNAL | Backend 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
| Evento | Cuá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.
- Timeout: 5 segundos por petición.
- Solo
https://. URLs a IPs privadas o metadatos cloud se rechazan al crear. - No seguimos redirects (un 3xx cuenta como fallo).
- Tras 5 fallos consecutivos el webhook se deshabilita automáticamente. Reactívalo desde el panel.
- Tu endpoint debería responder
2xxen menos de 5 s. Si tardas, encola y procesa asíncrono.
¿Necesitas ayuda? scannerwebsaas@gmail.com