Documentation
IP Intelligence
Look up any public IP address for geographic location, ASN, reverse DNS, abuse contacts, and threat intelligence.
Authentication
All requests require an API key passed via the X-API-Key header:
X-API-Key: your-api-key-here
Rate Limiting
Requests are rate-limited per API key using a sliding window. Responses report your current window state:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute for your plan |
X-RateLimit-Remaining | Requests remaining in the current window |
Retry-After | Seconds to wait before retrying (only on 429) |
| Plan | Rate Limit | Max IPs / Request |
|---|---|---|
| Free | 5 req/min | 1 |
| Pro | 120 req/min | 20 |
IP Intelligence
Submit one or more IP addresses and choose exactly which lookups to run via the lookups array — there is no default set. All requested lookups run concurrently.
Request Body
| Field | Type | Plan | Description |
|---|---|---|---|
ips | string[] | All | Array of IPv4 or IPv6 addresses (required). Canonicalized before use, so 2001:DB8::1 and 2001:db8:0:0:0:0:0:1 are one address, one result, one cache key. Entries that canonicalize to the same address are deduplicated; plan limits count the entries you submitted. |
lookups | string[] | All | Which lookups to run (required, non-empty). Names are trimmed, lowercased and deduplicated. |
Valid Lookups
| Name | Plan | Description |
|---|---|---|
geo | All | Country, city, region, coordinates, timezone (MaxMind GeoLite2). |
asn | All | ASN number, organization, network (MaxMind GeoLite2 ASN). |
reverse_dns | All | PTR record hostnames. |
abuse_contact | All | Network owner, country, abuse email (via RDAP). |
threat_intel | Pro | VirusTotal, URLhaus, and AlienVault OTX with a composite risk score. |
The IP and domain catalogs are separate. threat_intel appears in both; every other name belongs to one endpoint only, so asking for whois on an IP returns 400.
Input Validation
IP addresses are validated and canonicalized using Go's netip.ParseAddr. The following are rejected with a 400 response:
- Invalid IP addresses
- Private/RFC 1918 addresses (10.x, 172.16-31.x, 192.168.x)
- Loopback (127.0.0.1, ::1)
- Link-local (169.254.x, fe80::)
- Multicast, unspecified, and CGNAT (100.64.0.0/10)
Example
curl -X POST https://api.securityaccelerated.com/v1/ip/lookup \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your-api-key' \
-H 'X-Client-Request-ID: req-001' \
-d '{
"ips": ["8.8.8.8", "1.1.1.1"],
"lookups": ["geo", "asn", "reverse_dns"]
}'
const res = await fetch("https://api.securityaccelerated.com/v1/ip/lookup", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "your-api-key",
"X-Client-Request-ID": "req-001",
},
body: JSON.stringify({
ips: ["8.8.8.8", "1.1.1.1"],
lookups: ["geo", "asn", "reverse_dns"],
}),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(error);
}
// Results are wrapped in a "results" envelope. Match by `ip` rather than by
// position: addresses that canonicalize to the same value collapse into one.
const { results } = await res.json();
for (const r of results) {
console.log(r.ip, r.geo?.country, r.asn?.organization);
// Individual lookups can fail while the rest succeed.
for (const e of r.errors ?? []) {
console.warn(`${r.ip}: ${e.lookup} failed — ${e.message}`);
}
}
import requests
resp = requests.post(
"https://api.securityaccelerated.com/v1/ip/lookup",
headers={
"X-API-Key": "your-api-key",
"X-Client-Request-ID": "req-001",
},
json={
"ips": ["8.8.8.8", "1.1.1.1"],
"lookups": ["geo", "asn", "reverse_dns"],
},
)
if resp.status_code != 200:
print("Error:", resp.json()["error"])
else:
# Results are wrapped in a "results" envelope.
for r in resp.json()["results"]:
print(r["ip"], r["geo"]["country"], r["asn"]["organization"])
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]any{
"ips": []string{"8.8.8.8", "1.1.1.1"},
"lookups": []string{"geo", "asn", "reverse_dns"},
})
req, _ := http.NewRequest("POST",
"https://api.securityaccelerated.com/v1/ip/lookup",
bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", "your-api-key")
req.Header.Set("X-Client-Request-ID", "req-001")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var apiErr map[string]string
json.NewDecoder(resp.Body).Decode(&apiErr)
fmt.Println("Error:", apiErr["error"])
return
}
// Results are wrapped in a "results" envelope.
var response struct {
Results []map[string]any `json:"results"`
}
json.NewDecoder(resp.Body).Decode(&response)
fmt.Println(response.Results[0]["ip"], response.Results[0]["geo"])
}
Response Fields
Results are wrapped in a results envelope. Match elements by ip rather than by position — addresses that canonicalize to the same value collapse into one result.
geo -- Geographic Location
Location data from MaxMind GeoLite2. Available on all plans.
| Field | Description |
|---|---|
continent / continent_code | Continent name and two-letter code |
country / country_code | Country name and ISO 3166-1 alpha-2 code |
region / region_code | First-level subdivision (state/province) |
city | City name |
postal_code | Postal/ZIP code |
latitude / longitude | Approximate coordinates |
timezone | IANA timezone (e.g. America/New_York) |
accuracy_radius | Accuracy radius in kilometers |
asn -- Autonomous System
Network ownership data from MaxMind GeoLite2 ASN. Available on all plans.
| Field | Description |
|---|---|
asn | Autonomous System Number |
organization | Organization that owns the ASN (e.g. "Google LLC") |
network | CIDR block for the network |
reverse_dns -- PTR Records
Reverse DNS lookup via PTR records. Available on all plans.
| Field | Description |
|---|---|
hostnames | Array of hostnames from PTR records (e.g. ["dns.google"]) |
abuse_contact -- Network Abuse Contact
Abuse contact information from RDAP (Registration Data Access Protocol). Available on all plans.
| Field | Description |
|---|---|
network | IP range (e.g. "8.8.8.0 - 8.8.8.255") |
organization | Network owner organization |
country | Country of the network registration |
email | Abuse contact email address |
threat_intel -- Threat Intelligence Pro
Aggregated threat intelligence from multiple providers. Requires a Pro plan.
| Field | Description |
|---|---|
risk_level | Aggregated risk: none, low, medium, high, critical |
score | Numeric score (0-100) |
categories | Detected categories (e.g. "malicious", "suspicious") |
providers | Per-provider results with individual scores and details |
Providers: VirusTotal (50% weight), URLhaus (30%), AlienVault OTX (20%).
errors -- Partial Failures
If individual lookups fail, they appear here. The rest of the response is still populated.
"errors": [
{ "lookup": "reverse_dns", "message": "ptr lookup: no such host" }
]
A lookup that failed outright omits its field entirely
The IP is valid, the request succeeded, and one lookup did not answer. The field it would have filled is absent — not null, not an empty object:
{
"results": [
{
"ip": "8.8.8.8",
"inputs": ["8.8.8.8"],
"geo": { "cache_hit": false, "country": "US", ... },
"asn": { "cache_hit": false, "asn": 15169, ... },
"errors": [
{ "lookup": "abuse_contact", "message": "rdap lookup: i/o timeout" }
]
}
]
}
Requesting n lookups does not guarantee n fields, so code that reaches straight for result.abuse_contact.email works in testing and throws the first time an upstream has a bad minute. Failures are never cached — the next request retries rather than remembering the failure.
threat_intel fans out to several providers and reports a failing one as threat_intel:virustotal — a name qualified with a colon. That entry means one provider was unavailable, not that the lookup failed: threat_intel is still present, scored over the providers that did answer. A plain threat_intel means the whole lookup failed and the field is gone.Error Handling
| Status | Meaning |
|---|---|
400 Bad Request | Invalid request body, missing ips field, or non-routable IP address |
400 Bad Request | lookups missing or empty after normalization — lookups is required, e.g. ["geo", "asn"] |
400 Bad Request | Unrecognized lookup name (including domain-only names such as whois) — the message names the offending entry |
401 Unauthorized | Missing or invalid API key |
403 Forbidden | A requested lookup is outside your plan — e.g. threat_intel requires a pro plan |
413 Content Too Large | Request body exceeds the 1 MB limit |
429 Too Many Requests | Rate limit exceeded -- check the Retry-After header |
500 Internal Server Error | Server error -- contact support@securityaccelerated.com |
All error responses use this format:
{
"error": "description of the problem"
}
200 OK with successful lookups populated and failures listed in the errors array.Caching
Results are cached per lookup, per IP under ip:<ip>:<lookup> — a namespace separate from domain lookups, so threat_intel on the two endpoints never collides. Each lookup object carries its own cache_hit, so one result can mix cached and freshly fetched fields.
| Lookup | TTL |
|---|---|
abuse_contact | 24 hours |
reverse_dns | 6 hours |
threat_intel | 30 minutes |
geo, asn | never cached |
geo and asn are never cached
Both read a local MaxMind memory-mapped database with no network call, so a Redis round trip would cost more than simply doing the lookup again. They are recomputed on every request and their cache_hit is always false — which is accurate, since the data really was produced for that request.
Cache failures never fail a request
Caching is fail-open: an unreachable cache means every lookup is fetched live, an unreadable entry is treated as a miss and overwritten, and a failed write still returns the fetched result. Failed lookups are never cached — a failing lookup is retried on the next request rather than remembered.
Request Correlation
Every response carries an X-Request-ID the API generates for you. You can additionally send your own X-Client-Request-ID to tie a request to an identifier your systems already use — it is echoed back and recorded in server-side logs.
| Header | Direction | Description |
|---|---|---|
X-Client-Request-ID | Request | Your correlation ID. Up to 64 characters, limited to letters, digits, hyphens, underscores, dots, and colons — enough for UUIDs and common trace IDs. |
X-Client-Request-ID | Response | Echoed back byte for byte when the value you sent was accepted. |
X-Request-ID | Response | A UUID the API generates for every request, whether or not you sent a correlation ID. Quote this when contacting support. |
X-Client-Request-ID on the response as a signal that the value did not pass validation. X-Request-ID is always present — including on error responses, where correlation matters most — so it works as a fallback.Full Response Example
Response Headers
Returned alongside the body on a successful lookup:
Content-Type: application/json; charset=utf-8
X-Request-ID: 9f8b2c14-6d3a-4e57-b8f1-2a7c05e91d34
X-Client-Request-ID: req-001
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-Request-ID is generated by the API for every request — quote it when contacting support. X-Client-Request-ID appears only when you sent a valid one; see Request Correlation. The X-RateLimit-* pair reflects your plan's window, and a 429 adds Retry-After — see Rate Limiting.
Response Body
Results are wrapped in a results envelope. Each element carries the canonicalized ip and the verbatim inputs that mapped to it — match results by ip, not by array position. Every lookup object carries its own cache_hit; note that geo and asn are always false because they are never cached.
{
"results": [
{
"ip": "8.8.8.8",
"inputs": ["8.8.8.8"],
"geo": {
"cache_hit": false,
"continent": "North America",
"continent_code": "NA",
"country": "United States",
"country_code": "US",
"region": "California",
"region_code": "CA",
"city": "Mountain View",
"latitude": 37.386,
"longitude": -122.0838,
"timezone": "America/Los_Angeles",
"accuracy_radius": 1000
},
"asn": {
"cache_hit": false,
"asn": 15169,
"organization": "GOOGLE"
},
"reverse_dns": {
"cache_hit": true,
"hostnames": ["dns.google"]
},
"abuse_contact": {
"cache_hit": true,
"network": "8.8.8.0 - 8.8.8.255",
"organization": "Google LLC",
"country": "US",
"email": "network-abuse@google.com"
}
}
]
}