API reference · version 1
One endpoint for every check
POST /v1/lookup is the public lookup endpoint. Send an identifier; Orisift detects its type, runs the checks, and returns a verdict with the evidence and the limits behind it.
Authentication
Send your key as a bearer token. Keys are created in the dashboard and shown once; only a hash is stored, so a lost key is rotated rather than recovered. Keep it on your server. Anyone holding the key can spend your account’s credits. In the dashboard, select New key, save the revealed value securely, then select I have saved it. Revoke stops a key immediately. Rotate creates a replacement and leaves the previous key working for 60 minutes. Up to 20 active keys are allowed. Keys have no configurable scopes; each key can run all four lookup types.
Authorization: Bearer tl_live_xxxxxxxxxxxxxxxxxxxxxxxxThe API is server-to-server. It sends no CORS headers, so a browser cannot call it across origins. Do not put a key in frontend code, browser storage or a public repository. The Orisift dashboard uses a separate session-cookie and CSRF flow at /api/lookup; those cookies and response fields are not the public API contract.
Run a lookup
The only required field is input. Optional: type to skip detection, country for national-format phone numbers, and retain_input to store the submitted form against the lookup (off by default). Normalised identifiers are still stored; read Data handling.
input: string, 1 to 320 characters before trimming. Use one identifier per request.type: optional phone, email, ip or domain. This selects a checker; it does not guarantee a valid value.country: optional two-letter country code, case-insensitive. Use the phone number’s country, not the caller’s location. Unsupported codes leave a phone check unresolved, even if the input has an international calling code.retain_input: optional boolean, default false. Read Data handling before submitting personal data.- Send JSON with Content-Type: application/json. The examples use reserved values and may return a rejected verdict. Copying an example does nothing; running it with your key consumes the stated credits.
# Bash/zsh. Set ORISIFT_API_KEY securely in your server environment first.
# Save a fresh key per logical lookup; reuse it only when retrying that lookup.
IDEMPOTENCY_KEY="$(uuidgen)"
curl --fail-with-body -X POST "https://orisift.com/v1/lookup" \
-H "Authorization: Bearer $ORISIFT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{"input":"2001:db8::1"}'// Node.js 22+, on your server. Save as lookup.mjs.
const apiKey = process.env.ORISIFT_API_KEY;
if (!apiKey) throw new Error("Set ORISIFT_API_KEY on the server first.");
// Save this value with your job and reuse it for retries of the same lookup.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://orisift.com/v1/lookup", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({"input":"2001:db8::1"}),
});
const body = await res.json();
if (!res.ok) {
console.error(res.status, body.error?.type, body.error?.request_id);
throw new Error(body.error?.message ?? "Lookup failed");
}
// Decide using capabilities and limits as well as the overall verdict.
// insufficient_evidence and a null score require review, not automatic rejection.
console.log({
verdict: body.verdict,
riskScore: body.risk_score,
capabilities: body.capabilities,
limitations: body.coverage_limitations,
ai: body.analysis.ai,
});Country handling
A number in international form carries its own country, so nothing extra is needed. A number in national format is ambiguous, and Orisift will not guess: the same ten digits are a valid mobile in one country and nonsense in another. Without a country you get insufficient_evidence and a country_required coverage code, and a null risk score. This completed lookup still costs one credit. The dashboard asks you to choose a country before submitting.
# Reserved UK example. This is not a real subscriber number.
curl --fail-with-body -X POST "https://orisift.com/v1/lookup" \
-H "Authorization: Bearer $ORISIFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"020 7946 0000","country":"GB"}'Response
This example comes from a deterministic check of the reserved documentation address 2001:db8::1. It is rejected because it cannot be public client traffic. The lookup ID, time and latency are illustrative; the remaining fields match the reference response. A model name can be present while AI is disabled. Read analysis.ai to tell whether AI ran. Your Settings opt-out applies to future dashboard and API lookups. Anonymous previews never use AI.
{
"object": "lookup",
"api_version": "1",
"id": "lk_example",
"created": "2026-09-23T00:00:00.000Z",
"type": "ip",
"country": null,
"normalized": "2001:db8::1",
"verdict": "rejected",
"risk_score": 60,
"evidence_coverage": 95,
"headline": "This IP address will not work",
"summary": "Documentation addresses are never routable on the public internet. That is conclusive, so the IP address cannot be used.",
"recommendation": "Do not accept this IP address. Ask for a replacement at the point of capture.",
"attributes": [
{
"label": "Version",
"value": "IPv6",
"observed": true
},
{
"label": "Scope",
"value": "Special purpose",
"observed": true
}
],
"signals": [
{
"label": "IANA registry",
"value": "Documentation (RFC 3849). This address is not globally routable, so it cannot be a public client.",
"tone": "bad",
"source": "IANA special-purpose address registry",
"provenance": "static_registry"
}
],
"capabilities": [
{
"id": "syntax",
"state": "verified",
"detail": "Valid IPv6"
},
{
"id": "plan_validity",
"state": "failed",
"detail": "Documentation (RFC 3849)"
},
{
"id": "reputation",
"state": "unsupported",
"detail": "Not applicable to a non-routable address"
}
],
"coverage_limitations": [],
"analysis": {
"checks": [
"Address parsing",
"IANA special-purpose screen"
],
"ai": "disabled",
"model": "claude-sonnet-5"
},
"usage": {
"credits": 1,
"credit_cost_table": {
"phone": 1,
"email": 1,
"ip": 1,
"domain": 2
},
"balance": 499,
"idempotent_replay": false
},
"latency_ms": 1
}Field reference
| Field | Type | Description |
|---|---|---|
| object / api_version | string | "lookup" and "1". Additive fields may be introduced; ignore unknown fields. |
| id / created | string | Lookup ID and creation time in ISO 8601 format. |
| type | string | phone, email, ip or domain. Automatically detected unless supplied. |
| country | string | null | Phone country when determined, including from an international calling code. Otherwise null; email, IP and domain do not return geolocation. |
| normalized | string | Display form returned by the check. Parsed phones use international formatting with spaces, not guaranteed compact E.164. International domain names use Unicode display form. Inspect the E.164 attribute if your integration needs compact phone format. |
| verdict | string | verified, risky, rejected or insufficient_evidence. Verified is not proof of identity, reachability or ownership. An evidence gap is not an invalid identifier. |
| risk_score | integer | null | 0 to 100, higher indicates more risk under the scoring rules. Not a probability of fraud. Null for insufficient_evidence. |
| evidence_coverage | integer | 0 to 100: how much of the expected evidence was obtained. Separate from risk. |
| headline / summary / recommendation | string | Human-readable assessment. Inspect individual capabilities and limitations before acting. |
| attributes | array | Objects with label, value (string) and observed (boolean). observed=false marks an inference. |
| signals | array | label, value, tone, source and provenance. Tones: good, warn, bad, neutral, unknown. Provenance: live_dns, live_rdns, static_registry, library, heuristic, ai_inference, unavailable. |
| capabilities | array | Objects with id, state and detail. IDs: syntax, plan_validity, infrastructure, reputation, carrier, reachability. Returned capabilities vary by type. |
| capabilities[].state | string | verified: check established it; failed: check found a failure; inferred: indirect evidence; unsupported: not offered; unavailable: could not be checked. Missing support is not a negative finding. |
| coverage_limitations | array | Objects with code and message explaining missing coverage or evidence. |
| analysis.checks | string[] | Checks attempted for this identifier. |
| analysis.ai | string | disabled, failed or ok. Disabled and failed use deterministic scoring. ok means the optional AI step completed, not that its inference is independently verified. |
| analysis.model | string | null | Configured model label when supplied. A label alone does not establish that the model ran. |
| usage.credits | integer | Original lookup cost, including on a replay. Phone/email/IP cost 1; domain costs 2. Completed rejected and inconclusive results are charged. |
| usage.credit_cost_table | object | Current phone, email, ip and domain credit costs. |
| usage.balance | integer | Available account balance after the request. |
| usage.idempotent_replay | boolean | True when the saved result was returned without a new charge. |
| latency_ms | number | Lookup execution duration in milliseconds; not an uptime or latency guarantee. |
Idempotency
Successful responses include x-request-id, x-truelook-credits-charged, x-truelook-credit-balance and Server-Timing headers. Header names retain the existing API contract.
Send an Idempotency-Key header and a retry returns the original result without spending a second credit. Use a unique key for each logical lookup and reuse it only for retries of that same input, type and country. Do not use one signup ID for several different field checks.
- Keys are scoped to the account and truncated to 200 characters. Keep your key within that length. The service does not reject a key reused with different input; it can return the earlier result instead.
- While the stored result is available, a retry returns it with usage.idempotent_replay=true. usage.credits and x-truelook-credits-charged report the original cost, not an additional debit. Use usage.balance to inspect your remaining balance.
- Without a key, the service deduplicates identical input/type/country requests within fixed ten-second time buckets. Requests on opposite sides of a bucket boundary may each be charged, even when less than ten seconds apart. Supply a key for reliable retries.
- The result history has a retention window, so do not rely on indefinite response replay. Keep retry windows short. An interrupted request may be run again; abandoned reservations older than fifteen minutes are eligible for release by the reconciliation job, not guaranteed to clear at an exact wall-clock time.
Errors
Handled API errors carry a request_id, also returned in the x-request-id header. Quote it in support requests. A proxy or infrastructure failure may return a different response; check status and content type before assuming JSON. GET returns 405 method_not_allowed. OPTIONS returns 405 with Allow: POST and no response body; cross-origin browser calls are not supported.
{
"error": {
"type": "insufficient_credits",
"message": "Your credit balance cannot cover this lookup.",
"request_id": "req_4f8a1c92b7e3d0a6f512",
"balance": 0,
"required": 2
}
}| Status | Type | Meaning |
|---|---|---|
| 400 | invalid_request | The body was not valid JSON, or a field failed validation. |
| 401 | authentication_required | No bearer token was supplied. |
| 401 | invalid_api_key | The key is unknown, revoked, or its account is suspended. |
| 402 | insufficient_credits | The balance cannot cover this lookup. The response includes balance and required. |
| 422 | unrecognised_input | The value could not be read as any of the four types. Send type or country to disambiguate. |
| 429 | rate_limited | Plan rate limit exceeded. Honour the Retry-After header. |
| 405 | method_not_allowed | GET is not supported. Use POST. OPTIONS has an empty 405 response. |
| 500 | internal_error | A handled execution failure. The service attempts to release the credit reservation. Contact support if the balance does not recover. |
Rate limits and credits
| Plan | Requests / min | Credit allowance |
|---|---|---|
| Free | 30 | 500 once on signup |
| Starter | 120 | 20,000 / month |
| Growth | 600 | 85,000 / month |
| Professional | 1500 | 300,000 / month |
Credit cost per lookup: phone 1, email 1, IP 1, domain 2. Limits are enforced per account and, for API traffic, per key. A retry also counts toward the rate limit. Credit allowance is separate from requests per minute. Requests should be queued and retried with backoff after the Retry-After interval; no concurrency guarantee is published.
Coverage and limits
- Phone: format and numbering-plan validation in the territories supported by the numbering-plan library. Carrier, porting and reachability are not offered, because no provider is contracted; the API reports those capabilities as unsupported rather than guessing.
- Email: domain-level routing only. Orisift does not probe SMTP, so it never confirms that an individual mailbox exists or is monitored.
- IP: IANA special-purpose screening for IPv4 and IPv6, plus reverse DNS. Network classification is a naming heuristic, not proxy or VPN detection, and geolocation is not returned at all.
- Domain: delegation, address, mail and email-authentication policy. Registration data and domain age are not queried.
- A resolver failure can leave a result with insufficient_evidence when required evidence is missing. Other independent checks may still establish a conclusion. It is never reported as proof that an identifier is invalid.
- Orisift is not a consumer reporting agency. Its output is not a consumer report and must not be used to decide credit, employment, insurance or housing eligibility.
What happens to what you send
- Every API lookup saves a normalised identifier, fingerprint and result evidence in history. Normalisation is not anonymisation: an email, domain, phone number or IP address can remain identifiable, and the normalised value can equal the submitted value.
- retain_input defaults to false. This omits the input property from newly saved results and removes other strings containing the submitted form, except strings equal to the normalised value. It does not prevent storage of the identifier. retain_input=true also preserves the submitted form. Using the API without opening history does not avoid this storage.
- DNS checks send hostnames or IP addresses to public resolvers. When AI is allowed and available, evidence is sent to Anthropic. The submitted identifier is removed from the evidence before it is sent. Opt out in Settings for future dashboard and API lookups; anonymous previews never use AI.
- Scheduled daily cleanup targets lookups older than 90 days; deletion depends on that job running. Account deletion removes active account records, keys, history and credit ledger. Contact privacy@orisift.com about processor or backup retention; deletion does not recall data already shared with a processor.
Get a key
500 credits once on signup. No card required.