Sirveil Exposure
เครื่องมือแบบอ่านอย่างเดียวที่ใช้ตรวจสอบว่าบุคคลชาวอเมริกันตามชื่อที่ระบุถูกจัดทำดัชนีบนเว็บไซต์ที่ระบุอยู่ในปัจจุบันหรือไม่ — คืนค่าผลลัพธ์เป็น indexed, not_indexed หรือ indeterminate พร้อม URL ข้อความตัวอย่าง และเวลาประทับ
เอกสาร
The verify contract: three states
Every successful call returns one of exactly three states. There are not five.
indexed
A listing matching the identity was found on the domain, and evidence carries it.
not_indexed
The name shape — the broadest query this endpoint issues — ran against the domain and matched nothing. This is the only state that reports an observed absence, and it is deliberately narrow.
indeterminate
We looked and cannot call it. Three ways to get here: retrieval failed; the evidence was too weak either way; or the query we issued was scoped to one identifier you declared and the name shape never ran, so a miss says only that the domain’s index does not answer to that identifier. This is an answer, not an error, and it is not a synonym for not_indexed. Collapsing the two means reporting an absence we did not observe.
What we do not tell you, stated plainly
We report index presence. We do not fetch the broker page, so we cannot distinguish “the listing is live right now” from “the search engine has a stale entry for a page that has since been removed”. A mature exposure product eventually wants two further states — indexed_not_live and not_indexed_live — and returning them today would mean asserting a fetch we never performed. They do not exist yet. The same sentence ships inside every response as contract.limitation, so a downstream consumer cannot miss it.
Authentication
Pass your key as a bearer token. Keys are prefixed sk_live_ or sk_test_ so a leaked key is identifiable on sight. We store only a hash — if you lose a key it cannot be recovered, only revoked and reissued.
Authorization: Bearer sk_live_...
Reuse connections, and retry on a dropped one
Use an HTTP client with keep-alive enabled and hold one session open for the run rather than opening a fresh connection per call — a requests.Session, a single node-fetch agent, one HttpClient instance, curl without --no-keepalive. A small fraction of brand-new TLS connections drop before any HTTP response arrives; reused connections do not, and they are markedly faster.
Retry a connection-level failure — a reset, a TLS handshake error, a socket closed before any status line. Nothing was served, so nothing was charged, and the retry is safe. Do not blind-retry a request that returned an HTTP status: read error first, because 402, 403 and a 400 will never succeed on a retry, and a 429 needs the Retry-After wait.
GET /api/v1/whoami
The prove-your-key call. Returns your plan, remaining quota, spend ceiling and billing rail. It is free and consumes no quota, so you can confirm a key works, confirm which rail it bills on, and confirm your ceiling before you spend anything.
Call it first. Every other endpoint that answers 200 to a valid key is metered, so without this the cheapest way to test a key would be to buy a verification. A usage row is still written — every /v1/* call leaves a trace — with zero units and no charge. The quota gate and the spend ceiling do not apply to it, which is deliberate: an account at its ceiling is exactly the one that needs to be able to ask what its ceiling is.
curl https://ai.sirveil.ai/api/v1/whoami \
-H "Authorization: Bearer sk_live_your_key_here"
Response
{
"tenant": { "id": "3f8a1c04-...", "name": "Acme Compliance" },
"plan": { "id": "marketplace", "label": "Marketplace" },
"quota": {
"included_units": 1000000,
"used_units": 418,
"remaining_units": 999582,
"window_resets_at": "2026-09-01T00:00:00.000Z"
},
"spend_ceiling": {
"ceiling_usd": 1500,
"spent_usd": 6.42,
"remaining_usd": 1493.58
},
"billing_rail": "aws",
"channel": "api_key",
"rate_limit_per_minute": 1200,
"pipeline_version": "v11.0.0",
"linkage_weights_version": "lw-v4",
"calibration_version": "cal-v3"
}
billing_rail is which marketplace owns this account’s money, and channel is how this particular call proved who it was — api_key or rapidapi. A metered AWS Marketplace subscription holds no allowance, so its quota.included_units is not a limit you can exhaust; the spend_ceiling block is the one that can refuse you.
POST /api/v1/verify
The call performs no database write. It is a question about a domain, not a scan of a person. The identity you send is used to build the search query and held in an in-memory cache, partitioned to your account, for a few minutes — so a repeated question answers consistently and is not charged twice. It is not written to disk and not kept beyond that.
identity.firstName and identity.lastName are required. Everything else — city, state, street, phone, email, dob — is optional and narrows the search.
curl -X POST https://ai.sirveil.ai/api/v1/verify \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"identity": {
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"city": "Austin",
"state": "TX"
},
"domain": "whitepages.com"
}'
Response
{
"state": "indexed",
"observed_at": "2026-08-07T21:14:02.881Z",
"domain": "whitepages.com",
"evidence": {
"url": "https://www.whitepages.com/name/Jane-Doe/Austin-TX/...",
"title": "Jane Doe in Austin, TX",
"snippet": "Jane Doe, age 41, Austin TX. Known addresses ...",
"retrieved_at": "2026-08-07T21:14:02.402Z"
},
"confidence": 0.81,
"calibrated_probability": 0.78,
"linkage": { "bits": 14.2, "probability": 0.79, "calibrated": true, "weights_version": "lw-v4" },
"explains": [
"Issued 1 email-scoped query against whitepages.com.",
"name matched in full",
"city and state matched",
"one result from the domain, no competing candidate"
],
"contract": {
"states": ["indexed", "not_indexed", "indeterminate"],
"limitation": "Index presence only. We did not fetch the page, so we cannot say whether the listing is still served."
},
"pipeline_version": "v11.0.0",
"linkage_weights_version": "lw-v4",
"calibration_version": "cal-v3",
"meta": {
"identifier_used": "email",
"results_from_domain": 1,
"results_total": 8,
"latency_ms": 1284,
"search_ok": true
}
}
POST /api/v1/scan
Everything /verify is not. It builds a profile from what you supply, plans and issues searches across 12 pinned people-search domains and the open web, checks breach and public-record lanes, resolves candidates to the identity and ranks them — returning one card per distinct exposure, with the fields that exposure reveals and the URL each field was read from.
Set your timeout before you call this
A scan takes two to three and a half minutes. Measured wall clock across five runs of this endpoint: 135 / 144 / 154 / 155 / 203 seconds (median 154). Configure a read timeout of at least 240 seconds. PowerShell's Invoke-RestMethod defaults to 100 seconds — below the median — which is the failure measured on go-live day: the client saw a network error, the server completed and billed. The defaults in most HTTP clients and wrappers will abort mid-scan the same way. A dropped connection is not a refund. If your timeout is under 240 seconds, send Prefer: respond-async.
If your caller sits behind an API gateway, check its ceiling against those numbers. RapidAPI cuts at 180 seconds — its default and its maximum — which sits between our median and our slowest measured run, so the synchronous shape would fail on the slow tail. AWS API Gateway defaults to 29 seconds. A gateway timeout is not a refund: the scan still runs and is still billed. Use the asynchronous shape and the question does not arise.
identity.firstName and identity.lastName are required. Everything else is optional and improves the result. A value we cannot use is dropped rather than rejected, and its field name comes back in dropped_fields — check that field if a scan looks thinner than you expected.
curl -X POST https://ai.sirveil.ai/api/v1/scan \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
--max-time 300 \
-d '{
"identity": {
"firstName": "Jane",
"lastName": "Doe",
"city": "Austin",
"state": "TX",
"postalCode": "78701",
"email": "jane.doe@example.com"
}
}'
Response (abridged to one card)
{
"scan_id": "6f1c9d02-8a4e-4c1b-9f77-2b0e5d3a71c8",
"observed_at": "2026-08-10T18:22:41.117Z",
"latency_ms": 64218,
"cards": [
{
"id": "6f1c9d02-8a4e-4c1b-9f77-2b0e5d3a71c8-1",
"rank": 1,
"tier": "A",
"data_type": "person_record",
"risk_category": "identity_theft",
"why_it_matters": "Full address history alongside age and relatives is enough to answer most account-recovery questions.",
"source": "whitepages.com",
"raw_data": {
"title": "Jane Doe, 41 — Austin, TX",
"snippet": "Jane Doe, age 41, Austin TX. Known addresses, phone numbers, relatives ...",
"link": "https://www.whitepages.com/name/Jane-Doe/Austin-TX/..."
},
"exposed_fields": [
{ "kind": "address", "label": "Address", "value": "1200 W 6th St, Austin, TX 78703",
"evidence": "declared", "source_ref": "https://www.whitepages.com/name/..." },
{ "kind": "age", "label": "Age", "value": "41", "evidence": "corroborated", "derived": "decade" },
{ "kind": "relative", "label": "Relative", "value": "M. D." }
],
"provenance": [
{ "field": "addresses", "value": "1200 W 6th St", "source_ref": "https://www.whitepages.com/name/..." }
],
"sources": [{ "name": "whitepages.com", "link": "https://www.whitepages.com/name/..." }],
"also_seen_on": [{ "name": "spokeo.com", "link": "https://www.spokeo.com/Jane-Doe/..." }],
"match_probability": 0.9,
"match_probability_calibrated": true
}
],
"metrics": { "card_count": 23, "distinct_exposures": 23, "tier_a": 6, "tier_b": 11, "tier_c": 6 },
"degraded": false,
"search_ok": true,
"stopped_early": false,
"dropped_fields": [],
"coverage": { ... see Coverage below ... },
"contract": {
"limitation": "A scan reads search indexes and public sources at the moment you call it ..."
},
"pipeline_version": "v11.0.0",
"linkage_weights_version": "lw-v4",
"calibration_version": "cal-v3"
}
Streaming: Accept: application/x-ndjson
One JSON object per line. Progress events while the scan runs, then a single result event carrying exactly the body the synchronous shape returns. Both shapes cost the same and produce one usage record. A bad request or an unavailable search backend is still a real 400 / 503 with a JSON body — those are decided before the stream opens.
curl -N -X POST https://ai.sirveil.ai/api/v1/scan \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Accept: application/x-ndjson" \
-H "Content-Type: application/json" \
--max-time 300 -d '{"identity":{"firstName":"Jane","lastName":"Doe"}}'
{"type":"progress","phase":"building_profile","step":2,"steps":7,"at":1786...}
{"type":"progress","phase":"planning_searches","step":3,"steps":7,"at":1786...}
{"type":"progress","phase":"searching","step":4,"steps":7,"at":1786...}
{"type":"progress","phase":"matching","step":5,"steps":7,"at":1786...}
{"type":"progress","phase":"assembling","step":6,"steps":7,"at":1786...}
{"type":"result","result":{ ... identical to the synchronous body ... }}
What a scan does not do
It accumulates nothing. There is no dossier, no history and no “seen before” count: two scans of the same person are two independent observations, and nothing from an earlier call is carried forward. A synchronous scan writes nothing at all; an asynchronous one holds your input and your answer for 24 hours so you can collect them, and that is the only difference. It does not fetch and confirm each listing either — a card means this record is indexed and matches the identity you declared, not this page is live now. And an empty cards array is not proof of absence: check search_ok and degraded before reading it as one. All of this ships inside every response as contract.limitation.
Coverage: the shape of the hole
Every scan returns a coverage block alongside the cards. It reports how many domains were expected, how many were queried, how many came back empty, how many failed, and how many were never reached — and it names the domains it did not get to.
This exists because the honest failure mode of an exposure sweep is not a wrong answer, it is a quiet one. A sweep that reached three fewer domains than it planned to and a sweep that reached all 12 produce the same-looking card list. Coverage is how you tell them apart without trusting us.
Classification is backed by the 548-domain registry derived from the California CPPA public data-broker register plus a curated priority set. A base sweep queries 12 pinned people-search domains; coverage is reported over the registry, so you can see exactly which of the rest went unqueried on your call.
"coverage": {
"expected": 548,
"queried_hit": 9,
"queried_empty": 3,
"queried_failed": 0,
"never_queried": 536,
"never_queried_domains": [
"advancedbackgroundchecks.com",
"anywho.com",
"checkpeople.com",
"familytreenow.com",
"peekyou.com"
// ... capped per response; see never_queried_truncated
],
"never_queried_truncated": true
}
queried_empty and never_queried are opposite in meaning
queried_empty means we asked and the domain holds nothing on this identity — the scan is correct to show nothing there, and you may act on the absence. never_queried means we did not ask: the silence is ours and says nothing about the domain. Before this block was published the two were indistinguishable in the response, which made one of them quietly unusable.
The name list is capped per response and never_queried_truncated tells you when it was. The block carries counts and domain names only — a site: target identifies who we asked, never who we asked about, so no part of the identity you sent can reach it. And the whole block is null, not zeroed, on a run old enough to predate coverage recording: expected: 0, never_queried: 0 would read as “no gaps”, which is the opposite of “not known”.
Asynchronous scans
A scan takes minutes, and most gateways will not hold a connection that long. Send Prefer: respond-async and you get a job id back immediately, then poll for the answer. The synchronous shape is unchanged and remains the default — nothing you have already integrated is affected.
Your quota, rate limit and spend ceiling are all checked at submit. A 202 means the work is accepted; a refusal happens there and then, before anything is queued and before anything is charged. The job is metered once, when it actually runs.
curl -X POST https://ai.sirveil.ai/api/v1/scan \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Prefer: respond-async" \
-d '{"identity":{"firstName":"Jane","lastName":"Doe","state":"TX"}}'
HTTP/1.1 202 Accepted
Location: /api/v1/jobs/9c2f0b17-...
{
"job_id": "9c2f0b17-4d8a-4f2e-9c31-7a5b0e6d1f42",
"status": "queued",
"poll_url": "/api/v1/jobs/9c2f0b17-4d8a-4f2e-9c31-7a5b0e6d1f42",
"estimated_seconds": 154,
"queue": { "depth": 1, "position": 1 },
"throughput": { "capacity_per_hour": 58, "drain_concurrency": 1 },
"expires_at": "2026-08-12T18:22:41.117Z"
}
Then poll
curl https://ai.sirveil.ai/api/v1/jobs/9c2f0b17-4d8a-4f2e-9c31-7a5b0e6d1f42 \
-H "Authorization: Bearer sk_live_your_key_here"
{ "job_id": "9c2f0b17-...", "status": "queued",
"queue": { "depth": 3, "position": 2 }, "estimated_seconds": 214 }
# ... a little later ...
{ "job_id": "9c2f0b17-...", "status": "claimed", "started_at": "2026-08-11T18:24:02.410Z" }
# ... and finally ...
{ "job_id": "9c2f0b17-...", "status": "succeeded",
"finished_at": "2026-08-11T18:26:36.882Z",
"result": { ... identical to the synchronous body ... } }
Throughput: up to 58 scans per hour
Published rather than left for you to discover. One scan takes 154 seconds at the median and up to 203. The worker is invoked once a minute and runs 1 scan per invocation, which is what sets the ceiling at 58 per hour. Queue 20 jobs and the last one finishes in roughly 21 minutes.
That ceiling is computed from the schedule, not measured under load — it assumes every invocation claims a job and every job finishes. Plan against the estimated_seconds in your job response instead, which reflects your actual position in the queue. If you need more than this, say so before you queue the batch: the limit is a deployment setting, not a property of the product.
The queue is shared across all customers, so every job response tells you the current queue.depth, your queue.position and an estimated_seconds — you can watch it move rather than guess. Polling is free: it bills nothing and consumes no plan units, so there is no advantage to polling slowly.
If you need more than this, tell us before you queue the batch rather than after. The limit is a deployment setting, not a property of the product.
Retention: 24 hours, then deleted
The identity you submitted and the result are kept for 24 hours so you can collect them, then both are deleted and the job reports expired. The job record itself survives with its timings, so a late poll tells you what happened instead of returning a 404 you would have to interpret. A job id belonging to another account is a 404, not a 403 — a 403 would confirm the id exists.
Errors and limits
Five separate limits can stop a call, and they fail differently because they need different actions from you. The error field names which one was hit — always read it rather than switching on the status alone, because 401, 400 and 429 each carry more than one code.
| Status | error | What to do |
|---|---|---|
| 401 | missing_key | No Authorization header at all. Send Authorization: Bearer sk_live_…. |
| 401 | invalid_key | A key was sent, and it is malformed, unknown or revoked. |
| 403 | tenant_suspended | The key is valid, the account is not. Not retryable — contact us. |
| 429 | rate_limited | Too many calls this minute. Wait — Retry-After says how long. |
| 429 | quota_exceeded | The account’s included-units allowance is a hard cap: when it is spent, further calls are refused. There is no overage, no automatic upgrade, and no charge beyond what was agreed. Wait for the reset — Retry-After carries the seconds remaining. Metered AWS Marketplace subscriptions hold no allowance and never see this. |
| 402 | spend_ceiling_reached | Your account’s hard dollar ceiling — a runaway guard, not a limit you bought. It bounds our cost of goods rather than your invoice, and it is never raised silently: ask, and we raise it explicitly. |
| 503 | search_unavailable | Scan only. Search is down, so we refused rather than sell you an empty result that would read as “nothing found”. Nothing is charged. Retry shortly. |
| 429 | queue_depth_exceeded | Async scan only. Your queue — or the whole service’s — is at its depth cap, so we refused rather than hand you a 202 we knew we could not deliver before the job expired. Nothing is charged. Retry-After carries the wait. |
| 503 | capacity_reached | The service is temporarily not accepting new work. Your account limits are unaffected and nothing is charged. Retry shortly. |
| 400 | identity_required | Body was missing an identity object. See also seed_not_supported (scan takes the identity you send, not a seed) and invalid_json. |
| 400 | identity_incomplete | firstName or lastName was absent. The name is the anchor every candidate is scored against, so neither is optional. |
| 400 | invalid_domain | Verify only. domain must be a bare hostname — whitepages.com, not a full URL and not a path. |
| 400 | domain_excluded | Verify only, and not retryable. We do not run an identity against facial-recognition or reverse-face search sites on anyone’s behalf. This is a policy refusal and is not configurable per account. |
429 carries three different refusals and error tells them apart: rate_limited clears in seconds, queue_depth_exceeded clears as your queue drains, and quota_exceeded clears when your billing window turns over. A failed call is never billed — you are charged for answers, not attempts.
Error responses carry the same pipeline_version, linkage_weights_version and calibration_version stamps as successes, so a rejected request can be traced to the build that rejected it.
Pricing
Usage-based, metered per served call. There is no subscription, no minimum, no allowance to size in advance, and nothing at all to pay in a month you do not call us.
| Operation | Price per call | What one call is |
|---|---|---|
| Verification | $0.10 | One indexing check for one identity on one domain. |
| Full scan | $0.35 | One full exposure scan for one identity: 12 pinned people-search domains, plus the open web, breach and username lanes. |
A failed call is never billed. You are charged for answers, not attempts: a 400, a 401, a 403 and a 503 search_unavailable each cost nothing, and the refusal is decided before any search provider is called.
Every shape of a scan costs the same one call. Synchronous, streaming and asynchronous are the same work billed once; polling a job meters nothing at all, so there is no advantage to polling slowly. A dropped connection, however, is not a refund — the scan completed. Read the timeout note before you call /v1/scan synchronously.
A hard dollar ceiling sits on every account as a runaway guard. It is ours rather than something you bought: it bounds our cost of goods, and if it fires you get a 402 spend_ceiling_reached instead of an invoice nobody expected. Tell us before you queue a large batch and we raise it.
MCP for agents
A read-only MCP server ships alongside REST at /api/v1/mcp, protocol version 2025-06-18. It exposes one tool, verify_indexed — ask whether a named person is indexed on one data-broker domain and get the evidence back. There is no scan tool and no write path.
indeterminate is a real answer from this tool as much as from REST. An agent that reports it to a user as “not found” is claiming an absence that was not observed.
An MCP tool call performs a verification’s work and is billed at the verification price — $0.10 per call. It is not a free convenience wrapper.
The handshake — initialize and tools/list — needs no key, so a registry can index the server without an account. tools/call requires one and resolves the same tenant, quota and ceiling as every REST call. Usage is recorded against /v1/mcp rather than /v1/verify so you can see what arrived through which surface, but the charge is the same.
curl -X POST https://ai.sirveil.ai/api/v1/mcp \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "verify_indexed",
"arguments": {
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"city": "Austin",
"state": "TX",
"domain": "whitepages.com"
}
}
}'
Where you can buy
AWS Marketplace. You subscribe with the AWS account you already have, AWS invoices the usage on the bill you already get, and we report every served call against the two dimensions locked at product creation — verification and scan, priced above.
There is no card to enter here and no payment detail for us to collect — AWS forbids collecting payment information for a Marketplace product at any time, and we do not. When the subscription completes AWS sends you to our fulfilment page, which asks for the email address the subscription belongs to and issues your API key on the spot. Nothing to install, and no waiting on us.
Start now, without talking to anyone
Subscribe on AWS Marketplace with your existing AWS account and you are billed per served call — $0.10 a verification and $0.35 a full scan — on the AWS invoice you already receive. No subscription fee, no minimum, no sales call and no quote to wait for. Your API key is issued the moment the subscription completes.
Evaluating rather than buying, or need a volume or private-offer price? Email support@sirveil.ai.