Sirveil · v1.4.0
Exposure API
Two operations. Ask about one domain and get a verdict with its evidence in about a second, or scan an identity across the whole exposure surface and get every record we can attribute to it.
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
evidencecarries it. - not_indexed
- The domain was searched and nothing matched the identity.
- indeterminate
- The search ran and the evidence was too weak to call either way. 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_...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",
"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": [
"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": "name_only",
"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 the broker registry 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": [],
"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.
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
Three 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.
| Status | error | What to do |
|---|---|---|
| 401 | invalid_key | Missing, malformed, unknown or revoked key. |
| 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 for the window is spent. Wait for the reset — Retry-After carries the seconds remaining. Metered AWS Marketplace subscriptions are not allowance-capped 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. |
| 400 | identity_required | Body was missing an identity object. See also identity_incomplete (no first/last name) and seed_not_supported. |
429 carries two different refusals and error tells them apart: rate_limited clears in seconds, 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 across the broker registry. |
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.
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.
RapidAPI consumers reach the same two endpoints through the RapidAPI gateway, which authenticates with X-RapidAPI-Proxy-Secret and bills its own consumers on its own terms; a missing secret is a 401, not a fall-through to a Sirveil API key.
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.
View on AWS MarketplaceEvaluating rather than buying, or need a volume or private-offer price? Email support@sirveil.ai.