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.
Node and TypeScript
There is an official SDK. It is a thin client over the same endpoint documented below, with the response typed, the reason codes as a literal union so a typo is a compile error, and the retry and idempotency rules already applied. It has no runtime dependencies and refuses to construct in a browser.
npm install @orisift/sdkBuild on Node 22 LTS or later, which is the supported line today. The package installs on Node 20 and later, because an integration already running on 20 should not be locked out by an upgrade, but 20 reached end of life in March 2026 and receives no security fixes. import works on every supported version; require needs Node 20.19 or 22.12, where require(esm) is unflagged.
// Server-side only. The SDK refuses to construct in a browser.
import { Orisift } from "@orisift/sdk";
const apiKey = process.env.ORISIFT_API_KEY;
if (!apiKey) throw new Error("Set ORISIFT_API_KEY on the server first.");
const orisift = new Orisift({ apiKey });
const result = await orisift.validate({
input: "user@example.com",
type: "email",
policy: "contact_standard",
// One key per logical submission. Reuse it for a retry of the same
// submission; never share it between two different requests.
idempotencyKey: `${submissionId}:email`,
});
switch (result.action.type) {
case "continue": return accept();
case "ask_to_correct": return askToFix(result.action.reasons);
case "verify_contact": return acceptPendingVerification();
case "review": return acceptAndFlag();
case "retry_later": return acceptAndRecheckLater();
}That switch is exhaustive against the action type. If Orisift ever adds an action, your code fails to compile rather than falling through a default branch, and the SDK refuses an action it does not recognise rather than treating it as permission to proceed.
Who can fix it
The detail most integrations miss. A reason marked caller means your request was incomplete, not that the person typed something wrong, so showing them a field error blames the wrong party.
// Who can put a finding right decides what your application does about it.
for (const reason of result.action.reasons) {
if (reason.correctable_by === "end_user") {
// Show a message beside the field. They typed something fixable.
fieldError(reason.message);
} else if (reason.correctable_by === "caller") {
// Your integration sent an incomplete request. The usual case is a
// national-format phone number with no country. Do not blame the person:
// accept, flag internally, and fix the request.
logIntegrationFault(reason.code);
}
}Errors
Every failure is a typed class carrying requestId. The HTTP status mapping is in the errors section below; these are the same conditions, named.
import {
Orisift,
OrisiftAuthError,
OrisiftInsufficientCreditsError,
OrisiftRateLimitError,
OrisiftTimeoutError,
OrisiftMalformedActionError,
} from "@orisift/sdk";
try {
const result = await orisift.validate({ input, policy: "contact_standard" });
} catch (err) {
if (err instanceof OrisiftInsufficientCreditsError) {
// err.balance and err.required are numbers, or null if not supplied.
} else if (err instanceof OrisiftRateLimitError) {
// err.retryAfterSeconds, when the response carried one.
} else if (err instanceof OrisiftAuthError) {
// The key is wrong, revoked or missing. Retrying will not help.
} else if (err instanceof OrisiftTimeoutError) {
// No response inside the budget. The transport already retried.
} else if (err instanceof OrisiftMalformedActionError) {
// A policy was requested and no usable action came back. This is an
// error, not permission to proceed: the SDK never assumes "continue".
}
// Every error carries err.requestId. Quote it when asking us about a call.
}Retries and idempotency
The SDK retries transport failures only: 429 and 5xx, with full jitter, honouring Retry-After. Never any other 4xx, and never a retry_later action, which is a decision about evidence rather than a transport fault: asking again later is a new lookup, needing a new key and costing another credit.
An idempotency key is generated per call and reused across every one of those retries, which is what makes automatic retry safe: a retried attempt replays instead of billing again. Supply your own to make a user-visible operation idempotent, such as a double-clicked form. Two different requests must never share a key; that is a 409, not a replay.
Timeouts are bounded twice: per attempt, and across every attempt including backoff, so a caller holding an HTTP request open has a bound on the whole operation rather than on each part of it.
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, unless it establishes no evidence, in which case usage.credits is 0.
# 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 LTS or later, 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,
});Screen a signup
POST /v1/signup checks up to four identifiers from one signup as a single billable unit and, when you name a policy, returns one recommended action for the whole signup. Authentication is the same bearer key as /v1/lookup, and the same server-side rule applies: a key in browser code is a leaked key.
curl -sS https://orisift.com/v1/signup \
-H "Authorization: Bearer $ORISIFT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-8f21c4" \
-d '{
"email": "someone@stripe.com",
"phone": "020 7946 0000",
"country": "GB",
"policy": "contact_standard"
}'What you may send
At least one of email, phone, ip or domain. Each is independently optional and partial input never fails the call. country applies to phone only, and sending it without one is a 400 because it would otherwise do nothing.
Unknown fields are refused, not ignored. A field you believed was taken into account should never silently not be.
Policy and version
policy is optional. Omit it and no recommended_action is returned at all — that absence is the compatibility guarantee. policy_version pins a version; omitted means the latest at the time of the request, and the integer it resolved to always comes back inside the action, so a response never leaves the version ambiguous. A version that does not exist is a 400 rather than a charge.
Who the action is for
recommended_action.audience is caller or end_user, and every reason carries its own. A caller action means your request needs correcting — a missing country, say. Show the person signing up nothing, fix the request, and retry with a new idempotency key. Rendering a caller reason as an instruction to a customer is the mistake this field exists to prevent.
Partial outcomes
A check that could not finish does not fail the screening. Each check reports a status of completed, partial, attempted or skipped, and an incomplete[] entry naming the capability, the code and a reason_class of request_context, transient_dependency or unsupported_capability. established is separate: it says whether the check produced evidence that counts. A partial check is usually established: true, which is the point of the distinction.
When no action can be returned
If you named a policy and it could not be evaluated, you get action_unavailable naming the reason and the affected checks, and the screening is not charged for. It is never replaced by a default: a fabricated continue is the one failure an integration cannot detect. Three states stay distinguishable — no policy named (neither field), evaluated (recommended_action), asked for and broken (action_unavailable).
Quoted versus actual credits
quote.maximum_credits is stated before the work runs and reserved in full, so a request is refused with 402 rather than started and then found unaffordable. The actual charge is the standalone cost of the checks that established evidence, capped, and is settled down from the quote.usage.standalone_equivalent tells you what those same checks would have cost as separate lookups.
A screening is never more expensive than running the same establishing checks separately, and costs nothing when none of them established anything — a free screening still returns a useful next step.
Replay
The same Idempotency-Key with the same body returns the stored screening and charges nothing. usage.idempotent_replay is true on that response, and usage.credits repeats what that screening originally cost — it is not what the replayed request cost, which was nothing. Read idempotent_replay before treating the figure as a debit.
Any change to the identifiers, country, policy, policy_version or email_verification makes it a different request and is refused with 409. Nothing is reserved and no credit is spent.
Errors
| Status | When | Charged |
|---|---|---|
| 400 | No identifier, country without phone, unknown policy version, unknown field, or deep verification while no provider is connected | No |
| 401 | Missing, invalid or revoked key, or a suspended account | No |
| 402 | Balance cannot cover the quoted maximum. Refused before any work ran | No |
| 409 | Idempotency-Key reused for a different request | No |
| 429 | Plan rate limit exceeded. retry_after is in seconds | No |
| 500 | The service errored. The reservation is released | No |
Deep email verification
email_verification accepts fast, which is the default and the only mode backed by real evidence. deep returns a 400 rather than being quietly downgraded: a caller who asked for mailbox verification and received a format check would believe something about the address that nothing established. Orisift performs no SMTP verification and no provider is connected.
What this is not
cross_signal.implemented is false and says so in the response. No identifier is used to draw any conclusion about another; each result stands on its own evidence. This is not correlated fraud detection and should not be described as such. There is no combined score, because summing per-check scores would assert that a 40 on a phone and a 40 on a domain mean the same thing and combine linearly, and nothing measured supports either.
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 lookup establishes nothing, so no credit is deducted: usage.credits is 0 and usage.not_charged names the checks that could not be obtained. 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": "Documentation addresses are never routable on the public internet.",
"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, for display. This wording can change without an API version bump, so branch on verdict, capabilities and coverage_limitations rather than matching on the text. 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 | Credits this lookup cost, including on a replay. Phone/email/IP cost 1; domain costs 2. It is 0 when no credits were deducted because no evidence was established; see usage.not_charged. |
| usage.not_charged | object | absent | Present only when the lookup was free. Carries reason ("no_evidence_established") and capabilities, the ids of the checks that were offered but could not be obtained. Absent on every charged lookup, including rejected verdicts and partial results. |
| 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-orisift-credits-charged, x-orisift-credit-balance and Server-Timing headers. The same values are also sent under the legacy names x-truelook-credits-charged and x-truelook-credit-balance, retained so that integrations written before the Orisift names existed keep working. Prefer the x-orisift- names in new code.
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.
- Reusing a key decides between three outcomes. The SAME request replays the stored result. A DIFFERENT request is refused with 409 idempotency_key_reused: nothing is reserved, no lookup runs and no credit is spent, and the earlier result is never returned for a different request. And a matching request whose stored result has passed its retention period is refused with 409 idempotency_key_expired, which also runs no check and changes no accounting; use a new key for a fresh screening at the normal price. The stored result is never silently replaced by a newer one.
- While the stored result is available, a retry returns it with usage.idempotent_replay=true. usage.credits and x-orisift-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. |
| 409 | idempotency_key_reused | The Idempotency-Key was already used for a DIFFERENT request. Nothing was reserved, no lookup ran and no credit was spent. Use a new key, or resend the original request unchanged. |
| 409 | idempotency_key_expired | The Idempotency-Key matches a screening that has passed its retention period, so there is no stored result to return. No check ran and no accounting changed. Use a NEW key for a fresh screening, billed normally; retrying with the same key will always return this. |
| 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. |
| 429 | unbilled_limit_reached | PROVISIONAL. The account has reached its daily limit of lookups that established no evidence. Those are not charged, so the limit refuses further work rather than billing for it. Retry-After carries the seconds to 00:00 UTC, when it resets; retrying sooner will not clear it. It refuses new work only: a retry of a request that already completed still returns the stored result. |
| 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.
When a lookup is not charged
- No credits are deducted when a lookup establishes no evidence. usage.credits is 0, usage.not_charged names the checks that were offered but could not be obtained, and the balance is untouched. The x-orisift-credits-charged header reports 0 as well.
- Establishing evidence means at least one check reached an answer, either way. A conclusive negative is an answer: a number in an unassigned range or a domain on a disposable list is charged in full, because that finding is the result you asked for.
- Partial results are charged in full. If syntax validates but the DNS lookup times out, the response is charged: you learned that the address is well formed, which is a fact Orisift had to do work to establish. The unobtained check is still named in the response, but it does not make the lookup free.
- Capabilities marked unsupported never affect the charge in either direction. They are published limits, disclosed before you send the request, not failures that occurred during it.
- If the same Idempotency-Key arrives several times while the first request is still running, each arrival may execute the checks. Only one result is stored and only one charge is made, and every caller is given that same result and id, but the repeated execution still consumes resolver time and compute on your behalf. Sending a retry before the original has answered does not save work; it duplicates it.
- A validly submitted request that produces no evidence is free and returns 200 with a result you can replay, and usage.not_charged explains why. Input that cannot be read as any of the four types is a different thing: it is refused with 422 unrecognised_input at the type detector, before any credit is reserved and before any check runs. That is also free, but it is a refusal rather than a result, so there is nothing to replay and no usage block to read it from.
- PROVISIONAL: unbilled lookups are capped per account per UTC day, counted across every key on the account, currently at 200. Past the cap, further lookups are refused with 429 unbilled_limit_reached rather than charged, and that refusal carries Retry-After pointing at 00:00 UTC. The cap is an operational judgement rather than a measurement, and it will be revisited once there is real traffic to set it from.
- The cap is APPROXIMATE, not a strict maximum. It is checked by reading the day's count before a lookup runs, and that count only moves once a lookup finishes, so requests already in flight all read the same number and all pass the same boundary. Overshoot is bounded by how many of your requests overlap, which your plan's requests-per-minute limit bounds in turn, and it grows with how long a lookup takes. Treat 200 as the point at which refusals begin, not as a number that cannot be exceeded.
- What the cap costs you if you reach it: the refusal covers EVERY new lookup for the rest of the UTC day, including ones that would have established evidence and been charged, because whether a lookup establishes evidence is only knowable by running it. An integration that hits the cap stops being able to validate anything until 00:00 UTC. The cap is set high for that reason. Retries of requests that already completed are NOT refused: a stored result is returned as an ordinary replay, because serving it runs no checks and moves no credits. A replay is matched on the Idempotency-Key together with the request itself, and both are scoped to the account rather than to the API key that sent them, so a retry sent with a different key of the same account still replays.
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.