Logo

Whois

Version: v1

Description: Obtiene la informacion de servidores WHOIS o RDAP para cualquier dominio, con soporte para gTLD y ccTLD.

API de Información de Dominios

Tabla de Contenidos

URL Base: https://api.latinapi.com/api/whois

Endpoints de la Api

Consulta Directa - /whois

Realiza una consulta directa de la información del dominio sin considerar su existencia en cache.

Métodos: GET, POST

Consulta con Cache - /whoiscache

Si la información del dominio existe en nuestro cache (límite de 30 días), se provee dicha información. En caso contrario se realiza la consulta directa.

Métodos: GET, POST

Autenticación

Para autenticar las solicitudes es necesario enviar en encabezado X-API-Key con tu clave API válida en cada petición.

Métodos de Peticiones

1. Parámetro en Petición GET

code
GET /whois?domain=google.com
GET /whoiscache?domain=ejemplo.org

2. POST JSON

json
POST /whois
Content-Type: application/json

{
"domain": "google.com"
}

3. POST con Codificación de Formulario

code
POST /whois
Content-Type: application/x-www-form-urlencoded

domain=google.com

Formato de Respuesta

Respuesta Exitosa (Dominio Registrado)

json
{
  "status": "success",
  "type": "rdap",
  "cached": false,
  "data": {
    "creation_date": "1997-09-15T04:00:00Z",
    "updated_date": "2026-02-27T04:43:53Z",
    "expiration_date": "2028-09-14T04:00:00Z",
    "registrar_name": "MarkMonitor Inc.",
    "status": [
      "client delete prohibited",
      "client transfer prohibited"
    ],
    "dns_servers": [
      "NS1.GOOGLE.COM",
      "NS2.GOOGLE.COM"
    ]
  }
}

Respuesta Exitosa (Dominio Disponible)

json
{
  "status": "available",
  "type": "rdap",
  "cached": false,
  "message": "Domain is available for registration"
}

Respuesta con Error

json
{
  "error": true,
  "message": "Invalid or missing domain name",
  "http_code": 400,
  "error_code": "INVALID_DOMAIN"
}

Ejemplos de Uso

Línea de Comandos (cURL)

Petición GET Básica

bash
# Consulta Directa
curl "https://api.latinapi.com/api/whois?domain=google.com"

# Consulta con Cache
curl "https://api.latinapi.com/api/whoiscache?domain=google.com"

Petición POST con JSON

bash
curl -X POST "https://api.latinapi.com/api/whois" \
  -H "Content-Type: application/json" \
  -d '{"domain":"google.com"}'

Petición POST con Form Data

bash
curl -X POST "https://api.latinapi.com/api/whois" \
  -d "domain=google.com"

Respuesta JSON con Pretty Print

bash
curl -s "https://api.latinapi.com/api/whois?domain=google.com" | jq '.'

Extraer Campos Específicos

bash
# Obtener solo el nombre del registrador
curl -s "https://api.latinapi.com/api/whois?domain=google.com" | jq -r '.data.registrar_name'

# Obtener la fecha de vencimiento
curl -s "https://api.latinapi.com/api/whois?domain=google.com" | jq -r '.data.expiration_date'

# Consultar si el dominio está disponible
curl -s "https://api.latinapi.com/api/whois?domain=noexiste12345.com" | jq -r '.status'

Consultas por Lotes

bash
#!/bin/bash
domains=("google.com" "github.com" "stackoverflow.com")

for domain in "${domains[@]}"; do
echo "Consultando $domain..."
curl -s "https://api.latinapi.com/api/whoiscache?domain=$domain" | jq '{domain: "'$domain'", status: .status, registrar: .data.registrar_name}'
echo
done

JavaScript/Node.js

Usando la API Fetch (Browser/Node.js 18+)

javascript
// Consulta directa
async function lookupDomain(domain) {
  try {
    const response = await fetch(`https://api.latinapi.com/api/whois?domain=${domain}`);
    const data = await response.json();
    
    if (data.error) {
      throw new Error(data.message);
    }
    
    return data;
  } catch (error) {
    console.error('Consulta de dominio con error:', error.message);
    throw error;
  }
}

// Uso
lookupDomain('google.com')
.then(result => {
console.log('Estado del dominio:', result.status);
if (result.status === 'success') {
console.log('Registrador:', result.data.registrar_name);
console.log('Expira:', result.data.expiration_date);
}
})
.catch(error => console.error(error));

Usando Axios (Node.js)

javascript
const axios = require('axios');

class DomainLookupClient {
constructor(baseURL = 'https://api.latinapi.com/api') {
this.client = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
});
}

async lookup(domain, useCache = false) {
const endpoint = useCache ? '/whoiscache' : '/whois';

try {
const response = await this.client.post(endpoint, { domain });
return response.data;
} catch (error) {
if (error.response) {
throw new Error(error.response.data.message || 'API Error');
}
throw error;
}
}

async batchLookup(domains, useCache = true) {
const promises = domains.map(domain =>
this.lookup(domain, useCache).catch(error => ({
domain,
error: error.message
}))
);

return Promise.all(promises);
}
}

// Uso
const client = new DomainLookupClient();

// Consulta de un solo dominio
client.lookup('google.com', true)
.then(result => console.log(result))
.catch(error => console.error(error));

// Consultas por lotes
const domains = ['google.com', 'github.com', 'nonexistent12345.com'];
client.batchLookup(domains)
.then(results => {
results.forEach(result => {
if (result.error) {
console.log(`${result.domain}: Error - ${result.error}`);
} else {
console.log(`${result.domain}: ${result.status}`);
}
});
});

Ejemplo de Componente React

jsx
import React, { useState, useEffect } from 'react';

const DomainLookup = () => {
const [domain, setDomain] = useState('');
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const lookupDomain = async (domainName) => {
setLoading(true);
setError(null);

try {
const response = await fetch(`https://api.latinapi.com/api/whoiscache?domain=${domainName}`);
const data = await response.json();

if (data.error) {
throw new Error(data.message);
}

setResult(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

const handleSubmit = (e) => {
e.preventDefault();
if (domain.trim()) {
lookupDomain(domain.trim());
}
};

return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={domain}
onChange={(e) => setDomain(e.target.value)}
placeholder="Ingresa el dominio"
disabled={loading}
/>
<button type="submit" disabled={loading}>
{loading ? 'Consultando...' : 'Consultar'}
</button>
</form>

{error && <div className="error">Error: {error}</div>}

{result && (
<div className="result">
<h3>Dominio: {domain}</h3>
<p>Estado: {result.status}</p>
<p>Tipo: {result.type}</p>
{result.cached && <p>En Cache: Si</p>}

{result.status === 'success' && result.data && (
<div>
<p>Registrador: {result.data.registrar_name}</p>
<p>Creado: {result.data.creation_date}</p>
<p>Expira: {result.data.expiration_date}</p>
<p>Servidores DNS: {result.data.dns_servers.join(', ')}</p>
</div>
)}

{result.status === 'available' && (
<p className="available">El dominio se encuentra disponibel para registro!</p>
)}
</div>
)}
</div>
);
};

export default DomainLookup;

Python

Usando la Librería Requests

python
import requests
import json
from typing import Dict, List, Optional
from datetime import datetime

class DomainLookupClient:
def __init__(self, base_url: str = "https://api.latinapi.com/api"):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.headers.update({
'Content-Type': 'application/json',
'User-Agent': 'Domain Lookup Python Client/1.0'
})

def lookup(self, domain: str, use_cache: bool = False) -> Dict:
"""
Información de consultas

Args:
domain: Dominio a consultar
use_cache: Usar resultados en cache o no

Respuesta:
Diccionario conteniendo la información del dominio
"""
endpoint = '/whoiscache' if use_cache else '/whois'
url = f"{self.base_url}{endpoint}"

try:
response = self.session.post(url, json={'domain': domain}, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"Falla en la peticion a la API: {str(e)}")

def lookup_get(self, domain: str, use_cache: bool = False) -> Dict:
"""Consulta usando el método GET"""
endpoint = '/whoiscache' if use_cache else '/whois'
url = f"{self.base_url}{endpoint}?domain={domain}"

try:
response = self.session.get(url, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"API request failed: {str(e)}")

def batch_lookup(self, domains: List[str], use_cache: bool = True) -> List[Dict]:
"""Consulta múltiples dominios"""
results = []

for domain in domains:
try:
result = self.lookup(domain, use_cache)
result['domain'] = domain
results.append(result)
except Exception as e:
results.append({
'domain': domain,
'error': True,
'message': str(e)
})

return results

def is_available(self, domain: str) -> bool:
"""Consultar si un dominio está disponible"""
try:
result = self.lookup(domain, use_cache=True)
return result.get('status') == 'available'
except:
return False

def get_expiration_date(self, domain: str) -> Optional[datetime]:
"""Obtener la fecha de expiración"""
try:
result = self.lookup(domain, use_cache=True)
if result.get('status') == 'success' and result.get('data'):
exp_date = result['data'].get('expiration_date')
if exp_date:
return datetime.fromisoformat(exp_date.replace('Z', '+00:00'))
except:
pass
return None

# Ejempĺos de uso
if __name__ == "__main__":
client = DomainLookupClient()

# Consulta de un dominio
try:
result = client.lookup('google.com', use_cache=True)
print(f"Dominio: google.com")
print(f"Estado: {result['status']}")

if result['status'] == 'success':
data = result['data']
print(f"Registrador: {data.get('registrar_name', 'N/A')}")
print(f"Creado: {data.get('creation_date', 'N/A')}")
print(f"Expira: {data.get('expiration_date', 'N/A')}")
print(f"Servidores DNS: {', '.join(data.get('dns_servers', []))}")

except Exception as e:
print(f"Error: {e}")

print("\n" + "="*50 + "\n")

# Consulta por lotes
domains = ['google.com', 'github.com', 'nonexistent12345.com']
results = client.batch_lookup(domains)

for result in results:
domain = result.get('domain', 'Unknown')
if result.get('error'):
print(f"{domain}: Error - {result.get('message', 'Error desconocido')}")
else:
status = result.get('status', 'Desconocido')
print(f"{domain}: {status}")

if status == 'success' and result.get('data'):
registrar = result['data'].get('registrar_name', 'N/A')
print(f" Registrador: {registrar}")

print("\n" + "="*50 + "\n")

# Consultar disponibilidad de dominio
test_domains = ['google.com', 'nonexistent12345.com']
for domain in test_domains:
available = client.is_available(domain)
print(f"{domain}: {'Disponible' if available else 'No Disponible'}")

print("\n" + "="*50 + "\n")

# Obtener fechas de expiración
for domain in ['google.com', 'github.com']:
exp_date = client.get_expiration_date(domain)
if exp_date:
print(f"{domain} expira: {exp_date.strftime('%Y-%m-%d')}")
else:
print(f"{domain}: No se pudo obtener la fecha de vencimiento o expiracion")

Python Asíncrono con aiohttp

python
import aiohttp
import asyncio
import json
from typing import Dict, List

class AsyncDomainLookupClient:
def __init__(self, base_url: str = "https://api.latinapi.com/api"):
self.base_url = base_url.rstrip('/')

async def lookup(self, session: aiohttp.ClientSession, domain: str, use_cache: bool = False) -> Dict:
endpoint = '/whoiscache' if use_cache else '/whois'
url = f"{self.base_url}{endpoint}"

async with session.post(url, json={'domain': domain}) as response:
if response.status == 200:
return await response.json()
else:
error_data = await response.json()
raise Exception(error_data.get('message', f'HTTP {response.status}'))

async def batch_lookup(self, domains: List[str], use_cache: bool = True) -> List[Dict]:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
tasks = []

for domain in domains:
task = self.lookup_with_error_handling(session, domain, use_cache)
tasks.append(task)

return await asyncio.gather(*tasks)

async def lookup_with_error_handling(self, session: aiohttp.ClientSession, domain: str, use_cache: bool) -> Dict:
try:
result = await self.lookup(session, domain, use_cache)
result['domain'] = domain
return result
except Exception as e:
return {
'domain': domain,
'error': True,
'message': str(e)
}

# Uso
async def main():
client = AsyncDomainLookupClient()

domains = ['google.com', 'github.com', 'stackoverflow.com', 'nonexistent12345.com']
results = await client.batch_lookup(domains)

for result in results:
domain = result.get('domain')
if result.get('error'):
print(f"{domain}: Error - {result.get('message')}")
else:
print(f"{domain}: {result.get('status')}")

# Ejecutar el ejemplo asíncrono
# asyncio.run(main())

PHP

Cliente PHP Básico

php
<?php

class DomainLookupClient {
private $baseUrl;
private $timeout;

public function __construct($baseUrl = 'https://api.latinapi.com/api', $timeout = 30) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->timeout = $timeout;
}

/**
* Información de consulta de dominio
*/
public function lookup($domain, $useCache = false) {
$endpoint = $useCache ? '/whoiscache' : '/whois';
$url = $this->baseUrl . $endpoint;

$data = json_encode(['domain' => $domain]);

$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
]
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);

if ($error) {
throw new Exception("cURL Error: $error");
}

$result = json_decode($response, true);

if ($httpCode !== 200) {
$message = $result['message'] ?? "HTTP Error $httpCode";
throw new Exception($message);
}

return $result;
}

/**
* Consulta usando el método GET
*/
public function lookupGet($domain, $useCache = false) {
$endpoint = $useCache ? '/whoiscache' : '/whois';
$url = $this->baseUrl . $endpoint . '?domain=' . urlencode($domain);

$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_HTTPHEADER => [
'Accept: application/json'
]
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode !== 200) {
throw new Exception("HTTP Error $httpCode");
}

return json_decode($response, true);
}

/**
* Consulta por lotes de múltiples dominios
*/
public function batchLookup($domains, $useCache = true) {
$results = [];

foreach ($domains as $domain) {
try {
$result = $this->lookup($domain, $useCache);
$result['domain'] = $domain;
$results[] = $result;
} catch (Exception $e) {
$results[] = [
'domain' => $domain,
'error' => true,
'message' => $e->getMessage()
];
}
}

return $results;
}

/**
* Consultar si el dominio está disponible
*/
public function isAvailable($domain) {
try {
$result = $this->lookup($domain, true);
return $result['status'] === 'available';
} catch (Exception $e) {
return false;
}
}

/**
* Obtener la fecha de vencimiento o expiración
*/
public function getExpirationDate($domain) {
try {
$result = $this->lookup($domain, true);
if ($result['status'] === 'success' && isset($result['data']['expiration_date'])) {
return new DateTime($result['data']['expiration_date']);
}
} catch (Exception $e) {
// Ignore errors
}
return null;
}
}

// Ejemplos de uso
try {
$client = new DomainLookupClient();

// Consulta de un solo dominio
echo "=== Consulta de un solo dominio ===\n";
$result = $client->lookup('google.com', true);
echo "Dominio: google.com\n";
echo "Estado: " . $result['status'] . "\n";
echo "Tipo: " . $result['type'] . "\n";
echo "Cache: " . ($result['cached'] ? 'Si' : 'No') . "\n";

if ($result['status'] === 'success') {
$data = $result['data'];
echo "Registrador: " . ($data['registrar_name'] ?? 'N/A') . "\n";
echo "Creado: " . ($data['creation_date'] ?? 'N/A') . "\n";
echo "Expira: " . ($data['expiration_date'] ?? 'N/A') . "\n";
echo "Servidores DNS: " . implode(', ', $data['dns_servers'] ?? []) . "\n";
}

echo "\n=== Consulta por lotes ===\n";
$domains = ['google.com', 'github.com', 'nonexistent12345.com'];
$results = $client->batchLookup($domains);

foreach ($results as $result) {
$domain = $result['domain'];
if (isset($result['error'])) {
echo "$domain: Error - " . $result['message'] . "\n";
} else {
echo "$domain: " . $result['status'] . "\n";
}
}

echo "\n=== Consulta de disponibilidad ===\n";
$testDomains = ['google.com', 'nonexistent12345.com'];
foreach ($testDomains as $domain) {
$available = $client->isAvailable($domain);
echo "$domain: " . ($available ? 'Disponible' : 'No Disponible') . "\n";
}

echo "\n=== Fechas de Expiración o Vencimiento ===\n";
foreach (['google.com', 'github.com'] as $domain) {
$expDate = $client->getExpirationDate($domain);
if ($expDate) {
echo "$domain expira: " . $expDate->format('Y-m-d') . "\n";
} else {
echo "$domain: No se pudo obtener la fecha de vencimiento\n";
}
}

} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>

Java

Usando el Cliente HTTP en Java 11+

java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

public class DomainLookupClient {
private final String baseUrl;
private final HttpClient httpClient;
private final ObjectMapper objectMapper;

public DomainLookupClient(String baseUrl) {
this.baseUrl = baseUrl.replaceAll("/$", "");
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
this.objectMapper = new ObjectMapper();
}

public DomainLookupClient() {
this("https://api.latinapi.com/api");
}

/**
* Información de consulta de dominio
*/
public DomainResult lookup(String domain, boolean useCache) throws Exception {
String endpoint = useCache ? "/whoiscache" : "/whois";
String url = baseUrl + endpoint;

// Crear carga JSON
String jsonPayload = objectMapper.writeValueAsString(
new DomainRequest(domain)
);

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.timeout(Duration.ofSeconds(30))
.build();

HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
JsonNode errorNode = objectMapper.readTree(response.body());
throw new Exception("API Error: " + errorNode.get("message").asText());
}

return objectMapper.readValue(response.body(), DomainResult.class);
}

/**
* Consulta usando GET
*/
public DomainResult lookupGet(String domain, boolean useCache) throws Exception {
String endpoint = useCache ? "/whoiscache" : "/whois";
String url = baseUrl + endpoint + "?domain=" + domain;

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.GET()
.timeout(Duration.ofSeconds(30))
.build();

HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
throw new Exception("HTTP Error: " + response.statusCode());
}

return objectMapper.readValue(response.body(), DomainResult.class);
}

/**
* Consulta asíncrona por lotes
*/
public List<DomainResult> batchLookup(List<String> domains, boolean useCache) {
List<CompletableFuture<DomainResult>> futures = new ArrayList<>();

for (String domain : domains) {
CompletableFuture<DomainResult> future = CompletableFuture.supplyAsync(() -> {
try {
DomainResult result = lookup(domain, useCache);
result.setDomain(domain);
return result;
} catch (Exception e) {
DomainResult errorResult = new DomainResult();
errorResult.setDomain(domain);
errorResult.setError(true);
errorResult.setMessage(e.getMessage());
return errorResult;
}
});
futures.add(future);
}

return futures.stream()
.map(CompletableFuture::join)
.collect(java.util.stream.Collectors.toList());
}

/**
* Consulta disponibilidad de dominio
*/
public boolean isAvailable(String domain) {
try {
DomainResult result = lookup(domain, true);
return "available".equals(result.getStatus());
} catch (Exception e) {
return false;
}
}

// Data classes
public static class DomainRequest {
private String domain;

public DomainRequest(String domain) {
this.domain = domain;
}

public String getDomain() { return domain; }
public void setDomain(String domain) { this.domain = domain; }
}

public static class DomainResult {
private String domain;
private String status;
private String type;
private boolean cached;
private boolean error;
private String message;
private DomainData data;

// Getters and setters
public String getDomain() { return domain; }
public void setDomain(String domain) { this.domain = domain; }

public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }

public String getType() { return type; }
public void setType(String type) { this.type = type; }

public boolean isCached() { return cached; }
public void setCached(boolean cached) { this.cached = cached; }

public boolean isError() { return error; }
public void setError(boolean error) { this.error = error; }

public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }

public DomainData getData() { return data; }
public void setData(DomainData data) { this.data = data; }
}

public static class DomainData {
private String creation_date;
private String updated_date;
private String expiration_date;
private String registrar_name;
private List<String> status;
private List<String> dns_servers;

// Getters and setters
public String getCreation_date() { return creation_date; }
public void setCreation_date(String creation_date) { this.creation_date = creation_date; }

public String getUpdated_date() { return updated_date; }
public void setUpdated_date(String updated_date) { this.updated_date = updated_date; }

public String getExpiration_date() { return expiration_date; }
public void setExpiration_date(String expiration_date) { this.expiration_date = expiration_date; }

public String getRegistrar_name() { return registrar_name; }
public void setRegistrar_name(String registrar_name) { this.registrar_name = registrar_name; }

public List<String> getStatus() { return status; }
public void setStatus(List<String> status) { this.status = status; }

public List<String> getDns_servers() { return dns_servers; }
public void setDns_servers(List<String> dns_servers) { this.dns_servers = dns_servers; }
}

// Ejemplos de uso
public static void main(String[] args) {
DomainLookupClient client = new DomainLookupClient();

try {
// Consulta de un dominio
System.out.println("=== Consulta de un dominio ===");
DomainResult result = client.lookup("google.com", true);
System.out.println("Dominio: google.com");
System.out.println("Estado: " + result.getStatus());
System.out.println("Tipo: " + result.getType());
System.out.println("Cache: " + result.isCached());

if ("success".equals(result.getStatus()) && result.getData() != null) {
DomainData data = result.getData();
System.out.println("Registrador: " + data.getRegistrar_name());
System.out.println("Creado: " + data.getCreation_date());
System.out.println("Expira: " + data.getExpiration_date());
System.out.println("Servidores DNS: " + String.join(", ", data.getDns_servers()));
}

// Consulta por lotes
System.out.println("\n=== Consulta por lotes ===");
List<String> domains = List.of("google.com", "github.com", "nonexistent12345.com");
List<DomainResult> results = client.batchLookup(domains, true);

for (DomainResult r : results) {
if (r.isError()) {
System.out.println(r.getDomain() + ": Error - " + r.getMessage());
} else {
System.out.println(r.getDomain() + ": " + r.getStatus());
}
}

// Consulta de disponibilidad
System.out.println("\n=== Consulta de disponibilidad ===");
List<String> testDomains = List.of("google.com", "nonexistent12345.com");
for (String domain : testDomains) {
boolean available = client.isAvailable(domain);
System.out.println(domain + ": " + (available ? "Disponible" : "No Disponible"));
}

} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}

Go

Cliente HTTP en Go

go
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
)

type DomainLookupClient struct {
BaseURL string
HTTPClient *http.Client
}

type DomainRequest struct {
Domain string `json:"domain"`
}

type DomainData struct {
CreationDate string `json:"creation_date"`
UpdatedDate string `json:"updated_date"`
ExpirationDate string `json:"expiration_date"`
RegistrarName string `json:"registrar_name"`
Status []string `json:"status"`
DNSServers []string `json:"dns_servers"`
}

type DomainResult struct {
Domain string `json:"domain,omitempty"`
Status string `json:"status"`
Type string `json:"type"`
Cached bool `json:"cached"`
Message string `json:"message,omitempty"`
Data *DomainData `json:"data,omitempty"`
Error bool `json:"error,omitempty"`
}

func NewDomainLookupClient(baseURL string) *DomainLookupClient {
if baseURL == "" {
baseURL = "https://api.latinapi.com/api"
}

return &DomainLookupClient{
BaseURL: strings.TrimRight(baseURL, "/"),
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}

func (c *DomainLookupClient) Lookup(domain string, useCache bool) (*DomainResult, error) {
endpoint := "/whois"
if useCache {
endpoint = "/whoiscache"
}

url := c.BaseURL + endpoint

reqBody := DomainRequest{Domain: domain}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}

req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")

resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}

var result DomainResult
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", result.Message)
}

return &result, nil
}

func (c *DomainLookupClient) LookupGet(domain string, useCache bool) (*DomainResult, error) {
endpoint := "/whois"
if useCache {
endpoint = "/whoiscache"
}

url := fmt.Sprintf("%s%s/%s", c.BaseURL, endpoint, url.PathEscape(domain))

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

req.Header.Set("Accept", "application/json")

resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}

var result DomainResult
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, result.Message)
}

return &result, nil
}

func (c *DomainLookupClient) BatchLookup(domains []string, useCache bool) []*DomainResult {
var wg sync.WaitGroup
results := make([]*DomainResult, len(domains))

for i, domain := range domains {
wg.Add(1)
go func(index int, d string) {
defer wg.Done()

result, err := c.Lookup(d, useCache)
if err != nil {
result = &DomainResult{
Domain: d,
Error: true,
Message: err.Error(),
}
} else {
result.Domain = d
}
results[index] = result
}(i, domain)
}

wg.Wait()
return results
}

func (c *DomainLookupClient) IsAvailable(domain string) bool {
result, err := c.Lookup(domain, true)
if err != nil {
return false
}
return result.Status == "available"
}

func (c *DomainLookupClient) GetExpirationDate(domain string) (time.Time, error) {
result, err := c.Lookup(domain, true)
if err != nil {
return time.Time{}, err
}

if result.Status == "success" && result.Data != nil && result.Data.ExpirationDate != "" {
return time.Parse(time.RFC3339, result.Data.ExpirationDate)
}

return time.Time{}, fmt.Errorf("no expiration date available")
}

func main() {
client := NewDomainLookupClient("https://api.latinapi.com/api")

// Consulta de un dominio
fmt.Println("=== Consulta de un dominio ===")
result, err := client.Lookup("google.com", true)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("Dominio: google.com\n")
fmt.Printf("Estado: %s\n", result.Status)
fmt.Printf("Tipo: %s\n", result.Type)
fmt.Printf("Cache: %t\n", result.Cached)

if result.Status == "success" && result.Data != nil {
fmt.Printf("Registrador: %s\n", result.Data.RegistrarName)
fmt.Printf("Creado: %s\n", result.Data.CreationDate)
fmt.Printf("Expira: %s\n", result.Data.ExpirationDate)
fmt.Printf("Servidores DNS: %s\n", strings.Join(result.Data.DNSServers, ", "))
}

// Consulta por lotes
fmt.Println("\n=== Consulta por lotes ===")
domains := []string{"google.com", "github.com", "nonexistent12345.com"}
results := client.BatchLookup(domains, true)

for _, r := range results {
if r.Error {
fmt.Printf("%s: Error - %s\n", r.Domain, r.Message)
} else {
fmt.Printf("%s: %s\n", r.Domain, r.Status)
}
}

// Consulta de disponibilidad
fmt.Println("\n=== Consulta de disponibilidad ===")
testDomains := []string{"google.com", "nonexistent12345.com"}
for _, domain := range testDomains {
available := client.IsAvailable(domain)
status := "No Disponible"
if available {
status = "Disponible"
}
fmt.Printf("%s: %s\n", domain, status)
}

// Fechas de vencimiento o expiracion
fmt.Println("\n=== Fechas de vencimiento o expiracion ===")
for _, domain := range []string{"google.com", "github.com"} {
expDate, err := client.GetExpirationDate(domain)
if err != nil {
fmt.Printf("%s: No se pudo obtener la fecha de vencimiento - %v\n", domain, err)
} else {
fmt.Printf("%s expira: %s\n", domain, expDate.Format("2006-01-02"))
}
}
}

Manejo de Errores

Códigos de Error Comunes

Código HTTP Código de Error Descripción
400 INVALID_DOMAIN El formato del dominio es inválido
404 ENDPOINT_NOT_FOUND Endpoint de API inválido
429 RATE_LIMIT_EXCEEDED Demasiadas peticiones
500 DB_CONNECTION_ERROR Error en conexión a la base de datos
500 INTERNAL_ERROR Error interno

Formato de la Respuesta de Error

json
{
  "error": true,
  "message": "Mensaje de error en lenguaje humano",
  "http_code": 400,
  "error_code": "CODIGO_LEGIBLE_POR_SISTEMAS"
}

Manejo de Errores en Código

JavaScript

javascript
try {
  const result = await fetch('/whois?domain=dominio-invalido');
  const data = await result.json();
  
  if (data.error) {
    console.error(`Error ${data.error_code}: ${data.message}`);
  }
} catch (error) {
  console.error('Error de red:', error.message);
}

Python

python
try:
    result = client.lookup('dominio-invalido')
except requests.exceptions.HTTPError as e:
    if e.response.status_code == 400:
        error_data = e.response.json()
        print(f"Error de validación: {error_data['message']}")
    elif e.response.status_code == 429:
        print("Máximo de peticiones alcanzado. Por favor espera antes de reintentar.")
except requests.exceptions.RequestException as e:
    print(f"Error de red: {e}")

Límite de Peticiones

Esta API implementa límite de peticiones para evitar abusos

Mejores Prácticas

1. Usa Cache Cuando Sea Posible

bash
# Usa /whoiscache para mejorar el desempeño y disminuir el consumo de créditos
curl "https://api.latinapi.com/api/whoiscache?domain=google.com"

2. Implementa un Correcto Manejo de Errores

python
def safe_domain_lookup(client, domain):
    try:
        return client.lookup(domain, use_cache=True)
    except requests.exceptions.Timeout:
        return {"error": True, "message": "Tiempo de espera agotado"}
    except requests.exceptions.ConnectionError:
        return {"error": True, "message": "Error de conexión"}
    except Exception as e:
        return {"error": True, "message": str(e)}

3. Valida los Dominios ANTES de Hacer Llamadas a la API

javascript
function isValidDomain(domain) {
  const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$/;
  return domainRegex.test(domain) && domain.length <= 255;
}

async function lookupDomainSafe(domain) {
if (!isValidDomain(domain)) {
throw new Error('Formato de dominio inválido');
}

return await lookupDomain(domain);
}

4. Usa Tiempos de Espera Apropiados

go
client := &http.Client{
    Timeout: 30 * time.Second,
}

// Para operaciones por lotes incrementa el tiempo de espera
batchClient := &http.Client{
Timeout: 60 * time.Second,
}