smarter/datadocs

API reference · v1

One header. One envelope. Counts are free.

The Smarter Data API is a credit-metered REST API over a U.S. local-business warehouse, plus live web retrieval, email verification, on-demand crawling and (activating) skip-trace. Every endpoint takes the same x-api-key header, returns the same envelope, and follows the same billing rules — per record delivered, never per call. A hosted MCP server exposes the same calls to agents.

Base URLhttps://api.smarterdata.ai
Auth headerx-api-key: sk_live_…
MCP endpointhttps://mcp.smarterdata.ai/mcp

00 · Quick start

Size it free, price it free, then pull.

Create a key in the console, export it as $KEY, and the four calls below are the whole workflow. The first two cost nothing; the third is billed per company record delivered — 5 credits for the five-company pull below, so a new account's 100 free credits (one free trial per person, at most three per company email domain) cover this whole quick start — and the fourth prices a reveal for free before you spend 3 credits per contact revealed. (Example numbers were measured on 2026-10-25 and drift as the data changes; your balance will differ.)

quick startbash
# 1 · free — how big is the segment?
$ curl -H "x-api-key: $KEY" \
    "https://api.smarterdata.ai/v1/count?state=TX&niche=janitorial"
{ "ok": true, "entity": "companies", "count": 3958, "credits_used": 0, "credits_remaining": 100 }

# 2 · free — the exact cost of the pull you are about to make
$ curl -H "x-api-key: $KEY" \
    "https://api.smarterdata.ai/v1/companies?state=TX&niche=janitorial&limit=5&estimate=true"
{ "ok": true, "estimate": true, "entity": "companies", "count": 5, "credit_cost": 5, "credits_used": 0, … }

# 3 · 5 credits — the pull: limit=5 at 1 credit per company record. Keep limit small while you learn the API —
#     limit=100 would spend a new account's 100 free credits in one call. Repeat it verbatim for the NEXT 5 (auto-advance, no offset).
$ curl -H "x-api-key: $KEY" -H "idempotency-key: pull-tx-janitorial-001" \
    "https://api.smarterdata.ai/v1/companies?state=TX&niche=janitorial&limit=5"
{ "ok": true, "entity": "companies", "count": 5, "total_matching": 3958, "has_more": true,
  "pagination_mode": "auto_advance", "credits_used": 5, "credits_remaining": 95,
  "records": [ { "business_id": "co:d:examplejanitorial.com", "name": "Example Janitorial", … } ] }

# 4 · 3 credits per contact — the decision-makers at one company. Only about 5% of companies have one on file
#     (TX janitorial: 324 of 3,958 — ask /v1/count?…&entity=companies_with_named_dm, free), so price each reveal
#     first with estimate=true (free): "count": 0 means nobody is on file there — try the next business_id. URL-encode the id.
$ curl -H "x-api-key: $KEY" \
    "https://api.smarterdata.ai/v1/companies/co%3Ad%3Aexamplejanitorial.com/contacts?channels=email,cell&estimate=true"
{ "ok": true, "estimate": true, "entity": "contacts", "count": 2, "credit_cost": 6, "credits_used": 0, … }
# drop estimate=true to reveal them (6 credits here — 3 per contact, leaving 89 of the 100). Decision-makers across the whole segment in one call instead:
#     GET /v1/contacts?state=TX&niche=janitorial&limit=50 — 3 credits per contact, on segments of 25+ companies.
Three rules that prevent every common mistake. Read the records array (not data). Size with /v1/count and price with estimate=true — both free — before any paid call. Spell filter names exactly as listed in Company filters: an unknown parameter name is rejected with 400 unknown_filter naming it, before anything is read or charged — a typo never widens or bills a pull (an unknown value on a known filter correctly matches nothing).

01 · Authentication & keys

One header on every request

Send x-api-key: sk_live_… on every call. Only GET /healthz is open. A missing or unknown key returns 401 invalid_api_key. The brake behind that never turns a valid key away as invalid: a string that cannot be a key, or a key already found invalid, is 401 without a lookup, and the only thing limited is how fast one address can have keys it has not used recently looked up (a few a second, with a short wait for the next slot). A valid key always authenticates — a flood of bad keys from your address can only make a key's first call slower, and a key that authenticated in the last 15 minutes is never slowed at all. If you do see 429 auth_rate_limited, your address is sending unknown keys faster than they can be looked up: retry in a second with the same key (hosted-MCP callers are counted by their own address, not as one). A key sent as Authorization: Bearer sk_… is refused with a message naming the right header; a stray Bearer prefix inside x-api-key is forgiven.

Where keys come fromRule
The consoleSign in at smarterdata.ai/app → Create. The full key is shown once; afterwards only its prefix is listed. Up to 10 active keys per account.
RotateRotate in the console (or POST /v1/me/keys with revoke_prefix) mints the new key and revokes the old one in a single call — a rotated key is never left live: a revoke_prefix that is not one of your active keys returns 404 not_found and mints nothing. A rotate swaps one key for another, so it works at the 10-key limit.
ScopesKeys carry read and personal_data by default. A key minted without personal_data gets 403 scope_required on any reveal that would include a cell number or a personal email. A key can never mint another key with more scopes than its own.
Console sessionsYour signed-in browser session can count, estimate, read your balance, buy credits and manage keys — but never pull records: a billed call from a session returns 403 api_key_required. Records leave only through an API key.
Rate limitsPer key, not per IP: keys minted in the console allow about 20 requests per second (429 rate_limited beyond that). Live endpoints are additionally capped at 60 page-units per minute per account — all of its keys together (429 live_rate_limited). An optional per-key daily credit cap can be set for you as a brake on a leaked key or a runaway agent (429 daily_cap_reached).
IdempotencySend an idempotency-key header on any paid call. A retry with the same key replays the original response — same records, no second charge — for 24 hours, across restarts and deploys. The same key with a different request returns 409 idempotency_conflict; a retry while the first call is still running waits for it and replays its answer — 409 idempotency_in_progress means the first call was interrupted on our side and its claim has not expired yet (retry in a few minutes). A call that failed (for example 402) releases its key. The replay is marked replayed: true, says credits_used: 0 (and x-credits-used: 0) — this call charged nothing — keeps the first call's charge in original_credits_used, and its credits_remaining is your balance now. It is value-identical to the original answer (same records, same order), not byte-identical: key order may differ. The same holds for a retried POST /v1/verify/bulk, POST /v1/scrape or deep POST /v1/verify/email: the same job comes back (replayed: true), and once it is complete the replay says credits_used: 0 with the job's cost in original_credits_used.

02 · Envelope

Every success and every error, one shape each

success200
{ "ok": true,
  "entity": "companies",            // what the records are: companies · company · contacts · live_places · …
  "count": 25,                      // records in THIS response
  "credits_used": 25,               // what this call cost
  "credits_remaining": 99975,       // your balance after it (also the x-credits-remaining header)
  "records": [ … ] }                // ⚠ always `records` — never `data`, never `companies`

// search adds:  "has_more": true, "pagination_mode": "auto_advance"   // /v1/companies also "total_matching": 3958
// a replay:     "replayed": true   // the ORIGINAL records again — credits_used 0 (this call charged nothing), original_credits_used = the first charge, credits_remaining = the balance now
// live adds:    "live": true, "fetched_at": "2026-10-24T09:16:01Z", "next_page": 2
// estimates:    { "ok": true, "estimate": true, "entity": "…", "count": 25, "credit_cost": 25, "credits_used": 0, "credits_remaining": … }
// a 402 adds:   "credits_remaining": 2, "credits_needed": 5   // and the message says both
error4xx / 5xx
{ "ok": false,
  "error": { "code": "insufficient_credits",   // machine-stable — switch on this, never on the message
             "message": "…",
             "request_id": "7d2f9c1e-5b0a-4c8e-9f31-2a6b4e8d0c57" },  // a UUID, also the x-request-id header; quote it to support
  "credits_used": 0 }                           // an error never bills

Response headers on paid GET calls and on POST /v1/verify/email — and on every other successful answer that reports a balance (estimates, counts, live and skip-trace calls): x-credits-used, x-credits-remaining. Async job submissions (/v1/verify/bulk, /v1/scrape, a deep check) report credits_held in the body instead of a charge — plus credits_remaining, with x-credits-used: 0 in the headers (a hold is not yet a charge). On every response: x-request-id, a UUID unique across restarts. The full code list is in Error codes.

03 · Credits & billing rules

Per record delivered. Never per call, never per seat.

One credit balance across every product. The price table below is the standard one — the server's default prices, which every account pays unless its plan has its own price list; GET /v1/schema → credits is the table your account pays, read live at any time. Every estimate states its exact credit_cost before you spend.

Key in /v1/schemaCreditsBilled perEndpoint
count0always freeGET /v1/count
estimate0always freeestimate=true on any paid GET
company_search1company record deliveredGET /v1/companies
company_detail1company record — 0 for a company your account already boughtGET /v1/companies/{id}
contact_reveal3contact delivered (deduped; only contacts carrying a channel you asked for)GET /v1/companies/{id}/contacts
contact_search3contact deliveredGET /v1/contacts
verify_mx1every mail-host check — also one answered from our store (verified in the last 60 days: it comes back at once with its original verified_at); 0 when the string is not an email, or when the mail host could not be checked right now (unknown)POST /v1/verify/email
verify_smtp3email that completes deep verification (valid / invalid / catch_all) — a verdict we already hold too (verified in the last 60 days, original verified_at); a deep check of POST /v1/verify/email with smtp: true that gets no verdict settles to verify_mx (1) for the mail-host answer it returnedPOST /v1/verify/email {smtp:true} · /v1/verify/bulk
scrape_domain2domain successfully crawledPOST /v1/scrape
live_places2page of 10 fetched — even at zero results; pages not fetched are refundedGET /v1/live/places
live_search1query — even at zero resultsGET /v1/live/search
skiptrace_fullactivating6full match (no match = 0)POST /v1/skiptrace/address
reverse_phoneactivating4match (no match = 0)POST /v1/skiptrace/phone

The invariants — enforced in the billing engine, not promised in a brochure

Counts are free

/v1/count on any filter, any entity, always 0 credits. Size a market before you spend.

Estimates are free

estimate=true on any paid GET returns the exact credit_cost without executing or charging.

No match, no charge

Empty reveals, failed crawls, emails that could not be verified, unmatched skip-traces: 0 credits. You pay for data received — a deep check with no verdict pays only for the mail-host answer it returned.

Deduped before billed

A contact is one record: one person at one company. The same contact appearing twice in a result set never bills twice; a person listed at two companies is two contacts.

Holds settle to actuals

Async work (bulk verify, crawl) holds the maximum up front, bills only completed units, and refunds the rest at completion.

Never billed twice by accident

The delivery ledger remembers every record your account bought and excludes it from your next pull — so repeating a request returns the next batch, not the same one — and GET /v1/companies/{id} for a company you already received is free. Asking for a bought record again (exclude_delivered=false) is a new delivery and is charged again, and every verification is charged, an answer from our store too. Safe retries: an idempotency-key replays the original records at no charge — marked replayed: true, credits_used: 0, the first charge kept in original_credits_used, your balance unchanged.

Held before upstream, refunded if interrupted

Live and skip-trace calls hold their price before any upstream request and settle to what was fetched or matched. If our service stops mid-call, the hold is refunded automatically; a retry with the same idempotency-key is charged once.

Charge, then deliver — or refund

If the ledger write fails after a charge, the charge is refunded automatically and the call returns a clean 500 you can retry.

Agents price before they spend

The MCP reveal tool is two-step: estimate first, reveal only with confirm:true under max_credits. Other paid tools bill when called — each takes estimate (verify_email has one fixed price per check) and an idempotency_key that makes a retry free, and a per-key daily cap bounds the rest. Counting is free, so agents explore freely.

Two documented exceptions

Live web search bills per query and live local results per page fetched, even at zero results — live retrieval costs us per request regardless. Those are the only two, and they are written down here.

Two honest footnotes. Browse mode is metered: exclude_delivered=false switches the ledger filter off for that call so records you already own can appear again — that call is billed per record like any other pull, so use it deliberately, not as a free re-download. Coverage counts are aggregate-only: entity=contacts and entity=companies_with_named_dm are answered for segments of coverage_min_segment companies or more (25 by default, published in /v1/schema); narrower segments return count: null with note: "segment_too_small…", so a name-plus-ZIP probe cannot learn whether we hold a decision-maker for one specific company. The same rule holds for the wide contact search (GET /v1/contacts on a narrower segment → 400 segment_too_small, its estimate count: null), and a company's own contacts are revealed — and estimated — only once that company is yours (409 company_required otherwise): buy the company, then its contacts.

04 · Pagination

Auto-advance — there is no offset

Records your account already bought are excluded from your next pull, so repeating the identical request returns the next batch. has_more: false means no record you have not received is left at your filters — it is computed against your delivery ledger, so the batch that exhausts the segment already says false. Passing offset to a search endpoint (/v1/companies, /v1/contacts) returns 400 offset_not_supported; anywhere else it is simply an unknown parameter (400 unknown_filter).

  • limit — records per call: a whole number from 1; default 25, maximum 1000 on search endpoints (a larger value is treated as 1000). 0, a negative number, a fraction or a word is 400 bad_limit. It is an upper bound on spend.
  • exclude_delivered=false — include records your account already bought (browse mode; charged again per record, see above).
  • redeliver_after_days=180 — treat records delivered more than N days ago as deliverable again (list recycling). Integer 1–3650.

05 · Endpoints

Every customer endpoint, with its price

All paths are relative to https://api.smarterdata.ai. Boolean query parameters accept true/false/1/0; anything else is 400 bad_boolean. Comma lists are plain: state=TX,FL.

Discovery — free

GET/healthzno auth · 0 credits

Readiness and the running build. Returns { ok, control_ok, build, mode, control, ts } plus a few fields that describe the data it serves; while our ledger cannot be reached it answers 503 with ok: false (paid calls would fail then too).

GET/v1/count0 credits

Count what a filter combination matches. Takes every company filter plus:

ParamValuesMeaning
entitycompanies (default) · contacts · companies_with_named_dmcompanies = matching businesses. contacts = decision-maker contacts at them (strict definition — the same one the paid reveal uses), counted per company: a person listed at two companies is two contacts. companies_with_named_dm = businesses holding at least one named decision-maker that a reveal can deliver (the same strict definition; a contact whose "name" is the business itself never counts).
dm_onlytrue (default) · falseWith entity=contacts: false counts any reachable contact (an email, a cell or an office line), not only decision-makers. The response names the definition used.
countfree
$ curl -H "x-api-key: $KEY" "https://api.smarterdata.ai/v1/count?state=TX&niche=general_contractor&entity=contacts"
{ "ok": true, "entity": "contacts", "definition": "named_dm_strict", "count": 10448, "credits_used": 0, "credits_remaining": 1900 }

# a segment narrower than coverage_min_segment companies: coverage is withheld, never guessed
{ "ok": true, "entity": "companies_with_named_dm", "count": null,
  "note": "segment_too_small: coverage is reported for segments of 25+ companies", "credits_used": 0, … }

Warehouse

GET/v1/companies1 credit / record

Search the store. Takes every company filter plus limit (≤ 1000), estimate, exclude_delivered, redeliver_after_days. Ordered by data quality, so your first records are your best records. Response adds total_matching, has_more, pagination_mode. Record shape: Company.

GET/v1/companies/{business_id}1 credit · 0 if already yours

One company, same projection as search (entity: "company", one record). Counts as a delivery. Free for a company your account already bought (by a search or an earlier detail) — the answer then says already_delivered: true. Takes only estimate (free: count: 1 and the exact credit_cost, 0 or 1); any other parameter is 400 unknown_filter. Unknown id → 404 not_found.

URL-encode the id, and treat it as an opaque string. A business_id looks like co:d:examplepaving.com (keyed on the website), co:p:2145550142 (keyed on the company phone) or co:n:example paving|dallas|texas (keyed on name, city and state — the only kind with | and spaces), or co:h: followed by 24 hex characters (an opaque key used for a small set of companies). It always contains :. Whatever the shape, treat it as an opaque string. Send it percent-encoded — co%3Ad%3Aexamplepaving.com — or the route will not match. Store it as you received it; never build one yourself.
GET/v1/companies/{business_id}/contacts3 credits / contact

Reveal the contacts at one company your account already holds — get it first (a search that returns it, or GET /v1/companies/{business_id}); otherwise the reveal and its estimate answer 409 company_required and nothing is charged. An unknown company is 404 not_found. You are billed only for distinct contacts that carry at least one channel you asked for; a record with none of the requested channels is never returned and never billed. Record shape: Contact (reveal).

ParamDefaultMeaning
channelsemail,cell,officeWhich channels you want; billing follows this list. Any other word (or an empty list) is 400 bad_channels. A key without personal_data reveals channels=office only.
dm_onlytrueStrict named decision-maker definition — identical to the free entity=contacts count, so the number you sized is the number you get. false = any reachable person.
include_chainfalseFranchise / corporate-inherited contacts are withheld unless you ask for them.
exclude_deliveredtrueContacts your account already bought at this company are withheld; the answer (and its estimate) then carries already_delivered: n — "you already own them", not "nobody here". A person bought at another company is not withheld here: each company sells its own contacts. false returns them again, charged again per record. One exception: a contact bought before contacts were recorded per company whose company is not on record may stay withheld at every company that lists it (false returns it).
estimatefalsetrue returns count, credit_cost, credits_per_record and the definition used — for 0 credits. For a key without personal_data it adds personal_data_required: true when the paid call would need that scope.
redeliver_after_days—Ledger control, as in Pagination.
level · functioncomma listsOffered when GET /v1/schema → contact_filters lists them: level (Board · Executive · Owner · Vice President · Director · Manager · Staff) and function (the words in values.function), case-blind; an unknown word matches nothing. With dm_only=false they return staff by department. Same price per contact.
reveal3 cr / contact
$ ID=co%3Ad%3Aexamplepaving.com
$ curl -H "x-api-key: $KEY" "https://api.smarterdata.ai/v1/companies/$ID/contacts?channels=email,cell&estimate=true"
{ "ok": true, "estimate": true, "entity": "contacts", "count": 2, "credit_cost": 6, "credits_per_record": 3,
  "definition": "named_dm_strict", "credits_used": 0, "credits_remaining": 1900 }

$ curl -H "x-api-key: $KEY" "https://api.smarterdata.ai/v1/companies/$ID/contacts?channels=email,cell"
{ "ok": true, "entity": "contacts", "count": 2, "credits_used": 6, "credits_remaining": 1894, "definition": "named_dm_strict",
  "records": [ { "name": "Jordan Doe", "title": "Owner", "seniority": "owner_operator", "is_decision_maker": true,
                 "contact_class": "person",
                 "channels": { "email": { "value": "jordan@example.com", "type": "personal", "status": "unverified" },
                               "cell":  { "value": "2145550177", "type": "personal", "line_type": "unknown" } },
                 "email_capable": true, "sms_capable": true, "call_capable": true, … } ] }
GET/v1/contacts3 credits / contact

Wide search: contacts across all companies matching the filters — for "every owner of a roofing company in Arizona" without pulling the companies first. Takes every company filter plus the person filters below; limit ≤ 1000, estimate, ledger controls. One record per contact at each company: a person listed at two companies of the segment comes back once for each (each company sells its own contacts); inside one company an email appears once. Response adds has_more, pagination_mode and definition (repeat the call for the next batch). The company filters must match at least coverage_min_segment companies (25): a narrower segment is 400 segment_too_small (its estimate answers count: null) — for one company, use its own contacts route. Record shape: Contact (wide search) — flat fields.

ParamValuesMeaning
titletextTitle contains (case-insensitive), e.g. owner.
seniorityexecutive · operational · financial · owner_operator · staff · unknown (comma list)Tier. The first four are decision-maker tiers; staff and unknown only appear with dm_only=false.
contact_classperson · person_business_inbox · business_inbox · owner_name_only · linkedin_only · person_office_only · other (comma list)What kind of record — see Record shapes.
dm_onlytrue (default) · falsefalse = any contact with an email, a cell or an office line, not only decision-makers.
level · functioncomma listsOffered when GET /v1/schema → contact_filters lists them: level (Board · Executive · Owner · Vice President · Director · Manager · Staff) and function (the words in values.function), case-blind; an unknown word matches nothing. With dm_only=false they return staff by department. Same price per contact.

Verification

POST/v1/verify/email1 credit · 3 deep

Body { "email": "…", "smtp": false }. Without smtp: an instant mail-host check — status is mx_ok, no_mail_host, invalid_syntax or unknown (a domain that publishes a null MX — it accepts no mail — is no_mail_host). The answer always carries the same keys — email, domain, status, mx, cached, verified_at, origin — with null where a value is not known (an invalid_syntax or unknown answer has no verified_at or origin). Every verification is charged: 1 credit per check. An answer verified in the last 60 days comes back at once from our store, at the same price — it says cached: true, origin: "store" and its original verified_at, so you can see how old the check is; an older one is checked again live. 0 for a string that is not an email (invalid_syntax — no lookup happens) and when the mail host could not be checked right now (unknown — nothing is stored; try again later). email must be one address as a string (400 email_required), smtp a boolean — true/false/1/0 (400 bad_boolean). An idempotency-key replays the original answer — a retry of the same request is free. It checks that the domain accepts mail — not that the mailbox exists. With "smtp": true and a live mail host: deep mailbox verification is queued as a job — 3 credits held, settled when it completes: 3 with a deep verdict (the mail-host answer comes with it), 1 — the mail-host answer alone — when the deep check gives none (unknown, error, unavailable); the rest is refunded. Every answer you receive is paid, the mail-host answer included. The reply carries job_id (a UUID) and credits_held. A deep verdict reached in the last 60 days comes back from our store at the same 3 credits (the record says cached: true, origin: "store" and its original verified_at); an older one is verified again.

verify1 cr
$ curl -X POST -H "x-api-key: $KEY" -H "content-type: application/json" \
    -d '{"email":"jordan@example.com"}' https://api.smarterdata.ai/v1/verify/email
{ "ok": true, "entity": "email_verification", "email": "jordan@example.com", "domain": "example.com",
  "status": "mx_ok", "mx": true, "cached": false, "verified_at": "2026-10-24T09:14:02Z", "origin": "live",
  "credits_used": 1, "credits_remaining": 1893 }

$ … -d '{"email":"jordan@example.com","smtp":true}' …
{ "ok": true, "email": "…", "status": "mx_ok", "mx": true, "smtp": "queued", "job_id": "574e7696-a81e-4cd8-94c9-557df9140af1", "credits_held": 3,
  "note": "poll /v1/jobs/{id}; settled on completion: 3 credits with a deep verdict (the mail-host answer comes with it), 1 for the mail-host answer alone when the deep check gives none (unknown / error / unavailable); the rest of the hold is refunded" }
POST/v1/verify/bulk3 credits / completed email

Body { "emails": [ … ] }, 1 to 10,000 addresses. Async: a hold of 3 × n is taken up front (402 insufficient_credits if your balance cannot cover the maximum), then the job settles to completed verifications only — valid, invalid and catch_all bill 3 each — a verdict we already hold too (verified in the last 60 days: cached: true, origin: "store", its original verified_at); unknown, error, unavailable (deep verification is not switched on — the record carries note: "smtp_verification_unavailable") and invalid_syntax (not an email address — never sent for checking) are refunded. One address is verified and billed once: duplicates (case and spaces ignored) are removed first and counted in duplicates_removed. Returns job_id, total, status, credits_held, credits_remaining; poll /v1/jobs/{id}.

Fresh crawl

POST/v1/scrape2 credits / domain crawled

Body { "domains": [ … ] }, 1 to 500. Crawls each site and returns what is on it. A site we crawled in the last 30 days comes back from our store at the same price, with origin: "store" and the scraped_at of that crawl — never dated as crawled now. Each domain is crawled and billed once (https://Example.com/ and example.com are one domain; duplicates_removed says how many were dropped). Hold of 2 × n, settled to successfully crawled domains. A crawl succeeds when the site's own pages answer and carry at least one contact signal of it (a same-domain email, a phone or a social profile). A domain that cannot be fetched, a block / parked / for-sale page, or pages with no contact signal cost 0 and come back ok: false with a reason (unreachable · not_the_site · nothing_found). Per domain on completion: domain, ok, title, emails[] (same-domain addresses only), phones[] (10-digit), socials{} (profile links: facebook, instagram, linkedin, x, yelp), pages_fetched, scraped_at.

scrape2 cr / domain
$ curl -X POST -H "x-api-key: $KEY" -H "content-type: application/json" \
    -d '{"domains":["example.com","examplepaving.com"]}' https://api.smarterdata.ai/v1/scrape
{ "ok": true, "job_id": "9b1d7e40-2c6f-4f0a-8d3e-61a5c2b7f9e4", "total": 2, "status": "queued", "credits_held": 4,
  "note": "2 credits per successfully scraped domain; unused hold refunded at completion" }

Live web — real-time, billed per query

GET/v1/live/places2 credits / page of 10

Map-listing style local results from the live web — name, category, address, coordinates, phone, website, rating. A page fetched in the last 24 hours comes back from our store at the same price (origin: "store", with the fetched_at of that fetch); every other page is fetched now. The ranking comes in pages of 10.

ParamValuesMeaning
qrequiredWhat to find, e.g. hvac companies.
locationoptionalWhere, e.g. Austin, TX.
page1–20 (default 1)First page of the ranking to fetch. A whole number in range, else 400 page_range (never clipped).
pages1–10 (default 1)How many consecutive pages to walk in one call (up to 100 businesses). page + pages − 1 must be ≤ 20, else 400 page_range.
estimatefalsetrue = the exact credit_cost (and count = pages), free; nothing is fetched.

Billing: 2 credits per page actually fetched, even when a page is empty. The hold is 2 × pages; the walk stops at the end of the ranking and unfetched pages are refunded, so credits_used is what was fetched. If the upstream fails mid-walk you keep the pages already fetched (upstream_error is set) and pay only for those. Results are de-duplicated across pages by place_ref. Response: live: true, fetched_at (when the page was really fetched — the oldest page of a walk), origin (live, store or mixed), page, pages_fetched, page_size, next_page (null at the end), records[] of name, category, address, latitude, longitude, phone, website, rating, rating_count, place_ref, page, position, origin. Never served stale beyond 24 hours.

live places2 cr / page
$ curl -H "x-api-key: $KEY" "https://api.smarterdata.ai/v1/live/places?q=roofing+companies&location=Austin,+TX&pages=2"
{ "ok": true, "entity": "live_places", "count": 20, "credits_used": 4, "credits_remaining": 1889,
  "live": true, "fetched_at": "2026-10-24T09:16:01Z", "page": 1, "pages_fetched": 2, "page_size": 10, "next_page": 3,
  "records": [ { "name": "…", "category": "Roofing contractor", "address": "…", "latitude": 30.26, "longitude": -97.74,
                 "phone": "5125550123", "website": "…", "rating": 4.8, "rating_count": 213, "place_ref": "…", "page": 1, "position": 1 }, … ] }

Skip-trace & property activating

Both endpoints are routed and priced; until the provider is switched on they return 503 skiptrace_unavailable and charge nothing. Output is licensed for marketing use only — never for credit, employment, tenancy, insurance or any other FCRA-regulated purpose.

POST/v1/skiptrace/address6 credits / full match

Body { "address", "zip", "city"?, "state"? } (address and zip required, all four are text — 400 bad_request otherwise). Address → owner. A match is the owner with a cell, a phone or an email on file — an owner with none of them is no match. A hold of 6 is taken before the lookup and refunded on no match (count: 0, credits_used: 0) — and refunded automatically if the call is interrupted on our side; a retry with the same idempotency-key is charged once. The answer carries origin and fetched_at: an address traced in the last 90 days comes back from our store at the same price with origin: "store" and the date of that trace. Record: owner_first_name, owner_last_name, address, city, state, zip, cell_1, cell_1_dnc, phone_1, phone_1_type, phone_1_dnc, email_1, origin: "public_records".

POST/v1/skiptrace/phone4 credits / match

Body { "phone": "2145550177" } (10-digit U.S. — anything else is 400 bad_phone, nothing charged). Reverse phone → owner identity and address. Same hold-and-refund rule, same origin / fetched_at. Record: phone, first_name, last_name, address, city, state, zip, email, line_type, dnc, company_name, origin.

Jobs

GET/v1/jobs/{id}0 credits

Progress of a bulk verify or crawl job (the id is the UUID the submission returned): { ok, id, type, status, total, done }. status is queued, running, complete, failed or cancelled. When it is complete the response also carries records, credits_used and credits_remaining — the hold is settled exactly once; a failed job carries error and its hold is refunded in full. One exception: a deep check of POST /v1/verify/email that our service could not finish already gave you its mail-host answer, so it completes at 1 credit for that answer, its record saying it was interrupted; the deep check itself is refunded. Poll until it finishes; there is no callback URL. A job id that is not yours is 404 not_found.

Account & keys

These work with an API key or with a signed-in console session (the console calls them for you). Minting and revoking keys need the keys:manage scope, which a console session carries — a plain API key gets 403 keys_manage_required.

GET/v1/me0 credits

{ ok, email, name, credits, scopes, keys: [ { prefix, scopes, status, last_used_at } ] } — your balance and your keys (prefixes only; a full key is never shown again after minting).

POST/v1/me/keys0 credits

Mint a key. Body (optional) { "scopes": ["read","personal_data"], "revoke_prefix": "sk_live_…" }. Scopes default to read + personal_data and can never exceed the caller's own; read is required (400 bad_scopes). With revoke_prefix the named key is revoked in the same call — this is Rotate; it must be one of your active keys (otherwise 404 not_found, nothing minted) and, being a swap, it works at the limit. Minting an 11th active key → 409 too_many_keys. Response: { ok, api_key, prefix, scopes, revoked, note: "shown once — copy it now" }.

DELETE/v1/me/keys/{prefix}0 credits

Revoke one of your keys by prefix → { ok, revoked }. Needs keys:manage (a console session; a plain API key gets 403 keys_manage_required). No active key with that prefix on your account → 404 not_found.

GET/v1/me/usage0 credits

{ ok, usage, ledger } — usage is the last 30 days per action, [ { action, units, calls } ]: net credits (a hold counts only what it settled to — a refunded skip-trace is 0) under the price-table words of /v1/schema → credits (company_search, verify_smtp, scrape_domain, skiptrace_full, …) — a deep check of POST /v1/verify/email that settled at the mail-host price is listed under verify_smtp. ledger is the last 25 ledger entries (charges, holds, refunds, purchases); a call that neither charged nor held credits writes none; a refunded hold appears as the hold and its refund (net 0).

Billing & schema

GET/v1/billing/packs0 credits

{ ok, packs: [ { id, credits, amount_cents, name } ], billing }. billing is "live" when purchases are open; any other value (or an empty list) means billing is still being activated — the console shows a notice instead of Buy buttons.

POST/v1/billing/checkout0 credits

Body { "pack_id", "success_url"?, "cancel_url"? } → { ok, checkout_url, session_id }. Redirect the buyer to checkout_url; credits land on your balance moments after payment. Return URLs must be on https://smarterdata.ai or one of its subdomains (400 bad_return_url). Your card statement shows a SMARTEROUT… line with the suffix SMARTERDAT (the exact prefix is the account's shortened descriptor). If Stripe is unreachable the call answers 503 billing_unavailable — nothing is charged. Put Stripe's literal {CHECKOUT_SESSION_ID} in success_url (unencoded) and Stripe fills in the session id on the way back.

GET/v1/billing/sessions/{session_id}0 credits

Has this checkout landed on your balance? { ok, session_id, credited, credits, credited_at, credits_remaining } — credited: true once the purchase's credits are on your ledger (another account's session always reads false). An id that is not a Checkout Session id (cs_live_… / cs_test_…) is 400 bad_session_id. The console asks this after Stripe sends you back.

GET/v1/schema0 credits

The machine-readable contract: company_filters[], contact_filters[], values (the words each value filter accepts, read from the data: niche, sector, service_line, employees_band, revenue_band, seniority, contact_class, and level and function when contact_filters lists them), routes (every route's accepted query parameters), count_entities[], named_dm_definition, data_notes (honesty labels), credits (the live price table) and coverage_min_segment. Read it at startup if you cache prices or build a filter picker.

06 · Company filters

Slice the store on any combination

Accepted by /v1/count, /v1/companies and /v1/contacts. Names are exact — see the quick-start note on typos. The value lists below are the words the data holds today; GET /v1/schema → values publishes the same lists, so a program can read them instead of copying this table. An unknown value on a known filter matches nothing (0, not everything).

FilterFormMeaning
stateTX · TX,FL,AZTwo-letter codes, comma list (case-insensitive). A value that is not a state code (Texas) matches nothing — counts and searches then add a notes line saying so (an unknown niche word gets one too).
cityDallasCity name (case-insensitive), comma list.
zip75204 · 75204,75205Five-digit ZIP, comma list.
nichehvac · roofing,plumbingIndustry vertical, comma list (case-insensitive). A company is found by either niche word its record carries (niche or niche_refined). About 19% of companies carry no vertical yet (niche_refined = other) — sector, service_line, naics and geography still apply to them. 43 verticals plus other: apartment, automotive, concrete_paving, education, electrical, excavation_demo, fencing, financial, fitness, flooring, garage_doors, general_contractor, grocery, hospitality, hvac, insulation, janitorial, junk_removal, landscaping, manufacturing, medical, moving, nonprofit, painting, personal_care, pest_control, plumbing, pool_service, pressure_washing, professional, real_estate, religious, restaurant, restoration, retail, roofing, security_alarm, septic_sewer, solar, tree_service, warehouse, welding_fab, windows_glass, other.
service_lineroofingWhat the company actually does — multi-service aware, so a general contractor that also roofs matches roofing. One value: apartment, automotive, concrete_paving, education, electrical, excavation_demo, fencing, financial, fitness, flooring, garage_doors, general_contractor, grocery, hospitality, hvac, insulation, janitorial, junk_removal, landscaping, manufacturing, medical, moving, nonprofit, painting, personal_care, pest_control, plumbing, pool_service, pressure_washing, professional, real_estate, religious, restaurant, restoration, retail, roofing, security_alarm, septic_sewer, solar, tree_service, warehouse, welding_fab, windows_glass.
sectorConstruction · Health CareBroad sector, comma list (case-insensitive): Accommodation/Food, Admin/Support/Waste, Agriculture, Arts/Recreation, Construction, Education, Finance/Insurance, Health Care, Information, Management, Manufacturing, Mining/Oil&Gas, Other Services, Other/Unclassified, Professional Services, Public Admin, RealEstate/Rental, Retail, Transportation, Transportation/Warehousing, Utilities, Wholesale.
naics2382 · 238220NAICS code, prefix match accepted.
nametextCompany name contains. A full-text scan: at most 20 calls per key per minute may carry name, and at most 4 such scans run at once across all customers (429 rate_limited with Retry-After beyond either, nothing charged) — narrow with state / niche first.
employees_band1-4 · 5-9 · 10-49 · 50-249 · 250+ · unknownHead-count band, comma list. Known for about 8% of companies — a band filter keeps only those (use unknown to reach the rest). 250+ may be sent as 250 or 250%2B.
employees_min · employees_maxintegerHead-count range on the same sparse field (known for about 8% of companies; a minimum above 0 drops every company whose size is unknown — employees_min=0 means no minimum). Not a number → 400 bad_number.
revenue_band<1M · 1-10M · 10-50M · 50M+ · unknownBand of the modeled revenue estimate, comma list; unknown for about half of all companies. 50M+ may be sent as 50M or 50M%2B.
revenue_min · revenue_maxinteger (USD)On the modeled revenue estimate — segment with it, never quote it. revenue_min=0 means no minimum; not a number → 400 bad_number.
has_company_phone · has_websitetrue / falsePresence booleans.
is_publictrue / falsePublicly traded: true = flagged public; false = every other company (most are simply not flagged).
quality_min0–100Minimum public completeness score — the quality_score every record carries, 0 to 100. Not a number → 400 bad_number.
limit · estimate · exclude_delivered · redeliver_after_days—Pull controls — see Pagination.
Decision-maker coverage is deliberately not a per-company filter or field: exposing it would publish our coverage map and let callers cherry-pick. Size it for free in aggregate (entity=companies_with_named_dm or entity=contacts), buy company data, then estimate each reveal (estimate=true, free — count: 0 means nobody is on file there) and reveal contacts — billed only for what is delivered.

07 · Record shapes

Fixed schema, explicit nulls

Every column is always present; an unknown value is null, never a missing key. Records drop straight into a CSV or a pipeline.

Company — search and detail, one projection

company record1 credit
{ "business_id": "co:d:examplepaving.com",   // opaque — store it as received, URL-encode it in paths
  "name": "Example Paving Co.", "niche": "concrete_paving", "niche_refined": "concrete_paving",
  "sector": "Construction", "naics": "238110",
  "street": "1402 Oak St", "city": "Dallas", "state": "TX", "zip": "75204", "lat": 32.8007, "lon": -96.7899,
  "phone": "2145550142",                 // 10-digit, normalized
  "domain": "examplepaving.com",
  "domain_status": null,                 // null = no concern · mismatch · social_link · directory_listing · unverified
  "employees": 12,                       // known for about 8% of companies, else null
  "employees_band": "10-49",             // 1-4 · 5-9 · 10-49 · 50-249 · 250+ · unknown (unknown for about 92%)
  "revenue_est": 3200000,                // MODELED ESTIMATE — never quote as reported financials
  "revenue_band": "1-10M",               // <1M · 1-10M · 10-50M · 50M+ · unknown
  "is_public": null, "quality_score": 85,           // 0–100 public completeness
  "service_lines": ["concrete_paving", "general_contractor"],
  "is_multi_location": false,
  "origins": ["map_listing", "web_presence"] }   // business_directory · map_listing · public_records · web_presence

Contact — reveal (/v1/companies/{id}/contacts)

contact record · reveal3 credits
{ "id": "ct:h:3f9a51c07be2d48e6a1c0d27",  // opaque: ct:h: + 24 hex characters — one id per contact at one company
  "business_id": "co:d:examplepaving.com", "company_name": "Example Paving Co.",
  "contact_class": "person",             // what this record IS — see below
  "name": "Jordan Doe", "first_name": "Jordan", "last_name": "Doe",
  "title": "Owner", "seniority": "owner_operator",   // executive · operational · financial · owner_operator · staff · unknown
  "is_decision_maker": true, "is_chain_contact": false,
  "city": "Dallas", "state": "TX",
  "channels": {                          // only the channels you asked for, only when held
    "email":  { "value": "jordan@examplepaving.com", "type": "personal", "status": "unverified" },  // honest until YOU verify
    "cell":   { "value": "2145550177", "type": "personal", "line_type": "unknown" },
    "office": { "value": "2145550142", "type": "business" } },
  "email_capable": true, "sms_capable": true, "call_capable": true,
  "linkedin": null, "origin": "web_presence" }

Contact — wide search (/v1/contacts)

When GET /v1/schema → contact_filters lists level and function, every contact also carries level and function (null without a title).

Same contact, flat fields: id, business_id, company_name, contact_class, name, first_name, last_name, title, seniority, is_decision_maker, city, state, email, email_type (personal | business), cell, office, linkedin, origin.

contact_class — labeled for what it is, not what sells

ClassMeaning
personA named individual with a direct channel — personal-pattern email and/or a cell. The sellable core.
person_business_inboxA named individual whose only email is a shared inbox (info@…). The email is labeled type: "business" — a generic inbox is never sold as someone's personal address.
business_inboxA reachable company mailbox, no named person.
owner_name_onlyAn owner / officer name with no direct channel yet, screened against a 29,000-entry given-name dictionary — built to pair with skip-trace.
linkedin_onlyA named person with only a professional-network profile on file — no email or cell (on data that holds such contacts).
person_office_onlyA named person reachable only through the company's office line (on data that holds such contacts).
otherA name we could not confirm as a person (a page phrase or role text that looks like a name), or a contact whose "name" is the business itself. Never counted as a decision-maker; it comes back — with its email or cell — only with dm_only=false.
Honesty labels. revenue_est is a modeled estimate — segment on revenue_band (unknown for about half of all companies). employees and employees_band are known for about 8% of companies; filtering on employees_min or a band drops the rest. Every email we deliver has status: "unverified" — we do not verify at delivery; verify it with POST /v1/verify/email and the verdict carries verified_at. line_type is unknown on every cell we deliver today — no line-type check is run yet (it arrives with skip-trace). Chain-inherited contacts are flagged and withheld by default.

08 · Error codes

Machine-stable codes — switch on these

codeHTTPWhen
invalid_api_key401Missing, unknown or revoked key.
insufficient_credits402Balance cannot cover the call (for async jobs: cannot cover the maximum hold). The message says what the call needs and what you have; the envelope adds credits_remaining and credits_needed.
api_key_required403A billed call from a console session — records leave only through an API key.
account_suspended403The account is suspended — every call answers this and nothing is charged, from a key or a console session alike; only GET /v1/me, GET /v1/me/usage, GET /v1/schema and GET /v1/billing/packs still answer, so the console still opens and shows the notice. Write to us to have it reviewed.
login_not_accepted403A console sign-in that cannot open an account — an unconfirmed email or an anonymous session. Sign in again with a confirmed email.
scope_required403The key lacks personal_data and the result would include a cell or personal email.
keys_manage_required403Minting or revoking keys with a plain API key — use the console.
not_found404Unknown route — including a known path with the wrong method (DELETE /v1/count) — business_id, job id or key prefix.
unknown_filter400A query parameter name we do not know (a typo such as nich); the message names it. Nothing is read or charged.
bad_boolean · bad_entity · bad_scopes · bad_request · page_range · offset_not_supported400Malformed parameters: a boolean that is not true/false/1/0; an entity that is not companies / contacts / companies_with_named_dm; scopes without read; an empty or oversized list, or a live num outside 1–10; a live page / pages that is not a whole number in range (places: page 1–20, pages 1–10, window ≤ 20; search: page 1–10); an offset parameter on a search endpoint (elsewhere offset is an unknown parameter, 400 unknown_filter).
empty_filter400A filter given with no value (state=, niche=,) — leave it out instead. Nothing is read or charged.
bad_limit · bad_number · bad_channels · bad_redeliver_after_days400limit that is not a whole number ≥ 1; a numeric filter (employees_min, revenue_max, quality_min …) that is not a number; channels other than email / cell / office; redeliver_after_days outside 1–3650. Nothing is read or charged.
segment_too_small400GET /v1/contacts on a company segment narrower than coverage_min_segment (25) — reveal one company's contacts through its own route instead.
unknown_pack400POST /v1/billing/checkout with a pack_id that GET /v1/billing/packs does not list.
bad_phone400POST /v1/skiptrace/phone with something that is not a 10-digit U.S. number. Nothing charged.
q_required · email_required · address_zip_required · phone_required · pack_id_required400A required field is missing; the message names the expected body.
bad_return_url400Checkout return URLs must be on smarterdata.ai.
bad_session_id400GET /v1/billing/sessions/{id} with something that is not a Checkout Session id.
idempotency_conflict409The same idempotency-key reused with a different request.
idempotency_in_progress409The first call with this idempotency-key was interrupted on our side and is still claimed (a retry while it is merely running waits for it instead). Retry in a few minutes; nothing was charged by this call.
too_many_keys409More than 10 active keys — revoke one first.
company_required409A contact reveal (or its estimate) for a company your account does not hold yet — get the company first (a search, or GET /v1/companies/{id}). Nothing charged.
ambiguous_prefix409A key prefix that names more than one key — address the key by a longer prefix.
method_not_allowed · payload_too_large · unsupported_media_type405 · 413 · 415405 is the hosted MCP's answer to GET or DELETE on /mcp (send JSON-RPC with POST); on the REST API a wrong method on a known path is 404 not_found, its message naming the method and path. 413: a body over 1 MB (256 KB on the hosted MCP). 415: a body that is not application/json.
request_header_too_large431The request line and headers are over 16 KB — a very long query string. Shorten it; nothing charged. A body that is declared JSON but does not parse is 400 bad_request ("the body is not JSON").
rate_limited · live_rate_limited · daily_cap_reached · auth_rate_limited429Per-key request rate; more than 20 calls a minute carrying name=, more than 4 name= scans in flight across all customers, or more than 4 estimates running at once on one key (those three carry Retry-After; nothing is charged); live page-units per minute (per account); the key's daily credit cap; one address having more unknown keys looked up than the brake allows at once — a valid key always authenticates, at most after a short wait or a retry a second later; a key that authenticated recently is never affected.
auth_unavailable503A console sign-in could not be checked right now (on our side) — try again in a minute. API keys are not affected. Nothing charged.
skiptrace_unavailable503Skip-trace is not switched on yet. Nothing charged.
upstream_unavailable · upstream_paused502 / 503A live, verification or crawl provider failed or is paused for budget. Holds are refunded.
billing_unavailable503Checkout cannot be started right now (packs not configured, or the payment provider unreachable). Nothing charged.
busy · job_interrupted503The data service is at capacity — retry in a few seconds; an async job was closed before its hold could be taken — submit it again. Nothing charged either way.
internal_error500Our fault. Any charge on that request was compensated; retry with the same idempotency-key, and quote the request_id if it persists.

09 · MCP for agents

Your agent already knows how to use this

The hosted MCP server is a thin client of the REST API: every tool call is an HTTP request with your key, so MCP billing is REST billing — same ledger, same prices, same envelope. Connect once and Claude or any MCP client can size markets, pull records and verify emails mid-conversation.

connect oncestreamable http
# Claude Code — a remote server needs --transport http (the CLI's default is stdio)
$ claude mcp add --transport http smarterdata https://mcp.smarterdata.ai/mcp \
    --header "x-api-key: sk_live_…"

# Any other MCP client: a Streamable HTTP server at this URL, with the header
{ "mcpServers": { "smarterdata": { "type": "http", "url": "https://mcp.smarterdata.ai/mcp",
                                   "headers": { "x-api-key": "sk_live_…" } } } }
ToolCreditsWhat it does
count_businesses0Count businesses matching filters — size every segment with this first.
count_contacts0Count decision-maker contacts (strict definition) at matching businesses — counted per company: a person listed at two businesses is two contacts.
search_businesses1 / recordPull businesses; limit ≤ 200 (default 10). Auto-advance: the same call again returns the next batch; exclude_delivered: false returns businesses you already bought, charged again. estimate: true returns the exact cost for 0 credits.
get_schema0The live contract: filter names, the words each value filter accepts (values.niche …), the price table and the access rules.
get_business1 · 0 if yoursOne business_id — the same record a search returns. Free for a business you already received. estimate: true returns the cost for 0 credits.
find_contacts3 / contactTwo-step, for a business you already hold (get it with search_businesses or get_business first — otherwise company_required). Without confirm it returns only the count and exact credit_cost (free). With confirm: true it reveals — only when that estimate is within max_credits (default 50): over the cap it returns over_max_credits and charges nothing. A contact you already bought at that business is not revealed or charged again; exclude_delivered: false reveals it again, charged again.
live_places_search2 / pageLive map-listing results (q, location, page, pages); billed per page fetched, even at zero results. Continue with next_page. estimate: true is free. Keep pages at 3 or fewer per call: an MCP client may give up after 60 s while the pages are still being fetched and billed.
web_search1 / queryLive organic web results; billed per query. estimate: true is free.
verify_email1 / checkInstant mail-host check: does the address's domain accept mail. It does not confirm the mailbox exists. Every check is charged; a domain verified in the last 60 days answers at once with its original verified_at (cached: true).

Tool arguments are strict, as on the REST API: an argument name a tool does not know is refused with unknown_filter before anything is charged. Every failure — an API error, a refused argument, an unreachable API — comes back as the same error envelope { ok: false, error: { code, message }, credits_used }. Over raw HTTP, send Accept: application/json, text/event-stream and Content-Type: application/json (the SDKs do; a missing header, or a wildcard */*, is filled in). A body that is empty or not a JSON-RPC object is 400 bad_request in the same envelope.

Every tool result that reads or spends credits carries credits_used and credits_remaining — the free get_schema answers the contract alone, with neither — so the agent — and you — always know the balance; an error carries credits_used (0, or null when the answer never arrived and the charge cannot be known — check the balance with a free count before repeating the call). The MCP adds its own codes to the REST ones: over_max_credits (a find_contacts reveal whose estimate is above max_credits — nothing charged), estimate_unavailable (that estimate failed or carried no cost — nothing revealed or charged), bad_arguments (an argument of the wrong shape — nothing charged), api_unreachable (the API could not be reached — nothing charged), api_connection_lost (the API's answer never arrived — credits_used: null), bad_gateway (an answer that is not the API's envelope), unknown_tool and tool_error (anything else).

What needs a confirm, and what does not. Only find_contacts asks for confirmation. Every other paid tool charges when it is called — search_businesses up to 200 credits per call, live_places_search up to 20, get_business, web_search and verify_email 1 each. Price a call first with the free estimate argument, keep limit small, give each paid call an idempotency_key (the same call with the same key returns the original answer for 0 credits — the safe retry after a timeout), and ask us to set a daily credit cap on the key an agent uses (429 daily_cap_reached once it is spent).

10 · Acceptable use

Licensed for B2B sales & marketing. Nothing else.

  • Prohibited: FCRA-regulated decisions (credit, employment, tenancy, insurance), stalking or harassment, re-identification of individuals. Skip-trace output, when active, is marketing-use only.
  • You own channel compliance: TCPA / DNC for calls and texts, CAN-SPAM for email, and state-law equivalents. dnc flags we return are information, not permission.
  • Scopes: personal_data (cells, personal emails) is included with every account; the keys you mint carry at most your own scopes.
  • Suppression & deletion: a bring-your-own suppression list endpoint is planned. Until it ships, send opt-outs you receive to privacy@smarterdata.ai: we add the values to our suppression list, remove the record in the next data build (so it leaves every customer's future pulls) and confirm by email; California deletion requests are handled the same way.

This section is a summary. The binding documents are the Acceptable Use Policy, the Terms of Service (credits, license, liability) and the Privacy Policy (what we keep, opt-out and suppression via privacy@smarterdata.ai).

11 · Versioning & support

Breaking changes get a new path version

Everything here lives under /v1. A breaking change ships as a new path version and is announced at least 30 days ahead; GET /healthz exposes the running build. Questions, more volume than the largest pack, or a request_id that needs a human: ivan@smarteroutbound.com. Credits come in packs bought in the console — GET /v1/billing/packs lists them, and a new account starts with 100 free credits — and the credit price of each action is the one public table, GET /v1/schema → credits.

GET /healthzdocs smarterdata.ai/docsconsole smarterdata.ai/appapi api.smarterdata.aimcp mcp.smarterdata.ai/mcp