API
Use Have I Been Squatted’s API in security workflows.
Client SDKs
Section titled “Client SDKs”For Python, use the haveibeensquatted-python SDK on GitHub. A Go SDK is coming soon.
Authentication
Section titled “Authentication”API tokens are created in the platform. Go to Settings -> API Keys, create a token, select scopes, and optionally set an expiration. The token is only shown once after creation.
Scopes
Section titled “Scopes”API tokens are scoped. Requests made with a token must include the scope required by the endpoint.
| Scope | Description | Details |
|---|---|---|
lookup:squat |
Look up typosquatted domains | Squat |
lookup:nxdomain |
Look up non-existent domains | NXDOMAIN |
analyze |
Analyze domains for threats | Analyze |
ct |
Certificate transparency lookups | Certificate transparency |
Expiration policy
Section titled “Expiration policy”When creating a token, choose a 7, 30, 60, or 90 day expiration, or select No expiration. Expired or revoked tokens stop working immediately, and the full token value is only shown once at creation.
Endpoints
Section titled “Endpoints”| Endpoint | Description | Documentation |
|---|---|---|
GET /v2/squat/{domain} |
Look up typosquatted permutations for a domain. | Squat |
GET /v2/nxdomain/{domain} |
Look up unregistered permutations for a domain. | NXDOMAIN |
GET /v2/discover/{domain} |
Alias for the NXDOMAIN discovery operation. | NXDOMAIN |
GET /v2/analyze/{domain} |
Analyze a domain for infrastructure and threat signals. | Analyze |
GET /v1/ct/search |
Search certificate transparency names by pattern. | Certificate transparency search |
GET /v1/ct/search/domains |
Search certificate transparency data for exact FQDNs. | Certificate transparency search domains |
GET /v1/ct/hydrate |
Hydrate certificate transparency occurrences into certificate details. | Certificate transparency occurrences hydration |
GET /v1/meta/usage |
Retrieve usage totals and hourly breakdown. | Usage |
GET /v1/retrieve/{uuid} |
Retrieve a retained lookup result. | Retained results |
Request format
Section titled “Request format”Use the API origin https://api.haveibeensquatted.com. Encode the fully qualified domain name (FQDN) as one URL path segment.
export API_TOKEN="YOUR_API_TOKEN"domain="example.com"
curl -N "https://api.haveibeensquatted.com/v2/squat/$domain" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Accept: application/x-ndjson"Response formats
Section titled “Response formats”Lookup and Analyze endpoints default to streamed newline-delimited JSON (NDJSON) with Content-Type: application/x-ndjson.
- Send
Accept: application/x-ndjsonto request streaming explicitly. - Send
Accept: application/jsonto receive one buffered merged JSON object.
The merged object is keyed by domain. A top-level meta member can contain non-progress metadata such as an error, timeout, or retained-result identifier.
Response headers
Section titled “Response headers”Lookup and Analyze responses can include:
X-Hibs-Request-Id, which identifies the API request for diagnostics.X-Hibs-Result-Id, which identifies the retained result when persistence applies.
X-Hibs-Result-Id is optional. Its absence does not make an otherwise complete response invalid.
Handling streaming responses
Section titled “Handling streaming responses”Each non-empty NDJSON line is one object with an op field and operation-specific data. Domain events also include permutation.
Metadata events use op: "Meta" and a nested data.kind value:
Progressreports current and total work.Heartbeatkeeps an active response open.StoredResultsupplies a retained-result identifier.Errormarks the request as failed.Timeoutmarks the response as incomplete.Donemarks successful stream completion when no error or timeout was observed.
Do not report success from the HTTP status alone. A streaming client must observe Done; end-of-stream before Done is incomplete.
import jsonfrom urllib.parse import quote
import httpx
async def lookup_domain(domain: str, api_token: str): encoded_domain = quote(domain, safe="") url = f"https://api.haveibeensquatted.com/v2/squat/{encoded_domain}" headers = { "Authorization": f"Bearer {api_token}", "Accept": "application/x-ndjson", } completed = False
async with httpx.AsyncClient() as client: async with client.stream("GET", url, headers=headers) as response: response.raise_for_status()
async for line in response.aiter_lines(): if not line.strip(): continue
event = json.loads(line) if event.get("op") != "Meta": # Process the domain signal. Unknown operations should be # ignored or retained rather than treated as fatal. continue
metadata = event.get("data", {}) kind = metadata.get("kind") detail = metadata.get("data")
if kind == "Progress": print(f"Progress: {detail[0]}/{detail[1]}") elif kind == "StoredResult": print(f"Stored result: {detail}") elif kind == "Error": raise RuntimeError(detail or "Lookup failed") elif kind == "Timeout": raise RuntimeError(f"Lookup timed out: {detail}") elif kind == "Done": completed = True
if not completed: raise RuntimeError("Lookup response ended before completion")async function lookupDomain(domain, apiToken) { const url = "https://api.haveibeensquatted.com/v2/squat/" + encodeURIComponent(domain); const response = await fetch(url, { method: "GET", headers: { Authorization: `Bearer ${apiToken}`, Accept: "application/x-ndjson", }, });
if (!response.ok) { throw new Error(`Lookup failed: ${response.status} ${response.statusText}`); } if (!response.body) { throw new Error("Lookup response did not include a body"); }
const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let completed = false;
function handleLine(line) { if (!line.trim()) return;
const event = JSON.parse(line); if (event.op !== "Meta") { // Process the domain signal. Unknown operations should be ignored or // retained rather than treated as fatal. return; }
const kind = event.data?.kind; const detail = event.data?.data; if (kind === "Error") throw new Error(detail || "Lookup failed"); if (kind === "Timeout") throw new Error(`Lookup timed out: ${detail}`); if (kind === "Done") completed = true; }
while (true) { const { done, value } = await reader.read(); if (done) { buffer += decoder.decode(); break; }
buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) handleLine(line); }
if (buffer.trim()) handleLine(buffer); if (!completed) throw new Error("Lookup response ended before completion");}package main
import ( "bufio" "encoding/json" "fmt" "net/http" "net/url")
type streamEvent struct { Op string `json:"op"` Data json.RawMessage `json:"data"`}
type metaEvent struct { Kind string `json:"kind"` Data json.RawMessage `json:"data"`}
func lookupDomain(domain, apiToken string) error { endpoint := "https://api.haveibeensquatted.com/v2/squat/" + url.PathEscape(domain) req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { return err }
req.Header.Set("Authorization", "Bearer "+apiToken) req.Header.Set("Accept", "application/x-ndjson")
resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("lookup failed: %s", resp.Status) }
scanner := bufio.NewScanner(resp.Body) scanner.Buffer(make([]byte, 64*1024), 4*1024*1024) completed := false
for scanner.Scan() { if scanner.Text() == "" { continue }
var event streamEvent if err := json.Unmarshal(scanner.Bytes(), &event); err != nil { return err } if event.Op != "Meta" { // Process known domain signals and tolerate unknown operations. continue }
var metadata metaEvent if err := json.Unmarshal(event.Data, &metadata); err != nil { return err }
switch metadata.Kind { case "Error", "Timeout": var detail string _ = json.Unmarshal(metadata.Data, &detail) return fmt.Errorf("lookup %s: %s", metadata.Kind, detail) case "Done": completed = true } }
if err := scanner.Err(); err != nil { return err } if !completed { return fmt.Errorf("lookup response ended before completion") } return nil}Errors and retries
Section titled “Errors and retries”| Status | Meaning | Retry guidance |
|---|---|---|
400 |
Invalid domain or request parameters | Correct the request. |
401 |
Missing or invalid authentication | Replace or restore the token. |
403 |
Missing scope, plan access, or authorization | Correct access before retrying. |
429 |
Rate limit reached | Use bounded backoff and honor Retry-After. |
503 |
The service is unavailable or at capacity | Use bounded backoff and honor Retry-After. |
Do not automatically repeat a request after its response stream has started. A new invocation creates a new analysis request and can duplicate work.
GET /v1/meta/usage returns usage information for the authenticated API key as a regular JSON body. Pass the optional query parameter t for the usage window length in minutes. The default is 1440 minutes.
export API_TOKEN="YOUR_API_TOKEN"
curl -sS "https://api.haveibeensquatted.com/v1/meta/usage" \ -H "Authorization: Bearer $API_TOKEN"Retained results
Section titled “Retained results”Authenticated lookup streams can emit Meta messages with kind: StoredResult and a universally unique identifier (UUID). Use the UUID with the retrieval endpoint and select a format path:
curl "https://api.haveibeensquatted.com/v1/retrieve/$uuid"
curl "https://api.haveibeensquatted.com/v1/retrieve/$uuid/ndjson"
curl "https://api.haveibeensquatted.com/v1/retrieve/$uuid/json"
curl "https://api.haveibeensquatted.com/v1/retrieve/$uuid/csv"Processing merged results
Section titled “Processing merged results”These examples operate on a buffered merged JSON response:
# List returned domainsjq -r 'to_entries[] | select(.key != "meta") | .key' results.json
# List domains with a phishing score above 0.5jq -r 'to_entries[] | select(.key != "meta") | select(.value.classification.phishing? > 0.5) | .key' results.json
# Extract public web identifiersjq 'to_entries[] | select(.key != "meta") | .value.identifiers[]?' results.jsonFor stored and application export fields, see the JSON export reference. Rule-facing selectors are documented in the signals reference.
OpenAPI
Section titled “OpenAPI”The OpenAPI 3.1 Have I Been Squatted API specification is available as follows.
For an interactive view, open the OpenAPI specification in Swagger Editor.