Skip to main content

Progressive Home API — Reverse Engineering

Status: Proven and validated in both QA and Production. The entire Progressive Home agent portal has been reverse-engineered. Login, session creation, and the full quoting REST API are accessible via direct HTTP — no browser, no Playwright. Production validation completed 2026-03-30 (login + MFA + session creation + quoting gateway confirmed).

MetricPlaywrightDirect HTTP
Login~39s~3s
Login + session + first API call~64s~6s
Browser requiredYes (Chromium)No
ReliabilityFlaky (DOM timing, selectors)Deterministic

Architecture

QA Environment

Production Environment

1. Authentication (OAuth2 / PingFederate)

The login uses an OAuth2 Authorization Code flow via PingFederate. Six sequential HTTP requests, no browser rendering required.

StepMethodURLResult
1GET62.qa.foragentsonly.com/login/302 to PingFederate, sets ASP.NET_SessionId, FAOUserSessionId, and ~10 domain cookies
2GETqa-login.progressive.com/as/authorization.oauth2?client_id=ClientFao&response_type=code&...302 back to FAO with resumePath, sets PF cookie
3GET62.qa.foragentsonly.com/FederatedLogin/LoginResume/?resumePath=...Sets ResumePath cookie, 302 to /login/
4GET62.qa.foragentsonly.com/login/200 — renders login form. Inline JS contains var postUrl = "https://qa-login.progressive.com/as/.../resume/as/authorization.ping?app=fao"
5POSTqa-login.progressive.com/as/{id}/resume/as/authorization.ping?app=faoCredentials (user1, password1) go to PingFederate. Returns 302 with ?code=... auth code and LOGINOIDFAO_SESSION JWT
6GET62.qa.foragentsonly.com/federatedlogin/signin/?code={authCode}&state=loginType%3DSTANDARDExchanges code for session. Sets WebGuard cookie (Bearer JWT), AGENTCODE, FAOContext. Redirects to /home/

The WebGuard cookie contains a Bearer JWT issued by PingFederate:

{
"scope": [],
"client_id": "ClientFao",
"iss": "https://qa-login.progressive.com",
"sub": "43786",
"entity_type": "FaoPerson",
"role": [
"AR-1", "TSA-1", "IA-1", "AA-1",
"ddbapipolicysvc-01", "PSAPIReadOnly-01",
"psapisessions-01", "pproapifao-01"
],
"agent_code": "43786",
"exp": 1773305423
}

Expiry is approximately 12 hours from issuance.

Key Discovery: postUrl

The login form does not POST to /login/. JavaScript in the login page dynamically sets form.action = postUrl where postUrl is the PingFederate resume URL embedded inline. This is why a naive POST /login/ fails — credentials must go directly to PingFederate.

1b. Production Authentication (PingFederate Authn REST API — Validated 2026-03-30)

Production uses a completely different authentication mechanism than QA. Instead of the OAuth2 Authorization Code redirect flow, prod uses the PingFederate Authn REST API (/pf-ws/authn/flows/) — a stateful JSON API with mandatory MFA.

Auth Flow (7 sequential HTTP requests)

StepMethodURLResult
1GETwww.foragentsonly.com/login/200 — login page with inline var flowId = "{id}" and var pingBaseUrl = "https://login.progressive.com"
2GETlogin.progressive.com/pf-ws/authn/flows/{flowId}200 — { status: "USERNAME_PASSWORD_REQUIRED" }. Required header: X-XSRF-Header: FAO
3POSTlogin.progressive.com/pf-ws/authn/flows/{flowId}?action=checkUsernamePassword200 — { status: "AUTHENTICATION_REQUIRED" } on valid creds. 401 INVALID_CREDENTIALS on bad creds. Body: { "username": "...", "password": "..." }
4POSTlogin.progressive.com/pf-ws/authn/flows/{flowId}?action=authenticate200 — { status: "OTP_REQUIRED", devices: [...] }. Triggers OTP delivery to the configured device
5POSTlogin.progressive.com/pf-ws/authn/flows/{flowId}?action=checkOtp200 — { status: "MFA_COMPLETED" } on valid OTP. Body: { "otp": "123456" }
6POSTlogin.progressive.com/pf-ws/authn/flows/{flowId}?action=continueAuthentication200 — { status: "RESUME", resumeUrl: "https://login.progressive.com/as/{flowId}/resume/as/authorization.ping" }
7GET{resumeUrl} (follow redirects)302 → www.foragentsonly.com/federatedlogin/signin/?code={authCode}&state=... → sets WebGuard JWT, AGENTCODE, FAOContext cookies → 302 → /home/

Required Headers for All PingFederate Calls

HeaderValue
X-XSRF-HeaderFAO
Content-Typeapplication/json (for POST requests)

Cookies must be tracked across both www.foragentsonly.com and login.progressive.com domains. The PF cookie set by Step 2 is required for subsequent PingFederate calls.

MFA Device Configuration

The authenticate response returns the available MFA devices:

{
"status": "OTP_REQUIRED",
"devices": [{
"id": "b691aa85-1a3d-4df8-8258-10d53c10a1cb",
"type": "Email",
"target": "pr****@goosehead.com",
"usable": true,
"defaultDevice": true
}],
"selectedDeviceRef": { "id": "b691aa85-1a3d-4df8-8258-10d53c10a1cb" },
"otpLifetime": { "duration": 10, "timeUnit": "MINUTES" },
"changeDevicePermitted": true,
"manageDevicesAllowed": false
}

Supported device types: Email, Phone (SMS), Authenticator (TOTP app).

MFA Error Handling

StatusCodeMeaning
VALIDATION_ERRORINVALID_OTPWrong or expired OTP. attemptsRemaining indicates retries left
MFA_FAILEDOTP_ATTEMPTS_LIMITToo many failed OTP attempts. coolDownExpiresAt (Unix ms) indicates lockout duration
MFA_FAILEDNO_USABLE_DEVICESAll devices locked out. userMessage contains the lockout duration in minutes
MFA_FAILEDOTP_RESEND_LIMITToo many OTP resend requests

OTP resend: POST ?action=selectDevice with { "deviceRef": { "id": "{deviceId}" } }.

{
"scope": [],
"client_id": "ClientFao2",
"sub": "gshd_digitalagent",
"entity_type": "FaoPerson",
"role": [
"UR-1", "ddbapipolicysvc-01", "PSAPIReadOnly-01",
"clmapisumm-01", "olaapireadonly-01",
"psapisessions-01", "pproapiagntupdt-01", "pproapifao-01"
],
"guid": "3743AE34-4D25-48E8-8964-71C1A3DA879A",
"mfa": "true",
"agent_code": "04T42",
"exp": 1774937389
}

Key differences from QA JWT: client_id is ClientFao2 (not ClientFao), mfa field is "true", agent_code is 04T42 (not 43786), roles differ slightly.

QA vs Production Auth Comparison

AspectQAProduction
Login domain62.qa.foragentsonly.comwww.foragentsonly.com
PingFederate domainqa-login.progressive.comlogin.progressive.com
Auth mechanismOAuth2 Authorization Code (6-step redirect chain)PingFederate Authn REST API (7-step JSON API)
postUrl in login pagePingFederate resume URL (populated)Empty string (unused)
flowId in login pageNot usedRequired — drives the entire auth flow
MFANot presentMandatory (Email OTP confirmed)
Bot detectionNone observedNone observed (curl with default User-Agent accepted)
client_id in JWTClientFaoClientFao2

MFA Automation Strategies

  1. Email polling — programmatically read the OTP from the pr****@goosehead.com inbox (Microsoft Graph API, IMAP, or webhook)
  2. TOTP authenticator — if Progressive allows adding an authenticator device, generate TOTP codes programmatically (blocked: manageDevicesAllowed: false on this account)
  3. Manual relay with pause — pause automation, prompt for OTP input, continue. Viable for low-volume or testing scenarios
  4. Session caching — WebGuard JWT expires in ~12 hours. Cache the authenticated session and reuse across quotes to minimize MFA prompts

2. Session Creation (QuotingGateway)

After login, a quote session is created by hitting the QuotingGateway endpoint. This was the hardest piece to crack — the dashboard uses JavaScript (OpenNewQuote function in the HomeScripts bundle) to build a window.open() URL.

Endpoint

GET /NewBusiness/QuotingGateway/RouteQuote/?from=home&app=NewQuote&st_cd={state}&prod_cd={product}&dflt_st_prod=N&handshakeId={uuid}
ParameterValueNotes
st_cdTX, OH, etc.Two-letter state code
prod_cdHO, AU, CO, etc.Product code. HO = Home (HO3)
dflt_st_prodN or YWhether to remember defaults
handshakeIdRandom UUID v4Generated client-side

Redirect Chain

QA:

GET /NewBusiness/QuotingGateway/RouteQuote/...
→ 302 /NewBusiness/IaqQuoting/RouteQuote/...
→ 302 https://a-vendor.quoting.foragentsonly.com/Slot301/bridgequote/index?quoteAction=NQ&LookupId={token}&SessionIndex=0&SyncId={guid}&environment=Acceptance
→ 302 /Slot301/Quote/Index/?SessionIndex=0&SyncId={guid} (sets UQContext cookie)
→ 200 (SPA HTML with workflowStatesEmbeddedInPage JSON — establishes server-side session)

Production:

GET /NewBusiness/QuotingGateway/RouteQuote/...
→ 302 /NewBusiness/IaqQuoting/RouteQuote/...
→ 302 https://quoting.foragentsonly.com/bridgequote/index?quoteAction=NQ&LookupId={token}&SessionIndex=0&SyncId={guid}&environment=Production-East
→ 302 /Quote/Index/?SessionIndex=0&SyncId={guid} (sets UQContext cookie)
→ 200 (SPA HTML — establishes server-side session)

The LookupId is a server-generated encrypted token. The SyncId is a GUID linking the FAO session to the quoting session. The bridgequote/index hop sets the UQContext cookie containing the quoting session's ContextId, ContextGroupId, and SyncId, then redirects to the actual SPA entry point. In QA, the SPA lives at /Slot301/Quote/Index/; in production, it's at /Quote/Index/ (no slot prefix).

Product Codes

CodeProduct
AUAuto
HOHome (HO3)
H5Home (HO5)
HBHome (HOB)
COCondo (HO6)
RTRenters (HO4)
BTBoat/PWC
MCMotorcycle/ATV
MTMotor Home
TTTravel Trailer
UBUmbrella
FLFlood
CVCommercial Auto
BPBusinessowner/Contractor GL

3. Quoting REST API

Once the SPA page is loaded (establishing the server-side session), all form interactions are REST API calls.

Base URL

QA:

https://a-vendor.quoting.foragentsonly.com/Slot301/api/v1/composite/

Production:

https://quoting.foragentsonly.com/api/v1/composite/

The Slot301 prefix is QA-only. Production uses the root path directly.

Workflow Navigation Endpoints

MethodEndpointPurpose
POSTAppInitWorkflowState?expand=[{"descriptor":"view-model","expand":[]}]Initialize workflow after SPA load. Requires a JSON body (at minimum {}); omitting the body returns 411 Length Required
GETCurrentWorkflowState?workflowNode={step}Get current step state and navigation links
POSTNextWorkflowState?workflowNode={step}Submit form data, advance to next step
POSTGoToWorkflowState?workflowNode={step}Jump to a specific step
GET{Step}EditViewModelGet form field values for a step
POSTSaveAndExitWorkflowStateSave and exit the quote
POSTDuplicateQuoteWorkflowStateDuplicate a quote
GETPrintWorkflowStatePrint view

Context and Metadata Endpoints

MethodEndpointPurpose
GETIaqQuoteContextSession context (state, products, agent, environment)
GETIaqHeaderCustomer header (name, phone, email)
GETProgressBarStep progress and completion status
GETHelpContextual help content
GETProductGuidePdfProduct guide PDF link

Sub-Workflow Endpoints

MethodEndpointPurpose
variesSubWorkflowState?subWorkflowName=MaRmvPrefillAddress prefill
variesSubWorkflowState?subWorkflowName=OriginalPolicySummaryOriginal policy summary
variesSubWorkflowState?subWorkflowName=QuoteCommentQuote comments

Phone Number Endpoints

MethodEndpointPurpose
POSTAddedPhoneNumbersAdd a phone number
DELETEDeletedPhoneNumbers?id={guid}Remove a phone number
GETPhoneNumberViewModelPhone number form model

Stateless/Cache Endpoints

MethodEndpointPurpose
variescomposite-stateless/PropertyNonSaveRatingCacheProperty rating cache
variescomposite-stateless/AsiRentersTeaserCacheRenters teaser cache
variescomposite-stateless/AsiUmbrellaTeaserCacheUmbrella teaser cache

Error Logging

MethodEndpointPurpose
POSTClientErrorLoggerClient-side error/event logging

4. HAL Response Format

All API responses follow the HAL (Hypertext Application Language) pattern:

{
"CurrentPageNav": "Workflow/UnsoldQuote/QuoteFlow/PreCoveragesFlow/NamedInsuredGroup/NamedInsured",
"ActiveViewModelHasEdits": false,
"Links": [
{
"Name": "next",
"Descriptors": ["workflow", "next"],
"Uri": "/Slot301/api/v1/composite/NextWorkflowState?workflowNode=NamedInsured"
}
],
"Extenders": {
"ServerTransactionTime": "40"
},
"Embedded": {
"NamedInsured": { "FirstName": "mike", "LastName": "jones", "..." : "..." },
"ProgressBar": { "Items": ["..."] },
"IaqHeader": { "CustomerFirstName": "mike", "..." : "..." },
"IaqQuoteContext": { "QuoteState": "TX", "..." : "..." }
},
"ValidationDetails": {},
"Messages": {},
"ApplicationBuildVersion": "6.0.0.1518"
}

The Links array drives navigation — the SPA follows the next link to advance steps. Embedded contains the view model data for the current page. ValidationDetails contains field-level validation errors.

5. Named Insured — Confirmed Working Example

POST /Slot301/api/v1/composite/NextWorkflowState?workflowNode=NamedInsured&expand=[{"descriptor":"view-model","expand":[]}]

{
"FirstName": "mike",
"LastName": "jones",
"MiddleInitial": "",
"Suffix": "",
"DateOfBirth": "1990-01-01",
"Gender": "M",
"MailingAddress": "123 main st",
"ApartmentUnit": "",
"City": "Burleson",
"State": "TX",
"ZipCode": "76028",
"MailingZipType": "O",
"PriorAddress": "",
"PriorCity": "",
"PriorState": "",
"PriorZipCode": "",
"CurrentResidence": "",
"RecentlyMoved": "N",
"MaRmvPrefillOpen": null,
"AgentCode": "43786",
"AsiAgentCode": "446447",
"MembershipNumber": null,
"SellingCode": null,
"ReferenceNumber": null,
"PrimaryEmailAddress": "na@email.com",
"DisclosureProvided": "Y",
"PhoneNumbers": {
"List": [
{
"PhoneId": "665de64d-c4aa-4442-99ad-fd419ee7eff5",
"PhoneNumber": "321-242-2233",
"PhoneType": "M",
"IsVerified": "N",
"CurrentPageNav": null,
"ApplicationBuildVersion": "6.0.0.1518"
}
],
"Count": 1,
"ApplicationBuildVersion": "6.0.0.1518"
},
"ProductSpecificInformation": {
"List": [
{
"ContentType": "HO",
"Offerings": null,
"PolicyType": null,
"CurrentPageNav": null,
"ApplicationBuildVersion": "6.0.0.1518"
}
],
"Count": 1,
"ApplicationBuildVersion": "6.0.0.1518"
},
"HasInternationalAddress": "N",
"InternationalMailingAddress1": "",
"InternationalMailingAddress2": "",
"InternationalCity": "",
"InternationalState": "",
"InternationalCountry": "",
"InternationalZipCode": "",
"QuoteOrigin": "",
"CurrentPageNav": null,
"ApplicationBuildVersion": "6.0.0.1518"
}

DisclosureProvided must be "Y". Submitting with "N" returns a 400 with an edit message: "The disclosure must be provided to or read to the consumer." The SPA shows this as a disclosure dialog the agent must acknowledge before advancing. In direct HTTP, set it to "Y" upfront.

Response: 201 with ~208KB of JSON. The Embedded.NamedInsured mirrors the submitted data. ValidationDetails is empty (no errors). The Links array provides the next navigation URI for the Products step (NextWorkflowState?workflowNode=ProductsHO).

6. Environment Details

QA Domains

DomainPurpose
62.qa.foragentsonly.comQA login portal and dashboard (ASP.NET MVC)
qa-login.progressive.comPingFederate OAuth2/OIDC provider
a-vendor.quoting.foragentsonly.comQuoting SPA and REST API (Angular)
qa.test.faoservices.foragentsonly.comLegacy SOAP rating API (not used by quoting SPA)
q-rtds.progressive.comSplunk logging
tdmapi-main-as-tdm-api-qa.np.glb.pgrcloud.appDeal tracking/logging API

Production Domains (Validated 2026-03-30)

DomainPurpose
www.foragentsonly.comProd login portal and dashboard (ASP.NET MVC)
login.progressive.comPingFederate Authn REST API (MFA-enabled)
quoting.foragentsonly.comQuoting SPA and REST API (Angular)
rtds.progressive.comSplunk logging

QA vs Production Domain Mapping

PurposeQAProduction
Login portal62.qa.foragentsonly.comwww.foragentsonly.com
PingFederateqa-login.progressive.comlogin.progressive.com
Quoting SPA/APIa-vendor.quoting.foragentsonly.comquoting.foragentsonly.com
Quoting base path/Slot301// (no slot prefix)
Quoting API basea-vendor.quoting.foragentsonly.com/Slot301/api/v1/composite/quoting.foragentsonly.com/api/v1/composite/
Environment labelAcceptanceProduction-East
Agent code4378604T42
client_id (JWT)ClientFaoClientFao2

Key Cookies

CookieDomainPurpose
WebGuard.foragentsonly.comBearer JWT for authentication
ASP.NET_SessionIdportal domainServer-side session for the dashboard
FAOContext.foragentsonly.comSession context (group ID, client ID, server, location)
AGENTCODE.foragentsonly.comAuthenticated agent code
UQContext.foragentsonly.comQuoting session context (SyncId, ContextId)
PFlogin.progressive.comPingFederate session (prod only, required for authn flows)
BIGipServer~eCommerce~...quoting domainF5 load balancer affinity

7. Opening Existing Quotes (RC1 / MuleSoft)

CRITICAL: All testing MUST use RC1 quotes created by MuleSoft. Do NOT create new quotes via the NewQuote flow — the NewQuote workflow is fundamentally different (skips Products through Portfolio, jumps to PointOfSale with a modal acknowledgement). Only the RC1 OpenQuote path produces the full 8-step workflow needed for end-to-end testing.

Why RC1 Only

MuleSoft RC1 creates the quote in Progressive's system with pre-populated data (applicant, property, coverages). The Progressive Q-number (e.g., Q83806794) is generated during this process. Without RC1, Progressive has no property data to rate against.

NewQuote vs OpenQuote Workflow Differences

AspectNewQuoteOpenQuote (RC1)
StepsNamedInsured → AcknowledgeMVR modal → PointOfSaleNamedInsured → Products → HouseholdMembers → ... → FinalSale (8 steps)
Property dataNone (must be entered manually)Pre-filled from RC1 submission
AcknowledgeMVR modalAppears after NamedInsured (outlet: "modal")Does not appear
GoToWorkflowStateReturns 404 for intermediate stepsWorks as expected

Finding RC1 Quote Numbers

The Progressive Q-number is stored in the CRN sf_quote_response table (public schema), in the company_quote_number__c column.

SELECT company_quote_number__c AS q_number, status__c, premium_total__c, effective_date__c, request_date__c
FROM sf_quote_response
WHERE carrier__c ILIKE '%progressive%'
AND line_of_business__c ILIKE '%home%'
AND company_quote_number__c IS NOT NULL
AND status__c = 'SuccessWithChanges'
ORDER BY request_date__c DESC
LIMIT 10;
ColumnPurpose
company_quote_number__cProgressive Q-number (e.g. Q83807337)
company_client_id__cASI agent code used for the RC1
company_url__cDirect URL to ASI quote summary (UAT)
quote_request_heroku_idLinks to the sf_quote_request table
clue_status__c / mvr_status__cReport statuses (if returned by MuleSoft)
boomi_mulesoft__c"Mulesoft" confirms RC1 source

Alternative: Log in to the Progressive portal at 62.qa.foragentsonly.com → Quote Search → filter by Home (HO3), state, Unsold.

Opening an RC1 Quote

GET /NewBusiness/QuotingGateway/RouteQuote/
?app=OpenQuote
&asiQuoteNumber=Q83806794
&asiAgentCode=446447
&st_cd=TX
&prod_cd=HO
&qt_src=ASI
ParameterValueNotes
appOpenQuoteNot NewQuote
asiQuoteNumberQ83806794Progressive's Q-number (from portal dashboard, NOT from CRN)
asiAgentCode446447ASI agent code
st_cdTXState code
prod_cdHOProduct code
qt_srcASISource = ASI (MuleSoft RC1)

The redirect chain is identical to new quotes. The opened quote always starts at NamedInsured regardless of how far it previously progressed — each step must be submitted via NextWorkflowState to advance.

8. Products Step — Eligibility Validation (Critical)

The ProductsHO step has a multi-phase submission that differs from all other steps:

Phase 1: Set Fields

The HomeClosingRiskFlag uses standard Y/N values:

ValueMeaning
"Y"Yes (home closing)
"N"No (not a closing)

This is a Question-only field (not in the flat Details object). Required — Progressive returns 400 with edit "Please fill out this field to continue." if empty.

The HomeForeclosureFlag uses radio button values, not Y/N:

ValueMeaning
"1"Yes (foreclosure)
"2"No (not a foreclosure)

The eligibility verification checkboxes use Y/N:

  • VerifyNoInelCond: "Y" = agent verified no ineligible conditions
  • VerifyNoUwCond: "Y" = agent verified no underwriting conditions

Phase 2: Validate Eligibility (required before submit)

Before submitting NextWorkflowState?workflowNode=ProductsHO, you MUST call:

PUT /Slot301/api/v1/composite/PropertyAddressEligibility?flow=Validate&expand=[{"descriptor":"view-model","expand":[]}]

Body: the full ProductsHO form data (same as what would go to NextWorkflowState).

This call runs the server-side eligibility check and sets EligibilityVerifiedFlag = "Y". Without this call, NextWorkflowState returns 400 with edit "EligibilityVerifiedFlag: Please select this button to continue.".

Phase 3: Submit

After the eligibility PUT succeeds, submit the step normally:

POST /Slot301/api/v1/composite/NextWorkflowState?workflowNode=ProductsHO&expand=[...]

Common 400 Edits on ProductsHO

FieldErrorFix
HomeForeclosureFlag"Please fill out this field to continue."Set to "2" (No) — NOT "N"
VerifyNoInelCond"Please review the eligibility requirements"Set to "Y"
VerifyNoUwCond"Please review the eligibility requirements"Set to "Y"
EligibilityVerifiedFlag"Please select this button to continue."Call PUT PropertyAddressEligibility first

9. Products Step — Required Property Detail Fields (Discovered)

The ProductsHO step has deeply nested required fields inside Properties.List[0].Details.Embedded.Questions. These fields do NOT appear in top-level ValidationDetails — they are embedded HasEdit=true entries.

Correct Field Names (HAL vs. intuitive names)

HAL Field NameWhat It IsCommon Wrong Name
LivingAreaSquare footageTotalLivingArea
TypeOfConstructionConstruction type codeConstructionType
SubstructureFoundation typeFoundationType
ExteriorWallsExterior wall materialExteriorWallType
RoofMaterialRoof material typeRoofType
YearRoofInstalledYear roof replacedYearOfRoof
RoofDesignRoof shape/geometryRoofShape

Required Fields (Often Missing from RC1)

FieldValid ValuesNotes
NumberOfStories"1", "1H", "2", "2H", "3", "BILEVEL", "TRILEVEL"NOT text like "one"/"two"
GarageType"NO", "1A", "2A", "3A", "1B", "2B", "3B", "1C", "2C", "3C", "1D", "2D", "3D"Format: {count}{type} (A=Attached, B=Built-in, C=Carport, D=Detached)
CoolingType"None", "CentralAir", "EvapCooler", "WholeHouseFan", "HeatPump", "Humidifier"NOT "C" or "N"
HeatingType"None", "Radiant", "GeoClosed", "GeoOpen", "Electric", "GasForced", "HeatPump", "OilForced", "PropaneForced", "GasHotWater", "OilHotWater", "PropaneHotWater"Required
KitchenGrade"0" (Builder's), "1" (Custom), "2" (Designer), "3" (Semi-Custom)Required
BathroomGrade"Standard" (Builder's), "Custom", "Designer", "SemiCust"Required
DeckType"None", "Wood", "Redwood", "Composite"Required
PropertyAcreage"1" (0-10ac), "2" (11-20ac), "3" (21-30ac), "4" (31+)Required
RoofDesign"GABLE", "HIP", "FLAT"UPPERCASE only"Gable" is silently rejected. This field is Question-only (not in flat Details object).

Question-Only Fields (Critical Implementation Detail)

Some property detail fields exist ONLY inside Properties.List[0].Details.Embedded.Questions.List — they are NOT present as flat keys on the Details object. When extracting step data and stripping HAL metadata (which removes Embedded), these fields are lost.

Affected fields: RoofDesign (confirmed), potentially others for less common property types.

Fix: Before stripping, collect all Question Property/LastAnswer pairs. After stripping, inject missing fields into the flat Details object so setNestedField can find and update them.

DwellingCoverageValue

Located at Properties.List[0].FlowType.DwellingCoverageValue. The replacement cost estimate is at Properties.List[0].FlowType.ReplacementCostEstimate. Progressive calculates the estimate and dwelling coverage must meet or exceed it.

If the RC1 dwelling value is too low, the response includes:

  • HasEdit: true on DwellingCoverageValue
  • Edit message: "The value for this home is too low, Replacement cost is $X"
  • ReplacementCostEstimate field with the required minimum value

Strategy: Read ReplacementCostEstimate from the HAL state and use it as the minimum for DwellingCoverageValue.

10. Additional Details — Required Fields (Discovered)

Mortgagee Timing (Critical — Discovered 2026-03-14)

Some states (confirmed: OH) require a mortgagee to be added BEFORE AdditionalDetails. If the RC1 quote has MortgageeRequired = "Y" and no mortgagee is present, submitting AdditionalDetails triggers PropertyApologyKickout with "Required field 'Mortgagee Name' is missing." — this KILLS the session permanently.

Fix: The SubmitAdditionalDetailsDto accepts an optional mortgagee field. If provided, the mortgagee sub-workflow is called BEFORE submitting AdditionalDetails. The mortgagee can also still be added at PointOfSale for states that don't require it earlier (e.g. TX).

POST SubWorkflowState?subWorkflowName=Mortgagee  (before AdditionalDetails, if state requires it)
POST NextWorkflowState?workflowNode=AdditionalDetails

The AdditionalDetails step requires 9 fields that RC1 data typically doesn't provide. These are nested in ProductSpecificInformation.List[0].Embedded.Questions.

FieldValid ValuesNotes
PriorInsurerLong dropdown list of insurers, "First-Time Home Buyer", "No Prior", "Lapse in Coverage""No Prior" triggers knockout — use "First-Time Home Buyer" or an actual insurer name
PriorLiabilityLimitsType"300K+", "300K", "300K-", "FirstTime", "Lapse", "NoPrior"Must match PriorInsurer selection
AnyResidentsSmoke"N", "Y"
OwnADog"N", "Y"
AnimalType"X" (No), "A" (Akita), ..., "U" (Animal w/ Bite History)Required even if OwnADog=N, set to "X"
NumberResidentsInHouse"1" through "9"9 = "9 or More"
PackagePolicy"P5" (Auto 250/500), "P3" (Auto 100/300), "P1" (Auto 50/100), "PL" (Auto <50/100), "FL" (Flood), "NO" (None)Package discount selection
WeatherReportedClaimsCount"0" through "5"5 = "5 or More"
YearsClaimFree"3", "4", "5"5 = "5 or More"

11. Household Members, Coverages, Portfolio

These three steps are pass-through for RC1 quotes — submit the current HAL state unchanged.

Workflow Node Name Differences (Discovered 2026-03-14)

The RC1 OpenQuote flow uses DIFFERENT workflow node names than previously documented:

Old NameActual RC1 NameNotes
HouseholdMembersPeopleRC1 flow uses People step
CoveragesBillPlansCoveragesHORC1 flow uses CoveragesHO step
PortfolioPortfolioSame name

The step service detects the correct node name from the CurrentPageNav path.

POST NextWorkflowState?workflowNode=People        (body = extracted People from CurrentWorkflowState)
POST NextWorkflowState?workflowNode=CoveragesHO (body = extracted CoveragesHO from CurrentWorkflowState)
POST NextWorkflowState?workflowNode=Portfolio (body = extracted Portfolio from CurrentWorkflowState)

These always advance without edits when the upstream steps were completed correctly.

12. Point of Sale — Multi-Phase Submission (Critical)

PointOfSale has a three-phase submission, similar to Products:

Phase 1: Add Mortgagee (Sub-Workflow)

If MortgageeRequired = "Y" (TX requires this), add a mortgagee BEFORE submitting:

POST /Slot301/api/v1/composite/SubWorkflowState?subWorkflowName=Mortgagee

Body:

{
"MortgageeName": "JPMORGAN CHASE BANK NA",
"MortgageeLoanNumber": "999888777",
"MortgageeAddress": "PO Box 4465",
"MortgageeCity": "Springfield",
"MortgageeState": "OH",
"MortgageeZip": "45501"
}

Phase 2: Order CLUE/MVR Reports

Before submitting NextWorkflowState?workflowNode=PointOfSale, you MUST call:

PUT /Slot301/api/v1/composite/OrderPos?validate=OrderPos&expand=[{"descriptor":"view-model","expand":[]}]

Important: This is a PUT, not POST. A POST returns 405 Method Not Allowed.

Body: the full PointOfSale form data (same as what goes to NextWorkflowState). This triggers the CLUE (property) and MVR (driver) report ordering. After this call, the response will have:

  • ClueOrdered: true / PropertyClueOrdered: true
  • ClueReferenceNumber: "..."
  • ClueOrderDate: "..."
  • InsurViewStatus: "..."

Phase 3: Submit

After reports are ordered:

POST /Slot301/api/v1/composite/NextWorkflowState?workflowNode=PointOfSale

Required PointOfSale Fields

FieldLocationNotes
SocialSecurityNumberPointOfSale.Drivers.List[0]SSN for CLUE ordering. NOT in Persons (no Persons list for HO quotes).
AutoPolicyProductSpecificInformation.List[0].POSPropertyBuyViewModelProgressive Auto policy # for package discount. Required if PackagePolicy != "NO"
PropertyPolicyProductSpecificInformation.List[0].POSPropertyBuyViewModelProgressive Property policy # (may be empty)
AtvOnPremiseProductSpecificInformation.List[0].POSPropertyBuyViewModel"1" = Yes, "2" = No
MortgageeCountPointOfSaleSet by Mortgagee sub-workflow

MVR vs CLUE Report Ordering

  • MVR reports are ordered automatically during NextWorkflowState?workflowNode=PointOfSale submission. DriverMvrOrdered becomes true after submit.
  • CLUE reports require the separate OrderPos PUT call. The NextWorkflowState submission alone does NOT order CLUE. ClueOrdered and PropertyClueOrdered remain false without OrderPos.

13. Sub-Workflow Endpoints (New)

MethodEndpointPurpose
POSTSubWorkflowState?subWorkflowName=MortgageeAdd/edit mortgagee
POSTSubWorkflowState?subWorkflowName=PropertyAdditionalInterestAdd additional interest
POSTSubWorkflowState?subWorkflowName=PropertyAdditionalInsuredAdd additional insured
POSTSubWorkflowState?subWorkflowName=QuoteCommentAdd quote comments
PUTOrderPos?validate=OrderPosOrder CLUE/MVR reports (required before PointOfSale advance)

14. OrderPos — Reverse-Engineered from SPA (2026-03-14)

How the SPA calls OrderPos (from Angular bundle analysis)

The SPA does NOT call OrderPos as a standard REST PUT. It uses a proprietary refreshRelevancy mechanism:

// From main-KP3UWFOP.js (minified Angular bundle)
orderPos(e) {
this.httpBackend.refreshRelevancy(this.orderPosLink, "POS").then(...)
}

// refreshRelevancy builds the request as:
refreshRelevancy(linkUri) {
return this.put()
.jsonContentType() // Content-Type: application/progressive.quoting+json
.embedCurrentViewModel() // Body = trimViewModel(currentState)
.expandViewModel() // expand query param
.relativeUrl(linkUri) // URI from HAL Link
}

Required Headers — ALL Step Submissions (SPA Capture 2026-03-14)

The SPA uses identical headers for ALL workflow steps, not just OrderPos:

HeaderRegular StepsOrderPos
Content-Typeapplication/jsonapplication/progressive.quoting+json
actionNamesubmitrefreshRelevancy
SessionIndexFrom URL param (e.g. 0, 3)Same
X-Request-IDRandom UUIDSame
Acceptapplication/hal+jsonSame

CRITICAL: Content-Type determines response key casing:

  • application/json → PascalCase responses (CurrentPageNav, Embedded, Links)
  • application/progressive.quoting+json → camelCase responses (currentPageNav, embedded, links)

Our API uses application/json for regular steps (PascalCase HAL) and application/progressive.quoting+json only for OrderPos (which returns camelCase — handled separately).

SessionIndex must be extracted from the SPA URL (/Slot301/Quote/Index/?SessionIndex=X&SyncId=Y). Hardcoding 0 works for single-session scenarios but fails when Progressive assigns a different index.

Body Format

The SPA's trimViewModel strips: Embedded, Extenders, Links, ValidationDetails, Messages — same as our stripHalMetadata. The body is the stripped PointOfSale data, NOT the raw HAL response.

The OrderPos URI is in a nested HAL Link inside Embedded.PointOfSale.Links (NOT in the top-level Links array):

Embedded.PointOfSale.Links → { Name: "OrderPos", Uri: "/Slot301/api/v1/composite/OrderPos?validate=OrderPos" }

Current Status: ✅ OrderPos 200 (CLUE Ordered — 2026-03-14)

OrderPos now returns 200 and orders CLUE reports. The fix was two-fold:

  1. Adding actionName: submit and dynamic SessionIndex to regular step submission headers
  2. Calling OrderPos post-submit (after NextWorkflowState for PointOfSale), not pre-submit

The working flow:

1. Add mortgagee (SubWorkflowState) — sets mortgagee in server-side context
2. Submit PointOfSale (NextWorkflowState — may return 400 with SSN/mortgagee edits)
3. Re-submit PointOfSale with SSN + mortgagee resolved (advances to AcknowledgeMVR or stays at POS)
4. OrderPos post-submit (PUT — fresh GET of PointOfSale state, then PUT OrderPos) → 200 (10s)
5. Re-submit PointOfSale → advances to FinalSaleHO

E2E test (Q83806608, AZ): NamedInsured → Products → People → AdditionalDetails → Coverages → Portfolio → PointOfSale (SSN + OrderPos 200) → FinalSaleHO reached.

FinalSale (Step 8) — Terminal Step (Discovered 2026-03-14)

FinalSale has NO next link — it is the terminal workflow step. The HAL links at FinalSale:

NameURIPurpose
FinalSaleHOFinalSalePropertyEditViewModel?tabId=HOGET the quote summary / bind form
FinalSalePropertySellQuoteFinalSalePropertySellQuoteWorkflowStatePUT to bind/issue the policy
SellQuoteSellQuoteWorkflowStateAlternative sell link (auto quotes)
goToGoToWorkflowState?workflowNode=FinalSaleHOJump to FinalSaleHO
currentCurrentWorkflowState?workflowNode=FinalSaleHOGet current FinalSale state
backPreviousWorkflowState?workflowNode=FinalSaleHOGo back to PointOfSale

The workflow node is FinalSaleHO (not FinalSale). NextWorkflowState?workflowNode=FinalSale returns 500.

FinalSale Bind Flow — Reverse-Engineered from SPA (2026-03-14)

The SPA's "Issue Policy" button calls sellQuote(), which uses refreshRelevancy (same mechanism as OrderPos):

// From main-KP3UWFOP.js (minified Angular bundle)
sellQuote() {
if (!this.shouldDisableSellButton) {
this.shouldDisplaySellPropertyButton
? this.httpBackend.refreshRelevancy(this.finalSalePropertySellQuoteLink).then(n => {
let demoLink = n.resource.Links.find(d => d.Name === "DemoModeAsiSale");
demoLink != null && this.httpBackend.refreshRelevancy(demoLink);
})
: this.httpBackend.refreshRelevancy(this.sellLink);
}
}

Bind = PUT FinalSalePropertySellQuote link with refreshRelevancy headers:

HeaderValue
Content-Typeapplication/progressive.quoting+json
actionNamerefreshRelevancy
SessionIndexFrom URL param
X-Request-IDRandom UUID
Acceptapplication/hal+json

Body: stripped current FinalSaleHO view model (same stripping as OrderPos — remove Embedded, Links, ValidationDetails, Messages).

QA Environment: DemoModeAsiSale (Auto-Bind)

In QA environments, the FinalSalePropertySellQuote response includes a DemoModeAsiSale HAL link. Calling refreshRelevancy on this link auto-completes the sale without payment processing. This is the QA automation equivalent of the payment flow.

Bind Success Indicators

After successful bind:

  • Extenders.SoldPropertyPaymentSuccess = "True"
  • QuotedProducts[].PolicyNumber populated with the issued policy number
  • Button text changes to "View {HO} Policy"
  • AsiPostSaleLink becomes available for SSO redirect to ASI post-sale page

Payment Retry Flow

If the initial bind fails (payment issue), the SPA shows a PropertyPaymentRetry modal:

  • continue() calls putViewModelTo(finalSalePropertySellQuoteLink.Uri) (standard PUT, not refreshRelevancy)
  • Button text: "Submit Payment"
  • On success: transitions to sold state

15. AdditionalDetails — Required Defaults (Discovered 2026-03-14)

The AdditionalDetails step has additional Question-only fields beyond the 9 documented in section 10. These must have non-empty values or the step returns 201 but does NOT advance (no HasEdit, no errors — just stays at AdditionalDetails).

FieldDefaultNotes
OccupancyType"Primary"Empty in AZ quotes; TX pre-fills "Primary"
IntendEsign"N"E-sign intent flag
OpenFoundation"N"Foundation type flag
HomeUpdate"N"Home improvement flag; triggers re-render
SecuredSubdivision"N"Gated community
NonWeatherReportedClaimsCount"0"Separate from WeatherReportedClaimsCount
WoodburningStoveFlag"N"Woodburning stove present
AccreditedBuilder"N"Accredited builder

Root cause: The mapper must inject Question answers into ProductSpecificInformation.List[0] after stripping HAL metadata. Without this, the Question fields are lost during stripping and Progressive's server doesn't receive the values.

Fix: injectMissingQuestionFields now also handles ProductSpecificInformation (not just Properties and Drivers). Empty defaults are set via fillEmptyNestedField.

16. People Step — All Drivers Need Required Fields (Discovered 2026-03-14)

RC1 quotes can have additional household members (drivers) with empty Gender, MaritalStatus, and Relationship. The PNI (driver 0) is typically pre-filled, but additional drivers (index 1+) may have empty Question answers.

Fix: The mapper now iterates ALL drivers and sets defaults:

  • PNI: Gender from existing, MaritalStatus from existing or "S", Relationship: "I"
  • Non-PNI: Gender from existing or "M", MaritalStatus from existing or "S", Relationship from existing or "O"

17. E2E Timing (Observed 2026-03-14 AZ Quote Q83788438)

StepTimeNotes
Auth (OAuth2 login)~3-5sCached session reuse: ~0s
Session creation (OpenQuote redirect chain + AppInit)~10-15sIaqQuoting redirect is 5-7s alone
AcknowledgeMVR modal dismiss~0.3sAuto-detected and dismissed
NamedInsured (GET + POST)~2-3s
ProductsHO (eligibility + submit)~10-45sDwelling coverage auto-adjusted, varies by property
People submit~1-2s
AdditionalDetails submit~10-27sLongest step; includes mortgagee sub-workflow if needed
CoveragesHO submit~1-2s
Portfolio submit~1s
PointOfSale (mortgagee + submit + OrderPos attempt)~3-5sSSN must be set; OrderPos still returns 400 (mortgagee state persistence)
Total end-to-end (auth through PointOfSale)~40-90sFresh login adds ~5s

18. API Endpoint Reference

Base: POST /api/v1/progressive/home/

EndpointDescription200400500
new-quoteCreate new quote session{syncId, currentPage, quoteData}Missing state/productCodeAuth failure, portal unreachable
open-quoteOpen RC1 quote by Q-number{syncId, currentPage, quoteData, links}Missing quoteNumber/state/productCodeQuote not found, PropertyApologyKickout
get-stepRead step data without submitting{syncId, currentPage, embedded, links, validationDetails}Invalid workflowNodeSession expired, quote corrupted
go-to-stepJump to a completed step{syncId, currentPage, quoteData}Step not yet completedSession expired
view-modelFetch edit view model (FinalSale, etc.){syncId, currentPage, quoteData}Invalid viewModelNameSession error
named-insuredStep 1: Applicant info{syncId, currentPage, quoteData}DisclosureProvided=N, invalid email domainSession error
productsStep 2: Property details{syncId, currentPage, quoteData}Eligibility failed, DwellingCoverage below replacement cost, invalid field codes (TypeOfConstruction, ExteriorWalls must use Progressive codes)PropertyApologyKickout (NullReferenceException on bad RC1 data)
household-membersStep 3: Additional members{syncId, currentPage, quoteData}Married PNI without spouse listed, missing Gender/MaritalStatusSession error
additional-detailsStep 4: Insurance history{syncId, currentPage, quoteData}Missing PriorInsurer, mortgagee required (TX/AZ/OH)PropertyApologyKickout (missing mortgagee — session permanently killed)
coveragesStep 5: Coverages + billing{syncId, currentPage, quoteData}Invalid coverage valuesSession error
portfolioStep 6: Portfolio verification{syncId, currentPage, quoteData}Verification failedSession error
point-of-saleStep 7: SSN + reports + mortgagee{syncId, currentPage, quoteData, reportData?}Missing SSN, PropertyPolicy required, CLUE ordering failedPropertyApologyKickout
final-saleStep 8: Bind policy, return documents{syncId, currentPage, quoteData, bindResult}E-sign not completed, reports not ordered, payment failedBind failed, Progressive API error

Standard Step Response (200)

{
"success": true,
"data": {
"syncId": "8fed2734-b02b-438c-b0e5-30a79d2a32f7",
"currentPage": "Workflow/.../ProductsHO",
"quoteData": { "ProductsHO": { "...form fields..." } },
"validationDetails": {},
"messages": {},
"links": [{ "Name": "next", "Uri": "/Slot301/api/v1/composite/NextWorkflowState?workflowNode=ProductsHO" }]
}
}

Point of Sale Response with Report Data (200)

The point-of-sale endpoint returns reportData when runReports: true. The OrderPos call is synchronous (~10s) — Progressive processes the CLUE report inline, no polling needed.

{
"success": true,
"data": {
"syncId": "43e06a71-a1c9-4b64-a59c-87d92f9f0e2a",
"currentPage": "Workflow/.../PointOfSale",
"quoteData": { "PointOfSale": { "...SSN, mortgagee, drivers..." } },
"validationDetails": {},
"messages": {},
"links": [],
"reportData": {
"propertyClueOrdered": true,
"clueOrdered": false,
"driverMvrOrdered": true,
"eligibleDriversMvrOrdered": true,
"insurViewStatus": null,
"clueReferenceNumber": null,
"clueOrderDate": null,
"mvrOrderDate": null,
"shouldDisplayPropertyClueClaimsTable": "N",
"orderPosElapsedMs": 10398,
"rawOrderPosResponse": {
"pointOfSale": {
"propertyClueOrdered": true,
"propertyClueSelectIndicator": "Y",
"clueNotNeeded": false,
"clueOrdered": false,
"driverMvrOrdered": true,
"eligibleDriversMvrOrdered": true,
"insurViewStatus": null,
"insurViewReferenceNumber": null,
"mvrReorder": false,
"mvrNotNeeded": false,
"shouldDisplayPropertyClueClaimsTable": "N",
"claimDatesAffectRate": "Y",
"drivers": {
"list": [{
"driverMvrOrdered": true,
"mvrOrderDate": null,
"mvrStatMocked": "N",
"mvrDrvrFrst": null,
"mvrDrvrLnam": null,
"licenseStatus": null
}]
}
}
}
}
}
}

Key fields for Fastlane Portal:

  • reportData.propertyClueOrderedtrue = Property CLUE report ordered (HO quotes)
  • reportData.clueOrderedtrue = Auto CLUE ordered (auto quotes only, false for HO)
  • reportData.driverMvrOrderedtrue = MVR report ordered for PNI driver
  • reportData.shouldDisplayPropertyClueClaimsTable"Y" = property has CLUE claims (show claims table)
  • reportData.orderPosElapsedMs — time taken (typically 8-12s for bureau processing)
  • reportData.rawOrderPosResponse — full Progressive response (camelCase keys from OrderPos)

FinalSale Bind Response (200 — policy issued)

The final-sale endpoint returns bindResult after calling FinalSalePropertySellQuote. In QA, the DemoModeAsiSale link auto-completes the sale.

{
"success": true,
"data": {
"syncId": "8fed2734-b02b-438c-b0e5-30a79d2a32f7",
"currentPage": "Workflow/.../FinalSaleHO",
"quoteData": {
"FinalSaleHO": {
"PniFullName": "john doe",
"PolicyEffectiveDate": "03/28/2026",
"EsignAuthorization": "Y",
"EsignDeliveryType": "Email"
}
},
"validationDetails": {},
"messages": {},
"links": [],
"bindResult": {
"sold": true,
"policyNumber": "P123456789",
"premiumTotal": 1645.78,
"effectiveDate": "03/28/2026",
"paymentStatus": null
}
}
}

Key bindResult fields:

  • soldtrue when Extenders.SoldPropertyPaymentSuccess = "True"
  • policyNumber — Progressive policy number (from QuotedProducts[].PolicyNumber)
  • premiumTotal — Total premium from SelectedBillPlan.TotalAmount.Amount
  • effectiveDate — Policy effective date
  • paymentStatus — Payment processing status (null in QA demo mode)

FinalSale Bind Failure (200 — validation edits, not sold)

{
"success": true,
"data": {
"currentPage": "Workflow/.../FinalSaleHO",
"quoteData": { "FinalSaleHO": { "...HasEdit fields..." } },
"validationDetails": {},
"bindResult": {
"sold": false,
"policyNumber": null,
"premiumTotal": 1645.78,
"effectiveDate": "03/28/2026",
"paymentStatus": null
}
}
}

FinalSale Premium Summary (from get-step at FinalSale)

{
"success": true,
"data": {
"currentPage": "Workflow/.../FinalSaleHO",
"quoteData": {
"FinalSaleHO": {
"PniFullName": "john doe",
"PolicyEffectiveDate": "03/28/2026",
"PrimaryEmailAddress": "john.doe@email.com",
"EsignAuthorization": "N",
"SelectedBillPlan": {
"TotalAmount": { "Amount": 1645.78 },
"PaymentOptionDescription": "Mortgage Billed",
"NumberOfPayments": "1",
"PaymentSchedule": [
{ "DueDate": "Today", "Amount": { "Amount": 1645.78 } }
]
}
}
}
}
}

400 — Validation Error (step did not advance)

When Progressive returns 400 with ActiveViewModelHasEdits: true, the step stays on the same page. Errors are in two places:

1. Top-level validationDetails (rare — most edits are embedded):

{
"success": true,
"data": {
"currentPage": "Workflow/.../NamedInsured",
"quoteData": { "NamedInsured": { "...fields..." } },
"validationDetails": { "PrimaryEmailAddress": "Not a valid email address." },
"messages": {}
}
}

2. Embedded edits (common — in Embedded.{Step}.*.Questions.List[n].HasEdit):

{
"success": true,
"data": {
"currentPage": "Workflow/.../ProductsHO",
"quoteData": {
"ProductsHO": {
"Properties": {
"List": [{
"Details": {
"...nested Questions with HasEdit: true...": {
"Property": "TypeOfConstruction",
"HasEdit": true,
"Edits": [{ "Description": "Entered value is not in the valid value list." }]
}
}
}]
}
}
},
"validationDetails": {}
}
}

Common 400 errors by step:

StepFieldErrorFix
NamedInsuredPrimaryEmailAddress"Not a valid email address."Use real domain (@gmail.com, not @email.com)
ProductsHOTypeOfConstruction"Entered value is not in the valid value list."Use Progressive codes: F, MY, SNC, V
ProductsHOExteriorWalls"Entered value is not in the valid value list."Use: Stucco, Brick, Vinyl, Wood, etc.
ProductsHODwellingCoverageValue"The value for this home is too low"Read ReplacementCostEstimate and use it as minimum
PeopleMaritalStatus"Married primary named insured requires a spouse"Add spouse or change MaritalStatus to "S"
PointOfSalePropertyPolicy"Please fill out this field to continue."Set to "NONE" even when PackagePolicy=NO

500 — Fatal Error (session killed)

PropertyApologyKickout — the session is permanently destroyed. No recovery possible.

{
"success": false,
"error": "Step AdditionalDetails failed. Please try again."
}

Server-side response:

{
"Links": [],
"Extenders": {
"PropertyApologyKickout": "True",
"PropertyApologyUrl": "/Slot301/Apology/ApologyPropertyUnexpectedError?SessionIndex=0&edits=Required%20field%20%27Mortgagee%20Name%27%20is%20missing."
},
"Embedded": {},
"ValidationDetails": {},
"Messages": {}
}

Common 500 triggers:

CauseStepPrevention
Missing mortgageeAdditionalDetailsAlways add mortgagee via sub-workflow BEFORE this step
Bad RC1 property dataProducts (eligibility)Server returns NullReferenceException — skip this quote
Expired/corrupted sessionAny stepRe-open with a fresh session
NextWorkflowState?workflowNode=FinalSaleFinalSaleUse FinalSaleHO (not FinalSale)
OrderPos while at AcknowledgeMVR modalPointOfSaleDismiss modal first, then call OrderPos

19. Remaining Work

  1. Named Insured — ✅ Working.
  2. Products — ✅ Working. Field codes must use Progressive values (e.g. F not Frame, Stucco not BrickVeneer).
  3. People — ✅ Working. Married PNI requires a spouse to be listed.
  4. Additional Details — ⚠️ Partially working. Mortgagee required for ALL states (TX, AZ, OH confirmed). Non-advancing 201 issue persists for some AZ quotes — missing Question-only fields.
  5. CoveragesHO — ✅ Working.
  6. Portfolio — ✅ Working.
  7. Point of Sale — ✅ Working. OrderPos 200 (CLUE ordered), MVR ordered via NextWorkflowState, SSN accepted, mortgagee sub-workflow works. PropertyPolicy field required even when PackagePolicy=NO. Confirmed on Q83806608 (AZ).
  8. Final Sale / Bind — ✅ Endpoint identified and implemented. PUT FinalSalePropertySellQuote with refreshRelevancy headers binds the policy. DemoModeAsiSale auto-completes in QA. Success indicated by SoldPropertyPaymentSuccess extender and PolicyNumber in QuotedProducts. Awaiting full E2E validation — blocked by AdditionalDetails non-advancing issue.

Known Issues

  • Quote reuse corruption: Re-opening the same RC1 quote corrupts Progressive's server-side state. Each quote should go through the flow once per session.
  • Mortgagee timing: All tested states (TX, AZ, OH) require mortgagee before AdditionalDetails. If missing, PropertyApologyKickout kills the session permanently.
  • AdditionalDetails non-advancing (AZ): The step returns 201 with no errors but doesn't advance. Root cause: Question-only fields in ProductSpecificInformation are empty after HAL stripping. The injectMissingQuestionFields fix works for known fields but AZ quotes may have additional state-specific fields. Needs further investigation.
  • AcknowledgeMVR modal dismiss: The modal at POS→FinalSale transition requires the full stripped view model as body (not {}). The SPA uses httpBackend.forward() which sends submitViewModelTo(nextLink.Uri) with the current view model. Our dismiss now sends the stripped modal state.
  • SSN format: Test SSNs 123-45-6789 and 078-05-1120 rejected. Use 987-65-4320 (masked as XXXXX4320).
  • Email validation: Progressive rejects @email.com domain. Use real domains like @gmail.com.
  • ASI Agent Code: RC1 quotes may use ASI code 377 (different from .env's 440744). The OpenQuote gateway accepts either code for agent 43786.
  • FinalSale workflow node: Use FinalSaleHO (not FinalSale). The step service and mapper now resolve the correct embedded key dynamically.
  • PointOfSale multi-submit pattern: The first submit may return 400 (SSN/mortgagee edits). Required: submit → dismiss AcknowledgeMVR modal (if present) → OrderPos → re-submit.
  • PropertyPolicy field: Required at PointOfSale even when PackagePolicy=NO. Set to "NONE" or any non-empty value.
  • Products field codes: TypeOfConstruction must be F/MY/SNC/V (not Frame/Masonry). ExteriorWalls must match Progressive valid values exactly (e.g. Stucco, Brick, Vinyl — not BrickVeneer). Check valid values from the HAL response's ValidValues arrays.
  • OrderPos timing: Must be called AFTER POS submit advances (and AcknowledgeMVR modal is dismissed). Calling OrderPos while at the modal returns 500.
  • SPA header requirements (FIXED 2026-03-14): All step submissions require actionName: submit, dynamic SessionIndex, and X-Request-ID headers.