+

Two-way sync between quoting and the book of business

The bidirectional integration between Plansight's quoting platform and Vertafore's BenefitPoint agency management system — keeping every plan, rate, benefit summary, and renewal in sync between the system brokers quote in and the system of record they bill from.

Browse the interactive Sync Map
Plansight · Built 2021 · Continuous · Confidential · Vertafore BP REST API docs ↗
Overview

What is the BenefitPoint integration?

A purpose-built, two-way data bridge between Plansight and BenefitPoint. Brokers quote and shop plans in Plansight, then push them to BenefitPoint with one click — and BenefitPoint's authoritative book of business flows back into Plansight so brokers always work from a single source of truth.

42
API Operations
17+
Brokerages Synced
24
Plan Types Supported
2-Way
Sync & Push

Pull from BenefitPoint

Brokers associate a Plansight account with a BenefitPoint account. Every product, plan, rate, benefit summary, and contribution rule on the BP side is imported and kept current — automatically — through scheduled change-tracking calls.

Push to BenefitPoint

When a broker finishes shopping and the client picks a plan, Plansight builds the BenefitPoint product, benefit summary, rate structure, contributions, and eligibility — and pushes it directly into the BP record. No re-keying, no double entry.

Renewal-aware

Every plan tracks an origination reason — new, renewal, or replacement — and the integration applies the right copy-from-prior, send-fresh, or auto-match logic per reason. Renewal pushes auto-match the BP plan; new pushes prompt for taxonomy.

Multi-tenant by design

Every brokerage configures its own BenefitPoint API credentials and runs in isolation — separate Web Services authentication, separate per-brokerage cache, separate feature flags. The same code path serves single-office shops and 50-office multi-region brokerages alike.

Why

Why this integration exists

Brokerages live in BenefitPoint. It's where they manage commissions, books of business, renewals, and the agency operations that keep the lights on. Plansight is where the work of quoting happens. Without integration, those two worlds require duplicate data entry on every plan that gets sold.

For the broker

Quote in the platform built for it. Push the result into the system of record with one click. No spreadsheet hand-offs, no dual entry, no "I forgot to update BP." When BP is the source of truth on renewals, Plansight stays in lockstep — automatically.

For the agency

Compliance, commissions, and reporting all run off BenefitPoint. The integration means every plan Plansight produces is BP-correct: right product type, right carriers, right rate structure, right benefit summary, right effective dates. Audit-ready by default.

For the client

The plan that wins on screen in Plansight is the same plan that gets billed, renewed, and serviced. No translation errors. No "we're showing one rate but BP has another."

For Vertafore

Plansight is BenefitPoint's preferred quoting partner. The integration makes Plansight a natural extension of BP — the front-end where the modern broker workflow lives, feeding the agency-management spine BP already provides.

The shape of the value

Quoting, presentation, and shopping are upstream activities — they're best done in a tool purpose-built for them (Plansight). Servicing, billing, commissions, reporting, and historical tracking are downstream activities — they belong in the system that's done them for decades (BenefitPoint). The integration is the seam: it makes the upstream and downstream behave like one system.

How it works · Push

Plansight → BenefitPoint

When a broker hits "Push to BenefitPoint" in Plansight, here's what happens.

1

Validate the plan group

The job loads every quote in the plan group from a fresh database read, verifies they all belong to the same plan group, and clears any prior error state. Single-mode pushes one quote; multi-summary mode pushes every quote as its own benefit summary.

2

Pull live account context

Before building anything, the integration calls GetAccount to fetch the current primarySalesLeadUserID and primaryServiceLeadUserID directly from BP. These were historically cached on the Plansight quote, but recent changes always re-fetch them so renewals never carry stale lead assignments.

3

Build the BenefitPoint payload

ProductModel::fromPlansightModels($quotes) assembles the BP product object: name, plan type, carriers (writing + billing), origination reason, effective and renewal dates, policy/group number, and every benefit attribute mapped through Plansight's BP-code dictionary.

4

Push via REST (or SOAP)

By default, products push via the modern REST endpoint — CreateProductRest with a JSON body. Brokerages on custom WSDL URLs push via SOAP CreateProduct. The product is created with view level Core (110); admin overrides are available to "overwrite" an existing product.

5

Chain in the benefit summary and rates

After the product is created, postProcess() runs the dependent operations: UpdateBenefitSummary (or CreateBenefitSummaryFromCopy on renewal/replacement — which carries contributions and eligibility forward from the parent plan), CreateRateRest for the single rate type Plansight picked (tiered schedule matching the plan's 3/4/5-tier setup for medical, dental, and vision; age-banded for STD/LTD; per-unit volume basis for life; specific + aggregate pairs for stop loss), and a DeleteBenefitSummaryRest to clean up BP's auto-created "All Employees" empty summary if the broker wants to replace it. Contributions and eligibility are never pushed as standalone calls — on a fresh "new" product, the broker fills those in BP after the push lands.

6

Surface errors with context

If any step fails, the JSON error response is parsed and decorated. "Invalid billingCarrierID for the selected billingCarrierType" becomes "Billing carrier 'Aetna' is not valid for product type 'Medical PPO'" — with the carrier name, billing carrier type, and product type all resolved from the per-brokerage cache. The error is stored on the quote so the UI can show it next to the right plan.

Why a Job and not a synchronous request?

The push runs as an async queue job. Pushing a multi-option plan group with renewal carry-over from prior plans can fan out to 20+ BP API calls — and BP's API can take a few seconds per call. Running on the queue means the broker's UI returns instantly, the user sees a "pushing..." status, and any retries happen without breaking the user's flow.

Per-brokerage feature flags

Two push-side capabilities are toggled per-brokerage rather than universally. Both are off by default; turn-on is a deliberate decision per brokerage.

Both flags live on the brokerage record. Coordinate with whoever owns brokerage configuration before flipping either one — they meaningfully change what BP sees on the other side.

How it works · Pull

BenefitPoint → Plansight

Plansight stays current with the BenefitPoint book of business through three nested jobs running on a low-priority queue.

1

Brokerage-level fan out

PullProducts(brokerageId) finds every Plansight group with a benefitPointId and dispatches PullProductsByGroupId for each — typically hundreds of jobs for a large brokerage.

2

Group-level product list

FindProducts(benefitPointId) lists every product on the BP account. The integration deduplicates BP's known internal product-replication bug, applies the reachBackRenewalDate filter to skip ancient plans, and dispatches PullProductById for each survivor.

3

Single-product import

Service::importProduct(groupId, productId) calls GetProductRest, FindBenefitSummaries, FindRates, GetContributionsSummaryList, GetEligibilityRuleSummaryList — and assembles a complete Plansight quote from the responses.

4

Map BP IDs to Plansight fields

Every numeric BP attribute ID (45 = Per Individual deductible, 386 = Primary Care, 414 = Specialist, 902 = Telehealth, etc.) is translated to its Plansight equivalent through the master attribute map. Unsupported plan types are filtered against the deny-list.

5

Save as a Plansight quote

A QuoteBenefitHistory is created or updated with source = 'benefitPoint', BP's product ID written back via setIntegrationData, and the right origination reason inferred from the BP product's history.

Recurring delta sync

The full pull is heavy. For day-to-day freshness, the benefit-point:pull-changes artisan command runs on a schedule, calling BP's FindChanges API to pull only what's changed since last run — minutes of work instead of hours.

What we sync

The 24 plan types Plansight supports

BenefitPoint's product taxonomy has hundreds of entries — annuities, executive benefits, individual lines, retirement products, and more. Plansight integrates the 24 plan types brokers actually quote in modern employee benefits.

Medical

Synced Medical HMO, PPO, POS, EPO, Indemnity

Synced Prescription Drug Carve-Out

Synced Stop Loss

Synced Telehealth, Patient Advocacy

Synced Transplant Carve-Out

ToDo Medical Gap (GAP)

Administration

Synced HRA

Synced HSA

Synced FSA

Synced COBRA Administration

ToDo FMLA / Leave Management

Ancillary

Synced Dental — Managed, PPO, HMO, Triple Option, Indemnity

Synced Vision & Voluntary Vision

Synced Basic Life and AD&D

Synced Voluntary Life and AD&D

Synced Short Term Disability (STD & Voluntary)

Synced Long Term Disability (LTD & Voluntary)

Synced Employee Assistance Program (EAP)

Synced Legal, Pet Insurance, Identity Theft

Worksite / Voluntary

Synced Accident & Voluntary Accident

Synced Hospital Indemnity

Synced Critical Illness & Voluntary Critical Illness

Synced Cancer Coverage & Voluntary Cancer

Intentionally not synced

Skip Group Term Life, AD&D-only, BTA

Skip 401(k), 403(b), 457, 529, IRA

Skip Annuities, Universal Life, Whole Life

Skip Individual Medical / Dental / Vision

Skip Executive Benefits (FCC) lines

Skip International Bundled, State Statutory DBL/TDB/TDI/SDI

Plan vs Product

BenefitPoint divides offerings into Plans (carry rate and benefit data — Medical, Dental, etc.) and Products (services without rate structures — Telehealth, COBRA, etc.). Plansight syncs Plan Information for both, but only pulls Plan Design + Rates for actual Plans.

Open the full filterable Sync Map

Every plan type, product type, and benefit attribute · filterable by sync status

Customer-facing version: what we sync, by benefit type (print-ready) →

Customer-facing version: benefit attribute map, line by line (print-ready) →

Customer-facing version: how rates sync (print-ready) →

Renewals

Changing a plan's product type on renewal

BenefitPoint has always allowed a renewal to be a different product type than the plan it renews — productTypeID and parentProductID are independent fields on its Product struct. The restriction was Plansight's: the push hard-assigned the parent's type. PS-9170 lifts it, so a PPO group renewing as an HMO stays one renewal chain instead of becoming an orphan plan. The matrix below is the entire decision surface, derived from a live getProductTypes() call cross-referenced against Plansight's product-type registry — not from documentation.

31
Plansight benefit families
30
Can be exported to BenefitPoint
6
Swap freely — rates carry across
2
Swap, but rates may not carry

Full swap — 6 families, rates carry

Every type inside these families declares an identical rate-type set, so the prior plan's rates copy to the renewal untouched. This is the behaviour Medical demonstrates, and the case the feature was built for.

Benefit familyInterchangeable product typesRate typesExport
Medical
medical
100 HMO · 110 PPO · 120 PPO 3-Tier · 130 POS 2-Tier
140 POS 3-Tier · 150 EPO 1-Tier · 160 EPO 2-Tier · 170 Indemnity
1 / 33 / 48 Yes
Dental
dental
180 Managed · 190 PPO · 191 Vol PPO · 195 HMO
196 Vol HMO · 200 Triple Option · 210 Indemnity
+ 1170 Voluntary Dental (additional product)
3 / 35 / 49 Yes
State Disability / PFL
spfl
292 NY DBL · 293 NJ TDB · 294 HI TDI
295 RI TDI · 296 CA SDI · 297 PR TDI
46 / 47 Yes
Short Term Disability
stDisability
290 STD · 291 Voluntary STD
+ 1190 Voluntary STD (additional product)
5 / 6 / 7 / 8 / 9 / 10 / 11 / 15 Yes
Long Term Disability
ltDisability
300 LTD · 301 Voluntary LTD
+ 1180 Voluntary LTD (additional product)
12 / 13 / 14 / 16 Yes
DHMO
dhmo
195 Dental HMO · 196 Voluntary Dental HMO 3 / 35 / 49 No

DHMO is the one family of the 31 that cannot be exported at all. Its two types would swap cleanly, but benefitPointExport is disabled for it in config/plantypes, so it never reaches BenefitPoint in the first place. Worth confirming whether that is still intended.

Swap allowed, rates at risk — 2 families, needs a warning

Same benefit family, but the two types rate on different structures. The swap is legitimate and BenefitPoint accepts it — the danger is that the prior plan's rates have nowhere to land.

Benefit familyProduct typeIts rate typesExport
Basic Life
groupLife
240 Life and AD&D 25 / 26 / 27 / 28 / 51 Yes
250 Group Term Life 17 / 18 / 19 / 50
Voluntary Life
voluntaryLife
245 Vol Life and AD&D 25 / 26 / 27 / 28 / 51 Yes
260 Voluntary Life
+ 1370 Vol Life/AD&D (additional product)
34

Voluntary Life is the sharpest case. Swapping 245 to 260 goes from five rate types to a single one, with no overlap. Basic Life is the same shape — 240 and 250 share nothing. These two families are the entire reason a rates warning is worth building; no other family can hit it.

Plan to additional product only — 7 families, limited value

These have exactly one real plan type. A selector renders, but the only alternative is an additional product, which carries no rates and no benefit summary on the BenefitPoint side. Converting to one is a downgrade, not a lateral move.

Benefit familyPlan typeAdditional product
Vision vision230 Vision5690 Voluntary Vision
PBM Administration pbm173 Rx Carve-Out5510 Pharmacy Services
HSA hsa179 HSA1630 HSA / HRA Admin
FSA fsa330 Section 1251600 FSA Admin
Critical Illness criticalIllnessnone1160 Vol Critical Illness · 5480 Critical Illness
Cancer cancernone1400 Voluntary Cancer · 5470 Cancer Coverage
Accident accidentnone5500 Accident · 5792 Voluntary Accident

Critical Illness, Cancer and Accident have no real plan type at all. Both of their options are additional products, so a "swap" there moves between two things that neither hold rates nor a benefit summary. Worth deciding whether the selector should appear for these families, since offering it implies more than it delivers.

No swap offered — 16 families, one type each

One product type apiece, so there is nothing to change to and no selector renders. All of these still export to BenefitPoint normally — the restriction is only on changing type.

HRA Stop Loss New York Disability Employee Assistance Program COBRA Legal Leave Management New Benefits Medical Administration Pet Insurance Advocacy Identity Protection Telehealth Paytient Hospital Indemnity Organ Transplant

What the matrix decided about the build

Medical 125 (Tiered Benefit Medical PPO) exists in BenefitPoint and shares Medical's 1 / 33 / 48 rate structure, but it is commented out of Plansight's registry and therefore never offered. Uncommenting it would also change the new-plan dialog, so it stays out for now.

Verified 22 Aug 2026 against BrokerConnect V4.4, brokerage 1002 · registry source Service::$supportedProductTypesMap · PS-9170 is built and verified against live BenefitPoint pushes but is not yet merged to production.

The Frontier

What else BenefitPoint could give us

BenefitPoint's product taxonomy has 426 entries. Only 49 of those carry real plan-attribute structure — these are the plans where "sync" means moving actual benefit data instead of just registering a product category. Plansight syncs 26 of those 49 today, and the page above lists them. This is what the other 23 look like.

Coverage of the benefit-line surface is 100%

The 377 BP plan types not catalogued above are all service/fee admin entries that BP itself flags as "No structure" — annuities, consulting fees, executive-benefit codes, individual lines without plan attributes. Plansight isn't missing any structured plan data on the benefit-line side. The decision space below is purely: of the 23 remaining structured types, which ones do we want to extend the integration to cover?

True gaps — not classified today

10 plan types, ~850 BP attributes on the table

Medical extensions

Gap Medical PPO (3-Tier) — BP 120 · 179 attrs

Gap Tiered Benefit Medical PPO — BP 125 · 144 attrs

Gap Medical POS (3-Tier) — BP 140 · 180 attrs

Gap MH/SA Carve-Out 1-Tier — BP 175 · 50 attrs

Gap MH/SA Carve-Out 2-Tier — BP 177 · 55 attrs

Same shape as the Medical plans Plansight already syncs — likely a schema extension to existing types rather than new top-level benefits.

Limited Benefit ("mini-med")

Gap Limited Benefit 1-Tier — BP 232 · 65 attrs

Gap Limited Benefit 2-Tier — BP 233 · 65 attrs

Mini-med plans — capped-coverage products distinct from major medical. Would need a new top-level benefit type with its own field set.

Work-Life & Wellness

Gap Work-Life Program — BP 315 · 19 attrs

Gap Wellness Program — BP 317 · 73 attrs

Gap Section 125 — BP 330 · 20 attrs

Work-Life and Wellness could fold into EAP, or stand alone. Section 125 is a tax-treatment doc — useful as compliance metadata but probably not a quoting line.

Currently classified as "Skip" — worth revisiting

13 plan types, ~477 BP attributes

Life-side detail

Skip Group Term Life — BP 250 · 28 attrs

Skip AD&D (standalone) — BP 270 · 37 attrs

Skip Voluntary AD&D — BP 280 · 45 attrs

Plansight syncs combined "Life and AD&D" (240) and "Voluntary Life and AD&D" (245). These are the unbundled siblings. Would extend Basic Life and Voluntary Life rather than add new types.

State statutory disability

Skip New York DBL — BP 292 · 8 attrs

Skip New Jersey TDB — BP 293 · 8 attrs

Skip Hawaii TDI — BP 294 · 8 attrs

Skip Rhode Island TDI — BP 295 · 8 attrs

Skip California SDI — BP 296 · 8 attrs

Skip Puerto Rico TDI — BP 297 · 8 attrs

Six plan types, identical 8-attribute shape. Cheap to ship the whole set together — relevant for any brokerage with multi-state employers.

Out-of-scope today

Skip Business Travel Accident — BP 320 · 47 attrs

Skip 401(k) — BP 340 · 76 attrs

Skip International Bundled (1-Tier) — BP 420 · 98 attrs

Skip International Bundled (3-Tier) — BP 410 · 98 attrs

Structured in BP but outside Plansight's modern-benefits scope. Retirement and international plans would represent a product expansion, not a sync extension.

If we picked one to start

The cheapest win on the board is the state statutory disability set — six plan types with 8 attributes each, all the same shape. Ship them as one set and Plansight covers every U.S. jurisdiction with mandatory state disability. The highest-data win is the Medical extensions — PPO 3-Tier, POS 3-Tier, Tiered PPO, and the two MH/SA Carve-Outs together carry 608 BP attributes, all in benefit field shapes Plansight already knows how to handle. The two big strategic decisions are Limited Benefit (do we add mini-med as a top-level benefit?) and Wellness/Work-Life (do they belong to EAP, or stand on their own?).

The Field Map

How attribute codes get translated

BenefitPoint identifies every benefit attribute by an internal numeric ID. Plansight maintains a master mapping that translates those numeric codes into Plansight's named fields, in both directions.

Plansight Field BP ID Notes
Per Individual Deductible45Annual medical deductible, in-network individual
Per Family Deductible44Annual medical deductible, in-network family
Deductible Type973Embedded vs aggregate
HSA Eligible873High-deductible health plan flag
OOP Per Individual53Annual out-of-pocket maximum, individual
OOP Per Family52Annual out-of-pocket maximum, family
Primary Care386Office visit copay/coinsurance
Specialist414Outpatient specialist visit
Telehealth902Telemedicine
Emergency Room184ER copay/coinsurance
Urgent Care555Urgent care facility
Inpatient295Inpatient facility & physician
Mental Health Outpatient407Mental health outpatient services
Substance Use Inpatient670Substance use inpatient hospitalization
Rx Tier 1 (Generic)880Preferred generic drug tier
Rx Tier 484Non-preferred brand
Mail Order378Mail-order Rx

Why these codes matter

BenefitPoint doesn't expose its attribute codes in normal API responses — they're internal IDs. Plansight extracted them via metadata calls (GetBenefitSummaryStructure, GetProductTypes, FindRates) when the integration was first built and maintains them as the source of truth for every push and pull. The full dictionary covers Medical, Pharmacy, Stop Loss, Dental, Vision, EAP, Basic Life, Voluntary Life, HRA, STD, and LTD.

Connected Systems

What the integration talks to

Both sides of the pipe — what comes in from BenefitPoint, and where the resulting data lives in Plansight.

BrokerConnect REST API (Vertafore)

The modern REST endpoint for product creation, rate creation, contributions, eligibility rules, and benefit summary management. The default API surface for new pushes since 2024.

Live

BrokerConnect SOAP API (Vertafore)

The legacy SOAP API. Plansight binds V4.4 — the .env points at BrokerConnectV4.4.wsdl, whose types come from common-v3.4.xsd. Still used for accounts, listings, structure fetches, and any brokerage configured with custom WSDL URLs. Auth via session token cached for 55 minutes. Read the 3.4 schema, not 3.2 — 3.4 declares fields 3.2 does not, including BenefitSummary.isHdhp.

Legacy

Plansight DynamoDB

Where every imported account, plan, rate, contribution, and eligibility rule lands. The same store that powers Plansight's quoting flow — meaning BP-pulled data is immediately available to brokers without a translation layer.

Live

Plansight per-brokerage cache

Each brokerage has a dedicated cache holding BP offices, users, writing carriers, and billing carriers. Refreshed by the benefit-point:brokerage-cache command. Powers every dropdown that asks the broker to pick a BP entity.

Live

Redis queue (low-priority)

Bulk pulls and brokerage-wide syncs run on the redis-low queue. Push jobs run on the standard queue with retries and error context capture. Nothing blocks the broker's UI.

Live

Scheduled change-tracking

The benefit-point:pull-changes command runs on a recurring schedule, calling BP's FindChanges API to keep Plansight current without re-fetching the full book of business.

Live
Who it's for

The brokerages this serves

The integration is designed to work for any brokerage that runs its book of business on BenefitPoint and quotes new and renewal plans through Plansight. The shape of the work is the same regardless of size.

Single-office brokerages

One BP API user, one office, one set of carriers. Onboard once, push and pull from there. The integration's simplest case — and where most of the value comes through with the least configuration overhead.

Multi-office, multi-region brokerages

One BP API user can have access to multiple regions and offices. The integration scopes every operation to the offices the API user can see, so a national brokerage can run dozens of regional offices through a single Plansight tenant without cross-contamination.

Multi-entity groups

Brokerages that operate as multiple legal entities (acquisitions, subsidiaries, partner-brand structures) typically maintain separate BP carrier rosters. Each entity gets its own brokerage configuration — separate credentials, separate cache, separate feature flags — and shares only what should be shared.

High-volume agencies

Brokerages with hundreds or thousands of employer accounts move large amounts of data on a renewal cycle. The async queue architecture and scheduled FindChanges sync are designed for that scale — bulk pulls run on a low-priority queue, daily delta syncs keep things fresh without re-fetching the full book.

One code path, every brokerage

The same operations, the same struct definitions, the same BP code dictionary, the same job runners serve every brokerage running on the integration. Tenant-specific behavior is expressed through per-brokerage feature flags and credentials — never through forks of the integration code itself. That's how it stays maintainable as more brokerages join.

Architecture

How it's built

A self-contained Laravel package inside the Plansight monolith, designed to be testable, debuggable, and operable per-brokerage without affecting other tenants.

Tech Stack

PHP

Backend: Laravel · PHP 8 · ~300 files

API: Native PHP SoapClient + Symfony NativeHttpClient for REST

Storage: AWS DynamoDB (per-brokerage tenant data)

Async: Redis queues — default for push, redis-low for bulk pulls

Cache: Per-brokerage flat-file BenefitPointCache + 55-min session token

Auth: Per-brokerage encrypted Web Services credentials

Package Structure

integrations/BenefitPoint/
├── Api.php          ← auth, SOAP/REST client, retry
├── Service.php      ← 3,309-line orchestrator
├── BenefitPointCache.php
├── Controller.php   ← all UI/HTTP endpoints
├── ContributionData.php
├── RateData.php     ← 188KB rate-mapping helper
├── Operations/      ← 42 API call wrappers
├── Structs/         ← 128 PHP DTOs (Account, Product,
│                       Rate, BenefitSummary, …)
├── Enums/           ← 112 BP value sets
├── Jobs/            ← 6 async queue jobs
├── Commands/        ← 6 artisan CLI commands
├── Concerns/        ← shared traits
├── Contracts/       ← Operation interface
├── Views/           ← Blade templates
└── Scripts/BP.js

Operability

Per-brokerage feature flags

Integration on/off, multi-summary mode, custom WSDL URLs — all per-brokerage. Can disable mid-incident without code change.

Structured error capture

Every push error stores parsed JSON, raw response, and resolved entity names on the quote — surfaceable in the UI without digging through logs.

Truncation-aware logging

SOAP request/response logged to bpDebug, with smart truncation for noisy operations like findOffices, createBenefitSummary, and getBenefitSummaryStructure.

Lifecycle

The renewal year, in motion

A typical client's plan lives in both systems for years. Here's how the integration handles each phase of the renewal cycle.

1. New plan

Broker shops a new plan in Plansight, picks a winner, clicks "Push to BenefitPoint". The integration picks the BP plan type from BP's taxonomy, builds the product with origination reason new, sends rates and benefit summary, and stores the BP product ID on the Plansight quote.

2. Steady state

The plan lives in BP. Plansight's scheduled pull-changes command keeps the broker's view in sync — if anyone updates the plan in BP (rate change, carrier change, contribution adjustment), Plansight reflects it within the next sync window.

3. Renewal cycle begins

120 days from renewal, Plansight surfaces the renewing plans. The broker shops the renewal in Plansight, comparing carriers and options. The integration knows the parent plan and pre-populates Plansight with the prior plan's structure.

4. Renewal push

When the renewal is ready, the broker pushes with origination reason renewal. The integration auto-matches to the existing BP plan, copies forward Broker of Record, Splits, Eligibility, and Contributions from the prior plan, and overwrites only what changed — rates, benefit attributes, dates.

5. Replacement

If the broker switches carriers entirely, origination reason becomes replacement. The new plan gets pushed fresh, but with the parent's identity preserved so BP's reporting connects the old policy and the new one as a single ongoing relationship.

6. Mid-year changes

Mid-year amendments (rate changes, carrier corrections, eligibility tweaks) flow through the same push pipeline. The broker edits in Plansight, pushes, and BP's record updates. No re-keying.

Origination reason is the master key

Every quote in Plansight carries an originationReason: new, renewal, or replacement. The integration's behavior — what's copied from the prior plan vs. sent fresh, what's auto-matched vs. user-picked, what error patterns are tolerated — branches off this single field. Get the origination reason right and the rest of the integration follows.

Design principles

How the integration reasons

Patterns that hold across every operation, every brokerage, every plan type.

One-to-one association

One Plansight account binds to exactly one BP account, and vice versa. No fan-out, no fan-in. This constraint keeps multi-office brokerages modeling their structure deliberately, and simplifies every downstream operation by removing ambiguity about which BP record a Plansight quote belongs to.

Control splits required

BP's control splits setting must be enabled on the BP side for the integration to function. Without it, associations succeed silently but pushes do nothing — a known failure mode that's now part of the pre-onboarding checklist.

Pull is safe; push is explicit

Sync from BP → Plansight runs on a schedule. Push from Plansight → BP is always user-triggered. The integration never silently writes to BP — every push corresponds to a deliberate broker action with a visible audit trail.

The deny list is explicit

Plansight maintains a 150+ entry list of BP product type IDs the integration intentionally skips: annuities, executive benefits, individual lines, retirement products. The list is checked in code on every operation. New plan types are added deliberately, never by accident.

Errors stay with the data

Every push error is captured on the quote it failed for, with a parsed JSON payload, a raw response, and resolved names for billing carrier, writing carrier, and product type. The broker doesn't need a log file to understand what went wrong.

Sessions are short, retries are graceful

BP session tokens cache for 55 minutes. When BP returns "session is not valid", the retry loop forgets the cache and re-logs in once, transparently. Brokers never see auth churn.

Single sign-on

SSO is built on our side, awaiting BenefitPoint rollout

Plansight has the receiving side of a BenefitPoint single-sign-on flow scaffolded and routed. The endpoint is live in production. BenefitPoint hasn't enabled the emitter on their side yet — once they do, the controller fleshes out the click-through experience.

What's deployed today

A pair of routes registered in routes/web.php:128-130 — both GET and POST on /benefitPointSSO. The controller (app/Http/Controllers/BPSSOController.php) is currently a placeholder that logs the inbound request payload via Log::debug and returns. Built by Trevor.

What's pending

BenefitPoint hasn't shipped the SSO emitter yet. When they do (Vertafore handles the timing), Plansight will see real signed payloads at /benefitPointSSO and the controller body needs to: validate the signature, resolve the BP user to a Plansight user, mint a Plansight session, and redirect to the right deep link (account, quote, etc.) inside Plansight.

Why this matters

SSO from BenefitPoint into Plansight removes the second login. A broker working in BenefitPoint can click a Plansight link and land on the right account/quote without re-authenticating. It's also the most natural point of integration to surface Plansight inside the BenefitPoint UI itself once Vertafore exposes the deep-link API.

Owner: Trevor on the Plansight side. Status: Waiting on Vertafore. When they have a date, the controller body is a half-day of work.

Gap analysis

What a deep code audit surfaced

Eight findings from a fresh pass through integrations/BenefitPoint/ in Plansight APP 9d1afd54d5df. None are existential — the integration works in production every day — but each is the kind of issue that produces "unreproducible" bug reports until someone goes looking. Each finding ends with a Cursor prompt the developer can paste straight into Cursor on the Plansight APP repo. Rank order is rough impact-weighted.

1

FindChanges delta-sync is built but never invoked

Capability gap

Problem. Vertafore exposes FindChanges (returns only records modified since a timestamp) and Plansight has it implemented at integrations/BenefitPoint/Operations/FindChanges.php — but the only caller is Commands/PullChanges.php, which isn't wired into the main sync flow in Controller.php or Service.php. Every product sync today is a full refresh.

Why it matters. Full refreshes are slow and burn API budget. For brokerages with hundreds of accounts, this is the difference between a sync that takes seconds and one that takes minutes (or trips a rate limit, see #4 below).

Cursor prompt

Wire FindChanges into Service::syncBrokerageProducts(): check for a last_sync_at timestamp on the brokerage; if present, call FindChanges and use only the returned product IDs to drive detail fetches. Fall back to full sync when no timestamp exists or FindChanges errors. Update last_sync_at after a successful run.

2

Empty API responses overwrite cached data — silent corruption

Production-impacting

Problem. When FindOffices, GetAvailableCarriers, or FindUsers returns an empty collection (transient error, partial outage, auth slip), Service.php:2579-2590 still writes the empty result over the prior cache. The next UI request that depends on offices or carriers gets a silently empty list. Users see "no offices available" with no error surfaced.

Fix sketch. Reject empty replacements before persisting — keep the stale cached value and log a warning. An "empty after fetch" should be treated as "fetch failed" until proven otherwise.

Cursor prompt

In Service::putBrokerageUserCache(), before writing each of offices, users, availableCarriers, and billingCarriers to Storage::put, check if the new value is empty. If it is and a non-empty cached value exists, log a warning ("BP cache update rejected — empty {key} from API") and keep the prior value. Only overwrite when the new value is non-empty OR the prior value was also empty.

3

Login token cache keyed only by brokerage ID — multi-tenant collision risk

Multi-tenant correctness

Problem. Api.php:464-467 caches login tokens using just brokerage->id. The getLoginToken() method has a commented-out brokerageId parameter implying support for one Plansight brokerage bridging multiple BP tenants — but the cache key doesn't include it. If we ever turn that on (or a single brokerage's BP auth changes), tokens will collide across tenants.

Cursor prompt

Update loginCacheKey() in Api.php to include the Vertafore tenant ID when getBpData('brokerageId') is set: "benefitPointLoginCredentials_{$brokerageId}_{$tenantId}". Audit existing callers to confirm the right tenant context is passed.

4

Fixed retry sleeps, no 429 handling, no jitter

Rate-limit risk

Problem. Session-timeout retries use literal sleep(2) and sleep(5) at Api.php:122-127, 314-320, 330-336, 394-403. No exponential backoff, no jitter — under any kind of load spike, all retries hit BP at the same moment and amplify the spike. HTTP 429 (rate limit) is not detected at all on the REST path; we silently accept the failure and may retry-loop into a ban.

Cursor prompt

In Api::handle(), replace the fixed sleeps with exponential backoff + jitter: sleep(min(60, (2 ** $attempt) + mt_rand(0, 1000)/1000)). Detect HTTP 429 in the REST response branch and trigger the same retry loop with the documented carrier backoff (or default 30s) when a Retry-After header is present.

5

Null-safety gaps on API response data

Likely cause of "rare crashes"

Problem. Controller.php:459-475 and Service.php:1429-1446 chain methods on $response->data (.count(), .reject(), .first()) without checking $response->wasSuccessful() first. When auth retries silently produce a null payload, you get "Call to a member function count() on null" — usually filed as a one-off bug.

The REST path has the same shape (Api.php:297-323) — JSON unpacking assumes the structure is what we expect, no schema validation.

Cursor prompt

Audit every ->handle()->data chain in integrations/BenefitPoint/Controller.php and Service.php. Wrap each in a guard: if (!$response->wasSuccessful() || empty($response->data)) { /* surface error */ }. For REST operations, add a small helper that validates the expected JSON keys exist before accessing nested fields.

6

Hard-coded enum maps drift silently when Vertafore ships new product / rate types

Maintenance trap

Problem. Service.php:883-950 (the getBenefitType() switch on productTypeID), plus the static maps in ContributionData.php and RateData.php, hard-code BP's enum values. There's no validation that an incoming BP value is in the map. If Vertafore adds a new product type or renumbers a rate type, the parser silently maps to null or a default, and the data quietly stops flowing without anyone noticing.

Plansight's FindBPCodes command already exists to compare BP's GetProductTypes + GetRateTypes output against our hard-coded maps. It's just not run on a schedule.

Cursor prompt

Add a validating enum mapper to Service.php: mapBpEnum($value, array $map, string $context): ?string that logs a warning ("Unmapped BP enum {context}: {value}") when a value isn't in the map, then returns null. Use it in getBenefitType() and the equivalent helpers in ContributionData.php / RateData.php. Schedule FindBPCodes to run weekly and alert if maps have drifted.

7

Rate field copies and tier mismatches lose data without surfacing

Subtle

Problem. Service.php:1529-1597 (mapPlansightToBpRate) copies tier values between Plansight and BP rate shapes. Two issues: it mutates $destItem['valueNum'] from source values without validating type, range, or BP field constraints (so an out-of-range value silently propagates and BP rejects the eventual write); and when tier counts diverge between Plansight and BP (3-tier dental on our side, 5-tier on theirs), the fallback path quietly drops un-mapped tiers without logging.

Cursor prompt

In Service::mapPlansightToBpRate(): (a) validate copied valueNum values are numeric and within constraints from GetRateStructure before assignment, log + skip on mismatch; (b) when source and dest tier counts differ, log every dropped tier with source/dest IDs and surface a single user-facing warning per quote that data was incomplete.

8

Plan-type change at renewal/replacement is blocked in our code, not by BenefitPoint

Capability gap

Problem. Today, when a user pushes a renewal or replacement, both Structs/Product.php:182-194 (SOAP path) and Structs/ProductModel.php:223-235 (REST path) hardcode productTypeID to the parent plan's type — so a Medical PPO can only ever renew as a Medical PPO. The user-selected plan type from the UI is silently ignored on renewals (it's only honored when originationReason === 'new').

Why this is a Plansight choice, not a BP rule. The BP Product complex type (common-v3.2.xsd line 2830) defines productTypeID, parentProductID, and policyOriginationReason as three independent fields with no cross-validation. Confirmed against the live BP API: renewals with a different productTypeID than the parent are accepted (e.g. PPO renewing as HMO).

Why it matters. Brokers do change plan structures at renewal — moving a client from PPO to HMO, splitting bundled plans, swapping carriers and product types together. Today they have to push as a "new" plan and then manually re-link the parent in BP, losing renewal-aware behavior (rate carryover, commission continuity, parent metadata). Allowing the type change at renewal restores the complete renewal flow.

Cursor prompt

In both integrations/BenefitPoint/Structs/Product.php (lines 180–204) and Structs/ProductModel.php (lines 221–245), update the renewal/replacement branch to prefer selectedProductTypeId over parentProductTypeId when the user explicitly picked a different one — falling back to parentProductTypeId when not. In Scripts/BP.js, render the existing plan-type <select> on renewal/replacement rows too (currently shown only for "new"), pre-selected to the parent's type, persisting via the existing saveSelectMenuValues endpoint; show inline notice "Plan type will change from {parent} to {selected} on push" when the selection differs from the parent. Add a bpDebug log line in Jobs/PushProduct.php when the constructed productTypeID differs from the parent's. Full spec, tests, and rollback plan: cursor-prompts/bp-renewal-allow-plan-type-change.md.

Architecture-level questions

Five threads worth a 30-minute conversation with whoever owns BP integration. None are fixable in a single PR — they're decisions, not bugs.

Field notes

What live API testing proved

The gap analysis above came from reading code. This section came from driving the live BrokerConnect API against a real brokerage in August 2026 — every claim below was reproduced against BenefitPoint itself, not inferred. Several of them contradict things Plansight believed for a long time.

1

Plansight binds BrokerConnect V4.4, not V4.2

Corrects the record

The fact. The .env points at BrokerConnectV4.4.wsdl, whose types come from common-v3.4.xsd. Plansight's internal recipe documented v4.2, whose schema is missing fields that 4.4 has.

Why it matters. Reading the wrong schema hides real capability. BenefitSummary.isHdhp — finding #2 below — exists in 3.4 and does not exist in 3.2, which is the single reason it went unfound for years. Read the 3.4 schema, not the 3.2 one.

2

The "HDHP Qualified Plan" checkbox is isHdhp — it was never attribute 873

Corrects the record

The fact. It is a first-class boolean on BenefitSummary, declared in Vertafore's published common-v3.4.xsd and validated against their own IRS HDHP Values table. It is not a brokerage custom field, and it is not attribute 873. Before this pass, isHdhp appeared exactly once in the entire Plansight codebase: in a comment.

BenefitPoint gates it on three things, all confirmed by experiment. All three must hold or the flag is refused:

GateHow it fails
Effective year is the current or previous year2027 and 2028 are refused. 2026 and 2025 pass.
Attribute 873 says HSAAnything else is refused.
Deductible and out-of-pocket, individual and family — all four, as numbersEvery subset was refused live. All four together were accepted.

The consequence brokers will feel. A renewal is effective next year by definition, so the HDHP box will not tick on a renewal quoted the year before — which is most renewals. That is BenefitPoint's rule and not something Plansight can code around. If brokers expect it to tick, that is a conversation with Vertafore about their IRS HDHP Values window.

3

A refused HDHP flag failed the entire benefit-summary push

Production-impacting

Problem. BenefitPoint does not ignore a flag it disagrees with — it rejects the whole write. And because Base extends Model with $guarded = [] and fromSoap() copies every returned key, isHdhp has been round-tripping on the copy path all along. On production today, renewing an already-flagged HDHP plan into next year can fail the benefit-summary push, with nothing in the message pointing at the HDHP flag as the cause.

Fix. Plansight cannot predict BenefitPoint's answer without reproducing its IRS tables, so the flag now yields: on a refusal that names it, the operation drops the flag and re-sends the summary once. The push succeeds and the result explains what BenefitPoint would not do. All four write paths degrade this way, and the degrade is deliberately narrow — unrelated errors are not swallowed. Built on the PS-9170 branch, not yet merged.

4

A thousands separator silently turned currency into text

Production-impacting

Problem. getAttributeValueDetail() converts currency attributes to UOM=dollars, but its pattern had no room for a comma. $500 went to BenefitPoint as the number 500; $5,000 fell through and went as text. The behaviour changes silently at a thousand — and Plansight formats these values for people, so real deductibles nearly all landed on the text side. BenefitPoint cannot compare text to its IRS table, which is what was actually blocking the HDHP flag.

Scope. Fixed for the four IRS attributes (44 / 45 / 52 / 53). The same comma blindness affects every other currency attribute — a $500 copay sends a number and a $1,500 copay sends text — so removing the attribute-ID gate is the obvious follow-up. Built on the PS-9170 branch, not yet merged.

5

Plansight creates Medical HMO benefit summaries BenefitPoint will not take back

Production-impacting

Problem. BenefitPoint returns HMO summaries containing attribute 138 ("Deductible") with a null value, then refuses the same summary when it is sent back: Element value for attributeID = 138 must not be null. Attribute 138 sits at view level 110 (Core) on Medical HMO but 120 (Expanded) on Medical PPO, and Plansight never populates it. Plansight's summaries use view level 110, so it is required on HMO and not on PPO.

Reproduced on every fixture. Every rejection was an HMO; every PPO and Dental summary was accepted. This hits CreateBenefitSummaryFromCopy — the real renewal path — not just updates. All three broken summaries had been created by Plansight pushes in the first place.

Fix, proven. Drop attribute details BenefitPoint returned with no value before sending the clone; the same clone was then accepted. Built on the PS-9170 branch, not yet merged. Actually mapping attribute 138 would make the data right, rather than merely legal — that is a separate piece of work.

6

The import rewrote Level Funded to Level Funded Express on every sync

Production-impacting

Problem. Both Plansight funding values push as LEVEL_FUNDED, and BenefitPoint's answer is ambiguous coming back — the import resolved it to "Express" every time. Delta sync therefore changed the funding type on plans nobody had edited. NONE_SELECTED likewise came back asserting Fully Insured.

Fix. Plansight's stored value wins when BenefitPoint's answer is ambiguous. Built on the PS-9170 branch, not yet merged.

Worth knowing: funding type is mapped on the REST path, which is the default. The SOAP path writes it as a BenefitPoint custom field (customFieldID 123814) that the code's own comment says Vertafore no longer supports — which only affects brokerages configured with useUserDefinedWsdlURLs.

7

Only 4 of 50 rate types map Plansight's tier count to BenefitPoint's

Capability gap

Problem. Medical Fully Insured (rate type 1), Dental (3), Vision (4) and Stop Loss (39) map a plan's 3/4/5-tier structure to BenefitPoint's rateTypeTierID. Everything else silently falls back to 5-tier, regardless of how the plan is actually rated.

Why it matters. The rate lands, so nothing errors — the tiers are just wrong on the BenefitPoint side for most ancillary lines. A card covering the full mapping is drafted and not yet filed.

8

A successful push reports nothing until a manual refresh

User-visible

Problem. The push succeeds, BenefitPoint has the product, and the modal says nothing. The broker only learns it worked by reloading the page. Pre-existing, unrelated to any mapping work.

Why it matters. This is probably a bigger driver of "the BenefitPoint connection doesn't work" than any mapping bug on this page — it trains brokers to believe pushes fail. Needs its own card.

API surface — what actually works

Operational facts established by calling the API, each of which has cost real debugging time at least once.

FactDetail
BenefitPoint "test" is not a separate server The test credentials point at benefitpoint.vertafore.com — the live host. Isolation is the brokerage account, not the environment. 14119 is the Plansight production book; 1002 is the shared sandbox.
Each brokerage has its own API password Same username, different password per broker domain. A wrong one returns ns1:SYSTEM_ERROR, not "invalid login" — it looks like a Vertafore outage and is not.
SOAP getProductTypes() returns 220 of 426 types It silently omits types the registry uses (5510, 5823, 5859, 5873) and reports Voluntary Life 260 as having no rate types when REST reports rate type 34. Only GET /products/types?returnAllTypes=true is complete. Do not rebuild rate-type data from the SOAP call.
findBenefitSummaries is not denied The long-standing "not authorized" belief came from malformed calls. It needs criteria = {accountID, productID} with accountID required; with that it returns summaries normally. getBenefitSummary by id, getBenefitSummaryStructure and getCurrentUser all work too.
findUsers is genuinely denied Denied for the Plansight API user on both brokerages. Use getCurrentUser to resolve the numeric BenefitPoint user id when findUsers returns zero.
officeFilter takes office IDs, not names A name silently matches nothing and the employer search comes back empty rather than erroring.
Lead users must be on the account team Setting a primarySalesLeadUserID that is not on the team returns userID ... does not belong to the account team members.
There is no delete for accounts or products The API exposes only DeleteBenefitSummaryRest. Test fixtures created on a real account cannot be removed — plan accordingly before creating them.
Attachments are read-only Only findAttachments and getAttachment exist. There is no attachment write of any kind.
planInfo.stateList serializes to the literal string "Array" BenefitPoint rejects it with a generic Application_Error. Replace planInfo wholesale when cloning a product rather than mutating it.
The brokerage cache is a stored file storage/app/benefitPoint/brokerageCache_<id>. Changing the brokerage ID does not invalidate it, and the UI's flush button only queues an Artisan job — which does nothing without a queue worker running.

All findings reproduced 20–22 Aug 2026 against BrokerConnect V4.4, brokerages 14119 and 1002. Fixtures created during testing were restored or removed. Items marked "not yet merged" are built and verified on the PS-9170 branch but are not in production.

Attribute ID reference

Every BenefitPoint attribute ID, in one place

Developer reference for the attribute IDs the BenefitPoint API uses across plans and benefit summaries. Each row pairs a BP attribute ID with its human label and the Plansight field it maps to. Source: the same data that drives the interactive Sync Map — re-extracted as a flat ctrl-F-friendly table.

124 attribute mappings across 8 benefit types. Each row pairs a BenefitPoint attribute ID with its human label and the Plansight field it maps to. Use browser find (Cmd-F / Ctrl-F) to locate by ID, name, or Plansight column.

BP IDBenefitPoint namePlansight fieldSync
Medical (46 attributes)
General Plan Information
45Annual Deductible/IndividualDeductibleIndividualSynced
44Annual Deductible/FamilyDeductibleFamilySynced
973Deductible Embedded or AggregateDeductibleTypeSynced
112Coinsurance — Push uses BP ID 295.HospitalFacilitySynced
386Office Visit/ExamPrimaryCareDoctorSynced
414Outpatient Specialist VisitSpecialistSynced
902TelemedicineTelehealthSynced
53Annual Out-of-Pocket Limit/IndividualOOPIndividualSynced
52Annual Out-of-Pocket Limit/FamilyOOPFamilySynced
873High Deductible Health Plan (HSA-eligible)hsaSynced
Preventive Services
974Preventive Care — BP section ID 455.PreventativeServicesSynced
975Preventive Screenings — Push uses 974.PreventativeServicesSynced
571Well-Child Care — Push uses 974.PreventativeServicesSynced
254Immunizations — Push uses 974.PreventativeServicesSynced
570Well Woman Exams — Push uses 974.PreventativeServicesSynced
339Mammograms — Push uses 974.PreventativeServicesSynced
16Adult Periodic Exams with Preventive Tests — Push uses 974.PreventativeServicesSynced
168Diagnostic X-Ray and Lab TestsDiagnosticSynced
971Complex RadiologyImagingSynced
Maternity Care
450Pregnancy and Maternity Care (Pre-Natal)MaternityOfficeSynced
Inpatient Hospital Services
295Inpatient HospitalizationHospitalFacilitySynced
439Pre-Authorization of Services Required—N/A
503Semi-Private Room & Board (incl. services) — Push uses 295.HospitalFacilitySynced
Surgical Services
408Outpatient Facility ChargeOutpatientFacilitySynced
Emergency Services
184Emergency RoomEmergencyRoomCopaySynced
Urgent Care
555Urgent Care FacilityUrgentCareSynced
Mental Health
287Inpatient CareInpatientMentalSynced
407Outpatient CareOutpatientMentalSynced
Alcohol Abuse
294Inpatient Hospitalization — Push uses 670.InpatientSubstanceSynced
292Inpatient Detoxification Services — Push uses 670.InpatientSubstanceSynced
412Outpatient Services — Push uses 673.OutpatientSubstanceSynced
675Outpatient Detoxification Services — Push uses 673.OutpatientSubstanceSynced
Substance Abuse
670Inpatient HospitalizationInpatientSubstanceSynced
671Inpatient Detoxification Services — Push uses 670.InpatientSubstanceSynced
673Outpatient ServicesOutpatientSubstanceSynced
674Outpatient Detoxification Services — Push uses 673.OutpatientSubstanceSynced
Other Services and Supplies
174Durable Medical Equipment & Prosthetic DevicesMedicalEquipmentSynced
250Home Health CareHomeCareSynced
508Skilled Nursing or Extended Care FacilityNursingSynced
251Hospice CareHospiceSynced
Outpatient Rehabilitative Therapy
424PhysicalRehabilitationSynced
385Occupational — Push uses 424.RehabilitationSynced
515Speech — Push uses 424.RehabilitationSynced
Habilitative Services
877PhysicalHabilitationSynced
878Occupational — Push uses 877.HabilitationSynced
879Speech — Push uses 877.HabilitationSynced
Dental (17 attributes)
General Plan Information
44Annual Deductible/IndividualDeductibleIndividualSynced
45Annual Deductible/FamilyDeductibleFamilySynced
55Annual Plan MaximumAnnualMaxIndividualSynced
314Lifetime Orthodontia Plan MaximumLifetimeOrthoMaxSynced
Diagnostic and Preventive Services
390Oral ExamsExamsSynced
74Bitewing X-RaysXRaysSynced
109Cleaning and ScalingCleaningsSynced
Basic Services
64Basic FillingBasicFillingSynced
391Oral Surgery: Extractions and OtherOralSurgerySynced
190Endodontic TreatmentEndodonticsSynced
419Periodontic TreatmentPeriodonticsSynced
Major Services
137Crowns, Jackets and Cast Restoration BenefitsCrownsSynced
255ImplantsImplantsSynced
Orthodontia Services
392OrthodontiaOrthodonticsSynced
152Dependent ChildrenOrthodonticsAgeSynced
17Adults (and Covered Full-Time Students)OrthodonticsAdultSynced
314Adult Lifetime MaximumLifetimeOrthoMaxSynced
Vision (13 attributes)
General Plan Information
195ExaminationEyeExamsSynced
Benefit Frequency
194ExaminationExamFrequencySynced
309LensesLensesFrequencySynced
207FramesFramesFrequencySynced
122ContactsContactsFrequencySynced
Lenses
507Single Vision LensSingleVisionLensesSynced
73Bifocal LensBifocalLensesSynced
553Trifocal LensTrifocalLensesSynced
66Basic ProgressiveBasicProgressiveLensesSynced
Contact Lenses
356Medically NecessaryContactsNecessarySynced
178ElectiveContactsElectiveSynced
Frames
208Frame AllowanceFrameAllowanceSynced
Other Services
133Corrective Vision Services (e.g. Laser Surgery)LasikDiscountSynced
Basic Life (14 attributes)
Guarantee Issue Amount
187Employee—On Roadmap
Overall Maximum
188EmployeemaximumBenefit-1On Roadmap
Features
7Accelerated Benefit (Life Only)acceleratedDeathBenefitSynced
567Waiver of Premium (Life Only)premiumWaiverSynced
122Conversion (Life Only)conversionSynced
433PortabilityportabilitySynced
AD&D Schedule
497Seatbelt Benefit—Synced
28Airbag BenefitairBagSynced
590Repatriation of RemainsrepatriationSynced
100Child Care BenefitchildCareSynced
Reduction of Benefits Schedule
2Tier 1 (Age 64 or Younger)—Synced
3Tier 2 (65-69)—Synced
4Tier 3 (70-74)—Synced
5Tier 4 (75-79)—Synced
STD (10 attributes)
Elimination Period
252Accidentwaiting_period_accidentSynced
505Sicknesswaiting_period_illnessSynced
8HospitalizationFirst Day HospitalSynced
General Plan Information
71Benefit Percentage—Synced
569Weekly Benefit Maximum—Synced
350Maximum Period of Payment—Synced
373Minimum Weekly Benefit—Synced
142Definition of Earnings—Synced
449Pre-Existing Condition Limitations—Synced
433Portability—Not Synced
LTD (16 attributes)
General Plan Information
71Benefit PercentageltDisabilityBenefitSchedule-1Synced
582Guaranteed IssueltDisabilityGuaranteeIssueSynced
371Monthly Benefit MaximumltDisabilityMaximumMonthlyBenefit-1Synced
351Maximum Period of PaymentltDisabilityBenefitDurationSynced
374Minimum Monthly Benefit—Synced
181Elimination PeriodltDisabilityEliminationPeriodSynced
466Recurrent DisabilitiesltDisabilityRecurrentProtectionSynced
142Definition of EarningsltDisabilityEarningsDefinitionSynced
801Earnings TestltDisabilityEarningsTestSynced
Limitations
365Mental IllnessltDisabilityMentalNervousSynced
526Substance AbuseltDisabilityAlcoholDrugSynced
501Self-Reported—Synced
449Pre-Existing Condition LimitationsltDisabilityPreExistingLimitationsSynced
Features
539Survivor BenefitltDisabilitySurvivorBenefitSynced
485100% Return to Work Incentive—Synced
113COLAltDisabilityCOLASynced
EAP (4 attributes)
Plan Design
384Number of Visits / In-person sessions per IssueeapInPersonSessionsIssueNot Synced
553Chemical Dependency / Drug & Alcohol Counselors—Not Synced
202Legal Resource and Referral / Financial Counseling—Not Synced
557Utilization ReportingeapUtilizationReportsNot Synced
HRA (4 attributes)
Communication and Enrollment
192Enrollment Meetings—Synced
191Enrollment Kits / Startup Kits—Synced
Compliance
170Discrimination Testing—Synced
430Plan Documents—Synced
Roadmap

What's next

Active work and open ideas. Tracked in JIRA project PS.

●

PS-9170 — product-type change on renewal and replacement

Built, not merged

The feature described in Changing a plan's product type on renewal is built and verified against live BenefitPoint pushes — a replacement and a rate-carrying renewal both created as a different type with the parent chain intact. It is committed on a branch and has not yet been merged to production.

Riding along with it, each verified live and each described in the field notes above: the HDHP flag no longer fails the whole benefit-summary push, IRS currency values above $999 now send as numbers, Medical HMO summaries can be copied again, and the import stops rewriting Level Funded to Level Funded Express.

Follow-on work identified, not yet filed: remove the attribute-ID gate on the currency fix so every currency attribute stops flipping to text at a thousand; map attribute 138 rather than only stripping it; map tier counts for the other 46 rate types; and card the silent-success-on-push behaviour, which is likely the single biggest source of "the BenefitPoint connection doesn't work" reports.

Reconciled against the JIRA board on 24 Aug 2026. Status is BenefitPoint work only.

In development

  • PS-9170 — Change product type on renewal or replacement, all benefit types. Steve Overton, Q3 2026. Built on a branch, not merged.
  • PS-8675 — BenefitPoint attributes for Life & DI. Jeff Merrill, Q3 2026.

Open and untriaged

Eight reported BenefitPoint items sit at Un Reviewed — none assigned to a sprint, several unassigned entirely, and the oldest untouched since March.

  • PS-8692 — Self-funded Dental rate type ID not mapping ← the same gap as finding #7 above
  • PS-8693 — Voluntary Life rate push fails on ageBandGender enum casing
  • PS-8578 — Rates not pushing on renewal/replacement plans (unassigned)
  • PS-8740 — EE+Spouse and EE+Child rates missing on Dental renewals (unassigned)
  • PS-8697 — Base Life benefit fields completeness (unassigned)
  • PS-8694 — Multiple plan summaries, Black Diamond (unassigned)
  • PS-8727 — BenefitPoint renewal-only clients
  • PS-8655 — Attribute label customization (Under Review)

Four of these eight are rate-push problems. That clustering is worth reading alongside finding #7 — most ancillary lines fall back to 5-tier regardless of how the plan is rated.

Identified here, not yet filed

  • Remove the attribute-ID gate on the currency fix, so every currency attribute stops flipping to text above $999 (finding #4)
  • Map attribute 138 rather than only stripping it from the copy (finding #5)
  • Map tier counts for the other 46 rate types — card drafted, not filed (finding #7)
  • A successful push that reports nothing until a manual refresh (finding #8)
  • Confirm whether DHMO staying un-exportable is still intended
  • Decide whether the type selector should render for the seven additional-product-only families

Shipped

  • PS-8631 — Auto-populate primary renewal month from plan renewal dates (Q2 2026)
  • PS-8691 — STD rate field 203 decimal precision (Q2 2026)
  • PS-8541 — Renewals/replacements with multiple benefit summaries
  • PS-7674 — BP Plans Renew Only mode
  • PS-8587 — Federal Tax ID import fix
  • PS-8579 — Renewal rate dates correctly incremented
  • PS-8456 — Hotfix for null effective-date error

Longer-standing ideas still open: migrate the remaining SOAP operations to REST, and re-extract the attribute code dictionary from the current BenefitPoint API.