+

Connect every employer to their benefits administrator

Bidirectional integration with Employee Navigator. Push employer profiles, plan elections, and rates into EN. Pull census and enrollment data back for ACA quoting. Built like BenefitPoint — same OAuth pattern, same operation framework, same broker-controlled flow.

Browse the interactive Sync Map
Direction: Bidirectional Auth: OAuth 2.0 Status: Production · Working Docs: Employee Navigator API docs ↗ (partner login)
What it is

One handshake, then plans and people flow both ways

A broker activates the integration on a Plansight employer, links the employer to its EN counterpart once, and from then on plans push out and census pulls back with a click.

OAuth 2.0
Authorization Code + Client Credentials
3
Operations: CompanyIntegration, QuoteNotification, QuoteCensus
7
Routes for callbacks, status, push, pull
All
Plan types: medical, dental, vision, life, ancillary
🔗

Activated per employer

Toggle on under Edit Employer → Employee Navigator. A new "Employee Navigator" tab appears for that employer. Activation is per-employer, not per-brokerage — brokers control which employers sync.

🤝

One-time link, then automatic

First push prompts EN OAuth login. After the broker links the Plansight employer to an EN company (existing or new), all subsequent pushes / pulls happen without re-auth — system-level token handles ongoing operations.

📤

Push: plans + rates + benefits

From Plansight's Benefit History or post-election views, broker selects elected plans and pushes to EN. Plan design, rates (composite or age-banded), benefit summary all flow over. EN side accepts as new plans or renews existing ones by matching benefit type + dates.

📥

Pull: census for ACA quoting

Inside an RFP marked community-rated, broker can pull EN census directly at the quoting / census step — employee roster, demographics, current enrolled plan. Skips the spreadsheet upload entirely.

Why

Where this saves time

Each side of the integration eliminates a manual step in the renewal workflow.

👤

Broker

No more re-keying employer data into EN, no more uploading censuses for community-rated quotes, no more manually entering elected plan details on the EN side at renewal close.

🏢

Agency

One canonical record of an employer that lives in both systems. Reduces drift between Plansight's quote/election history and EN's enrollment record.

Renewal velocity

Push elected plans the moment elections close. Census comes back instantly when starting next year's RFP. Cuts hours off both ends of the renewal cycle.

Two integrations in one

The EN integration handles two distinct flows: employer + plan push (Plansight → EN) when elections close, and census pull (EN → Plansight) when starting an ACA / community-rated quote. They share auth and data-link plumbing but solve different broker problems and run at different points in the year.

Activation Flow

One-time setup per employer

Connecting a Plansight employer to an Employee Navigator company. Done once; all future pushes / pulls run on the persisted link.

1

Toggle the integration on

From the Plansight employer page, Edit Employer → Employee Navigator. Turn on, save. A new Employee Navigator tab appears for that employer.

2

Push the employer over

From the new tab, click push. Plansight redirects to EN's OAuth login. Broker authenticates with their EN credentials. Plansight calls POST /api/Quoting/companies/{groupId}/integration (the CompanyIntegration operation). EN receives the request and creates a Pending Integration on its dashboard.

3

Link on the EN side

Broker switches to EN, opens the pending integration. Either links to an existing EN company OR creates a new one (broker enters state + SIC code). EN persists the link.

4

Update status back in Plansight

Broker returns to Plansight's EN tab, refreshes status. Plansight calls GET /api/Quoting/companies/{groupId}/status. EN responds with Accepted. Connection is now live for this employer — both sides know which Plansight group maps to which EN company.

Why a manual link step?

EN companies and Plansight employers don't share a stable external ID. The broker is the only one who knows whether "ACME Industries" in Plansight is the same as "ACME Industries Inc." in EN, or whether to spin up a new EN company. The link step puts that decision in the broker's hands once, then the system tracks the pairing forever.

Push Flow · Plansight → EN

Send elected plans to Employee Navigator

Once the employer is linked, pushing elected plans is a one-click operation. Plan design, rates, and benefit summary all transfer.

1

From Benefit History or election view

After an RFP closes and elections are in, broker views the elected plans in Plansight. Selects one or more plans → Send to Employee Navigator.

2

Plansight calls QuoteNotification

For each selected plan, Plansight builds the benefit details payload from the Quote record and POSTs to EN's notification endpoint. Multiple plans push in a single request batch with error aggregation per plan.

3

EN shows plan notifications

On the EN dashboard, broker sees N new Plan Notifications (one per pushed plan). Each shows carrier, plan name, plan type, dates, and a preview of benefit summary + rates.

4

Broker accepts: new plan or renewal

Broker clicks into each notification. EN matches by benefit type + date range: if there's a plan with the same benefit type and adjacent dates, EN offers Renew; otherwise Add as new plan. Renewal carries forward eligibility, contributions, and rate history from the previous year.

5

Plan is live in EN

Plan design, benefit summary, and rates (composite or age-banded — preserved as-is) are all in EN's record. Broker can edit anything that needs to differ between Plansight's quote and EN's enrollment configuration (e.g., eligibility rules).

📋

What flows over

Plan name, carrier, dates, plan type · benefit summary (deductibles, copays, coinsurance, OOP max) · contribution structure · composite rates OR age-banded rates · funding type · network info.

🚫

What doesn't flow

Eligibility classes (broker sets these in EN at acceptance) · enrollment data (this flows the other direction) · employer-specific contribution overrides not captured in the Plansight quote.

Renewal vs new

EN matches incoming plans by benefit type + date adjacency. If matched, plan history (eligibility, contributions, rates from prior year) carries forward. If not, plan is added fresh — broker fills eligibility / contribution rules from scratch.

Pull Flow · EN → Plansight

Pull census for ACA / community-rated quoting

When starting an RFP marked community-rated, the census step can pull directly from EN — no spreadsheet upload, no manual entry of demographics.

1

Inside the RFP, mark community-rated

Standard RFP setup — broker sets up the RFP and marks it for community-rated quoting (small group / ACA market). The integration is only available for community-rated workflows where individual demographics drive premiums.

2

At the census step, pull from EN

At the quoting / census section, broker picks "Pull from Employee Navigator." Plansight calls EN's QuoteCensus operation, which returns the employer's current employee roster + dependents + currently enrolled plans + enrollment counts per plan.

3

Map EN plans to Plansight options

EN returns N plans (e.g., $250 deductible, $750, $1000) with enrollment counts per plan. Broker maps each EN plan to a Plansight option slot (option 1 / 2 / 3) — typically by deductible tier or some other ordering. This is just a mapping decision; no data is being transformed.

4

Census applied automatically

Plansight builds the census from EN data: each employee + their demographics (gender, birthDate, ZIP, tobacco status) + which option they're enrolled in. Quote rates calculate against the actual census without any spreadsheet upload.

Demographic mapping

Plansight maps several EN fields when building the census:

Wiring

Authentication & connection

OAuth 2.0 with two grant types — authorization code for broker-initiated actions, client credentials for system-level operations.

🔐

Authorization Code (broker-initiated)

Used when a broker pushes an employer or pushes plans. Plansight redirects to EN's identity provider, broker authenticates, EN redirects back to /integrations/employeeNavigator/callback. Token cached with TTL = expiry minus 60 seconds.

⚙️

Client Credentials (system-initiated)

Used for census polling and status checks that don't require broker interaction. System token is cached separately and reused across requests until expiry.

Configuration

SettingValue / Source
OAuth scopesopenid · EnApi · QuotingCompanyIntegrationApi · QuotingPushNotificationApi
Identity URL (production)identity.employeenavigator.net
Identity URL (QA)identity.employeenavigator.net (QA tenant)
API base URLwww.employeenavigator.com
Credentials storageconfig/services.employeeNavigator
HTTP clientSymfony NativeHttpClient (registered as Laravel singleton)
Token cacheLaravel cache, tagged integrations, TTL = expiry − 60s
Session pairingKeyed session state pairs OAuth callback to the original action (groupPush / quotePush / censusPull)
Codebase

Where it lives in the Plansight repo

All source paths relative to the plansight repo root.

FilePurpose
app/Providers/EmployeeNavigatorClientProvider.phpService provider — registers Symfony NativeHttpClient as injectable singleton (lines 20–22)
app/integrations/EmployeeNavigator/Api.phpCore request dispatcher. Auth headers, body serialization, response parsing, error logging (lines 155–221)
app/integrations/EmployeeNavigator/TokenService.phpOAuth state machine. Authorization URL generation (70–102), user token exchange (206–252), client-credentials token fetch (149–198)
app/integrations/EmployeeNavigator/Controller.phpHTTP entry points — callback handler (35–124), groupPush() (133–205), quotePush() (214–336), pullCensus() (345–578)
app/integrations/EmployeeNavigator/Operations/CompanyIntegration.phpOperation class for employer integration (push employer)
app/integrations/EmployeeNavigator/Operations/QuoteNotification.phpOperation class for plan/quote pushes. Builds benefit details payload (line 790)
app/integrations/EmployeeNavigator/Operations/QuoteCensus.phpOperation class for census retrieval. Orchestrates employee/dependent extraction (line 145)
app/integrations/EmployeeNavigator/routes.php7 routes loaded into routes/web.php — callbacks, status queries, push/pull endpoints

Notable design choices

Operation pattern

All EN interactions implement an Operation interface — pluggable request, response, and post-processing handlers. Adding a new operation doesn't duplicate HTTP plumbing.

Session-state pairing

OAuth callback uses a keyed session entry to pair the auth return with the original user action. No interim DB record needed; flow stays seamless.

Tagged cache invalidation

Token cache uses Laravel's tagged cache with the integrations tag. One call invalidates EN tokens (and any other integrations on the same tag) wholesale.

Synchronous push, modal feedback

Quote and employer pushes are synchronous with immediate modal alerts — no background queue. Trade-off: simpler UX, but a slow EN response blocks the broker on screen.

Supported benefit types

What we sync today vs. what EN exposes

The push side covers six benefit types. The pull (census) side intentionally only processes Medical — every other product line that EN sends in the census payload is filtered out. The asymmetry is itself a gap and is captured below.

Benefit typePush to EN
(QuoteNotification)
Pull from EN
(QuoteCensus)
Notes
Medical✓ Synced✓ SyncedOnly line that's bi-directional today.
Dental✓ Synced— filtered outPlan + rates push, but enrollments are dropped on import.
Vision✓ Synced— filtered outSame — pushed, not pulled.
Short-Term Disability✓ Synced— filtered outComposite + age-banded + ASO flat-fee rate shapes supported on push.
Long-Term Disability✓ Synced— filtered outSame as STD.
Group Life (incl. AD&D)✓ Synced— filtered outFixed-amount and salary-multiple variants both supported. Contributions hardcoded as Employer / Percentage.
Voluntary Life— not mapped— not mappedEN exposes; Plansight has Voluntary Life elsewhere but no EN mapping.
Accident · Cancer · Critical Illness · Hospital Indemnity— not mapped— filtered outVoluntary lines absent from QuoteNotification.
HRA · HSA · FSA · COBRA · EAP— not mapped— filtered outAdmin/non-insurance products absent from EN integration.

Sources: QuoteNotification.php:85-108 (push type map), Controller.php:494-497 + line 424 (pull filter — hardcoded useTypes = ['Medical']). See the interactive Sync Map for field-level coverage.

Per-benefit field mapping

Field-by-field, per benefit line

For each benefit type we push to EN, here's exactly which Plansight column lands in which EN field path — and the fields EN's schema accepts that we don't currently send. Plain table, three columns: what we have, where it goes, and whether it's mapped.

Source coverage Every row in the four tables below carries a Source badge that tells you where the EN-side claim is grounded. Confidence drops left to right:
Health benefits (Medical · Dental · Vision) Group Life Short-Term Disability Long-Term Disability Cross-cutting observations

Health benefits (Medical · Dental · Vision)

All three health-shaped lines share the same EN schema and the same Plansight push code path. The mappings below apply identically to Medical, Dental, and Vision — the only differences are which Plansight quote rows are populated for each line, not how the fields map.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Configuration
QuoteBenefitHistory.inForcePolicyNumberplan.configuration.policyNumberMappedCodeQuoteNotification.php:115QuoteBenefitHistory.inForcePolicyNumberplan.configuration.policyNumberMappedQuoteNotification.php:115
Carrier.name / employeeNavigatorNameplan.configuration.carrierNameMappedCodeLines 116, 135 — uses employeeNavigatorName when populatedCarrier.name / employeeNavigatorNameplan.configuration.carrierNameMappedLines 116, 135 — uses employeeNavigatorName when populated
QuoteBenefitHistory.nameplan.configuration.planNameMappedSpecLine 117QuoteBenefitHistory.nameplan.configuration.planNameMappedLine 117
QuoteBenefitHistory.inForceStartDateplan.configuration.startDateMappedCodeLine 118 — UTC ISO 8601QuoteBenefitHistory.inForceStartDateplan.configuration.startDateMappedLine 118 — UTC ISO 8601
QuoteBenefitHistory.inForceEndDateplan.configuration.endDate + nextRenewalDateMappedCodeLines 119–120 — same value to bothQuoteBenefitHistory.inForceEndDateplan.configuration.endDate + nextRenewalDateMappedLines 119–120 — same value to both
QuoteBenefitHistory.fundingTypeplan.configuration.isSelfFundedMappedCodeLine 122 — true when == 'Self Funded'QuoteBenefitHistory.fundingTypeplan.configuration.isSelfFundedMappedLine 122 — true when == 'Self Funded'
Rfp.serviceState or Quote.stateplan.configuration.situsStateConditionalSpecLines 142–161 — uses RFP value if originalRfpId set, else falls backRfp.serviceState or Quote.stateplan.configuration.situsStateConditionalLines 142–161 — uses RFP value if originalRfpId set, else falls back
Plan design (services)
Plansight quote design fields (per Plantypes config)plan.planCommunications.benefitSummary.services[].typeMappedSpecLines 183–242 — driven by field.integrations.employeeNavigator configPlansight quote design fields (per Plantypes config)plan.planCommunications.benefitSummary.services[].typeMappedLines 183–242 — driven by field.integrations.employeeNavigator config
Plansight design value (in-network)plan.planCommunications.benefitSummary.services[].inNetworkValueMappedCodeLines 213–224Plansight design value (in-network)plan.planCommunications.benefitSummary.services[].inNetworkValueMappedLines 213–224
Plansight design value (preferred network)plan.planCommunications.benefitSummary.services[].preferredNetworkValueConditionalCodeOnly sent when inNetworkOptions == 2Plansight design value (preferred network)plan.planCommunications.benefitSummary.services[].preferredNetworkValueConditionalOnly sent when inNetworkOptions == 2
Plansight design value (out-of-network)plan.planCommunications.benefitSummary.services[].outOfNetworkValueMappedCodeLines 227–230Plansight design value (out-of-network)plan.planCommunications.benefitSummary.services[].outOfNetworkValueMappedLines 227–230
plan.benefits[].employee.flatBenefit.amountNot mappedSpecEN supports flat-benefit copays / deductibles. Plansight doesn't sendplan.benefits[].employee.flatBenefit.amountNot mappedEN supports flat-benefit copays / deductibles. Plansight doesn't send
plan.benefits[].dependents.incrementsBenefitNot mappedSpecEN supports dependent-specific cost-share structuresplan.benefits[].dependents.incrementsBenefitNot mappedEN supports dependent-specific cost-share structures
Rates — composite (non-ACA)
QuoteBenefitHistory.rateEmployeeOnlyplan.rates.compositeRates.tierRates[Employee].rateConditionalSpecLine 625 — sent when aca != 1QuoteBenefitHistory.rateEmployeeOnlyplan.rates.compositeRates.tierRates[Employee].rateConditionalLine 625 — sent when aca != 1
rateEmployeeSpouse...tierRates[EmployeePlusSpouse].rateConditionalSpecLine 630rateEmployeeSpouse...tierRates[EmployeePlusSpouse].rateConditionalLine 630
rateEmployeeChild...tierRates[EmployeePlusChild].rateConditionalSpecLine 635rateEmployeeChild...tierRates[EmployeePlusChild].rateConditionalLine 635
rateEmployeeChildren...tierRates[EmployeePlusChildren].rateConditionalSpecLine 640rateEmployeeChildren...tierRates[EmployeePlusChildren].rateConditionalLine 640
rateEmployeeFamily...tierRates[EmployeePlusFamily].rateConditionalSpecLine 645rateEmployeeFamily...tierRates[EmployeePlusFamily].rateConditionalLine 645
...tierRates[EmployeePlusDomesticPartner].rateNot mappedSpecEN supports a domestic-partner tier; Plansight schema has no equivalent column...tierRates[EmployeePlusDomesticPartner].rateNot mappedEN supports a domestic-partner tier; Plansight schema has no equivalent column
Rates — ACA age-banded + tobacco
QuoteACARate.rates[Y##]plan.rates.acaAgeBasedRates.rates[].rateConditionalCodeLines 611–616 — sent when aca == 1QuoteACARate.rates[Y##]plan.rates.acaAgeBasedRates.rates[].rateConditionalLines 611–616 — sent when aca == 1
Age extracted from key...rates[].fromAge / toAgeConditionalCodeSame age in both fields per ACA conventionAge extracted from key...rates[].fromAge / toAgeConditionalSame age in both fields per ACA convention
QuoteACARate.rates[Y##]...tobaccoBasedRates[].rateNonsmokerConditionalCodeLines 596, 600 — when tobacco rates exist on the rating areaQuoteACARate.rates[Y##]...tobaccoBasedRates[].rateNonsmokerConditionalLines 596, 600 — when tobacco rates exist on the rating area
QuoteACARate.tobaccoRates[Y##]...tobaccoBasedRates[].rateSmokerConditionalCodeLines 593, 601QuoteACARate.tobaccoRates[Y##]...tobaccoBasedRates[].rateSmokerConditionalLines 593, 601
Contributions
plan.contributions.contributionGroupings[].monthlyContributions[]Not mappedCodeEN accepts premium-sharing data on health plans; Plansight doesn't populate (only Group Life does — see below)plan.contributions.contributionGroupings[].monthlyContributions[]Not mappedEN accepts premium-sharing data on health plans; Plansight doesn't populate (only Group Life does — see below)

Field-level mappings — by plan type

Pulled from config/plantypeFiles/{medical,dental,vision}.php. These are the design fields the broker fills in on Plansight that get pushed into plan.planCommunications.benefitSummary.services[] on the EN side — each row shows the Plansight key/label and the EN service-type identifier it lands as.

Plansight Medical fields   29 fields   configured in config/plantypeFiles/medical.php
— / —
Plansight fieldEN service type
Plan Design - Medical Deductible
Per Individual
DeductibleIndividual
DeductibleIndividual
Per Family
DeductibleFamily
DeductibleFamily
Deductible Type
DeductibleType
DeductibleType
Deductible | OOP Accumulation
DeductibleOopAccumulation
CalendarOrPlanYearDeductible
Plan Design - Out-of-pocket Maximum
Per Individual
OOPIndividual
OutOfPocketMaximumIndividual
Per Family
OOPFamily
OutOfPocketMaximumFamily
Plan Design - Office Visit
Primary Physician
PrimaryCareDoctor
PrimaryCarePhysician
SpecialistSpecialist
Specialist Referral
SpecialistReferral
SpecialistReferralRequired
TelehealthVirtualTelemedicineVisit
Plan Design - Tests
Preventive Services
PreventativeServices
PreventiveServices
Diagnostic Test (X-Ray, Blood Work)
Diagnostic
DiagnosticTest
Imaging (CT/PET Scans, MRIs)
Imaging
Imaging
Plan Design - Prescription Drugs
Per Individual
RxIndividualDeductible
DeductiblePrescription
Tier 1
RxTier1
GenericDrugsTier1
Tier 3
RxTier3
PreferredBrandDrugsTier2
Tier 4
RxTier4
NonPreferredBrandDrugsTier3
Tier 5
RxTier5
SpecialtyDrugsTier4
Mail Order
RxMailOrder
MailOrderRx
Plan Design - Outpatient Surgery
Facility Fee
OutpatientFacility
HospitalizationOutpatient
Free Standing Facility
OutpatientFreeStandingFacility
HospitalizationOutpatient
Physician / Surgeon Fee
OutpatientPhysician
SurgeryOutpatient
Plan Design - Immediate Attention
Emergency Room Copay
EmergencyRoomCopay
EmergencyRoom
Urgent Care
UrgentCare
UrgentCare
Plan Design - Inpatient Services
Facility Fee
HospitalFacility
HospitalizationInpatient + Coinsurance
Physician / Surgeon Fee
HospitalPhysician
SurgeryInpatient
Plan Design - Substance Abuse
Outpatient Services
OutpatientMental
MentalHealthOutpatient
Inpatient Services
InpatientMental
MentalHealthInpatient
Plan Design - Maternity / Pregnancy
Office Visits
MaternityOffice
MaternityVisits
Plansight Dental fields   21 fields   configured in config/plantypeFiles/dental.php
— / —
Plansight fieldEN service type
Schedule Of Benefits
Individual Deductible
DeductibleIndividual
DeductibleIndividual
Family Deductible
DeductibleFamily
DeductibleFamily
Deductible Accumulation
DeductibleAccumulation
BenefitPeriod
Annual Maximum
AnnualMaxIndividual
AnnualMaximumBenefit
Preventive (I)
BenefitSchedPreventiveI
???
Preventive (I)
BenefitSchedPreventiveI
OralExaminations
Basic (II)
BenefitSchedBasicII
Fillings
Dental Services
ExamsOralExaminations
CleaningsCleaningsAdult + CleaningsChildren
Basic Filling
BasicFilling
Fillings
PeriodonticsPeriodontal
CrownsCrowns
DenturesCompleteDenture
ImplantsImplantCoverage
Dental Waiting Periods
Preventive (I)
WaitingPeriodPreventiveI
WaitingPeriodPreventativeServices
Basic (II)
WaitingPeriodBasicII
WaitingPeriodBasicServices
Major (III)
WaitingPeriodMajorIII
WaitingPeriodMajorServices
Orthodontics
Orthodontics (IV)
Orthodontics
OrthodontiaServices
Lifetime Ortho. Max.
LifetimeOrthoMax
OrthodontiaLifetimeMaximum
Orthodontics Age
OrthodonticsAge
OrthodontiaEligibility
Ortho (IV) Wait Period
OrthodonticsIVWaitPeriod
WaitingPeriodOrthodontiaServices
Plansight Vision fields   14 fields   configured in config/plantypeFiles/vision.php
— / —
Plansight fieldEN service type
Benefit Frequency
Eye Exam
ExamFrequency
EyeExams
Frames
FramesFrequency
Frames
Lenses
LensesFrequency
Lenses
Contacts (in lieu of glasses)
ContactsFrequency
ContactLenses
Plan Provisions
Eye Exam
EyeExams
ExamCopay
Single Vision Lenses
SingleVisionLenses
SingleVisionLenses
Bifocal Lenses
BifocalLenses
BifocalLenses
Trifocal Lenses
TrifocalLenses
TrifocalLenses
Basic Progressive Lenses
BasicProvisionLenses
ProgressiveLenses
Premium Progressive Lenses
PremiumProgressiveLenses
PremiumProgressiveLensOptions
Contacts Allowance - Elective
ContactsElective
ContactsAllowance
Contacts Visually Necessary
ContactsNecessary
ContactsAllowanceMedicallyNecessary
Frames
Coverage Allowance
FramesCovered
FrameAllowance
Corrective Vision Services
Lasik Vision Correction
LasikDiscount
LaserEyeCorrection

Group Life

Group Life uses a different shape — a benefit-amount calculation (flat or salary-multiple) plus an explicit contributions block. Plan Type itself can't be set via the API in v3.0 (it's a manual selection in EN); Plansight sends a descriptive label only.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Configuration
(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, isSelfFunded, situsState}MappedSpecIdentical to Health benefits — see above(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, isSelfFunded, situsState}MappedIdentical to Health benefits — see above
QuoteBenefitHistory.groupLifeCoverageplan.configuration.carrierPlanTypeDescriptionMappedCodeLines 514–525 — sent as descriptive text; EN's API can't set Plan Type itself in v3.0 (manual in EN UI)QuoteBenefitHistory.groupLifeCoverageplan.configuration.carrierPlanTypeDescriptionMappedLines 514–525 — sent as descriptive text; EN's API can't set Plan Type itself in v3.0 (manual in EN UI)
Benefits (employee)
QuoteBenefitHistory.deathBenefit-X (when groupLifeCalculation == 'fixed')plan.benefits[].employee.flatBenefit.amountConditionalSpecLines 383–415QuoteBenefitHistory.deathBenefit-X (when groupLifeCalculation == 'fixed')plan.benefits[].employee.flatBenefit.amountConditionalLines 383–415
deathBenefit-X (when groupLifeCalculation == 'salary')plan.benefits[].employee.multipleOfEarningsBenefit.multipleOfEarningsConditionalSpecLines 406–414deathBenefit-X (when groupLifeCalculation == 'salary')plan.benefits[].employee.multipleOfEarningsBenefit.multipleOfEarningsConditionalLines 406–414
maximumBenefit...multipleOfEarningsBenefit.maximumBenefitConditionalCodeLines 410–411 — salary-multiple mode onlymaximumBenefit...multipleOfEarningsBenefit.maximumBenefitConditionalLines 410–411 — salary-multiple mode only
(combined Life + ADD when basic+ADD)deathBenefit (combined into employee benefit)MappedCodeLines 283–285 — Life and ADD values combined into a single deathBenefit(combined Life + ADD when basic+ADD)deathBenefit (combined into employee benefit)MappedLines 283–285 — Life and ADD values combined into a single deathBenefit
Benefits (dependents)
plan.benefits[].dependents.approvedBenefitAmountNot mappedSpecEN supports per-dependent benefit amountplan.benefits[].dependents.approvedBenefitAmountNot mappedEN supports per-dependent benefit amount
plan.benefits[].dependents.incrementsBenefitNot mappedSpecEN supports spouse/child benefit incrementsplan.benefits[].dependents.incrementsBenefitNot mappedEN supports spouse/child benefit increments
Contributions
hardcoded "Percentage"...monthlyContributions[].contributionTypeMappedCodeLine 448hardcoded "Percentage"...monthlyContributions[].contributionTypeMappedLine 448
hardcoded "Employer"...monthlyContributions[].contributionBasisMappedCodeLine 449hardcoded "Employer"...monthlyContributions[].contributionBasisMappedLine 449
groupLifePremiumPaidBy...monthlyContributions[memberType=Dependents].contributionMappedCodeLines 453–463 — set to 0 or 100 (percentage)groupLifePremiumPaidBy...monthlyContributions[memberType=Dependents].contributionMappedLines 453–463 — set to 0 or 100 (percentage)
contributionVoluntary...monthlyContributions[memberType=Employee].contributionMappedSpecLines 465–479 — set to 0 or 100 (percentage)contributionVoluntary...monthlyContributions[memberType=Employee].contributionMappedLines 465–479 — set to 0 or 100 (percentage)
...contributionGroupings[].memberContributionsNot mappedCodeEN supports per-member contribution rules; Plansight only sends type-level...contributionGroupings[].memberContributionsNot mappedEN supports per-member contribution rules; Plansight only sends type-level
Rates
plan.rates.compositeRates.tierRates[]Not mappedCodeEN accepts composite life rates; Plansight doesn't send (optional in EN)plan.rates.compositeRates.tierRates[]Not mappedEN accepts composite life rates; Plansight doesn't send (optional in EN)

Field-level mappings — Group Life

Pulled from config/plantypeFiles/groupLife.php — Plansight design fields with integrations.employeeNavigator.attributePath set, and the EN payload paths they land in.

Plansight Group Life fields   8 fields   configured in config/plantypeFiles/groupLife.php
— / —
Plansight fieldEN field path
Other
groupLifeCalculation-Xfixed: benefits.employee.flatBenefit · salary: benefits.employee.multipleOfEarningsBenefit
Death Benefit
deathBenefit-X
fixed: benefits.employee.flatBenefit.amount · salary: benefits.employee.multipleOfEarningsBenefit.multipleOfEarnings
Maximum Benefit
maximumBenefit-X
benefits.employee.multipleOfEarningsBenefit.maximumBenefit
Dependent Benefits
Spouse Benefit Amount
spouseBenefit
benefits.dependents.incrementsBenefit.spouseIncrement
Child(ren) Benefit Amount
childBenefit
benefits.dependents.incrementsBenefit.childIncrement
Rates
Life Rate /per $1,000
Life
rates.compositeRate.rate
Spouse Rate /per Unit
SPOUSE
benefits.dependents.incrementsBenefit.monthlyPremiumOption.combinedPremium.dependentMonthlyPremium
Child Rate /per Unit
CHILD
benefits.dependents.incrementsBenefit.monthlyPremiumOption.combinedPremium.dependentMonthlyPremium

Short-Term Disability

STD supports both Insured and ASO funding via the benefitType field, which overrides the global isSelfFunded. Rate structure is selectable: Composite, Age-Banded, or ASO Flat-Fee. Plansight sends the right rate shape based on rateStructure.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Configuration
(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, situsState}MappedSpecIdentical to Health benefits(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, situsState}MappedIdentical to Health benefits
QuoteBenefitHistory.<benefitType> (Insured / ASO)plan.configuration.isSelfFundedMappedSpecLines 305–327 — overrides the global funding flag for STDQuoteBenefitHistory.<benefitType> (Insured / ASO)plan.configuration.isSelfFundedMappedLines 305–327 — overrides the global funding flag for STD
Benefit design
plan.benefits[].employee.approvedBenefitAmountNot mappedSpecEN supports active benefit cap amountsplan.benefits[].employee.approvedBenefitAmountNot mappedEN supports active benefit cap amounts
plan.benefits[].employee.benefitCalculationMethodNot mappedSpecEN schema allows; Plansight has the data but doesn't send itplan.benefits[].employee.benefitCalculationMethodNot mappedEN schema allows; Plansight has the data but doesn't send it
plan.benefits[].employee.eliminationPeriodNot mappedSpecEN supports waiting periods before benefit kicks inplan.benefits[].employee.eliminationPeriodNot mappedEN supports waiting periods before benefit kicks in
plan.benefits[].employee.integrationWithOtherIncomeNot mappedSpecEN supports coordination with other income sourcesplan.benefits[].employee.integrationWithOtherIncomeNot mappedEN supports coordination with other income sources
Rates
Y19_Under (when rateStructure == 'Composite')plan.rates.compositeRate.rateConditionalCodeLines 344–349Y19_Under (when rateStructure == 'Composite')plan.rates.compositeRate.rateConditionalLines 344–349
Y20_24Y70_Above (when rateStructure == 'Age Banded')plan.rates.ageBasedRates.rates[].fromAge / toAge / rateConditionalCodeLines 350–372 — age boundaries extracted from key namesY20_24Y70_Above (when rateStructure == 'Age Banded')plan.rates.ageBasedRates.rates[].fromAge / toAge / rateConditionalLines 350–372 — age boundaries extracted from key names
Y19_Under (when rateStructure == 'ASO')plan.rates.asoFlatRate.flatMonthlyFeeConditionalCodeLines 334–340 — STD-only path; LTD has no ASO supportY19_Under (when rateStructure == 'ASO')plan.rates.asoFlatRate.flatMonthlyFeeConditionalLines 334–340 — STD-only path; LTD has no ASO support
plan.rates.ageBasedRates.rates[].rateAdjustmentFactorNot mappedCodeEN supports rate modifiersplan.rates.ageBasedRates.rates[].rateAdjustmentFactorNot mappedEN supports rate modifiers

Field-level mappings — STD

Pulled from config/plantypeFiles/stDisability.php.

Plansight STD fields   8 fields   configured in config/plantypeFiles/stDisability.php
— / —
Plansight fieldEN field path
Schedule of Benefits
Injury Elimination Period
injuryEliminationPeriod
benefits.details.accidentWaitingPeriodDays
Sickness Elimination Period
sicknessEliminationPeriod
benefits.details.sicknessWaitingPeriodDays
Minimum Weekly Benefit
minimumWeeklyBenefit
benefits.percentEarningsBenefit.minimumBenefit
Maximum Benefit Duration
maximumBenefitDuration
benefits.details.benefitDurationWeeks
Other
Benefit Percentage
benefitPercentage-X
benefits.percentEarningsBenefit.benefitPercentEarnings
Maximum Weekly Benefit
maxWeeklyBenefit-X
benefits.percentEarningsBenefit.maximumBenefit
Rates
Rate Type
benefitType
aso: configuration.isSelfFunded
Rate Structure
rateStructure
composite: rates.compositeRate.rate · ageBanded: rates.ageBasedRates.rates · asoFlatMonthlyFee: rates.asoFlatRate.flatMonthlyFee

Long-Term Disability

LTD is structurally identical to STD with two exceptions: (1) no ASO mode — Plansight always sends LTD as Insured (line 315 forces this); (2) the Maximum Benefit Duration field is uniquely relevant to LTD and isn't mapped on either side.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Differences from STD
(no ASO equivalent)plan.rates.asoFlatRate.flatMonthlyFeeNot supportedSpecLine 315 — $asoSelected = true only when insuranceType != 'ltDisability'. LTD always pushed as Insured(no ASO equivalent)plan.rates.asoFlatRate.flatMonthlyFeeNot supportedLine 315 — $asoSelected = true only when insuranceType != 'ltDisability'. LTD always pushed as Insured
plan.benefits[].employee.maximumBenefitDurationNot mappedSpecEN supports max benefit period (months / years) — LTD-specific conceptplan.benefits[].employee.maximumBenefitDurationNot mappedEN supports max benefit period (months / years) — LTD-specific concept
All Configuration, Benefit Design, and non-ASO Rate fields are identical to STD above.

Field-level mappings — LTD

Pulled from config/plantypeFiles/ltDisability.php.

Plansight LTD fields   7 fields   configured in config/plantypeFiles/ltDisability.php
— / —
Plansight fieldEN field path
Schedule of Benefits
Elimination Period
eliminationPeriod
benefits.details.waitingPeriodDays
Benefit Duration
benefitDuration
benefits.details.benefitDuration
Other
Benefit Schedule
benefitSchedule-X
benefits.percentEarningsBenefit.benefitPercentEarnings
Maximum Monthly Benefit
maximumMonthlyBenefit-X
benefits.percentEarningsBenefit.maximumBenefit
Minimum Monthly Benefit
minimumMonthlyBenefit-X
benefits.percentEarningsBenefit.minimumBenefit
Rates
Rate Type
benefitType
aso: configuration.isSelfFunded
Rate Structure
rateStructure
composite: rates.compositeRate.rate · ageBanded: rates.ageBasedRates.rates

Cross-cutting observations

Patterns across multiple benefit lines

Gap analysis

What EN exposes that we don't read — and what's worth fixing

Eight gaps came out of comparing the EN OpenAPI spec (Quote Census v1, 2024-08-21) against integrations/EmployeeNavigator/ in Plansight APP commit 9d1afd54d5df. Each is a fixable change rather than a structural rewrite.

1

Dependent tobacco status uses the parent's value, not the dependent's own

Likely bug

Problem. EN exposes a per-dependent tobaccoUseInformation.useTobacco field. Controller.php:486 assigns the parent employee's tobacco value to every dependent instead of reading each dependent's own value.

Fix. Read employees[].dependents[].demographics.tobaccoUseInformation.useTobacco per dependent. Spouse-tobacco rates differ from employee-tobacco rates for many carriers, so this matters.

Cursor prompt

In integrations/EmployeeNavigator/Controller.php around line 486, replace the dependent tobacco assignment so it reads $dependent['demographics']['tobaccoUseInformation']['useTobacco'] for each dependent rather than copying from the parent employee. Confirm by adding a fixture where parent and spouse have different tobacco values and verifying the census record reflects the dependent's own value.

2

Census import filters out everything except Medical

Asymmetry

Problem. Controller.php:424 hardcodes $useTypes = ['Medical']. Even though we successfully push Dental, Vision, STD, LTD, and Group Life plans to EN, when we pull census back, we silently drop every enrollment for those same lines. Brokers see employees from EN but not their dental/vision/life/disability elections.

Fix. Expand $useTypes to match the push side (Medical, Dental, Vision, STD, LTD, Group Life). The benefit-type loop and plan-storage logic in lines 494-549 should already handle additional types — verify by tracing each enrollment branch.

Cursor prompt

In Controller.php:424, change the hardcoded useTypes array from ['Medical'] to the full set we push: ['Medical', 'Dental', 'Vision', 'ShortTermDisability', 'LongTermDisability', 'Life']. Confirm that the downstream code at lines 494-549 stores enrollments per benefit type without conflating them, and that the existing tests in tests/Feature/EmployeeNavigator/Operations/ still pass.

3

Org structure (class, division, department) is dropped on the floor

Capability gap

Problem. EN exposes employees[].companyStructureMapping with eligibilityClass, division, department, businessUnit, and officeLocation. None of these are read by Plansight. For a multi-class employer (different contributions / waiting periods / plan eligibility per class), this means we can't honor the broker's class-based logic.

Fix (medium lift). Capture eligibilityClass on the census record at minimum. Treat it as a typed enum — surface in the RFP UI and let the broker map EN classes to Plansight rate classes. Division/department can be string fields for display.

4

Hire date, job title, work state, and salary are exposed but not stored

Capability gap

Problem. EN sends employmentInformation.benefitHireDate, jobTitle, benefitWorkState, and employmentCompensation.annualBenefitSalary. None are read in the census import.

What we'd gain. Hire date drives waiting-period logic and ACA eligibility — without it, brokers manually enter dates we already have. Salary enables salary-based plan modeling (multiple-of-earnings life, salary-banded disability). Work state matters for situs-state rating on multi-state employers.

5

Coverage tier from EN is ignored

Note

Problem. EN sends benefitTier per enrollment (EmployeePlusFamily, EmployeePlusSpouse, etc.). Plansight ignores it and re-derives tier from dependent count. For most cases the answer matches, but EN's value is the source of truth for what the employee actually elected — re-deriving can disagree (e.g., if an employee has dependents but waived spouse coverage).

Fix. Store EN's benefitTier as the authoritative tier on the census enrollment. Use Plansight-derived tier as a sanity check, log when they differ.

6

Effective and termination dates are dropped

Note

Problem. EN sends employeeBenefit.effectiveDate + terminationDate (and dependent equivalents). Plansight stores neither. For renewal pulls or mid-year changes, we can't tell from the census whether someone is actively enrolled or aged off.

Fix. Capture both dates on the census enrollment. Treat enrollments where terminationDate < today as inactive.

7

Voluntary lines (Accident, Cancer, CI, Hospital Indemnity, Vol Life) not mapped

Capability gap

Problem. Plansight quotes voluntary product lines through Plan Facts, but the EN integration's QuoteNotification only maps Medical/Dental/Vision/STD/LTD/Life. Voluntary lines never reach EN — the broker has to re-key them.

Fix (larger). Add benefit-type branches in QuoteNotification.php:85-108 for the voluntary lines. Each needs its own service-attribute map and rate shape. EN's API supports them — Plansight's mapping doesn't yet.

8

OpenAPI spec drift — version is from August 2024

Maintenance

Problem. The OpenAPI spec we audit against (Quoting Engine QuoteCensusApi v1 08 21 24.json) is from 2024-08-21. The Plansight code references API v1.0 for census (QuoteCensus.php:63) and v3.0 for notifications (QuoteNotification.php:700). No version negotiation. EN may have published changes since.

Also: the example census response includes benefitEligibility.isEligible, period, and decision fields that aren't in the OpenAPI schema. Plansight reads decision == 'Elected'. If EN tightens the schema, we'd silently break.

Fix. Confirm with EN that v1 census + v3 notifications are still current and that the example-response fields are formally part of the schema. Pin the spec we ship in integrations/EmployeeNavigator/docs/ to a known-good date and refresh on a schedule.

9

Potential capability — pull plan data from EN to seed "current plans"

Exploration

The idea (not built). Today, when a broker starts a renewal RFP in Plansight, they have to either upload current-plan PDFs (which Plan Facts then parses) or hand-key the existing plan design. If EN already holds the same employer's current plan data, we may be able to pull plans + plan-design + current rates from EN and pre-populate the "current plans" side of the renewal automatically — bypassing the PDF dance for employers that are already on EN.

What's documented today. The OpenAPI spec we have covers Quote Census only — employee + dependent + enrollment data, not plan design. EN's wider API surface likely includes plan endpoints (the OpenAPI health benefit / life benefit / supplemental benefit / disability benefit objects within the census imply richer plan-data shapes exist server-side). We need the broader EN partner API spec to confirm exactly what's reachable.

If feasible, what we'd gain. A "Connect EN → import current plans" button on renewal kickoff that eliminates 80% of the data-entry work for any employer already on EN. Better-than-Plan-Facts accuracy for those employers, since EN's plan data is the source of truth for what's actually being administered.

Next step

Ask EN partner support for the partner API spec covering plan/group/benefit administration (not just Quote Census). Confirm endpoints exist for: list plans by company, get plan-design detail, get current rates by tier. If yes, scope a new integrations/EmployeeNavigator/Operations/ImportCurrentPlans.php that mirrors the QuoteCensus token + auth pattern.

10

Push the new Type I–IV dental benefit roll-ups under Schedule of Benefits

Capability gap

What changed. Plansight's dental plantype now has category-level summary fields under Schedule of Benefits: BenefitSchedPreventiveI (Type I), BenefitSchedBasicII (Type II), Type III (Major), and Type IV (Orthodontia). These are new — before, we only had the individual procedure-level fields (Exams, Cleanings, Basic Filling, Crowns, Dentures, Implants, Periodontics, Orthodontics, etc.), which already flow into EN's service-type fields (OralExaminations, CleaningsAdult/CleaningsChildren, Fillings, Crowns, etc.). Brokers were already filling those individual benefits in — they just rolled up implicitly under each category.

The gap. The new Type I–IV summary fields in config/plantypeFiles/dental.php aren't yet wired into the EN push the way the individual procedures are. Today their integrations.employeeNavigator config either points back at one of the same procedure-level EN attributes (e.g. BenefitSchedPreventiveI → OralExaminations) or is missing entirely (Type III, Type IV). That means the category-level value the broker enters at the top of the dental Schedule of Benefits doesn't actually land in EN as a Schedule-of-Benefits roll-up — it overwrites a single procedure on EN's side, or gets dropped.

What to do. Update the dental EN integration so each Type I–IV field pushes to the correct EN Schedule of Benefits roll-up rather than overwriting individual procedure values. Confirm the corresponding EN attribute names with the broader partner API spec (Edie's pending request) before wiring — we want the roll-ups to coexist cleanly with the individual procedure fields, not collide with them.

Cursor prompt

In config/plantypeFiles/dental.php, audit the integrations.employeeNavigator.attribute values on BenefitSchedPreventiveI, BenefitSchedBasicII, the missing Type III field (Major), and Type IV (Orthodontia) under Schedule Of Benefits. They should map to EN's category-level Schedule-of-Benefits attributes, not the individual procedure attributes already used by Exams, Cleanings, BasicFilling, Crowns, etc. (which live under the per-procedure Dental Services section). Verify EN attribute names with the partner API spec before wiring.

The bigger pattern

Most of the gaps are EN sends data, Plansight ignores it. That's the definition of a one-way valve — we built the integration to push our quote data out and pull the bare minimum to identify employees on the way back in. The pieces of EN data that would most help our quoting (class, salary, work state, hire date, coverage tier) are exactly the ones we drop.

Recommendation: tackle gaps #1 and #2 first — they're small, well-scoped, and unblock real broker pain (tobacco accuracy + showing all enrollments after a pull). Treat #3-#7 as a separate quarter since they require RFP / quote schema changes. #8 is a 30-minute conversation with EN. Gap #9 (current-plan import) is the big strategic prize — turns EN from a one-way push into a renewal accelerator. Gap #10 (dental Type I–IV roll-ups) is a small, well-scoped fix that pairs naturally with Edie's pending API spec.

Things to verify

Items worth a developer's review

Surface-level findings from a static read of the integration code. None block production; some are worth a quick verify next time the EN code is touched.

🔍

Census field name: demographics.firstInitialLastName

Used at Controller.php:454 for both employee and dependent. The name suggests "first initial + last name" but is mapped to a full name field downstream. Worth a quick verify against EN API docs.

Verify
📮

ZIP truncation cites Vericred

Controller.php:454–455 truncates ZIP to 5 chars with a comment about "Vericred processing." Now that we're on IDEON, worth confirming this is still needed and updating the comment.

Stale comment
🚬

Tobacco copies from employee to dependent

If a dependent's tobacco status is missing, Controller.php:465–486 copies the employee's value. Reasonable default, but may not reflect actual dependent tobacco use — worth flagging in the UI when this fallback fires.

Verify
⚠️

Census callback may skip full flow

Line 374 has 'onlyRedirect' => true which skips the actual QuoteCensus operation execution on callback (line 68). Suggests census pull's auth callback may not complete the census fetch in the same hop. Worth tracing once.

Verify
🔁

No retry on census / status checks

Unlike the IDEON integration which has exponential backoff, census and status queries have no retry logic. A transient EN failure shows the user an error rather than retrying. Low priority unless EN reliability becomes an issue.

Note
📋

Limited error context surfaced

Quote push errors aggregate but only surface title and errors from EN's ProblemDetails (Controller.php:292–301). Some EN-specific error contexts (e.g., field-level validation) may be obscured.

Note
Resources

Help center articles & training videos

External documentation that explains the broker-side workflow. Use these alongside this audit page when onboarding new team members or troubleshooting customer questions.

Help center articles

Enabling and using the Employee Navigator integration

Article 17379 · End-to-end broker setup walkthrough

Help →

Plansight ↔ Employee Navigator plan data sync guide

Article 17402 · What flows over and how it maps

Help →

Connect employers (across BenefitPoint and Employee Navigator)

Article 18773 · Cross-integration linking workflow

Help →

Master training videos

Activate Employee Navigator Integration

18 seconds · Wistia · turn the integration on for an employer

Video →

Connect Employer to Employee Navigator

90 seconds · Wistia · the linking handshake (push, link, status check)

Video →

Push Renewal Plans to Employee Navigator

2 minutes · Wistia · post-elections plan push, EN's renewal-vs-new logic

Video →

Push Plans from Plansight to Navigator

2 minutes · Wistia · longer-form push walkthrough (medical, dental, vision)

Video →

Employee Navigator Census Import

2 minutes · Wistia · pulling census directly into a community-rated RFP

Video →