Documentación

El sitio funciona sobre una API pública con dieciocho endpoints instantáneos. Cada uno está documentado aquí: qué hace, sus límites y un ejemplo de solicitud/respuesta. Sin cuenta; los resultados instantáneos no se almacenan. La APIs de gestión al final de esta página (monitorización de uptime, páginas de estado, enlaces cortos) son la excepción con estado: persisten bajo sus propias claves de gestión.

Códigos de estado

  • 301 — Redirección permanente

    Redirección permanente. Los buscadores transfieren las señales de posicionamiento al destino.

  • 302 — Redirección temporal

    Redirección temporal. La URL original sigue siendo la canónica.

  • 307 — Redirección temporal

    Redirección temporal que conserva el método y el cuerpo de la solicitud.

  • 308 — Redirección permanente

    Redirección permanente que conserva el método y el cuerpo de la solicitud.

  • 200 — Éxito

    Destino final alcanzado. La cadena termina aquí.

  • 404 — Error de cliente

    El destino no existe — un objetivo de redirección roto.

  • 500 — Error de servidor

    El destino se resolvió pero el servidor no respondió correctamente.

Resultados clasificados, no errores

En los cuatro endpoints instantáneos, los destinos bloqueados, los dominios irresolubles y las URL inválidas no son errores: cada solicitud bien formada devuelve HTTP 200 con un estado que clasifica el resultado. Solo las solicitudes malformadas, los cuerpos demasiado grandes y la limitación de tasa producen respuestas de error.

Límites compartidos

Protocolos
http, https
Método
GET
Límites de tasa
30 solicitudes/minuto y 500/día por IP — compartidos por todos los endpoints
Cuerpo de la solicitud
hasta 10 KiB, JSON estricto

Directorio de servicios e informes de la comunidad (Phase 14)

Un directorio público y curado de servicios en línea populares con informes comunitarios anónimos. Las lecturas son públicas y están cacheadas; el POST de informes es la excepción con estado — persiste y lleva límites de frecuencia, deduplicación y moderación. La señal comunitaria y la comprobación automática son siempre indicadores separados; nada aquí afirma una caída confirmada.

GET /api/v1/catalog/services?sort=reports_24h&page=1&page_size=20
→ 200
{ "items": [ { "slug": "chatgpt", "name": "ChatGPT",
               "category": { "slug": "ai-assistants", "name": "AI Assistants" },
               "community": { "signal": "reports_increasing", "reports_15m": 3,
                              "reports_1h": 11, "reports_24h": 42,
                              "last_report_at": "2026-10-20T09:58:12Z" },
               "automated_check": { "state": "operational",
                                    "checked_at": "2026-10-20T09:55:00Z" } } ],
  "page": 1, "page_size": 20, "total": 1 }

POST /api/v1/catalog/services/chatgpt/reports
{ "problem_type": "site_unreachable", "comment": "Login loop on mobile" }
→ 201
{ "service_slug": "chatgpt", "problem_type": "site_unreachable",
  "comment": "Login loop on mobile", "created_at": "2026-10-20T10:01:33Z" }

Reads are public and cached (ETag + max-age=30); unknown/draft/hidden
slugs all answer 404 (no existence oracle); merged slugs 301 to the
target. Reports are anonymous: no account, no network fetch on
submission, rate limited per IP, comments plain text (≤500 chars).
Community signal and the automated check are always separate blocks —
no value on this API means "confirmed down".
Paginación
page ≥1, page_size por defecto 20, máx 100; orden determinista (desempate por slug)
Límites de informes
dedup 1 por servicio cada 10 min por IP, luego 10/h y 30/día por IP; 429 + Retry-After; fail-closed
Moderación y privacidad
política auto-publish o hold-all; interruptores de apagado; solo se almacena un hash seudónimo con rotación diaria — nunca IP cruda ni user-agent

Redirect Checker — POST /api/v1/check

Sigue la cadena completa de redirecciones y almacena el resultado.

POST /api/v1/check
Content-Type: application/json

{ "url": "https://bit.ly/example" }

{
  "id": "9pr2kxq4",
  "status": "completed",
  "input_url": "https://bit.ly/example",
  "final_url": "https://example.com/product",
  "final_domain": "example.com",
  "final_status": 200,
  "redirect_count": 3,
  "duration_ms": 482,
  "redirects": [
    { "sequence": 0, "requested_url": "https://bit.ly/example",
      "status_code": 301, "next_url": "https://tracker.example/click",
      "duration_ms": 83 },
    { "sequence": 3, "requested_url": "https://example.com/product",
      "status_code": 200, "duration_ms": 184 }
  ],
  "errors": null
}

GET /api/v1/check/{id} — obtiene un resultado guardado por su ID público.
Redirecciones máximas
20 saltos
Tiempo de espera
15 segundos

GET /api/v1/check/{id} — obtiene un resultado guardado por su ID público.

Uptime Checker — POST /api/v1/uptime-check

Comprueba si un sitio responde. El resultado se clasifica: UP para una respuesta correcta, HTTP_ERROR si el sitio responde con 4xx/5xx, más TIMEOUT, DNS_ERROR, TLS_ERROR, CONNECTION_ERROR y BLOCKED_TARGET.

POST /api/v1/uptime-check
Content-Type: application/json

{ "url": "https://example.com" }

{
  "url": "https://example.com",
  "status": "UP",
  "reachable": true,
  "httpStatus": 200,
  "httpStatusText": "OK",
  "responseTimeMs": 184,
  "finalUrl": "https://example.com/",
  "redirectCount": 1,
  "tls": { "enabled": true, "valid": true }
}
Redirecciones máximas
10 saltos
Tiempo de espera
10 segundos

HTTP Status Checker — POST /api/v1/http-status-check

Devuelve el código de estado final de una URL, las cabeceras de la respuesta final, un resumen compacto de la cadena de redirecciones y el estado TLS.

POST /api/v1/http-status-check
Content-Type: application/json

{ "url": "https://example.com" }

{
  "url": "https://example.com",
  "status": "UP",
  "reachable": true,
  "httpStatus": 200,
  "httpStatusText": "OK",
  "finalUrl": "https://example.com/",
  "redirectCount": 1,
  "headers": {
    "Content-Type": ["text/html; charset=utf-8"],
    "Server": ["nginx"]
  },
  "headers_analysis": {
    "security_missing": ["Strict-Transport-Security", "Content-Security-Policy"],
    "caching": { "Cache-Control": "max-age=604800" }
  },
  "tls": { "enabled": true, "valid": true }
}
Redirecciones máximas
10 saltos
Tiempo de espera
10 segundos

Response Time Checker — POST /api/v1/response-time-check

Mide de 1 a 5 muestras y clasifica un veredicto de velocidad a partir de la mediana del time to first byte. Devuelve la duración de cada muestra, mín/mediana/máx/media y el desglose de tiempos del último salto.

POST /api/v1/response-time-check
Content-Type: application/json

{ "url": "https://example.com", "samples": 3 }

{
  "url": "https://example.com",
  "samples": 3,
  "status": "UP",
  "verdict": "FAST",
  "completed": 3,
  "timesMs": [184, 201, 176],
  "stats": { "minMs": 176, "medianMs": 184, "maxMs": 201, "meanMs": 187 },
  "breakdown": { "dnsMs": 12, "connectMs": 23, "tlsMs": 45, "ttfbMs": 184, "totalMs": 190 },
  "httpStatus": 200,
  "httpStatusText": "OK",
  "finalUrl": "https://example.com/",
  "redirectCount": 1,
  "tls": { "enabled": true, "valid": true }
}
Muestras
1–5 (predeterminado 3)
Tiempo de espera por muestra
10 segundos
Límite de la serie
30 segundos

SSL Checker — POST /api/v1/ssl-check

Inspecciona el certificado TLS que presenta un host: validez, emisor, SAN, protocolo y cifrado negociados, y la cadena. Sonda solo TLS: la página nunca se descarga; un certificado defectuoso se notifica con un motivo concreto.

POST /api/v1/ssl-check
Content-Type: application/json

{ "url": "https://example.com" }

{
  "url": "https://example.com",
  "host": "example.com",
  "status": "UP",
  "reachable": true,
  "handshakeMs": 45,
  "tls": {
    "version": "TLS 1.3",
    "cipher": "TLS_AES_256_GCM_SHA384",
    "verified": true,
    "status": "valid",
    "daysRemaining": 89,
    "notBefore": "2026-08-01T00:00:00Z",
    "notAfter": "2026-10-30T00:00:00Z",
    "subject": "CN=example.com",
    "issuer": "CN=R3,O=Let's Encrypt,C=US",
    "sans": ["example.com", "www.example.com"],
    "chain": ["CN=example.com", "CN=R3,O=Let's Encrypt,C=US"]
  }
}
Puerto
el de la URL (443 por defecto)
Tiempo de espera de la sonda
10 segundos

DNS Checker — POST /api/v1/dns-check

Consulta los registros A, AAAA, CNAME, MX, TXT y NS de un host. Solo consulta e informe: no se realizan conexiones y cada dirección resuelta se etiqueta como pública o no pública.

POST /api/v1/dns-check
Content-Type: application/json

{ "url": "example.com" }

{
  "url": "example.com",
  "host": "example.com",
  "status": "UP",
  "durationMs": 38,
  "records": {
    "a": ["93.184.216.34"],
    "aaaa": ["2606:2800:220:1:248:1893:25c8:1946"],
    "cname": [],
    "mx": [{ "host": "mail.example.com", "pref": 10 }],
    "txt": ["v=spf1 include:_spf.example.com ~all"],
    "ns": ["ns1.example.com", "ns2.example.com"]
  },
  "addressLabels": [
    { "address": "93.184.216.34", "classification": "public", "reason": "public" }
  ]
}
Familias de registros
A, AAAA, CNAME, MX, TXT, NS
Tiempo de espera de consulta
10 segundos en total, 3 segundos por familia

Comprobador de caducidad de dominio — POST /api/v1/domain-expiry-check

Responde cuándo caduca un dominio vía el flujo RDAP fijo de rdap.org: veredicto, días restantes, fechas de registro/caducidad y estados del registro. La primera herramienta del repositorio que consulta a un tercero fijo: solo HTTPS en cada salto, sin proxy, redirecciones y tamaño limitados; el dominio consultado nunca es contactado. 404 se convierte en veredicto not_found (no registrado / registro sin RDAP / nada publicado), 429 y 5xx en UNKNOWN_ERROR honesto.

POST /api/v1/domain-expiry-check
Content-Type: application/json

{ "url": "example.com" }

{
  "url": "example.com",
  "host": "example.com",
  "status": "UP",
  "verdict": "ok",
  "daysRemaining": 214,
  "registration": "2019-01-15T05:00:00Z",
  "expiration": "2027-04-15T04:00:00Z",
  "lastChanged": "2026-08-01T11:20:00Z",
  "registryStatuses": ["ok"],
  "source": "rdap",
  "durationMs": 420
}
Veredictos
ok, expiring (≤30 días), expired, not_found, unknown
Tiempo límite de consulta
10 segundos en total (DOMAIN_EXPIRY_TIMEOUT)
Conexión saliente
solo rdap.org; HTTPS en cada salto; proxy desactivado; límite de 10 redirecciones; tope de 1 MiB; el dominio nunca es contactado

Consulta WHOIS — POST /api/v1/whois-check

Responde qué sabe el registro sobre un dominio: registrador, nameservers, fechas e indicadores de estado. RDAP primero vía rdap.org, con respaldo del WHOIS clásico (TCP 43 en bruto) para zonas sin RDAP — la primera herramienta del repositorio con TCP en bruto: cada reenvío debe ser un objetivo host[:43], se resuelve por el pipeline SSRF compartido y se conecta a la dirección pública fijada; límite de 2 reenvíos, tope de 64 KiB; el dominio consultado nunca es contactado. Un 404 de RDAP activa el respaldo; una respuesta definitiva de inexistencia aparece como notFound.

POST /api/v1/whois-check
Content-Type: application/json

{ "url": "mail.ru" }

{
  "url": "mail.ru",
  "host": "mail.ru",
  "status": "UP",
  "source": "whois",
  "registrar": "VK-TECH",
  "nameservers": ["ns1.mail.ru", "ns2.mail.ru"],
  "registration": "1997-10-01T00:00:00Z",
  "expiration": "2026-10-01T00:00:00Z",
  "statuses": ["REGISTERED, DELEGATED, VERIFIED"],
  "durationMs": 640
}
Fuentes
RDAP vía rdap.org; WHOIS clásico (TCP 43) vía la cadena de reenvíos de whois.iana.org
Tiempo límite de consulta
15 segundos en total (WHOIS_TIMEOUT)
Conexión saliente
reenvíos solo host[:43]; validados por SSRF y fijados antes de conectar; límite de 2 reenvíos; tope de 64 KiB; el dominio nunca es contactado

Disponibilidad de dominio — POST /api/v1/domain-availability

Responde si un nombre está libre para registrar, por TLD, con una máquina de estados honesta: AVAILABLE solo cuando la consulta RDAP responde 404 y los metadatos TLD integrados confirman que el registro opera RDAP; un no-encontrado solo-WHOIS permanece UNKNOWN (probablemente disponible); RESTRICTED se basa en metadatos. Soporte IDN completo (Unicode de entrada, punycode canónico, displayDomain de salida) y fan-out multi-TLD opcional (1–10 por solicitud). Premium es siempre unknown en el MVP: los precios son un subsistema futuro aparte.

POST /api/v1/domain-availability
Content-Type: application/json

{ "domain": "пример.рф", "tlds": ["com", "io"] }

{
  "url": "пример.рф",
  "displayDomain": "пример.рф",
  "host": "xn--e1afmkfd.xn--p1ai",
  "status": "UP",
  "results": [
    {
      "tld": "com",
      "availability": "AVAILABLE",
      "reason": "rdap_not_found",
      "confidence": "high",
      "source": "rdap",
      "premium": "unknown",
      "checkedAt": "2026-09-03T12:00:02Z"
    },
    {
      "tld": "io",
      "availability": "UNKNOWN",
      "reason": "tld_rdap_unknown",
      "confidence": "low",
      "source": "tld-metadata",
      "premium": "unknown",
      "checkedAt": "2026-09-03T12:00:02Z"
    }
  ],
  "tldMetaVersion": "2026-09-02",
  "durationMs": 1240
}
TLDs por solicitud
1–10 únicos (Unicode o punycode); como máximo 4 consultas simultáneas bajo un presupuesto de 15 segundos (DOMAIN_AVAILABILITY_TIMEOUT)
Veredictos
AVAILABLE | REGISTERED | RESTRICTED | INVALID | UNKNOWN | ERROR — UNKNOWN nunca se disfraza de AVAILABLE; PREMIUM es inalcanzable en el MVP
Caché
di:avail 60s, di:reg 6h, fail-open; los resultados negativos nunca se guardan en caché

Antigüedad de dominio — POST /api/v1/domain-age

Informa la antigüedad de un dominio: la fecha de creación exacta del registro y un desglose determinista en años/meses/días. Proyección ligera sobre los datos WHOIS — una sola consulta sirve a disponibilidad, antigüedad y registrador dentro de la ventana de caché.

POST /api/v1/domain-age
Content-Type: application/json

{ "domain": "example.com" }

{
  "url": "example.com",
  "host": "example.com",
  "status": "UP",
  "age": {
    "known": true,
    "createdAt": "2019-01-15T05:00:00Z",
    "ageInDays": 2809,
    "years": 7,
    "months": 7,
    "days": 20
  },
  "source": "rdap",
  "durationMs": 390
}
Cálculo de edad
solo fechas UTC; años/meses completos con ancla de mes ajustada; días totales redondeados a la baja
Antigüedad desconocida
age.known=false + motivo acotado (no_data, inconsistent_data) — nunca una suposición
Caché
di:reg 6h compartida con disponibilidad/registrar (fail-open)

Comprobador de registrador — POST /api/v1/domain-registrar

Informa con qué registrador está registrado un dominio, normalizado contra una tabla de alias curada (GoDaddy, Namecheap, Cloudflare y más) — los nombres desconocidos se muestran exactamente como se publicaron. Incluye la organización del registro y la procedencia RDAP/WHOIS; comparte la caché de registro con antigüedad/disponibilidad.

POST /api/v1/domain-registrar
Content-Type: application/json

{ "domain": "example.com" }

{
  "url": "example.com",
  "host": "example.com",
  "status": "UP",
  "registrar": "GoDaddy.com, LLC",
  "registrarCanonical": "GoDaddy",
  "registrarIanaId": 625,
  "registry": "Verisign, Inc.",
  "source": "rdap",
  "checkedAt": "2026-09-04T12:00:00Z",
  "durationMs": 410
}
Registrador
nombre visible + badge de alias canónico cuando coincide; el IANA ID muestra «—» (los datos del registro aún no lo incluyen)
No publicado
los registros con proxy de privacidad o censurados responden UP con motivo no_data — nunca una cáscara vacía

Generador de dominios — POST /api/v1/domain-generate

Genera dominios candidatos a partir de 1–3 palabras clave: formas directas, prefijos, sufijos, compuestos y formas cortas — deduplicados, ordenados y limitados. Puro e instantáneo: sin llamadas al registro durante la generación; las comprobaciones por candidato son acciones separadas y explícitas.

POST /api/v1/domain-generate
Content-Type: application/json

{ "keywords": ["coffee", "map"], "tlds": ["com", "io"], "limit": 24 }

{
  "status": "UP",
  "candidates": [
    { "domain": "coffee.com", "strategy": "direct" },
    { "domain": "coffee.io", "strategy": "direct" },
    { "domain": "getcoffee.com", "strategy": "prefix" },
    { "domain": "coffeemap.com", "strategy": "compound" }
  ],
  "total": 24
}
Candidatos
tope estricto 50, 24 mostrados por defecto; ≤10 TLDs filtrados por la instantánea de metadatos TLD
Sin tráfico al registro
la generación hace cero llamadas al registro — las comprobaciones por candidato usan el endpoint de disponibilidad con sus propios límites

Resumen de dominio — POST /api/v1/domain-overview

Un perfil compuesto: disponibilidad en el propio TLD, fecha de creación con el desglose determinista de antigüedad, registrador (normalizado, con número IANA), proyección de caducidad y los hallazgos de configuración del registro. Las secciones son independientes — una consulta fallida nunca vacía las demás.

POST /api/v1/domain-overview
Content-Type: application/json

{ "domain": "example.com" }

{
  "url": "example.com",
  "host": "example.com",
  "status": "UP",
  "availability": {
    "tld": "com", "availability": "REGISTERED", "reason": "registered",
    "confidence": "high", "source": "rdap", "premium": "unknown",
    "checkedAt": "2026-09-05T12:00:00Z"
  },
  "registration": {
    "known": true, "createdAt": "2019-01-15T05:00:00Z",
    "ageYears": 7, "ageMonths": 7, "ageDays": 21,
    "registrar": "GoDaddy.com, LLC", "registrarCanonical": "GoDaddy",
    "registrarIanaId": 625, "registry": "Verisign, Inc.", "source": "rdap",
    "checkedAt": "2026-09-05T12:00:01Z"
  },
  "expiry": {
    "host": "example.com", "source": "rdap", "verdict": "ok",
    "daysRemaining": 222, "expiration": "2027-04-15T04:00:00Z",
    "checkedAt": "2026-09-05T12:00:02Z"
  },
  "holds": [],
  "dnssec": [{ "id": "dnssec-absent", "state": "info", "severity": "info", "detail": "unsigned" }],
  "tldMetaVersion": "2026-09-02",
  "durationMs": 1620
}
Secciones
disponibilidad (con puerta de metadatos) + registro (di:reg, 6h compartido) + caducidad (rdap.org) + hallazgos holds/dnssec
Los hallazgos son hechos
holds/DNSSEC son señales de configuración del registro — nunca veredictos de seguridad; DNS/SSL permanecen en sus herramientas independientes

Comprobador de seguridad — POST /api/v1/security-check

Revisión pasiva y consultiva de la configuración de una respuesta obtenida: cabeceras de seguridad (profundidad HSTS, aspectos CSP, COOP/COEP/CORP, X-XSS-Protection), flags de cookies, datos TLS, degradaciones de cadena y divulgación. Sin sondeos, sin afirmaciones de vulnerabilidad, sin notas; los valores de las cookies nunca salen del backend. El estado HTTP no condiciona la revisión (un 404/500 alcanzable se revisa).

POST /api/v1/security-check
Content-Type: application/json

{ "url": "https://example.com" }

{
  "url": "https://example.com",
  "finalUrl": "https://example.com/",
  "status": "UP",
  "durationMs": 610,
  "summary": { "total": 14, "passed": 9, "warn": 3, "failed": 2 },
  "findings": [
    { "id": "hsts-present", "state": "fail", "severity": "high" },
    { "id": "tls-cert", "state": "pass", "severity": "info", "detail": "valid" }
  ],
  "cookies": [
    { "name": "session", "secure": false, "httpOnly": true, "sameSite": "Lax" }
  ],
  "tls": { "enabled": true, "version": "TLS 1.3", "certStatus": "valid", "daysRemaining": 89 }
}
Alcance
~23 IDs de comprobación entre cabeceras/cookies/TLS/cadena/divulgación; solo solicitud pasiva
Cookies
Solo nombres y flags (Secure/HttpOnly/SameSite): los valores nunca salen del backend
Tiempo límite
10 segundos en total (SECURITY_CHECK_TIMEOUT)

URL Analyzer — POST /api/v1/url-analyze

Desglose sintáctico puro de una URL: identidad canónica redactada (esquema, host, formas IDN, dominio registrable ingenuo, puerto, ruta, query, fragmento), parámetros ordenados con duplicados y clasificación de rastreo versionada, conteos de rastreo, señales informativas de seguridad y notas de normalización. Sin peticiones, sin DNS, sin almacenamiento: el análisis es local y tarda milisegundos.

POST /api/v1/url-analyze
Content-Type: application/json

{ "url": "https://xn--e1afmkfd.xn--p1ai/search?q=thing&utm_source=news&id=7&id=8" }

{
  "status": "UP",
  "identity": {
    "hasCredentials": false,
    "scheme": "https",
    "host": "xn--e1afmkfd.xn--p1ai",
    "displayHost": "пример.рф",
    "registrableDomain": "пример.рф",
    "isIdn": true,
    "path": "/search",
    "query": "q=thing&utm_source=news&id=7&id=8",
    "normalized": "https://xn--e1afmkfd.xn--p1ai/search?q=thing&utm_source=news&id=7&id=8"
  },
  "parameters": [
    { "name": "q", "value": "thing", "classification": { "category": "unknown", "confidence": "none" } },
    { "name": "utm_source", "value": "news", "classification": { "category": "utm", "label": "utm_source", "confidence": "exact" } },
    { "name": "id", "value": "7", "occurrence": 1 },
    { "name": "id", "value": "8", "occurrence": 2 }
  ],
  "tracking": { "utm": 1, "analytics": 0, "adsClickId": 0, "sessionSensitive": 0, "unknown": 3 },
  "signals": ["idn_host"],
  "normalization": { "schemeAssumed": false, "punycodeApplied": true, "defaultPortOmitted": false, "hostLowercased": false },
  "registryVersion": "2026-09-1"
}
Alcance
Componentes + parámetros ordenados + clasificación del registro + señales informativas; cero red
Redacción
Las contraseñas nunca se devuelven; los valores de parámetros sensibles se enmascaran (***) en todas partes, los nombres quedan visibles

URL Expander — POST /api/v1/url-expand

Expansión instantánea y sin persistencia de una cadena de redirecciones sobre el motor compartido: cada salto pasa la misma validación SSRF central, los bucles y el límite de 10 redirecciones se distinguen por reason, y la URL final recibe un análisis de rastreo completo. Las URLs devueltas se redactan — las contraseñas nunca salen del backend. Sin caché: las cadenas son sensibles al tiempo, la frescura es la respuesta correcta.

POST /api/v1/url-expand
Content-Type: application/json

{ "url": "https://bit.ly/3xyz" }

{
  "url": "https://bit.ly/3xyz",
  "status": "UP",
  "finalUrl": "https://example.com/landing?utm_source=n",
  "hopCount": 2,
  "chain": [
    { "url": "https://bit.ly/3xyz", "status": 301, "location": "https://bit.ly/abc" },
    { "url": "https://bit.ly/abc", "status": 302, "location": "https://example.com/landing?utm_source=n" }
  ],
  "analysis": { "status": "UP", "tracking": { "utm": 1, "analytics": 0, "adsClickId": 0, "sessionSensitive": 0, "unknown": 0 } },
  "source": "engine",
  "durationMs": 820
}
Alcance
Cadena de saltos redactada + estado clasificado (bucle vs límite) + análisis de la URL final; sin DNS/TLS/tiempos por salto (terreno del Redirect Checker)
Presupuesto de expansión
10 segundos en total (URL_EXPAND_TIMEOUT); límite de 10 saltos

UTM Builder — POST /api/v1/utm-build

Construcción determinista y sin estado de URLs de campaña: los parámetros no relacionados se conservan en orden, las UTM canónicas se sobrescriben o mantienen por campo, los duplicados se colapsan al último, el fragmento se conserva y los destinos con credenciales se rechazan in-band. La misma entrada produce siempre una salida idéntica byte a byte.

POST /api/v1/utm-build
Content-Type: application/json

{ "url": "https://example.com/landing?foo=1&utm_source=old#pricing", "utmSource": "newsletter", "utmMedium": "email", "utmCampaign": "spring_sale", "utmContent": "header_cta" }

{
  "status": "UP",
  "generatedUrl": "https://example.com/landing?foo=1&utm_source=newsletter&utm_medium=email&utm_campaign=spring_sale&utm_content=header_cta#pricing",
  "warnings": ["existing_utm_overwritten:utm_source", "empty_field_skipped:utm_term"],
  "parameters": { "utm_source": "newsletter", "utm_medium": "email", "utm_campaign": "spring_sale", "utm_content": "header_cta" }
}
Alcance
Añadido de UTM en orden fijo + política preserve/overwrite/collapse + avisos; puro, sin red
Determinismo
Salida idéntica byte a byte para la misma entrada; campos ≤256 caracteres; campos desconocidos rechazados

Website Health — POST /api/v1/website-health

El diagnóstico compuesto: una petición normaliza el objetivo y ejecuta lanes concurrentes acotados sobre los motores existentes — una sola comprobación HTTP alimenta cinco secciones (disponibilidad, estado, tiempo de respuesta, cabeceras y configuración de seguridad); DNS, la sonda de certificados TLS y el perfil del dominio se ejecutan como lanes propias. El veredicto es un estado agregado con detalles por comprobación y enlaces profundos a las herramientas independientes. Los hallazgos describen solo las comprobaciones realizadas — nunca una garantía de seguridad.

POST /api/v1/website-health
Content-Type: application/json

{ "url": "https://example.com" }

{
  "target": {
    "canonical": "https://example.com/",
    "displayHost": "example.com",
    "scheme": "https",
    "hasCredentials": false,
    "isDomain": true,
    "isHttp": true,
    "valid": true
  },
  "overall": "WARNING",
  "summaryKey": "health.summary.warning",
  "checks": [
    { "type": "http_availability", "state": "pass",
      "summaryKey": "health.check.httpAvailability.pass",
      "value": "200", "durationMs": 612, "applicable": true,
      "detailUrl": "/uptime-checker" },
    { "type": "http_status", "state": "pass",
      "summaryKey": "health.check.httpStatus.pass",
      "value": "200 OK", "applicable": true,
      "detailUrl": "/http-status-checker" },
    { "type": "response_time", "state": "pass",
      "summaryKey": "health.check.responseTime.pass",
      "value": "612 ms", "applicable": true,
      "detailUrl": "/response-time-checker" },
    { "type": "headers", "state": "warn",
      "summaryKey": "health.check.headers.warn",
      "value": "2 missing", "reason": "findings", "applicable": true,
      "detailUrl": "/security-checker" },
    { "type": "security", "state": "warn",
      "summaryKey": "health.check.security.warn",
      "value": "14 of 21 passed", "reason": "findings", "applicable": true,
      "detailUrl": "/security-checker" },
    { "type": "tls", "state": "pass",
      "summaryKey": "health.check.tls.pass",
      "value": "TLS 1.3 · valid, 89 days", "durationMs": 140,
      "applicable": true, "detailUrl": "/ssl-checker" },
    { "type": "dns", "state": "pass",
      "summaryKey": "health.check.dns.pass",
      "value": "A, AAAA, MX, NS", "durationMs": 95,
      "applicable": true, "detailUrl": "/dns-checker" },
    { "type": "domain", "state": "pass",
      "summaryKey": "health.check.domain.pass",
      "value": "expires in 214 days", "durationMs": 730,
      "applicable": true, "detailUrl": "/domain-overview" }
  ],
  "meta": { "checksTotal": 8, "checksPassed": 6, "checksWarn": 2,
            "checksFailed": 0, "checksUnknown": 0,
            "tldMetaVersion": "2026-09-03", "durationMs": 1480 }
}
Alcance
Una ejecución HTTP alimenta cinco secciones; lanes de DNS/TLS/dominio; agregado HEALTHY/WARNING/ERROR/PARTIAL/UNKNOWN
Límites
Presupuesto global de 25 s + presupuestos por lane, ≤3 lanes simultáneas; objetivos inválidos in-band; sin caché — frescura primero

Uptime Monitoring — la API de gestión /api/v1/monitors

La excepción con estado respecto a las herramientas instantáneas: crea un monitor para una URL y el worker lo comprueba en un calendario fijo, con historial de ejecuciones, estadísticas de disponibilidad, episodios de incidentes y notificaciones webhook. La propiedad es una clave de gestión que se muestra exactamente una vez al crearla; cada lectura y escritura se autentica con ella, y una clave incorrecta es indistinguible de un monitor inexistente (404 uniforme).

POST /api/v1/monitors
Content-Type: application/json

{ "url": "https://example.com", "name": "Prod site", "interval": 300 }

201 Created
{
  "monitor": { "id": "b1…", "name": "Prod site",
               "normalizedTarget": "https://example.com/",
               "state": "unknown", "interval": 300 },
  "manageUrl": "/monitors/b1…?key=…",
  "key": "…shown exactly once…"
}

GET /api/v1/monitors/{id}?key=…       detail + stats window + incidents
GET /api/v1/monitors/{id}/history     cursor-paginated executions
GET /api/v1/monitors/{id}/history/daily  per-day buckets (30-day cap)
GET /api/v1/monitors/{id}/incidents   incident episodes
POST /api/v1/monitors/{id}/check      enqueue a priority manual check
PATCH /api/v1/monitors/{id}           name / interval / webhook / pause
DELETE /api/v1/monitors/{id}          cascade delete

Credentials in the target are rejected (credentials_not_allowed); the
target is SSRF-pre-validated at creation and re-validated on every run.
Alcance
Crear / listar / detalle / pausa / eliminar / comprobación manual / historial / incidentes; intervalos fijos de 60–3600s; INVALID_URL y BLOCKED_TARGET en banda al crear
Gestión de claves
Clave de 256 bits mostrada una sola vez (se guarda el sha256); las credenciales en los objetivos se rechazan; SSRF prevalidado en la creación y revalidado en cada ejecución

Páginas de estado públicas — la API de gestión /api/v1/status-pages

Componga sus monitores en una página de estado compartible: elija componentes, ordénelos, ajuste los nombres visibles y publíquela bajo un slug estable. Las páginas viven bajo la misma clave de gestión que los monitores; los borradores son privados, publicar es idempotente y reversible, y cada mutación borra la entrada de la caché pública: los cambios de acceso aplican dentro de la propia petición.

POST /api/v1/status-pages
Content-Type: application/json
X-Monitor-Key: <your key>

{ "name": "Prod API" }

201 Created
{
  "page": { "publicId": "p1…", "name": "Prod API",
            "slug": "prod-api", "published": false },
  "manageUrl": "/status-pages/p1…?key=…"
}

GET  /api/v1/status-pages/{id}?key=…   editor payload (members + monitors)
PATCH /api/v1/status-pages/{id}        name/description/slug + membership
POST /api/v1/status-pages/{id}/publish      → live at /status/{slug}
POST /api/v1/status-pages/{id}/unpublish    → removed within the request
DELETE /api/v1/status-pages/{id}       cascade delete

GET /api/v1/public/status-pages/{slug}   the public DTO (no key; cached 60s)

Membership is same-key only (≤25 monitors, replace-list semantics);
publish requires ≥1 visible member; the public DTO is an explicit
projection — target URLs, keys and internal ids never cross.
Alcance
Crear / listar / editar / lista de membresía de reemplazo (≤25, solo la propia clave) / publicar / retirar / eliminar; slugs de 3-63 caracteres con palabras reservadas; máx. 5 páginas por clave
Superficie pública
GET /api/v1/public/status-pages/{slug} + /status/{slug}; solo un DTO público explícito — las URLs de destino, claves, hosts de notificación e ids internos nunca cruzan; caché Redis 60s, fail-open

Convierte una URL larga en un enlace corto /go/{code} que puedes gestionar: destino validado, código aleatorio criptográfico, estadísticas de clics con ventanas de hoy/7 días/30 días. Los enlaces viven bajo la misma clave de gestión que tus monitores y páginas de estado: la creación se adjunta a una clave existente y nunca genera una nueva. Los destinos se validan al crear o editar (sintaxis, credenciales rechazadas, clasificación SSRF); la redirección pública nunca obtiene nada y responde 302 sin caché.

POST /api/v1/short-links
Content-Type: application/json
X-Monitor-Key: <your key>

{ "destinationUrl": "https://example.com/launch?utm_source=newsletter" }

201 Created
{
  "link": { "id": "sl1…", "code": "a3f9k2mz",
            "shortUrl": "/go/a3f9k2mz",
            "destination": "https://example.com/launch?utm_source=newsletter" }
}

GET    /api/v1/short-links?key=…              your links (newest first)
GET    /api/v1/short-links/{id}?key=…         full link + stats
PATCH  /api/v1/short-links/{id}?key=…         destination/title/expiry (re-validated)
POST   /api/v1/short-links/{id}/disable       public 404, stats kept (enable restores)
DELETE /api/v1/short-links/{id}?key=…         soft delete (idempotent)
GET    /api/v1/short-links/{id}/stats?key=…   total + today/7d/30d clicks

GET /go/a3f9k2mz   → 302 Found + Location (Cache-Control: no-store);
                     unknown/disabled/expired/deleted all answer 404.

Destinations are validated at create/update (http/https, credentials
rejected, SSRF classification); the redirect itself never fetches.
Alcance
Crear / listar / editar / deshabilitar / habilitar / borrado lógico / estadísticas; ≤100 enlaces activos por clave, 30 creaciones por clave y hora; el destino se revalida en cada cambio
Redirección pública
GET /go/{code} → 302 + no-store; desconocido/deshabilitado/caducado/eliminado responden el mismo 404 neutro; la redirección es una pura búsqueda — nunca acepta un parámetro de destino ni resuelve u obtiene el objetivo; sin IPs ni user-agents almacenados

Si un enlace /go/{code} parece phishing, malware, spam o estafa, denúncialo — no hace falta cuenta y no recibirás respuesta. Las denuncias llegan a la cola de revisión del operador; solo el operador puede retirar un enlace. El endpoint responde igual a toda denuncia bien formada: quien denuncia no averigua si el código existe.

POST /api/v1/short-link-reports
Content-Type: application/json

{ "code": "a3f9k2mz", "reason": "phishing",
  "detail": "Imitates a login page" }

202 Accepted
{ "status": "received" }

Reasons: phishing | malware | spam | scam | other (detail ≤500 chars,
optional plain text). The SAME 202 comes back for unknown, disabled
and deleted codes — no existence oracle. Limits per IP: one report
per code per 10 min, 10/hour, 30/day (429 + Retry-After; Redis
failure = 503, fail-closed). Reports go to the operator queue; only
the operator can take a link down (the owner key cannot override).
Límites y privacidad
1 denuncia por código cada 10 min por IP, luego 10/hora y 30/día (429 + Retry-After; ante errores de Redis, 503 fail-closed); motivos phishing | malware | spam | scam | other; sin cuenta, sin correo, sin IP cruda — solo una huella rotativa diaria
Respuesta uniforme
toda denuncia bien formada recibe el mismo 202 received — para códigos vivos, desactivados, borrados o desconocidos (sin oráculo de existencia); un código malformado o un motivo desconocido responde 400 antes de cualquier búsqueda

Cuentas y claves API — /api/v1/auth/register y /api/v1/api-keys

Cuentas de navegador (correo + contraseña, registro abierto; el correo no se verifica en esta versión) con sesiones del lado del servidor en una cookie endurecida. Las claves API autentican llamadas de servidor a servidor sobre una lista explícita de familias de recursos. Los fallos de inicio de sesión son genéricos: correo desconocido y contraseña errónea son indistinguibles, incluso por tiempo de respuesta.

POST /api/v1/auth/register
Content-Type: application/json

{ "email": "you@example.com", "password": "…", "displayName": "…" }

201 Created + Set-Cookie: wg_session (HttpOnly; Secure; SameSite=Lax)
{ "account": { "id": "acc…", "email": "you@example.com", … } }

POST /api/v1/auth/login     200 + Set-Cookie (401 generic on failure)
POST /api/v1/auth/logout    deletes the server-side session
GET  /api/v1/account        the session's own profile
PATCH /api/v1/account       update display name (displayName)
GET  /api/v1/account/summary  account-owned resource counts
POST /api/v1/account/claim  management key → re-own key-held resources

POST /api/v1/api-keys               (SESSION) create; secret shown ONCE
{ "name": "CI", "scopes": ["monitors:read"] }
GET  /api/v1/api-keys               (SESSION) list — no secrets
POST /api/v1/api-keys/{id}/revoke   (SESSION) immediate 401 on next use

Bearer access (Authorization: Bearer wg_live_…) is an EXPLICIT
allowlist only: account GET/PATCH, monitors CRUD + reads, status-pages
GET, short-links CRUD + stats; everything else refuses API keys.
Missing/unknown/revoked → generic 401; wrong scope → 403; per-key and
per-account rate limits → 429 + Retry-After.
Lista Bearer
account GET/PATCH (account:read), monitores (lectura/escritura), páginas de estado GET (lectura), enlaces cortos (lectura/escritura); todo lo demás rechaza claves API. Los recursos de clave no vinculados son invisibles hasta adjuntarse vía claim-by-key.
Manejo del secreto
La clave wg_live_ completa se muestra exactamente una vez al crearla; solo se guarda un hash unidireccional. Claves ausentes/desconocidas/revocadas responden un 401 genérico único; ámbito insuficiente → 403; límites → 429 + Retry-After.

Identidad de correo de la cuenta (confirmación y recuperación)

Base de correo de la fase J: confirma tu dirección, recupera una contraseña perdida y demuestra conocimiento reciente de la contraseña para acciones sensibles.

POST /api/v1/auth/verification/request     (SESSION) 202 {accepted:true}
POST /api/v1/auth/verification/confirm     { "token": "…" } → 200 {verified:true}
POST /api/v1/auth/password-reset/request   { "email": "you@example.com" } → 202 (always the same answer)
POST /api/v1/auth/password-reset/confirm   { "token": "…", "password": "…" } → 200 {reset:true}
POST /api/v1/auth/reauthenticate           (SESSION) { "password": "…" } → 200 {confirmed:true}

Reset revokes ALL sessions in the same transaction; API keys stay
active. Tokens ride mail links as URL fragments — never query strings —
and every mismatch (wrong account, replay, expiry, unknown) answers the
same generic 404.
Tokens
de un solo uso, 24 h (verificación) / 30 min (restablecimiento), solo en el fragmento, 404 genérico ante cualquier discrepancia
Restablecimiento
cierra todas las sesiones de forma atómica; las claves API siguen válidas
Reautenticación
5 minutos, del lado del servidor, ligada a la sesión
Espacios de trabajo
GET /api/v1/workspaces — personal + organizaciones, no-store, solo sesión
Organizaciones
crear/listar/leer/renombrar/eliminar + transferencia de propiedad; sesiones verificadas, versiones optimistas, reauth para acciones destructivas
Invitaciones
invitaciones por correo con enlaces de un solo uso de 7 días, cuatro roles fijos, límites con reserva de plazas y ventanas de frecuencia obligatorias
Recursos de organización
monitores/páginas/enlaces compartidos bajo /organizations/{orgId}; RBAC de sesión, mutaciones versionadas, claves legacy excluidas en SQL
Traslado de recursos
mover monitores/páginas/enlaces personales a una organización de forma atómica (cierre ≤100, versiones verificadas, idempotente)
Claves de organización
claves de servicio (wg_live_…), se muestran una vez, con ámbitos, sobreviven a la salida del creador, revocación inmediata
Auditoría de organización
historial de solo adición de cambios confirmados (365 días), lectura Owner/Admin, divulgación mínima de actores