Resolve any NPI. Resolve any PECOS-ID.
Two endpoints. No credentials. The Fonteum federated identity layer is the canonical NPPES (NPI) ↔ PECOS (PECOS-ID) mapping. Every link carries methodology version + per-field source evidence sufficient to manually audit the decision.
Two reads.
GET /api/v1/identity/[source]/[id]— canonical link record for one provider.source∈['nppes', 'pecos']. 200 returns{ npi, pecos_id, link_confidence, link_method, methodology_version, attested_at, source_evidence_summary, provenance }. 404 distinguishes “no link at any confidence” from “link exists below default threshold” (with hint to retry with?include_low_confidence=true). Cachepublic, max-age=300.GET /api/v1/identity/stats— aggregate counts across the layer:total_linked,by_confidence_band,by_link_method,methodology_versions,updated_at. Cachepublic, max-age=900.
High by default. Opt in for the rest.
Public endpoints default to link_confidence ≥ 0.9(the "high" band). Lower-confidence links are stored, methodology-pinned, and queryable, but require explicit opt-in:
GET /api/v1/identity/nppes/1245319599?include_low_confidence=trueThis is intentional: a canonical-identity claim that gets re-quoted across the web should be conservative-by-default. Diligence and operator workflows that need broader recall pass the opt-in.
Nine strategies across 5 federal sources, confidence-ordered.
NPPES ↔ PECOS
exact_npi_in_pecos· 1.0 PECOS row carries an explicitnpimatching an NPPES record.exact_name_address· 0.95 Normalized provider name AND byte-equal address. Not 1.0 because group practices can share an office.name_address_fuzzy· 0.7-0.9 Jaro-Winkler similarity, gated by exact state + zip5. Fuzzy ceiling 0.9 — only saturating fuzzy matches reach the default-visible band.
NPPES ↔ Care Compare CCN
exact_npi_in_carecompare· 1.0 Care Compare row carries an NPI matching the NPPES record.exact_name_address_carecompare· 0.95 Facility name + byte-equal normalized address. No fuzzy strategy: Care Compare facility names have high lexical variance (Mercy Hospital vs Mercy Health Hospital LLC) — deferred to a future Phase 3 wave with a dedicated facility-name normalizer.
NPPES ↔ OIG LEIE
exact_npi_in_leie· 1.0 Modern LEIE rows carry an NPI matching the NPPES record.name_dob_leie· 0.95 Legacy LEIE rows (pre-NPI-era exclusions). Normalized name AND identical DOB. No fuzzy strategy: false-positive cost extremely high for exclusion claims (claiming a provider is OIG-excluded when they aren't is reputationally damaging).
NPPES ↔ HRSA HPSA
exact_npi_in_hrsa· 1.0 HRSA HPSA row carries an NPI matching the NPPES record.source_evidence_summary.hpsa_designationechoes the HPSA category (Primary Care / Mental Health / Dental).
Operator-asserted
manual· 1.0 Operator-asserted via direct SQL. Self-service curation UI deferred to Phase 3.
Strategy precedence is fixed per source (highest-confidence first); the linker stores the BEST match it finds for each (npi, partner_id) pair. Re-runs with improved methodology only UPDATE a row if the new candidate has STRICTLY HIGHER confidence than the existing — never downgrade a link silently.
Pinned per link.
Every link row stores its methodology_version (e.g. v2026.05.0-identity-matcher). When a future wave bumps the matcher (improved normalizers, new fuzzy threshold, additional strategies), the new methodology version flows into all newly-extended links. Existing links stay pinned to whichever version produced them — so "why does this link have confidence 0.84?" is always answerable.
Methodology version bumps surface in the public methodology changelog (/methodology/changelog) since the source-snapshots methodology watcher (PR #134) tracks every version-bump event.
Phase 2 ships 5-source coverage.
- Phase 1 (PR #145): NPPES (NPI) ↔ PECOS (PECOS-ID). Foundational identity layer, resolver, dashboard.
- Phase 2 (this wave): NPPES ↔ Care Compare (CCN); NPPES ↔ OIG LEIE (NPI for modern rows, name+DOB for legacy rows); NPPES ↔ HRSA HPSA (Provider-ID). Schema extended with composite UNIQUE NULLS NOT DISTINCT. Resolver aggregates across all 5 sources per NPI.
- Phase 3 (queued): manual operator-asserted linking UI; cross-source ownership chain resolution; real-time identity update webhook events via the PR #136 webhook registry.
Three reads. No credentials.
# Resolve an NPI to its canonical PECOS-ID
curl https://fonteum.com/api/v1/identity/nppes/1245319599
# Resolve a PECOS-ID to its canonical NPI
curl https://fonteum.com/api/v1/identity/pecos/PAC-100001
# Aggregate stats: total linked + confidence-band distribution
curl https://fonteum.com/api/v1/identity/stats
# Opt in to surface low-confidence (<0.9) links for diligence
curl 'https://fonteum.com/api/v1/identity/nppes/1245319599?include_low_confidence=true'stdlib fetch.
// Node 18+ (built-in fetch)
const npi = "1245319599";
const res = await fetch(`https://fonteum.com/api/v1/identity/nppes/${npi}`);
if (res.status === 200) {
const link = await res.json();
console.log("PECOS-ID:", link.pecos_id);
console.log("Confidence:", link.link_confidence);
console.log("Method:", link.link_method);
console.log("Methodology version:", link.methodology_version);
} else if (res.status === 404) {
// Either no link at any confidence, or below default 0.9 threshold.
// Re-try with the opt-in to widen the search:
const retry = await fetch(`https://fonteum.com/api/v1/identity/nppes/${npi}?include_low_confidence=true`);
if (retry.status === 404) console.log("no link at any confidence");
}stdlib + requests.
# Python 3.10+ (stdlib + requests)
import requests
npi = "1245319599"
res = requests.get(f"https://fonteum.com/api/v1/identity/nppes/{npi}")
if res.status_code == 200:
link = res.json()
print("PECOS-ID:", link["pecos_id"])
print("Confidence:", link["link_confidence"])
print("Method:", link["link_method"])
print("Methodology version:", link["methodology_version"])
elif res.status_code == 404:
# Either no link at any confidence, or below default 0.9 threshold.
retry = requests.get(
f"https://fonteum.com/api/v1/identity/nppes/{npi}",
params={"include_low_confidence": "true"},
)
if retry.status_code == 404:
print("no link at any confidence")stdlib only.
// Go 1.21+ (stdlib only)
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type IdentityLink struct {
Npi string `json:"npi"`
PecosID string `json:"pecos_id"`
LinkConfidence float64 `json:"link_confidence"`
LinkMethod string `json:"link_method"`
MethodologyVersion string `json:"methodology_version"`
}
func main() {
npi := "1245319599"
res, err := http.Get(fmt.Sprintf("https://fonteum.com/api/v1/identity/nppes/%s", npi))
if err != nil {
panic(err)
}
defer res.Body.Close()
if res.StatusCode == 200 {
var link IdentityLink
if err := json.NewDecoder(res.Body).Decode(&link); err != nil {
panic(err)
}
fmt.Println("PECOS-ID:", link.PecosID)
fmt.Println("Confidence:", link.LinkConfidence)
}
}