API pública

Los endpoints JSON que este sitio usa para sus propias consultas, documentados tal como se comportan hoy. Solo lectura, sin registro.

Sin claves de API, sin SLA, puede cambiarNo hay claves de API, ni planes de pago, ni compromiso alguno de disponibilidad o latencia. La API no está versionada: los campos pueden añadirse, renombrarse o eliminarse sin previo aviso, y un endpoint puede limitarse más o retirarse si se abusa de él. No construya nada que falle de mala manera cuando una solicitud no salga bien.

Convenciones

Todos los endpoints responden a solicitudes GET con JSON (UTF-8). El dominio es un segmento de la ruta; los nombres internacionalizados (con ñ o tildes, por ejemplo) pueden enviarse en Unicode o como etiquetas A (xn--…). Las fechas siguen ISO 8601 en UTC.

Cada observación se hace desde una única ubicación de servidor en el momento de la solicitud, e indica de dónde procede: cada sección incluye status, source y observedAt. Una sección con estado "error" o "unsupported" no se pudo comprobar. Eso nunca demuestra que un registro no exista, y un estado de registro "not-found" nunca garantiza que el nombre pueda registrarse.

Los nombres reservados para documentación (example.com, example.net, example.org y todo lo que cuelga de .example, .test, .invalid o .localhost) nunca se consultan en vivo: responden 422. Los ejemplos de respuesta de esta página usan example.com y rangos de IP de documentación solo como ilustración; para probar la API, use un dominio que usted administre.

Informe de dominio

GET /api/lookup/{domain}

Un informe con hasta cinco secciones independientes: registration (RDAP, con WHOIS por el puerto 43 para algunos registros de extensión que no ofrecen RDAP), dns, mail (MX, SPF, DMARC), hosting (de la IP al ASN y la red) y web (una única solicitud HTTPS y el certificado TLS con el que se sirvió). Que una sección falle no hace fallar el informe.

El "www." inicial se elimina. En un subdominio, los datos de registro se consultan para el dominio registrable y las demás secciones para el nombre de host que usted envió.

Parámetros

NombreUbicaciónDescripción
domainrutaDominio o nombre de host, 253 caracteres como máximo.
sectionsconsultaSubconjunto separado por comas de registration, dns, mail, hosting, web. Los nombres desconocidos se ignoran; si ninguno es válido, la respuesta es 400. Las secciones que no pidió vuelven con el estado "skipped". Valor predeterminado: las cinco.

Códigos de estado

EstadoSignificado
200{ "report": … }. Se envía con Cache-Control: public, max-age=60, s-maxage=300.
400{ "error": "invalid-domain", "reason": … } cuando el nombre no supera la validación, o { "error": "invalid-sections", "allowed": […] }.
422{ "error": "reserved-name" } para nombres de documentación y de prueba, o { "error": "blocked-target" } cuando el nombre resuelve únicamente a direcciones privadas o reservadas, a las que el servicio no se conecta.
429{ "error": "rate-limited", "retryAfterSeconds": n } con una cabecera Retry-After.
Solicitud de ejemplo
curl -s "https://www.orbitprobe.com/api/lookup/yourdomain.com?sections=registration,dns"
Respuesta de ejemplo (valores ilustrativos, abreviada)
{
  "report": {
    "input": "example.com",
    "ascii": "example.com",
    "unicode": "example.com",
    "registrableDomain": "example.com",
    "isSubdomain": false,
    "tld": "com",
    "generatedAt": "2026-01-01T12:00:00.000Z",
    "dataMode": "live",
    "cached": false,
    "registration": {
      "status": "ok",
      "source": "RDAP · rdap.registry.example",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 412,
      "state": "registered",
      "registrableDomain": "example.com",
      "registrar": "Example Registrar, Inc.",
      "registrarIanaId": "9999",
      "registrarUrl": null,
      "abuseEmail": "abuse@registrar.example",
      "createdAt": "2015-03-01T00:00:00Z",
      "updatedAt": "2025-02-10T00:00:00Z",
      "expiresAt": "2027-03-01T00:00:00Z",
      "statusCodes": ["client transfer prohibited"],
      "nameservers": ["ns1.example.net", "ns2.example.net"],
      "dnssec": false,
      "registrant": { "organization": null, "country": null, "redacted": true },
      "rdapUrl": "https://rdap.registry.example/domain/example.com"
    },
    "dns": {
      "status": "ok",
      "source": "DNS · public recursive resolvers",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 95,
      "resolver": "1.1.1.1, 8.8.8.8",
      "records": [
        { "type": "A", "name": "example.com", "value": "203.0.113.10", "ttl": 300 },
        { "type": "AAAA", "name": "example.com", "value": "2001:db8::10", "ttl": 300 },
        { "type": "MX", "name": "example.com", "value": "mail.example.com", "priority": 10 },
        { "type": "TXT", "name": "example.com", "value": "v=spf1 mx -all" }
      ],
      "failedTypes": [],
      "nxdomain": false
    },
    "mail": {
      "status": "ok",
      "source": "DNS",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 140,
      "mx": [{ "priority": 10, "host": "mail.example.com", "addresses": ["203.0.113.25"], "provider": null }],
      "spf": "v=spf1 mx -all",
      "dmarc": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com",
      "dkim": "selector-required",
      "findings": [{ "level": "ok", "code": "…", "title": "…", "detail": "…" }]
    },
    "hosting": {
      "status": "ok",
      "source": "IP lookups",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 310,
      "addresses": [
        { "address": "203.0.113.10", "family": 4, "reverse": null, "asn": 64500, "asName": "EXAMPLE-NET", "prefix": "203.0.113.0/24", "country": null, "networkName": null, "networkOrg": null, "edge": null }
      ],
      "summary": "EXAMPLE-NET (AS64500)"
    },
    "web": {
      "status": "ok",
      "source": "HTTPS request",
      "observedAt": "2026-01-01T12:00:00.000Z",
      "durationMs": 520,
      "finalUrl": "https://example.com/",
      "httpStatus": 200,
      "redirects": [],
      "serverHeader": null,
      "poweredBy": null,
      "hsts": true,
      "responseMs": 180,
      "page": { "title": "Example Domain", "description": null, "keywords": null },
      "headers": [{ "name": "content-type", "value": "text/html; charset=UTF-8" }],
      "tls": {
        "protocol": "TLSv1.3",
        "issuer": "Example CA",
        "subject": "example.com",
        "validFrom": "2025-12-01T00:00:00.000Z",
        "validTo": "2026-03-01T00:00:00.000Z",
        "altNames": ["example.com", "www.example.com"],
        "authorized": true,
        "authorizationError": null
      }
    }
  }
}

Propagación DNS

GET /api/propagation/{domain}

Hace la misma pregunta a cada resolver de una lista fija de resolvers públicos recursivos y agrupa las respuestas idénticas. Lea el resultado como "9 de 10 resolvers devolvieron esta respuesta": no dice nada sobre los resolvers que no están en la lista, y un resolver anycast puede responder de otro modo en otra región.

Aquí el "www." se conserva, porque la pregunta se refiere exactamente al nombre que usted envía.

Parámetros

NombreUbicaciónDescripción
domainrutaNombre de host por el que se pregunta, 253 caracteres como máximo.
typeconsultaA, AAAA, NS, MX, TXT o CNAME (sin distinguir mayúsculas de minúsculas). Valor predeterminado: A.

Códigos de estado

EstadoSignificado
200{ "report": … } con una entrada por resolver (estado answer, no-records, nxdomain, timeout o error), los grupos de respuestas distintas y los contadores total, answered y failed. Cache-Control: public, max-age=30.
400{ "error": "invalid-domain" } o { "error": "invalid-type" }.
422{ "error": "reserved-name" }.
429{ "error": "rate-limited" } con una cabecera Retry-After.
Solicitud de ejemplo
curl -s "https://www.orbitprobe.com/api/propagation/www.yourdomain.com?type=A"
Respuesta de ejemplo (valores ilustrativos, abreviada)
{
  "report": {
    "ascii": "www.example.com",
    "unicode": "www.example.com",
    "type": "A",
    "generatedAt": "2026-01-01T12:00:00.000Z",
    "cached": false,
    "answers": [
      {
        "resolver": { "id": "cloudflare", "name": "Cloudflare", "address": "1.1.1.1", "operatorCountry": "US" },
        "status": "answer",
        "values": ["203.0.113.10"],
        "ttl": 300,
        "durationMs": 21,
        "group": 0
      },
      {
        "resolver": { "id": "quad9", "name": "Quad9", "address": "9.9.9.9", "operatorCountry": "CH" },
        "status": "timeout",
        "values": [],
        "ttl": null,
        "durationMs": 2800,
        "group": null
      }
    ],
    "groups": [{ "values": ["203.0.113.10"], "count": 9 }],
    "total": 10,
    "answered": 9,
    "failed": 1,
    "consistent": false
  }
}

Single-purpose checks

GET /api/tools/{tool}/{domain}

The engines behind the tool result pages, one request per check: dkim-checker, subdomain-finder, mta-sts-checker, tls-rpt-checker, bimi-checker, dnssec-checker, reverse-dns and security-txt-checker (the same keys as the site's tool URLs). The answer carries ok, tool, source, observedAt and cached, and the engine's report unchanged.

A query that failed stays visible inside the report as a state such as "failed", "error" or "unknown", or as a finding: it is never turned into "no record". A leading "www." is removed, except for reverse-dns, which keeps it and also accepts an IPv4 or IPv6 address.

Parámetros

NombreUbicaciónDescripción
toolrutaOne of the eight tool keys above. Anything else answers 404.
domainrutaDomain or hostname, at most 253 characters; for reverse-dns also an IP address (percent-encode the colons of an IPv6 address).
selectorconsultadkim-checker and bimi-checker only. DKIM: the s= selector of a real message; leave it out to try a fixed list of common provider selectors. BIMI: default "default".

Códigos de estado

EstadoSignificado
200{ "ok": true, "tool": …, "source": …, "observedAt": …, "cached": …, "report": … }. Sent with Cache-Control: public, max-age=60, s-maxage=300.
400{ "ok": false, "code": "invalid-domain", "reason": … } or { "ok": false, "code": "invalid-selector" }.
404{ "ok": false, "code": "unknown-tool", "tools": […] }.
422{ "ok": false, "code": "reserved-name" } for documentation and test names.
429{ "ok": false, "code": "rate-limited", "retryAfterSeconds": n } with a Retry-After header.
Solicitud de ejemplo
curl -s "https://www.orbitprobe.com/api/tools/tls-rpt-checker/yourdomain.com"
Respuesta de ejemplo (valores ilustrativos, abreviada)
{
  "ok": true,
  "tool": "tls-rpt-checker",
  "source": "Public DNS (1.1.1.1, 8.8.8.8)",
  "observedAt": "2026-01-01T12:00:00.000Z",
  "cached": false,
  "report": {
    "ascii": "example.com",
    "unicode": "example.com",
    "observedAt": "2026-01-01T12:00:00.000Z",
    "durationMs": 38,
    "cached": false,
    "host": "_smtp._tls.example.com",
    "state": "ok",
    "records": ["v=TLSRPTv1; rua=mailto:tls-reports@example.com"],
    "parsed": { "version": "TLSRPTv1", "valid": true, "rua": [{ "raw": "mailto:tls-reports@example.com", "scheme": "mailto", "valid": true, "target": "example.com" }] },
    "findings": [{ "level": "ok", "code": "RPT_OK", "params": { "n": 1 } }]
  }
}

Contadores de actividad

GET /api/stats/activity

Las cifras que hay detrás del globo de la página de inicio: cuántas consultas se ejecutaron en las últimas 24 horas, por país del visitante y por país en el que está registrado el bloque de direcciones consultado. Solo existen códigos de país y contadores; no se guarda ninguna dirección IP ni ningún nombre de dominio. Los contadores viven en la memoria de un único proceso del servidor, así que se reinician con él y difieren entre instancias: tómelos como una señal aproximada, no como estadísticas.

Códigos de estado

EstadoSignificado
200La instantánea. Cache-Control: public, max-age=30. Este endpoint no tiene límite de frecuencia.
Solicitud de ejemplo
curl -s "https://www.orbitprobe.com/api/stats/activity"
Respuesta de ejemplo (valores ilustrativos, abreviada)
{
  "windowHours": 24,
  "total": 3,
  "visitors": [{ "country": "TR", "count": 2 }, { "country": "DE", "count": 1 }],
  "hosting": [{ "country": "US", "count": 3 }],
  "generatedAt": "2026-01-01T12:00:00.000Z"
}

Límites de frecuencia

Los límites se cuentan por dirección IP del cliente, por separado para cada familia de endpoints. Un resultado servido desde la caché del servidor no cuenta para el límite. Cuando se alcanza un límite, la respuesta es 429 con una cabecera Retry-After; espere ese tiempo en lugar de reintentar en bucle.

La insignia y las herramientas MCP pasan por los mismos motores, de modo que comparten estos límites.

EndpointLímite por dirección IPCaché del servidor
/api/lookup/{domain}30 por minuto · 300 por hora300 s (30 s si falló alguna sección)
/api/propagation/{domain}12 por minuto · 120 por hora60 s
/api/tools/{tool}/{domain}10 por minuto · 80 por hora (per tool; DKIM 10 por minuto · 80 por hora, subdomain finder 5 por minuto · 40 por hora)300 s / 1800 s
/api/stats/activityningunaninguna

Caché

Los informes se guardan en la caché del servidor por nombre de host y lista de secciones, de modo que pedir las mismas secciones de nuevo dentro del tiempo de caché devuelve la misma observación con "cached": true y sus horas observedAt originales. Las cabeceras Cache-Control indicadas arriba también permiten que los navegadores y la CDN reutilicen una respuesta durante poco tiempo. No hay ningún parámetro para forzar una consulta nueva.

CORS

Los endpoints de esta página envían Access-Control-Allow-Origin: * y responden a las solicitudes previas OPTIONS (pre-flight), solo para GET. Puede llamarlos desde una página de otro origen. Nunca leen cookies, así que no intervienen credenciales.

Uso razonable

Cada solicitud desencadena consultas reales a registros de extensión, resolvers DNS y al propio sitio consultado. Use la API para herramientas interactivas, paneles y comprobaciones ocasionales de dominios que tenga motivos para revisar. No rastree listas de dominios, no rote direcciones para esquivar los límites y no revenda los resultados. La política de uso aceptable se aplica al tráfico de la API exactamente igual que al sitio.

Política de uso aceptable

Outbound webhooks

Signed-in members can have watch alerts, expiry reminders and marketplace notices posted to a URL they own: a Slack incoming webhook or a generic https endpoint (workspace → Settings → Notification channels). This section documents what the receiver gets. It is outbound only: OrbitProbe never accepts requests on these URLs, and there is no inbound API.

Receivers must be https on a public host (default port, no credentials; private, local and reserved hosts are refused, and the host is resolved and checked again at send time). Redirects are not followed. A delivery counts as done when the receiver answers 2xx; after five failed deliveries in a row the webhook is switched off and the owner sees a notice in the workspace.

Request

NombreDescripción
POSTJSON body, Content-Type: application/json; charset=utf-8, User-Agent: OrbitProbe-Webhook/1.0
X-OrbitProbe-EventThe kind (see below).
X-OrbitProbe-DeliveryThe delivery id (ntf_<number>). Repeated on a retry, so you can de-duplicate.
X-OrbitProbe-SignatureGeneric endpoints only: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> with the signing secret shown in Settings.

Body (generic endpoints)

NombreDescripción
idDelivery id, the same as X-OrbitProbe-Delivery.
kindOne of the kinds below.
categorywatch, expiry, market or test.
title, textThe notification in the account’s language: the same wording the push notification carries. Counts are "N of M resolvers", never percentages.
urlWhere to look in the workspace.
createdAtWhen the event was queued (ISO 8601, UTC).
accountThe recipient’s account id, so one receiver can serve several accounts.
dataThe raw event as stored: domain, counts, listing id, amount in integer minor units and currency, and so on. Fields vary by kind; anything missing was not measured, never "no".
Body (generic endpoints)
{
  "id": "ntf_1042",
  "kind": "watch-changed",
  "category": "watch",
  "title": "Watch: unexpected change",
  "text": "example.com TXT: 9 of 12 resolvers return values that differ from the guarded baseline.",
  "url": "https://www.orbitprobe.com/app/watch/00000000-0000-4000-8000-000000000000",
  "createdAt": "2026-01-01T00:00:00.000Z",
  "account": "00000000-0000-4000-8000-0000000000aa",
  "data": {
    "domain": "example.com",
    "kind": "dns-change",
    "record_type": "TXT",
    "name": "example.com",
    "matched": 3,
    "total": 12,
    "failed": 0,
    "differing": 9,
    "watch_id": "00000000-0000-4000-8000-000000000000",
    "event_id": 77
  }
}

Body (Slack)

Slack incoming webhooks receive {"text": "…"} only: title, text and URL on three lines, no Block Kit and no attachments, so the message renders in every Slack client and in Slack-compatible receivers such as Mattermost or Rocket.Chat.

Body (Slack)
{
  "text": "*Watch: unexpected change*\nexample.com TXT: 9 of 12 resolvers return values that differ from the guarded baseline.\nhttps://www.orbitprobe.com/app/watch/…"
}

Verifying the signature

Recompute the HMAC over "<t>.<raw body>" with your secret, compare in constant time, and reject timestamps older than five minutes. Do not parse and re-serialise the JSON before hashing: sign the bytes as received.

Verifying the signature
// Node.js receiver: verify X-OrbitProbe-Signature before trusting the body
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret, rawBody, header, nowSeconds = Math.floor(Date.now() / 1000)) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? '');
  if (!m || Math.abs(nowSeconds - Number(m[1])) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest('hex');
  return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(m[2], 'hex'));
}

Kinds

watch-changed, watch-target, watch-failed, watch-recovered, watch-partial · expiry · offer-received, offer-countered, offer-accepted, offer-rejected, offer-withdrawn, offer-expired, listing-submitted, listing-approved, listing-paused, listing-removed, agreement-cancelled · test (the "Send test" button).

Limits

At most 5 webhooks per account and 20 posts per account per hour; beyond that, events still reach the in-app inbox and e-mail but are not posted. Deliveries are retried up to five times over later runs of the five-minute job. An "offer-accepted" event records an agreement only: no payment, escrow or transfer is implied.

Descripción OpenAPI

A partir del mismo código se genera un documento OpenAPI 3.1 de estos endpoints, legible por máquinas: /api/openapi.json

Preguntas frecuentes

¿Necesito una clave de API?

No. La API no tiene claves ni cuentas. En su lugar, las solicitudes se limitan por dirección IP.

¿Puedo depender de la API en producción?

Solo para cosas que puedan fallar. No hay SLA ni versionado; los endpoints existen porque el sitio los necesita, y están documentados para que usted también pueda usarlos. Guarde los resultados en caché por su parte y gestione las respuestas 429 y 5xx.

¿Por qué example.com responde 422?

Los nombres reservados para documentación y pruebas se atienden con datos de muestra dentro de la aplicación y nunca se consultan en vivo. Use un dominio real para probar la API.

¿Una sección fallida significa que el registro no existe?

No. "error" y "unsupported" significan que la fuente no se pudo leer desde nuestro servidor en ese momento. Solo una sección con estado "ok" o "partial" indica lo que se encontró, y únicamente para los tipos que no aparecen en failedTypes.