API documentation
A small, predictable REST API over the source-linked facts layer. Base URL: https://xcircl.com/api/v1. Responses are JSON by default; pass format=csv for a download.
Authentication
Public endpoints (coverage, sample) need no key. Every provider-endpoint call requires one — get a free key in 30 seconds at signup (identity fields, 500 calls/mo, evaluation & non-commercial use). Paid keys come in three plans — Builder ($99/mo: full fields, one vertical, 5,000 calls/mo), Developer ($750/mo: multi-vertical, 25,000 calls/mo) and Enterprise (scoped by phone). A Builder key is bound to its vertical — querying another returns an upgrade notice; exceeding a quota returns 429. See pricing.
# Pass your key as a Bearer token
curl https://xcircl.com/api/v1/providers/ \
-H "Authorization: Bearer YOUR_API_KEY"
# x-api-key header and ?key= are also acceptedPublication boundary
Every tier delivers the verified publication set by default (publish_tier='verified' in our pipeline). The paid tier, the public sample, and all CSV exports are strictly verified-only. The wider tracked set exists for transparency: the free tier can inspect it by adding include=tracked (JSON only), and each non-verified record self-declares with "publish_tier": "internal_only". Responses state their boundary in the publish_boundary field.
List providers
Filter by vertical, state, city, business_mode. Page with limit (1–1000) and offset (a non-integer, negative, or out-of-range offset returns 400 invalid_offset). Free tier may add include=tracked for the transparency view.
Vertical availability: glp1 is the verified vertical today. pet_health and medspa are loading; dental and senior are queued. Non-live verticals are reserved in the schema but return no data yet, so don’t integrate against them until they’re announced.
GET /api/v1/providers/?vertical=glp1&state=CA&business_mode=online&limit=20&offset=0
# format=csv for a CSV download (column set follows your tier){
"tier": "paid",
"pagination": { "total": 142, "limit": 20, "offset": 0, "returned": 20 },
"filters": { "vertical": "glp1", "state": "CA", "city": null, "business_mode": "online" },
"data": [
{
"entity_id": "glp1_…",
"slug": "…",
"vertical": "glp1",
"name": "Example Weight Care",
"city": "Los Angeles",
"state": "CA",
"business_mode": "online",
"latitude": 34.05, "longitude": -118.24,
"npi": "1234567890",
"legitscript": { "status": "unverified", "source": null, "verified_at": null },
"license": { "status": "reported", "states_count": 3, "source": "Provider-reported", "verified_at": "2026-05-01T…" },
"fda": { "status": "clear", "source": "FDA Warning Letters database", "verified_at": "2026-06-01T…" },
"price": { "range": "$200–$400/mo", "monthly_min": 200, "monthly_max": 400, "source": "…", "verified_at": "…" }
}
]
}L0 identity layer (free, all specialties)
Beyond the verified verticals, xcircl exposes a free horizontal identity layer sourced from CMS NPPES — every US provider, all specialties. Query it by NUCC taxonomy code (with no vertical), optionally filtered by state / city, paged with the same limit / offset.
Rows are identity-only and self-declare "layer": "identity" and "source": "NPPES" with a checked_at date. Each carries a human-readable taxonomy_display (e.g. “Family Medicine”, resolved from the NUCC taxonomy map) and a free oig screening field (see below). The vertical compliance fields (legitscript, license, fda, price) are returned as locked placeholders — identity rows never carry a verified signal, so they must not be read as the verified tier.
Query by name, too. taxonomy accepts either a 10-character NUCC code or a classification name — ?taxonomy=Family Medicine — which we resolve to codes via the taxonomy map.
# Free key. Query by NUCC code OR a classification name:
curl "https://xcircl.com/api/v1/providers/?taxonomy=122300000X&state=TX&limit=5" \
-H "Authorization: Bearer YOUR_FREE_KEY"
curl "https://xcircl.com/api/v1/providers/?taxonomy=Family%20Medicine&state=TX" \
-H "Authorization: Bearer YOUR_FREE_KEY"
# → rows self-declare "layer":"identity", "source":"NPPES",
# carry a human "taxonomy_display", and a free "oig" screening fieldOIG exclusion screening (free)
Every provider response — L0 identity and vertical alike — carries a free oig field that screens the provider’s NPI against the HHS-OIG List of Excluded Individuals/Entities (LEIE). A match returns the exclusion type, date, source, and check time.
A no-match is not a clearance. The LEIE NPI-keys only ~10.5% of its records, so the absence of a match tells you nothing about whether the provider is excluded. We never emit a clean / cleared value — only an explicit match, or an explicit "npi_match": "none" with a note that says so. Treat it as one signal among many, not a green light.
# OIG screening is FREE on every provider row (L0 and vertical):
# match:
"oig": { "excluded": true, "type": "1128b8", "date": "2022-03-20",
"source": "oig_leie", "checked_at": "…" }
# no match (NOT a clearance):
"oig": { "npi_match": "none",
"note": "No NPI-keyed match in the OIG LEIE. Absence of a match is
not a clearance — 89.5% of LEIE records carry no NPI." }Single provider
Look up one provider by entity_id or slug. Same tiering as the list.
GET /api/v1/providers/{entity_id-or-slug}/Coverage
Public aggregate — counts, business-mode split, and a per-state breakdown, computed live.
GET /api/v1/coverage/ # public — live counts, by_state breakdownRate limits & quota headers
Quotas are monthly, per key, on a UTC calendar month. The free key allows 500 provider calls/mo; Builder 5,000; Developer 25,000; Enterprise unmetered. There is no fixed per-second cap today — metering is by monthly volume.
Metered responses expose your live position in X-RateLimit-* headers, so you can back off before hitting the ceiling. When the monthly quota is exhausted, calls return 429 with a Retry-After header (seconds until reset). Metering fails open — if the counter is ever unavailable, your request still succeeds and no rate-limit headers are sent.
# Metered (Builder / Developer) responses carry:
X-RateLimit-Limit: 25000 # your plan's monthly call quota
X-RateLimit-Remaining: 24994 # calls left this UTC month
X-RateLimit-Used: 6 # calls used so far, including this one
X-RateLimit-Reset: 1717200 # seconds until reset (00:00 UTC on the 1st)
# On 429, additionally:
Retry-After: 1717200 # seconds until the quota resetsFields & tiers
Free returns public identity. Paid adds verified compliance signals and price — each as an object with status, source, and verified_at.
Locked fields. On a free key the paid fields are still present in the response, as placeholders: {"locked":true,"unlock":"Builder $99/mo — https://xcircl.com/pricing/"}. “Locked” only means the field is paid — it is never a claim that the provider is verified. A paid key returns each field in its real state (verified / clear / flagged / reported / unverified) with its source and check date — we never invent one.
| Field | Type | Tier | Description |
|---|---|---|---|
entity_id | string | free | Stable cross-SoMo identifier. |
name | string | free | Provider name. |
vertical | enum | free | glp1 · pet_health · … |
city / state | string | free | Location. |
business_mode | enum | free | online · physical · both. |
latitude / longitude | number | free | Geocoordinates. |
npi | string | free | NPI from NPPES. |
legitscript | signal | paid | Certification status + source + date. |
license | signal | paid | State licensing + states_count + source + date. |
fda | signal | paid | Warning-letter screen + source + date. |
price | object | paid | Monthly range + source + date. |
Errors
Every error returns { error, code } — a human message plus a stable machine-readable code. A 5xx never leaks internal detail; it always returns code: "internal_error".
400— bad parameter:invalid_vertical,invalid_business_mode,invalid_limit,invalid_offset.401key_required— no key was supplied for a provider endpoint; get a free key at signup.401invalid_key— invalid, unknown, rotated, or revoked API key.403vertical_not_permitted— Builder key queried outside its bound vertical.404not_found— no active provider matches.429quota_exceeded— monthly call quota reached (see rate limits).500internal_error— an unexpected server error (details are logged, never returned).
OpenAPI spec
The full contract is published as OpenAPI 3.1 at /openapi.json. Point any OpenAPI client (Swagger UI, Postman, or a code generator like openapi-generator) at that URL to explore the API or generate a typed client in your language.
# Explore in Swagger UI, or generate a client:
npx @openapitools/openapi-generator-cli generate \
-i https://xcircl.com/openapi.json \
-g typescript-fetch -o ./xcircl-clientTry it now
Run your first call in five minutes with the quickstart.