Docs
OpenAPI live at https://api.traceexit.com/swagger-ui
Lookup examples
curl https://api.traceexit.com/v1/ip/203.0.113.10 \
-H "Authorization: Bearer te_live_…" \
-H "Accept: application/json"const res = await fetch("https://api.traceexit.com/v1/ip/203.0.113.10", {
headers: { Authorization: "Bearer " + process.env.TRACE_EXIT_API_KEY },
});
const dto = await res.json();import { TraceExitClient } from "@traceexit/sdk";
const client = new TraceExitClient({
apiKey: process.env.TRACE_EXIT_API_KEY,
});
const dto = await client.lookupIp("203.0.113.10");import os
import requests
res = requests.get(
"https://api.traceexit.com/v1/ip/203.0.113.10",
headers={"Authorization": f"Bearer {os.environ['TRACE_EXIT_API_KEY']}"},
)
dto = res.json()req, _ := http.NewRequest("GET", "https://api.traceexit.com/v1/ip/203.0.113.10", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("TRACE_EXIT_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()$ch = curl_init('https://api.traceexit.com/v1/ip/203.0.113.10');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('TRACE_EXIT_API_KEY'),
'Accept: application/json',
],
CURLOPT_RETURNTRANSFER => true,
]);
$dto = json_decode(curl_exec($ch), true);HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.traceexit.com/v1/ip/203.0.113.10"))
.header("Authorization", "Bearer " + System.getenv("TRACE_EXIT_API_KEY"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",
Environment.GetEnvironmentVariable("TRACE_EXIT_API_KEY"));
var json = await client.GetStringAsync("https://api.traceexit.com/v1/ip/203.0.113.10");require "net/http"
require "json"
uri = URI("https://api.traceexit.com/v1/ip/203.0.113.10")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV['TRACE_EXIT_API_KEY']}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
dto = JSON.parse(res.body)let client = reqwest::Client::new();
let dto = client
.get("https://api.traceexit.com/v1/ip/203.0.113.10")
.bearer_auth(std::env::var("TRACE_EXIT_API_KEY")?)
.send()
.await?
.json::<serde_json::Value>()
.await?;{
"mcpServers": {
"traceexit": {
"command": "npx",
"args": ["-y", "@traceexit/mcp"],
"env": {
"TRACE_EXIT_API_KEY": "te_live_…",
"TRACE_EXIT_BASE_URL": "https://api.traceexit.com"
}
}
}
}Tools: lookup_ip, check_privacy, check_fraud, compare_network, lookup_ip_batch. Same keys and quotas as REST. Connect MCP.
Authentication
Send your API key as a Bearer token on every lookup. Create keys in the console under Keys. Keys start with te_live_ (production) or te_test_ (sandbox). Browser session cookies authenticate the console only — they are not used for /v1/ip lookups.
Authorization: Bearer te_live_…Rate limits and quota
Two caps apply: a short-term requests-per-second fair-use limit by plan, and a monthly lookup allowance (daily × 30, UTC). REST and MCP share one meter. When you hit the RPS cap, wait briefly and retry. When monthly quota is exhausted on Free, requests stop until the next UTC month; paid plans may allow overage per your plan.
Soft RPS by plan (approximate): Free 10 · Lite 25 · Starter 50 · Pro 100 · Business 200 · Scale 500. See Pricing for daily allowances.
Errors
Errors use application/problem+json with at least title and status. Common cases:
- 401 — Missing or invalid API key. Check the Authorization header.
- 429 — Too many requests in a short window. Respect Retry-After (often 1 second) and back off.
- 429 — Monthly quota exceeded. Upgrade, wait for the next UTC month, or reduce volume. X-Quota-Remaining may be 0.
- 400 / 404 — Invalid IP or path. Fix the request; do not retry blindly.
- 5xx — Temporary service issue. Retry with exponential backoff; treat unresolved privacy as unknown if you must decide immediately.
Example problem body
{
"title": "Monthly quota exceeded",
"status": 429,
"detail": "Upgrade plan or wait for next UTC month"
}Response shape
Nested lookup response with location, network, tri-state privacy, risk, fraud, and meta.
{
"ip": "203.0.113.10",
"ipVersion": 4,
"privacy": {
"vpn": false,
"proxy": false,
"tor": false,
"datacenter": true,
"residentialProxy": false,
"anonymizer": false
},
"risk": {
"score": 42,
"level": "medium",
"reasons": ["datacenter"]
},
"location": {
"countryCode": "US",
"city": "Ashburn"
},
"meta": {
"cacheHit": true,
"partial": false,
"confidence": 85
}
}Partial results and confidence
Use meta.partial and meta.confidence together with tri-state privacy (true / false / null) to decide how hard to enforce.
meta.partial
When true, the lookup did not gather enough evidence to treat every privacy field as settled. Scores and flags are still returned — prefer soft enforcement (challenge, review, or log-and-allow) over hard blocks unless other signals agree. When false, the main privacy read was complete enough to rely on.
meta.confidence
Overall trust in this response, 0–100 (higher is stronger). Rough bands: 80+ high — act on clear privacy flags; 55–79 moderate — combine with your own context; below 55 limited — treat unknown privacy as unknown, not safe. Confidence drops when meta.partial is true, key privacy fields are unresolved, or evidence is thin.
risk.reasons and fraud.reasons
Short tags explaining why a score was raised. Use them for logging, allowlists, and rule tuning — not as a sole deny list.
- vpn, proxy, tor, residential_proxy — a matching privacy flag contributed to risk.
- datacenter, hosting_asn — the address maps to hosting or datacenter infrastructure.
- high_network_abuse, elevated_network_abuse — sampled traffic on this network shows elevated anonymizer use.
- anonymizer — the derived anonymizer flag contributed to fraud.
- high_geo_risk — location context adds checkout or fraud risk.
- ml_anonymizer — probable masking when privacy is not fully confirmed; pair with meta.partial and confidence.
- feedback_abuse — prior operator reports flagged this network.
CGNAT / mobile
Shared carrier NATs can look noisy. Prefer ASN allowlists for known mobile ranges; see enterprise guidance when you scale.
Webhooks
Configure an HTTPS webhook URL in Settings to receive change notifications. Each delivery is a POST with a JSON body and signed headers.
Destination
Set your webhook URL in Settings. Only HTTPS endpoints are accepted.
Events
- quota.threshold — monthly usage crosses 80% or 100%.
- watch.privacy_changed — a watched IP’s privacy flags or primary type changed.
- watch.risk_changed — risk score moved by your threshold or the risk level band changed.
- watch.network_changed — ASN, organisation, or network identity changed for a watched target.
- watch.location_changed — country code changed for a watched IP.
Headers
Every delivery includes Content-Type: application/json plus:
- X-TraceExit-Event-Id — unique id for this delivery (same as eventId in the body).
- X-TraceExit-Signature — sha256=<hex> HMAC of the payload.
- X-TraceExit-Signature-Timestamp — Unix seconds used in the signature.
Verify signatures
Compute HMAC-SHA256 of {timestamp}.{rawBody} using your signing secret (UTF-8), where timestamp is X-TraceExit-Signature-Timestamp and rawBody is the exact request body bytes. Compare to X-TraceExit-Signature (sha256=<hex>). Reject requests when the timestamp is more than 5 minutes from your clock.
Example: watch.risk_changed
{
"event": "watch.risk_changed",
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"occurredAt": "2026-07-27T12:00:00Z",
"organizationId": 42,
"watch": {
"id": 7,
"label": "Login gateway",
"targetType": "ip",
"targetValue": "203.0.113.10"
},
"subject": {
"ip": "203.0.113.10",
"ipVersion": 4
},
"changes": [
{
"field": "risk.score",
"previous": 25,
"current": 72
},
{
"field": "risk.level",
"previous": "low",
"current": "high"
}
]
}Deliveries are at-least-once. Use eventId to deduplicate on your side.
Rotate your signing secret in Settings. The new secret is shown once — store it before leaving the page.