A fast, dependency-free linter for FHIR R4 prior-authorization request bundles. You hand it the Bundle you would POST to a payer's Claim/$submit endpoint, and it tells you, in plain English with an IG citation on every finding, which parts of the Da Vinci PAS request contract you are not meeting yet. It exists because most teams building CMS-0057 PAS flows (rule effective January 1, 2027) have a request bundle long before they have a payer to test it against.
It is a linter for common, deterministic failure modes, not a conformance verdict. It checks required elements, fixed values, and intra-bundle reference integrity: the things that are cheap to get wrong and annoying to discover from a 400 with no body. It does not validate terminology bindings against value sets, run FHIRPath invariants, or replace the official HL7 validator. If pas-lint is green, run the real validator before you claim conformance.
Rules were written and verified against HL7 Da Vinci Prior Authorization Support (PAS) STU 2, v2.2.1 (FHIR R4 / 4.0.1). The version is printed by pas-lint --version and included in the JSON output.
Requires Node >= 18.
# one-off, from the published package (once released)
npx pas-lint path/to/bundle.json
# or clone + build locally
git clone <repo> && cd pas-lint
npm install # builds dist/ via the prepare script
node dist/cli.js examples/valid-request-bundle.jsonOptions:
pas-lint <bundle.json> [options]
--json Emit machine-readable JSON (for CI)
-q, --quiet Only show errors; suppress warnings and the summary
-v, --version Print pas-lint and IG version
-h, --help Show help
Exit codes: 0 = no errors 1 = errors found 2 = usage / parse error
Example against a broken bundle:
$ pas-lint examples/broken-request-bundle.json
✗ error CLM-003 Claim.use is "claim"; the PAS Claim profile fixes it to "preauthorization".
at Claim.use
fix Set "use": "preauthorization".
https://hl7.org/fhir/us/davinci-pas/StructureDefinition-profile-claim.html
✗ error REF-004 Claim.insurance[0].coverage references "Coverage/NotInBundle" but no matching Coverage entry exists in the bundle.
at Claim.insurance[0].coverage
fix Include the Coverage resource in the bundle; a missing Coverage is one of the most common PAS submission failures.
https://hl7.org/fhir/us/davinci-pas/StructureDefinition-profile-pas-request-bundle.html
✗ failed: 2 errors, 0 warnings
import { lint } from "pas-lint";
const result = lint(JSON.parse(bundleJson));
// { ok, igVersion, errorCount, warnCount, findings: [{ id, severity, rule, ig, message, location, hint }] }
if (!result.ok) {
for (const f of result.findings) console.log(f.id, f.message);
}lint() never throws on malformed input: bad JSON structure produces findings, not exceptions.
pas-lint exits non-zero when it finds errors, so it drops into CI directly:
# .github/workflows/pas.yml
name: PAS bundle lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npx pas-lint fixtures/*.json --jsonUse --json to capture findings as an artifact, or --quiet to fail only on errors and keep the log short.
30 deterministic checks. error = the bundle violates a required element or fixed value in the PAS profiles (or a reference that cannot resolve); warn = valid but likely a mistake or a missing best-practice declaration.
| ID | Severity | Rule | IG citation |
|---|---|---|---|
BDL-001 |
error | The root resource must be a Bundle. | PAS Request Bundle profile |
BDL-002 |
error | Bundle.type must be "collection" for a PAS request bundle. | PAS Request Bundle profile |
BDL-003 |
error | Bundle.identifier is required (1..1). | PAS Request Bundle profile |
BDL-004 |
error | Bundle.timestamp is required (1..1). | PAS Request Bundle profile |
BDL-005 |
error | Bundle must contain at least one entry (1..*). | PAS Request Bundle profile |
BDL-006 |
error | Every Bundle.entry must have a fullUrl (1..1). | PAS Request Bundle profile |
BDL-007 |
error | Bundle.entry.fullUrl values must be unique. | PAS Request Bundle profile |
BDL-008 |
error | Bundle must contain exactly one PAS Claim entry (1..2 in the profile; one for a new request). | PAS Request Bundle profile |
BDL-009 |
warn | Bundle.meta.profile should declare the PAS Request Bundle profile. | PAS Request Bundle profile |
CLM-001 |
warn | Claim should declare the PAS Claim profile in meta.profile. | PAS Claim profile |
CLM-002 |
error | Claim.status is required (1..1) and must be a valid financial-management status. | PAS Claim profile |
CLM-003 |
error | Claim.use is required (1..1) and PAS fixes it to "preauthorization". | PAS Claim profile |
CLM-004 |
error | Claim.type is required (1..1) with a coding. | PAS Claim profile |
CLM-005 |
warn | Claim.type.coding should use the standard claim-type code system. | PAS Claim profile |
CLM-006 |
error | Claim.patient is required (1..1). | PAS Claim profile |
CLM-007 |
error | Claim.created is required (1..1) and must be a valid dateTime. | PAS Claim profile |
CLM-008 |
error | Claim.insurer is required (1..1). | PAS Claim profile |
CLM-009 |
error | Claim.provider is required (1..1). | PAS Claim profile |
CLM-010 |
error | Claim.priority is required (1..1) with a coding. | PAS Claim profile |
CLM-011 |
error | Claim.identifier is required (1..2), each with system and value. | PAS Claim profile |
CLM-012 |
error | Claim.insurance is required (1..*); each entry needs focal and a coverage reference. | PAS Claim profile |
CLM-013 |
error | Claim.item is required (1..*); each item needs productOrService and a category. | PAS Claim profile |
REF-001 |
error | Claim.patient must resolve to an entry inside the bundle. | PAS Request Bundle profile |
REF-002 |
error | Claim.insurer must resolve to an entry inside the bundle. | PAS Request Bundle profile |
REF-003 |
error | Claim.provider must resolve to an entry inside the bundle. | PAS Request Bundle profile |
REF-004 |
error | Every Claim.insurance.coverage must resolve to a Coverage entry in the bundle. | PAS Request Bundle profile |
REF-005 |
error | Every Claim.careTeam.provider must resolve to an entry inside the bundle. | PAS Request Bundle profile |
REF-006 |
error | Claim.supportingInfo.valueReference must resolve to an entry inside the bundle. | PAS Request Bundle profile |
REF-007 |
error | Each Claim.item.careTeamSequence must point at an existing Claim.careTeam.sequence. | PAS Claim profile |
SUP-001 |
error | Each Claim.supportingInfo entry must have a sequence and a category coding. | PAS Claim profile |
Reference resolution accepts the forms real bundles use: absolute fullUrl matches, urn:uuid: matches, and relative ResourceType/id references resolved against each entry's fullUrl or the contained resource's own id. Contained (#local) references are treated as satisfied and left to a full validator.
This is a linter for common failure modes, not a conformance verdict. A clean run means the bundle passes 30 cheap structural checks; it does not mean the bundle is PAS-conformant. It does not validate value-set bindings, X12 278 code content, slicing, extension cardinality, or the response side of the exchange. For a conformance verdict, run the official HL7 FHIR validator with the hl7.fhir.us.davinci-pas package.
npm install # installs devDeps and builds dist/
npm run build # tsc -> dist/
npm test # builds, then runs vitest (real IG example bundles + one broken variant per check)The checks live in src/checks.ts as a registry of { id, severity, rule, ig, run }. Adding a check is a single array entry plus a mutation test in test/pas-lint.test.ts; the test suite asserts that every registered check has a corresponding failing fixture.
MIT.
pas-lint is built by the team behind MockPayer, a hosted synthetic payer you can test CRD/DTR/PAS flows against.