Sirveil Exposure

Read-only tool that checks whether a named US person is currently indexed on a named website — returns indexed, not_indexed, or indeterminate with URL, snippet and timestamp.

Tài liệu

API reference

Two billable endpoints. One rate card. Ship in an afternoon.

$0.10 a completed check. $0.35 a completed sweep. $0.00 for anything we couldn’t serve — our faults and our upstreams’ are our cost, not yours. Everything below is readable without an account.

Preview build — figures locked at launch.

Quickstart

Base URL https://ai.sirveil.ai. Auth is one bearer key. No SDK to install, no OAuth dance, no sandbox to apply for — your first real answer is one curl away. Grab a key at /scan-api/signup and the meter starts at zero.

Quickstart

curlPythonNode

Copy

A check — one person, one site you name. Built for broker

and people-search domains; "couldn't tell" is a real answer.

Identity fields go inside "identity"; domain stays top-level.

curl https://ai.sirveil.ai/api/v1/verify
-H "Authorization: Bearer sk_live_…"
-d '{ "identity": { "firstName":"Jane", "lastName":"Doe", "phone":"5035551212" }, "domain":"examplebroker.com" }'

→ one structured answer, with its evidence

{ "state": "indexed", "domain": "examplebroker.com", "evidence": [ … ], "dropped_fields": [], "billed": "$0.10" // failed calls: $0.00 }

pip install requests — that's the whole SDK story

import requests

r = requests.post("https://ai.sirveil.ai/api/v1/verify", headers={"Authorization": "Bearer sk_live_…"}, json={"identity": {"firstName": "Jane", "lastName": "Doe", "phone": "5035551212"}, "domain": "examplebroker.com"})

print(r.json()["state"]) # indexed | not_indexed | indeterminate

// no SDK to install — fetch is the client const r = await fetch("https://ai.sirveil.ai/api/v1/verify", { method: "POST", headers: { Authorization: "Bearer sk_live_…" }, body: JSON.stringify({ identity: { firstName: "Jane", lastName: "Doe", phone: "5035551212" }, domain: "examplebroker.com" }) }); const result = await r.json(); console.log(result.state); // indexed | not_indexed | indeterminate

That call billed ten cents. If it had failed, it would have billed nothing. That is the entire commercial relationship — the rest of this page is detail.

Authentication

Every request carries one header. Keys look like sk_live_… and come from /scan-api/signup — minutes, not meetings. The key is the account: it identifies you, it meters you, and that is all it does.

HeaderCopy

Authorization: Bearer sk_live_…

  • Keep it server-side. A bearer key in browser JavaScript is a bearer key you have published. Proxy calls through your backend.
  • Rotate freely. Issue a new key, move traffic, revoke the old one — the meter follows the account, not the key.
  • Missing or bad key → 401, billed $0.00, like every error.

Building a good request

Both endpoints take the same identity shape: a JSON identity object holding everything we know about the subject. Only identity.firstName and identity.lastName are required — but the name only gets you through the door. What makes the answer worth paying for is one strong identifier alongside it. A request carrying a name and a phone number is a materially different product experience from a request carrying a name alone.

Which identifier to send first

We rank identifiers by how well they actually retrieve on our own runs against our own sample — not by how selective they look on paper. The ranking below is the order the search actually values them, on both endpoints:

RankIdentifierWhy it ranks there
1stphoneBroker profile pages are keyed on phone numbers. The single best field to send.
2ndemailNear-unique across the open web. A strong second if you have no phone.
3rdstreetAddress1 + city/stateStrong — broker pages are keyed on address too — but it’s the field people are most reluctant to hand over on a first pass.
name onlyThe fallback. It works, but this is the weak case — see the floor warnings on each endpoint.

Why phone outranks email here. On a general web search, an email address is the more selective identifier — a bare email query is near-unique across the whole internet. But once a query is scoped to a single broker domain, that ranking inverts: broker profile pages are built around phone numbers and addresses, and very often don’t print an email at all. So the search leads with phone deliberately, against the more obvious ordering. The ordering came out of our own runs — we publish no precision figure for the Service, and this isn’t one.

  • city + state are the cheapest defence against namesake collapse. For any common name, they’re the difference between an answer about one person and an answer about four.
  • streetAddress1 is recommended, not minimal — send it when you have it, but don’t let it stand between you and your first call.
  • usernames extends the search across a large list of public sites. It only runs on a handle you declare — we never guess one for you.

Fields we can’t use: dropped_fields

A field we can’t use — a malformed phone number, an unparseable date of birth — is dropped and named in the dropped_fields array on the response. It never fails the whole request, and it is never silently ignored: a silently-ignored field would mean you paid full price for a weaker search with no way to find out. Naming the dropped field is how you find out.

Integration tip: log dropped_fields during your build-out. It’s the fastest way to catch a formatting mismatch between your data model and ours — and it turns a vague “the results seem thin” ticket into a one-line fix on your side.

POST/api/v1/verify

The check — one person, one domain you name. Synchronous. Measured 890 ms median, 1,463 ms p95 (benchmarks).

Ask whether a person is indexed on a single site. Broker and people-search domains return decidable verdicts; login-walled or noindex’d sites return an honest indeterminate instead of a fake “no”. Every answer arrives with its evidence.

Request body

Identity fields travel inside one identity object; domain stays top-level. This is the body we recommend sending — four fields, phone-led:

Recommended — 4 fieldsCopy

{ "identity": { "firstName": "Jane", "lastName": "Doe", "phone": "5035551212" }, "domain": "examplebroker.com" }

Phone is the top-ranked identifier on this endpoint — broker profile pages are keyed on phone numbers — so this shape produces an identifier-anchored query instead of the name-only fallback (see Building a good request). No phone? Substitute email. Neither? streetAddress1 plus city.

Minimum — 3 fields (the floor, not the recommendation)Copy

{ "identity": { "firstName": "Jane", "lastName": "Doe" }, "domain": "examplebroker.com" }

The floor works, but don’t lead with it. A name-only request runs the weakest possible search at full price, and it’s the request most likely to answer indeterminate — a technically successful call that feels like a failure. Send one strong identifier with the name.

FieldTypeRequiredNotes
identity.firstNamestringrequiredSubject’s given name, as a broker would list it.
identity.lastNamestringrequiredSubject’s family name.
identity.phonestringoptionalTop-ranked identifier here. Supplying phone or email is what turns a name-only fallback into an identifier-anchored search.
identity.emailstringoptionalSecond-ranked. Near-unique on the open web; less so on broker pages, which rarely print one.
identity.citystringoptionalWith state, the cheapest defence against namesake collapse on common names.
identity.statestringoptionalTwo-letter US state, e.g. "OR".
identity.streetAddress1stringoptionalRecommended, not minimal — the third identifier shape, strong when supplied.
identity.dateOfBirthstringoptionalHelps confirm a match. Unparseable values are dropped and named in dropped_fields.
identity.employerstringoptionalExtra disambiguation when brokers list one.
identity.usernamesarrayoptionalExtends the search across public sites — only for handles you declare; we never guess one.
domainstringrequiredTop-level, next to identity. The site to check, bare hostname — e.g. examplebroker.com. Any public site is accepted except one class (see domain_excluded under Errors); decidability is class-dependent (see Statuses).

Why the name is required. There are no phone-only or email-only lookups — ever, on any tier. The name is the anchor every candidate record is scored against; without it, nothing decides whether the record we found is the right human being. Other vendors will return a match on a phone number without ever telling you how confident they are that it’s the right person. We won’t. One strong identifier alongside the name is what makes the result worth paying for.

Response

200 OKCopy

{ "state": "indexed", // indexed | not_indexed | indeterminate "domain": "examplebroker.com", "evidence": [ { "source": "https://examplebroker.com/profile/jane-doe-tx-1982", "query": ""Jane Doe" site:examplebroker.com", "observed_at": "2026-08-16T14:02:11Z" } ], "dropped_fields": [], // any unusable inputs, named — never silently ignored "billed": "$0.10" // failed calls: $0.00 }

dropped_fields lists any identity fields we couldn’t use — a malformed phone, an unparseable date of birth — dropped and named rather than failing the request or vanishing silently. See Building a good request.

Billing: $0.10 per completed check — and indeterminate is a completed check, because “public search can’t see it” is a real answer. A call we could not serve — our fault or an upstream’s — bills $0.00. A call that completes bills, including one your client stopped waiting for.

POST/api/v1/scan

The full sweep — one person, twelve domains queried, all 548 reconciled. Synchronous by default. Measured 154 s median, 203 s slowest (n=5; our runs on our data, not a commitment).

A sweep queries a pinned set of twelve people-search domains; findings are classified against, and coverage reported over, the Registry — currently 548 domains, derived from the California data-broker register, a public record you can audit, plus a curated priority set. The Registry is not the query list, and a sweep does not query every domain in it. What $0.35 flat buys is twelve domains queried and all 548 reconciled — the ones we reached, and by name the ones we didn’t.

It’s a plain old synchronous POST. You call, the line stays open, the finished report comes back in the body. No job to babysit, no callback to host, nothing to opt into — that’s just what happens. The only thing to know: it takes about two and a half minutes, and the slowest run we’ve measured was 203 seconds, so set your client timeout north of that. If your stack hates long connections, there’s a header for that — see below.

Request body

Same shape as a check: identity fields inside one identity object. This is the body we recommend for a sweep — six fields:

Recommended — 6 fieldsCopy

{ "identity": { "firstName": "Jane", "lastName": "Doe", "phone": "5035551212", "email": "jane.doe@example.com", "city": "Portland", "state": "OR" } }

Every field earns its place: the name clears the gate; phone and email are what the identifier-led query search actually leans on; city and state are the cheapest defence against namesake collapse — on a common name, the difference between a report about one person and a report about four. Details in Building a good request.

Minimum — 2 fields (the floor, not the recommendation)Copy

{ "identity": { "firstName": "Jane", "lastName": "Doe" } }

The floor works, but don’t lead with it. A name-only sweep costs the same $0.35 as any other and runs the weakest possible search — full price for the configuration least likely to find what’s out there, and the one most likely to answer indeterminate where a stronger request would have decided. Send one strong identifier with the name.

FieldTypeRequiredNotes
identity.firstNamestringrequiredSubject’s given name.
identity.lastNamestringrequiredSubject’s family name.
identity.phonestringoptionalTop-ranked identifier. Supplying phone or email is what turns a name-only sweep into an identifier-anchored one.
identity.emailstringoptionalSecond-ranked identifier; near-unique across the open web.
identity.citystringoptionalWith state, defends against namesake collapse on common names.
identity.statestringoptionalTwo-letter US state, e.g. "OR".
identity.streetAddress1stringoptionalRecommended, not minimal — the third identifier shape; broker profile pages are keyed on address.
identity.dateOfBirthstringoptionalHelps confirm a match. Unparseable values are dropped and named in dropped_fields.
identity.employerstringoptionalExtra disambiguation when brokers list one.
identity.usernamesarrayoptionalExtends the sweep across a large list of public sites — only for handles you declare; we never guess one.
webhook_urlstringoptionalTop-level, next to identity. Not live yet — the delivery surface is specced, not shipped; see Webhooks. When it lands it applies to Prefer: respond-async calls only, since a synchronous call already handed you the report.

Response — the default

Nothing to opt into. The finished report is the response body.

200 OK · the whole reportCopy

{ "job_id": "job_9f2c…", "status": "complete", "summary": { "indexed": 5, "not_indexed": 7, "indeterminate": 0 }, "coverage": { … }, // all 548 reconciled — see Statuses & evidence "results": [ … ], "billed": "$0.35" }

Don’t want to hold the line? Prefer: respond-async

One header, and we take the job and hand you a ticket instead:

Request · async opt-inCopy

curl https://ai.sirveil.ai/api/v1/scan
-H "Authorization: Bearer sk_live_…"
-H "Prefer: respond-async"
-d '{ "identity": { "firstName":"Jane", "lastName":"Doe", "phone":"5035551212" } }'

202 Accepted · async onlyCopy

{ "job_id": "job_9f2c…", "status": "queued" }

Then poll GET /api/v1/jobs/:job_id as often as you like — polling is free. (A webhook_url field exists in the shape, but webhooks are not built yet — don’t design around it today.) Async changes when the answer arrives, not what it says or what it costs. Same report, same $0.35.

Billing: $0.35 per completed sweep, billed when the sweep finishes — not when it starts. A sweep we could not serve bills $0.00, either mode — but a sweep that completes bills even if your client gave up waiting. See the trap below.

One trap, and it’s the one synchronous-by-default creates. A sweep that runs to completion is a completed answer, and it bills whether or not you were still on the line to catch it. Hang up at 120 seconds on a run that finishes at 154 and the work was done, the meter says so, and you never saw the report. Two ways not to pay for an answer you didn’t read: set the timeout above 203 seconds, or send Prefer: respond-async and collect it from the job. Better you hear it here than from an invoice.

GET/api/v1/jobs/:job_id

Collect an async sweep — the finished report, or how far along it is.

You only need this if you sent Prefer: respond-async; a default sweep already handed you the report. While an async sweep runs, status is queued or running. When it flips to complete, the whole report is in the body, every entry with its verdict and its evidence. Fetching a job is free — poll as much as you like.

Response

200 OK · completeCopy

{ "job_id": "job_9f2c…", "status": "complete", // queued | running | complete | failed "summary": { "indexed": 5, "not_indexed": 7, "indeterminate": 0 // verdicts for the domains actually queried }, "coverage": { … }, // and the rest of the registry, named — see Statuses "results": [ { "domain": "examplebroker.com", "state": "indexed", "evidence": [ … ] }, { "domain": "quietbroker.example", "state": "not_indexed", "evidence": [ … ] } // … one entry per domain queried, each with its source ], "billed": "$0.35" // a failed job: $0.00 }

Billing: the $0.35 belongs to the sweep, not the fetch. GET /api/v1/jobs/:iditself is free at any polling frequency.

GET/api/v1/whoami

Your key, your plan, your limits — free, and it burns no quota.

Hand it your bearer key and it tells you which account the key belongs to, which plan that account is on, and the rate limit, remaining allowance and ceiling that apply to you. It is the real answer to “what are my limits?” — better than anything we could print on a page, because a printed number can go stale for your account and this one can’t.

It’s also the right first call in any integration: it proves the key works before you spend a cent. A wrong or revoked key returns 401 here for $0.00, instead of on your first billable call.

200 OKCopy

curl https://ai.sirveil.ai/api/v1/whoami
-H "Authorization: Bearer sk_live_…"

→ who you are and what applies to you

{ "plan": "developer", "rate_limit_per_minute": …, "quota_remaining": …, // units: a check is 1, a sweep is 100 "spend_ceiling" // our supplier-cost guard, not a cap on your bill: … }

ConfirmExact field names are being locked with engineering ahead of launch. The four facts — account, plan, limits, remaining — are the committed content; read the live response as the source of truth rather than this example.

Billing: free. No charge, no quota unit, at any polling frequency. We are not going to meter you for asking how much you have left.

POST/api/v1/mcppreview

A Model Context Protocol surface for agents — one read-only tool today.

Wiring this into an agent rather than an application? This endpoint speaks MCP over the same bearer key. It exposes a single read-only tool — not the whole API — and nothing about the answers changes: same verdicts, same evidence, same three states.

It bills exactly like the endpoint underneath it. A check driven through MCP is a $0.10 check and one unit. We’re saying so here rather than letting you find it on an invoice, because an agent in a loop spends real money at machine speed. Read your remaining allowance back from GET /api/v1/whoami before you point one at this.

PreviewThe MCP surface isn’t version-frozen: tool names and schemas may move ahead of the endpoints they wrap, and the breaking-change process doesn’t cover it yet. Changes land on /scan-api/changelog.

Statuses & evidence

Every verdict is one of three strings. There is no fourth, and there is no “probably”.

indexed

Public search can see a page for this subject on this domain. The evidence array says exactly where and how.

not_indexed

Absent from the search index for this domain — which is not the same as absent from the site, and we won’t pretend it is. Returned only when the name query shape ran and came back empty.

indeterminate

No honest yes/no exists. Four ways to get here: the identity was name-only; the page text didn’t carry the declared full name; confidence fell below the ship floor; or the domain is login-walled or noindex’d.

A name on its own can never come back indexed. A name match alone can’t tell this person from everyone else who shares the name, so it lands on indeterminate every time. Send a phone if you have one — selectivity runs phone → email → street → name, which is upside-down from general web search on purpose: a site:-scoped broker profile is keyed on phone and address and often prints no email at all.

Decidability is class-dependent, and we tell you which class you’re in. Data-broker and people-search domains return decidable verdicts, because being publicly findable is their business model. Login-walled or noindex’d sites return indeterminate — if public search can’t see the subject page, neither can we, and neither can the stranger you’re worried about. An indeterminate we computed is a completed, billed answer; a call we could not serve bills $0.00. And if our search provider falls over, you get indeterminatewith outcome: unserved and a bill of nothing — we paid for that attempt, not you.

**Negatives are the product — and so is owning up to what we didn’t reach.**A sweep queries a pinned priority set of people-search domains, then reports a result for every domain in the registry in four buckets: indexed, not_indexed, indeterminate, and not reached — named, up to the per-response limit (never_queried_truncated tells you when the list was cut). The dated no’s let you tell a customer “you’re clear here” and prove it. The named gaps are what let them believe the rest. We publish what we missed, which as far as we can tell makes us the only ones who do.

The coverage block

Every sweep returns a coverage object next to the findings. Nothing hides in a rounding error:

coverageCopy

{ "expected": 548, "queried_hit": 5, "queried_empty": 7, "queried_failed": 0, "never_queried": 536, "never_queried_domains": [ "..." ], // named, capped at 100 per response "never_queried_truncated": true }

ConfirmThe bucket figures above are illustrative shapes, not a real run. A live distribution goes here once engineering hands one over — we’d rather show you a placeholder we labelled than a number we invented.

Every response carries its own limits, in the body. A contract.limitation string ships inside every verify response saying what the endpoint actually did: read a search index, not fetch the page. It’s in the payload, not in a footnote you’d have to go looking for.

Every response is replayable — including the ones that went wrong. Each carries pipeline_version, linkage_weights_version and calibration_version, on 400s and 500s as well as on successes. Deliberately: a ledger that stamps only its wins has a hole exactly where the losses are.

Confidence and linkage are diagnostics, not determinations. Where confidence, calibrated_probability or per-field linkage come back, they explain how a verdict was reached. Don’t treat them as a measure of accuracy, and please don’t show one to a subject as a score.

The evidence array

Each finding shows its work: where it came from, what we asked, and when we looked. We check what public search can see, because that’s what an abuser or a stranger can see.

**The two endpoints don’t reach the same places, and we won’t blur them together.**A check reaches exactly one third party — the search provider — and makes no model calls. Public queries only, no logins, no back doors. A sweep reaches further: in addition to the search provider it may query identity-enrichment, breach and stealer-log, and court-record sources, plus public username pages where you supplied a handle, and it uses model inference to adjudicate and phrase results. Some of those are commercial subscription data, not public web pages. Neither endpoint ever logs into anything or steps around an access control. The full account is in API Privacy Notice §5.

Evidence entryCopy

{ "source": "https://examplebroker.com/profile/jane-doe-tx-1982", // public URL we saw "query": ""Jane Doe" site:examplebroker.com", // the query we ran "observed_at": "2026-08-16T14:02:11Z" // when we looked (UTC) }

PreviewThe three fields above — source URL, query used, observed-at timestamp — are the committed shape; exact field names are locked at launch.

What we keep

Short section, because there isn’t much to keep. The identity you send is used to answer the call you made, and that’s the whole job it does.

  • A synchronous call writes nothing about the subject to storage. The verify path is guarded in code against database writes.
  • One exception, and it’s the cache. The normalized query text — which contains the identifier — sits in memory on one instance for about 15 minutes, partitioned by account and never written to disk. Same cache that makes a repeat answer consistent, and the same one that bills in full. We’d rather name it than let “persists nothing” do work it can’t do.
  • An async job holds the identity you submitted and the finished report for 24 hours so you have time to collect it, then the stored body is nulled out. That window is the price of not holding a connection open.
  • The metering row is kept indefinitely — account, key, endpoint, timestamp, status, latency, units billed, outcome, run identifier. No request bodies, no response bodies. An invoice you can audit has to outlive the data it was computed from; it doesn’t have to contain it.
  • We don’t sell or share your identifiers or your output, and we don’t train models on them.

These are current behaviours, not the outer bound. The API Privacy Notice is the governing document, and §7 is the authority on retention. It is explicit that the per-call activity row has no expiry set today — an unresolved decision, not a designed one, and it says so in those words. What’s above is what the system does today. Where the two differ, the Notice governs.

Errors

Errors are JSON, they say what went wrong in words, and they never bill. One shape, everywhere:

Error bodyCopy

{ "error": { "type": "identity_incomplete", "message": "identity.firstName and identity.lastName are required — the name is the anchor every match is scored against.", "doc_url": "https://sirveil.ai/scan-api/docs#errors" } }

StatusMeaningBilled
400Malformed request — broken JSON, missing required field. Named types below.$0.00 — never
401Missing, revoked, or wrong API key.$0.00 — never
402spend_ceiling_reached — an account-level ceiling is in effect. It guards our supplier cost, not a cap on your bill. See Rate limits.$0.00 — never
403tenant_suspended — the account is suspended; or domain_excluded — you named a domain in the facial-recognition and biometric-identification class, the one refusal that is not waivable.$0.00 — never
404No such route, or no such job_id on your account.$0.00 — never
410result_expired — you polled an async job after its 24-hour window. An explicit answer, not a silence you’d have to interpret.$0.00 — never
422Valid JSON, unusable values — e.g. a domain that isn’t a hostname.$0.00 — never
429rate_limited — too many requests a minute. Back off, honor Retry-After, carry on.$0.00 — never
429quota_exceeded — the month’s unit allowance is spent. Same status, different problem: waiting won’t fix this one.$0.00 — never
500Our fault. Safe to retry; if it persists, tell us. /scan-api/status is informational only — not an availability metric, not a service-level commitment (API Terms §11).$0.00 — never
503search_unavailable — a retrieval source is down. On a check you may instead get an honest indeterminate with an unserved outcome.$0.00 — never
503async_not_available — you sent Prefer: respond-async on a deployment where async isn’t available. Nothing is charged.$0.00 — never
503capacity_reached — a platform-wide daily guard. Not you, not your integration. Try later.$0.00 — never

Named error types

TypeStatusWhat it means
identity_incomplete400The request is missing a name. The API’s actual message: “identity.firstName and identity.lastName are required — the name is the anchor every match is scored against.” Both endpoints return it.
invalid_domain400POST /api/v1/verify only: the top-level domain is missing or isn’t a usable bare hostname. There is no allowlist — any public hostname is accepted bar the one excluded class below — but it has to be a hostname.
rate_limited429Too many calls a minute. Retry-After tells you how long to sit still. Waiting fixes it.
quota_exceeded429The month’s unit allowance is gone. Waiting does not fix it — the allowance resets at the top of the next UTC month, or ask us to raise it.
spend_ceiling_reached402An account-level ceiling is in effect and further calls are refused until it’s lifted. It measures our supplier cost, not a cap on your bill. It isn’t a charge; nothing is billed. Seeing it in normal use? support@sirveil.ai and we’ll raise it — we’d rather move a number than lose your traffic.
capacity_reached503A platform-wide daily guard. Nothing wrong with your key or your request.
tenant_suspended403The account is suspended. Suspension is governed by the API Terms; if you’re seeing this and don’t know why, support@sirveil.ai will tell you.
domain_excluded403You named a domain in the facial-recognition and biometric-identification class — a service whose primary function is identifying a person from an image or a biometric template. Refused before any query is built and before anything is billed. It is a control in the code, on both endpoints, and it is not waivable — not by us, not at any price.

Every one of these is refused before any search runs — no work done, nothing billed, and the recorded cost is null, not zero. A refused request cost us nothing to answer, and we’re not going to assert a measurement we never made.

The billing column is not a courtesy footnote — it is the pricing model. You are billed per completed answer, and an error is not an answer. Infrastructure trouble is our cost, not yours.

Rate limits

Generous by default, raised on request. Limits are set to accommodate normal integration traffic — polling GET /api/v1/jobs/:id while a sweep runs very much included. They’re applied on a best-efforts basis and we can adjust them, so read yours from whoami rather than assuming. Four bounds can say no, checked in order so you always get the most specific answer available. All four bill $0.00.

  • Rate limit429 rate_limited, with a Retry-After header. Honor it and you’re fine.
  • Unit quota, where your account has one — 429 quota_exceeded. A check costs 1 unit, a sweep costs 100, on a UTC calendar month. Marketplace accounts are provisioned without a unit cap.
  • Account ceiling402 spend_ceiling_reached. Calls are refused, not charged. Ask and we’ll raise it. One thing it is not: the ceiling is measured against our supplier cost for your account, not against your invoice. It is not spend protection and we won’t pretend it is — the only thing that bounds a metered bill is the number of calls your integration makes.
  • Platform capacity503 capacity_reached. A daily platform-wide guard. Rare, and not a fault in your integration.

Read your own limits, free, whenever you like. GET /api/v1/whoamireturns the plan, rate limit, remaining allowance and ceiling that apply to your account. It costs nothing and burns no quota, which makes it a better answer than any number printed on a page — a printed number can be stale for you, and that one can’t.

There’s no plan ladder to look yours up in — whoamiis the answer, and it’s free. Running hot on purpose? Raised limits and volume pricing: support@sirveil.ai.

Billing semantics

A meter and a monthly invoice. That is the whole apparatus.

  • Pure postpaid. Every completed answer increments the meter: $0.10 a check, $0.35 a sweep. At month’s end you’re invoiced for what the meter read. No packs, no credits, no subscriptions, no minimums.
  • Zero calls, zero dollars. A quiet month produces no charge and no invoice. Nothing is prepaid, so nothing can expire and no money of yours sits on our books.
  • Marketplace metering. The Service is available for purchase through AWS Marketplace — that is the whole list today (API Terms, Section 3.1) — and usage lands on your existing AWS bill through the marketplace meter. A channel named in an artifact but not listed in the Terms is not an offer. For a Marketplace purchase, the price published on that listing when the call is made is the price for that call.
  • Rate-card changes take at least 30 days’ notice and never apply retroactively. Committed volume gets private offers below the published rates — support@sirveil.ai.
  • Errors bill $0.00 — see Errors. The full worked math lives at /for-business#math.
  • A cache hit bills in full. Ask the same question twice inside a short window and you may get the same answer back for consistency — and it bills and burns quota exactly like a fresh call. The cache makes a repeat answer consistent, not free. We’d rather say so here than have you find it on an invoice.
  • An unserved answer bills nothing. If our search provider is down you get indeterminate with outcome: unserved and a zero charge. We paid for that attempt; you didn’t.
  • A refusal records cost as null, not zero. Nothing was spent and nothing was measured, and we won’t assert a measurement we never made.

Versioning & deprecation

/api/v1 is the stable surface: the endpoints, request fields, statuses, and error envelope on this page. We add fields without warning — parse tolerantly and ignore what you don’t recognize. We do not remove or repurpose anything under /api/v1 without a breaking-change process:

  • At least 30 days’ notice before a breaking change takes effect. That’s the commitment we publish and hold ourselves to, and in practice we aim to give a good deal longer. The API Terms govern the agreement itself.
  • Announced on /scan-api/changelog first, before any other channel.
  • A breaking change ships as /api/v2; /api/v1 keeps answering through the notice window.
  • Anything marked preview or not yet built on this page sits outside that process until it’s marked stable. That’s what the label is for.

Where this page and the contract disagree, the contract wins. These docs describe how the API behaves and we keep them honest; the API Terms, API Privacy Notice and Acceptable Use Policy are what we’re actually bound to. Nothing on this page is a warranty — we’d rather say that plainly than let a nice sentence in the docs get read as one.

Webhooks not yet built

Not yet built**This one doesn’t exist yet.**It’s specced and on the build list, not shipped — so don’t design an integration around it today. The shape below is what it will be, and it lands on /scan-api/changelog when it’s real. Polling works now, and it’s free.

When it ships: pass webhook_url on an async sweep and we POST the finished report to you instead of making you poll. Deliveries are signed with HMAC-SHA256 over the raw body, in the X-Sirveil-Signature header — verify it before you trust the payload.

Delivery (draft)Copy

POST https://yourapp.example/hooks/sirveil X-Sirveil-Signature: sha256=6b4f… // HMAC-SHA256 of the raw body Content-Type: application/json

{ "job_id": "job_9f2c…", "status": "complete", "summary": { … }, "results": [ … ] }

Polling GET /api/v1/jobs/:id works today and keeps working — webhooks are a convenience, not a dependency.

Where next

See the receipts

890 ms median / 1,463 ms p95 checks (n=18, cache-cold), 154 s median / 203 s slowest sweeps (n=5) — our runs on our data, slow runs very much included. Measurements, not commitments.

Benchmarks →

Run the math

The interactive meter: sliders, an example invoice, and the whole rate card on one page.

Pricing →

Get a key

Minutes, not meetings. The meter starts at zero and stays there until you call.

Sign up →

{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://sirveil.ai"}, {"@type": "ListItem", "position": 2, "name": "Sirveil for Business \u2014 Scan API", "item": "https://sirveil.ai/for-business"}, {"@type": "ListItem", "position": 3, "name": "API Docs", "item": "https://sirveil.ai/scan-api/docs"}]}