What happens to the NPI list you upload.
Match Filter lets you cross your own HCP target list against a Scope cohort. The hashing happens inside an AWS KMS-backed worker with per-tenant key derivation; the list is never stored, never sent to an EHR partner, and never visible to our engineers in either source or hashed form. This page walks through exactly how that works.
In plain English (read this if you're not a techie)
You upload a list of doctors you already care about. We use it once, only to count how many of them this cohort can reach, and then we let it go. The list never gets sent to any EHR partner - they don't see it and don't even know it exists. We don't save it on our side either. What you get back is a number (“X of your Y targets are reachable”) and a breakdown by specialty and region. That's it. No doctor names, no NPIs, nothing that says “this specific doctor matched.”
The two things people most worry about, answered:
- Can the EHRs see who's on my list? No. They never receive it, are never notified, and aren't involved in this step at all.
- Does Scriptlane keep my list? No. The file is processed in memory and discarded. We keep four numbers about it (counts + a scrambled fingerprint to recognize a re-upload) - never the contents.
The technical version (for security reviewers)
You upload a file of NPIs. The Next.js application authenticates the request and forwards the parsed NPIs to a dedicated AWS Lambda worker reachable only via a signed Function URL. The worker calls kms:GenerateMac with an HMAC_256 KMS key plus a per-tenant context string, deriving a tenant-scoped MAC key whose root never leaves KMS. Each NPI is HMAC'd with that derived key, the resulting tokens are intersected against the cohort the scoping fan-out produced, and a specialty + region rollup is returned. We persist only counts, the rollup, and a one-way list digest. NPIs - source or hashed - are never stored at rest, never logged, and never sent to an EHR partner. Every KMS derivation is recorded to CloudTrail; the application cannot read the HMAC root material under any circumstance.
What we persist, field by field
| Field | Value | Why |
|---|---|---|
| uploaded_count | integer | So you can see how many rows we parsed. |
| matched_count | integer | The headline output - intersection size. |
| rejected_count | integer | Rows we couldn't parse or that failed Luhn; never the row contents. |
| result_summary | JSON | The same shape Scope uses - totals + by-specialty + by-region - restricted to matched HCPs. No per-HCP rows. |
| list_digest | SHA-256 of sorted NPIs, per-org salted | Lets us detect a re-upload of the same list. One-way; no NPI is recoverable from the digest. |
What we never persist, process, or log
- Your NPIs. Neither the full list nor the matched subset. The database schema doesn't have a column for them.
- Per-HCP attribution. We never tell you which specific doctor matched which specific EHR partner. The rollup is aggregate.
- EHR partner identity in the result. You see a specialty + region rollup of the matches; you don't see which EHR returned which match.
- Your list in application logs. Our logger drops any field named
npi,npis,list, ortokensbefore it writes, and our match code never passes NPI values to the logger directly.
How it travels and processes
What this posture does and does not guarantee
The Match Filter worker is an isolated AWS Lambda whose only inbound surface is a signed Function URL the application calls per-request. It is the only component that can call kms:GenerateMac against the HMAC root key, and the application's IAM role cannot. That gives us the following:
- The HMAC root never leaves KMS. Even a full compromise of the application or our Vercel environment cannot recover it. Per-tenant MAC keys are derived at request time and not persisted.
- No engineer can read your list at rest. It isn't there. The DB schema has no column for uploaded NPIs or matched NPIs; both are dropped from memory before the request returns.
- Every key derivation is independently logged. CloudTrail records every
GenerateMaccall - we cannot un-log a derivation, and you can request the CloudTrail extract for your tenant during an audit.
Honest disclosures, because this matters: the Lambda worker's working memory is not hardware-attested. A sufficiently-privileged AWS root operator with physical access to the host could in principle inspect the worker's memory while a request is in flight. AWS's own controls (service operator separation, no console access to running Lambdas, attested host hardware for the underlying compute) mitigate this; the Nitro Enclave path on the roadmap eliminates it for tenants who need cryptographic non-disclosure as a contract term.
Enterprise option
Enterprise customers can run Match Filter against a KMS customer-managed key in their own AWS account (BYOK). We receive the derived per-tenant MAC tokens for the intersection; you keep root control of the key and can revoke it on demand. Ask us if you need this.
What we reject on ingest
The deny-list runs before any processing. If your file has any of these columns, the upload fails with a specific error and no data is persisted:
- PHI-coded:
mrn,patient_id,patient_name,dob,ssn,diagnosis,icd10,dosage,medication,drug,pharmacy,insurance,payer,copay, and close variants. - Targeting-logic-coded:
name,email,phone,specialty,state,zip,address- anything that lets a list-owner re-introduce filtering we explicitly keep out of this step. - Unknown: any column name we don't recognize. The allowlist is positive, not negative.
Questions we expect you to ask
How to report a concern
Email ben@scriptlanedata.com. Security issues get a one-business-day acknowledgement. If you have reason to believe the posture described on this page doesn't hold, tell us immediately and we'll publish a correction with the technical details of what changed.