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. RC1 Quotes, Agent Visibility, and the OpenQuote Flow

How RC1 Quotes Are Created (DA → MuleSoft → Progressive)

When a customer uses the Digital Agent (DA), MuleSoft submits an RC1 request to Progressive's system. This creates a quote with pre-populated data (applicant, property, coverages) and generates a Progressive Q-number (e.g., Q83806794). The Q-number is stored in our CRN sf_quote_response table. Without this RC1 submission, Progressive has no property data to rate against.

Agent Visibility Constraint (Critical)

MuleSoft uses its own dedicated agent credentials (user ID and password) to submit RC1 quotes to Progressive. These quotes are bound to MuleSoft's agent account in the Progressive portal and are only visible to that agent. Our API automation and config-driven systems authenticate with different credentials — we cannot see or open MuleSoft-created RC1 quotes.

The OpenQuote endpoint (documented below) only works when the requesting agent matches the agent that originally created the quote. Our agent credentials will fail when attempting to open a quote created under MuleSoft's agent.

Previous assumption (INCORRECT): Earlier testing used OpenQuote with specific Q-numbers that happened to be accessible in QA. This approach does not generalize — in production, MuleSoft quotes are strictly scoped to MuleSoft's agent.

Future: RC1 Clone/Recreate Strategy

To bridge this gap, the system must:

  1. Receive RC1 quote data from MuleSoft — applicant info, property details, coverages, and any other pre-populated fields from the RC1 submission
  2. Create a new quote session in Progressive using our agent credentials (via NewQuote gateway)
  3. Populate each step with the RC1 data — programmatically submit NamedInsured, ProductsHO, etc. with the data MuleSoft originally provided, effectively cloning the quote under our agent

This clone approach preserves the pre-populated data advantage of RC1 while using credentials we control. The key challenge is that NewQuote produces a different workflow than OpenQuote (see table below) — the clone implementation must account for these differences.

Why RC1 Data Matters

Even though we cannot directly open MuleSoft's quotes, the RC1 data itself is essential. Progressive needs property data (address, construction, roof, etc.) to generate a replacement cost estimate and rate the policy. The RC1 submission provides this data upfront. In the clone approach, we re-submit this same data through Progressive's step workflow under our own session.

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 (Reference)

Visibility note: These Q-numbers belong to MuleSoft's agent and are not accessible via our agent credentials. This reference is retained for debugging, cross-referencing MuleSoft submissions, and extracting RC1 data for the clone/recreate strategy.

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 (MuleSoft agent only): Log in to the Progressive portal at 62.qa.foragentsonly.com using MuleSoft's credentials → Quote Search → filter by Home (HO3), state, Unsold. These quotes will NOT appear when logged in with our automation credentials.

OpenQuote Endpoint (Reference)

Credential constraint: This endpoint requires the requesting agent to match the agent that created the quote. It will not work with our credentials for MuleSoft-created quotes. Retained here for technical reference and for cases where quotes are created under our own agent (e.g., via the future clone strategy or manual portal testing).

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 (must match the agent that created the quote)
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 Data)

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 submitted dwelling value is too low (common with RC1-sourced data), 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 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-sourced data typically doesn't include. These are nested in ProductSpecificInformation.List[0].Embedded.Questions and must be supplied by the mapper.

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 when the upstream data is already populated (e.g., from RC1-sourced data) — submit the current HAL state unchanged.

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

The OpenQuote flow uses DIFFERENT workflow node names than previously documented. These same names apply regardless of whether the quote was opened via OpenQuote or created via NewQuote with RC1 data populated through the clone strategy.

Old NameActual NameNotes
HouseholdMembersPeopleUses People step
CoveragesBillPlansCoveragesHOUses 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 — via MuleSoft agent): NamedInsured → Products → People → AdditionalDetails → Coverages → Portfolio → PointOfSale (SSN + OrderPos 200) → FinalSaleHO reached. (This test used MuleSoft's agent credentials to open an RC1 quote; see Section 7 for agent visibility constraints.)

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)

Quotes with RC1-sourced data 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 — via MuleSoft Agent)

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 existing quote by Q-number (requires matching agent credentials — see Section 7 agent visibility constraint){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 property 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 property data (from RC1 or clone)Products (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. Config-Driven Frontend Architecture (Discovered 2026-04-02)

Progressive's quoting portal is fully config-driven. The Angular SPA does not hardcode any form fields — every field's type, validation, visibility, options, and labels are delivered by the REST API in each step's HAL response. This means the portal can be white-labeled by consuming Progressive's API directly and rendering our own UI from their field configuration.

Discovery Method

Authenticated against QA (62.qa.foragentsonly.com) via curl, created a quote session through the QuotingGateway redirect chain, then called CurrentWorkflowState and NextWorkflowState to capture raw HAL responses.

Question Schema

Every form field in every step is represented as a Question object inside Embedded.{StepName}.Embedded.Questions.List[]. Each Question contains:

PropertyTypePurpose
PropertystringField name/key (e.g. FirstName, TypeOfConstruction)
FieldTypestringData type: MVP_TEXT, MVP_INTEGER, date
ControlTypestringUI widget hint (empty in practice — SPA uses FieldType)
LabelstringDisplay label
ShortLabelstringCompact label variant
SubLabelstringSecondary label text
PlaceholderstringInput placeholder
LabelTypestringLabel display mode
AdditionalHeaderTextstringSection header text
RequiredbooleanWhether field is mandatory
DisabledbooleanWhether field is read-only
ShouldDisplaybooleanField visibility
ShouldDisplayControlbooleanUI control visibility
ShouldDisableControlbooleanUI control enabled state
ValidValuesarrayDropdown options: [{ Value, Display }]
MaxLengthintegerCharacter limit
MaskablebooleanWhether to mask input (e.g. SSN)
IsValueAutoTrimmedbooleanAuto-trim whitespace
LastAnswerstringCurrent/default value
HasEditbooleanHas validation error
EditsarrayValidation error messages: [{ Description }]
ForceEditToBeDisplayedbooleanForce show validation error
ForceRefreshRelevancybooleanTrigger re-render on value change
ValidationIdstringValidation rule ID (e.g. alphaNumSpcl1b, numeric, alphaSpcl1)
AdditionalCharacterValidationstringExtra character validation
ClientValidationIdOverridestringClient-side validation override
HoverHelpIdstringTooltip/help content ID
HoverHelpDisplayNamestringTooltip display name
ValuesToTranslatearrayi18n translation mappings
ShouldTranslateLastAnswerbooleanWhether to translate the answer

ProgressBar Configuration

The ProgressBar is also config-driven. Each step is an item with navigation state:

{
"Title": "NAMED INSURED",
"Id": "NamedInsured",
"Enabled": true,
"Complete": false,
"HasEdits": false,
"Disabled": false,
"Visited": true
}

The 8-step flow: NamedInsuredProductsHouseholdMembersAdditionalDetailsCoveragesPortfolioPointOfSaleFinalSale.

Step Response Sizes

StepResponse SizeQuestion FieldsFields with ValidValues
NamedInsured~164 KB2912 (Gender, State, Suffix, etc.)
ProductsHO~213 KB6445 (construction, roof, exterior, etc.)

NamedInsured Field Configuration (29 fields)

All fields delivered by CurrentWorkflowState?workflowNode=NamedInsured in Embedded.NamedInsured.Embedded.Questions.List:

PropertyFieldTypeRequiredValidValuesValidationId
FirstNameMVP_TEXTYesalphaNumSpcl1b
LastNameMVP_TEXTYesalphaSpcl1
MiddleInitialMVP_TEXTNoalpha
SuffixMVP_TEXTNo6 options (JR, SR, IIV)
DateOfBirthdateYes
GenderMVP_TEXTYesF, M
MailingAddressMVP_TEXTYesalphaNumSpcl3
CityMVP_TEXTYesalphaSpcl6
StateMVP_TEXTYes57 options (all US states + territories)
ZipCodeMVP_INTEGERYesnumeric
RecentlyMovedMVP_TEXTYesY, N
PriorAddressMVP_TEXTYesalphaNumSpcl3
PriorStateMVP_TEXTYes51 options
AgentCodeMVP_TEXTYes6 options (agent's available codes)
AsiAgentCodeMVP_TEXTYes3 options (440744, 446447, 453719)
DisclosureProvidedMVP_TEXTYesY, N
HasInternationalAddressMVP_TEXTNoY, N
InternationalCountryMVP_TEXTYes246 options (ISO country codes)
PrimaryEmailAddressMVP_TEXTNoalphaNumSpcl4

Hidden fields (ShouldDisplay: false) include PriorAddress, PriorCity, PriorState, PriorZipCode (shown only when RecentlyMoved=Y) and all International* fields (shown only when HasInternationalAddress=Y).

ProductsHO Field Configuration (64 fields across 5 sections)

The ProductsHO step nests Questions into sub-objects matching the property structure:

Address Section (Properties.List[0].Address.Embedded.Questions) — 30 fields:

Eligibility flags (all MVP_TEXT, Required, ValidValues Y/N or 1/2): HomeClosingRiskFlag, HomeForeclosureFlag, ResidenceType (11 options), NewConstructionFlag, ExcludeWindFlag (hidden), PriorLiabilityFlag, SinkholeFlag, TrampolineFlag, UndergroundTankFlag, IneligibleDogFlag, RoofOverlayFlag, ConstructionFlag, UnrepairedFlag, BusinessAtLocationFlag, PoolFenceFlag, ResidentEmployeeFlag, HomeDaycareFlag, TwoPlusClaimsFlag, CanceledNonrenewedDeclinedFlag, IneligibleElectricalSystemFlag, IneligiblePlumbingSystemFlag, PropertyEndorseAsLocationAddressFlag, VerifyNoInelCond, VerifyNoUwCond.

Two of these merit a clarifying note because their FAO presentation differs from how Fastlane surfaces (or hides) them:

  • HomeClosingRiskFlag is the FAO "Is this home a new purchase?" Y/N radio. It is captured upstream in DA/GaQ (CRN disclosures.homeClosing) and prefilled into HAL by CrnToProductsHoMapper.applyDisclosures; Fastlane does NOT re-ask it on the Property Details step. The value round-trips through HAL LastAnswer on every /step-config/submit, so its Y/N answer remains authoritative for downstream gating (e.g. PurchasePrice.ShouldDisplay).
  • NewConstructionFlag is the FAO "New Construction" inline-checkbox attribute (a brand-new build that has not yet been finished/occupied). HAL reports Required: false because on FAO the unchecked checkbox implies N. Fastlane renders it as a Yes/No tile via ControlledHalRadioCard and adds it to FORCE_REQUIRED_FIELDS so the user must make an explicit selection — Yes/No tiles have no implicit "no answer = N" the way the FAO checkbox does. The CRN mapper does NOT prefill this field; the user's Fastlane choice is the sole source.

RCE Section (Properties.List[0].Rce.Embedded.Questions) — 3 fields:

FieldValidValues
YearBuilt(integer, no options)
NumberOfStories7 options: 1, 1H, 2, 2H, 3, BILEVEL, TRILEVEL
LivingArea(integer, no options)

Details Section (Properties.List[0].Details.Embedded.Questions) — 23 fields:

FieldValidValues
TypeOfConstruction4: F, MY, SNC, V
ExteriorWalls17 options
RoofMaterial17 options
RoofDesign3: GABLE, HIP, FLAT
Substructure9 options
GarageType13 options
CoolingType6 options
HeatingType12 options
SwimmingPool4 options
KitchenGrade4: 0, 1, 2, 3
BathroomGrade4: Standard, Custom, Designer, SemiCust
DeckType4: None, Wood, Redwood, Composite
PropertyAcreage4: 1 (0-10ac), 2 (11-20), 3 (21-30), 4 (31+)
ProtectionClass10: 0110
NumberOfFullBaths8: 18
NumberOfHalfBaths9: 08

Conditionally hidden: PurchasePrice (revealed when HomeClosingRiskFlag === 'Y', i.e. the GaQ/RC1 flow captured the quote as a new purchase). Hidden fields: FireHydrant1000Feet, PctBasement, PctFinishedBasement, DeckArea.

Two Details Section fields are intentionally NOT re-asked on Fastlane and are instead prefilled into HAL from CRN by CrnToProductsHoMapper.applyConstruction (same upstream-capture pattern as HomeClosingRiskFlag):

  • RoofMaterial — captured upstream in DA/GaQ as "Roofing Materials" and stored in crn.dwelling.roofMaterialCd. The CRN code is translated to Progressive's enum via the ROOF_MATERIAL_CD_TO_PROGRESSIVE table (e.g. CompShingleCompositeShingle) and round-trips through HAL LastAnswer on every /step-config/submit.
  • YearRoofInstalled — captured upstream in DA/GaQ as "Year Roof Constructed/Replaced" and stored in crn.dwelling.roofingImprovementYear. The integer year is passed through unchanged.

FlowType Section (Properties.List[0].FlowType.Embedded.Questions) — 2 fields:

  • DwellingCoverageValue (Required, editable)
  • ReplacementCostEstimate (Hidden, Disabled — read-only calculated value)

Each step response includes a Links array (21 links for NamedInsured) providing the full navigation graph:

Link NameDescriptorsPurpose
{StepName}view-model, activeGET the edit view model
nextworkflow, nextPOST to advance to next step
goToworkflow, goToPOST to jump to a specific step
currentworkflow, currentGET current step state
PrintLinkPrint view
ProgressBarauto-expandStep progress config
IaqHeaderauto-expandCustomer header
IaqQuoteContextauto-expandSession context
helpgetContextual help
ApologyIndexGeneric error page
ApologySessionExpiredSession timeout page
ApologySessionAccessSession recovery
ApologyPropertyProperty-specific error

ScriptInfoLibrary

The AppInitWorkflowState response includes a ScriptInfoLibrary array for external script injection:

[
{ "Name": "Quantum", "EnvironmentAttributes": { "src": "https://perf-www.qa.progressive.com/js/cdn/qm/quantum-progressivenonprod.js" } },
{ "Name": "ForMotiv", "EnvironmentAttributes": { "endPoint": "https://testapi.formotiv.com/v1", "apiHost": "https://testapi.formotiv.com", "userId": "..." } }
]

White-Label Implications

Since Progressive's API delivers the complete form configuration for every step:

  1. Field rendering — Our portal can read Questions.List and render each field using FieldType, ValidValues, Required, ShouldDisplay, MaxLength, and ValidationId
  2. Validation — Client-side validation rules are provided via ValidationId (e.g. alphaNumSpcl1b, numeric). Server-side validation is returned as HasEdit/Edits on re-submission
  3. Conditional visibilityShouldDisplay and ShouldDisplayControl change per response (e.g. PriorAddress fields appear when RecentlyMoved=Y). The ForceRefreshRelevancy flag on a field means changing its value should trigger a re-fetch for updated visibility
  4. Dropdown options — All select/radio options come from ValidValues arrays — no hardcoding needed
  5. Navigation — The Links array and ProgressBar items drive step sequencing, enabling/disabling, and completion tracking
  6. Form stateLastAnswer provides the current value for every field, enabling prefill from RC1-sourced data (via clone strategy) or previously submitted values

The key constraint: Progressive's server maintains the session state. All form data must be submitted via their API (NextWorkflowState) — we cannot skip steps or submit out of order. But we can fully control the UI/UX while Progressive's backend handles quoting logic, eligibility, rating, and policy issuance.

20. Remaining Work

Critical: RC1 Clone/Recreate Strategy (Blocking Production Use)

MuleSoft RC1 quotes are created under MuleSoft's agent credentials and are not visible to our automation agent (see Section 7). Before the direct HTTP API can be used in production, we must implement the clone/recreate flow:

  1. Extract RC1 data from MuleSoft — Pull the applicant, property, coverage, and other pre-populated fields from the MuleSoft RC1 submission (available in CRN sf_quote_request/sf_quote_response tables or via a MuleSoft webhook/API)
  2. Create a new session under our agent — Call NewQuote via the QuotingGateway with our credentials
  3. Map RC1 data into each Progressive step — Submit NamedInsured, ProductsHO, People, AdditionalDetails, CoveragesHO, Portfolio, and PointOfSale with the RC1-sourced data
  4. Handle NewQuote vs OpenQuote workflow differencesNewQuote produces a different step flow (see Section 7 table). The clone implementation must account for step ordering and any modal acknowledgements that differ from the OpenQuote path

Open questions:

  • Does NewQuote produce the full 8-step workflow when property data is submitted at NamedInsured/Products, or does it always truncate to the abbreviated flow?
  • Can DuplicateQuoteWorkflowState be used to clone a quote across agents?
  • Should the clone happen at RC1 time (proactively) or at quote-open time (on demand)?

Step Implementation Status

  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, via MuleSoft agent).
  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

  • RC1 agent visibility (BLOCKING): MuleSoft-created RC1 quotes are bound to MuleSoft's agent credentials and invisible to our automation agent. The OpenQuote endpoint returns "quote not found" when our agent tries to open them. See Section 7 for the full explanation and the clone/recreate strategy required to resolve this.
  • Quote reuse corruption: Re-opening the same 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: MuleSoft RC1 quotes may use ASI code 377 (different from .env's 440744). When using OpenQuote under the matching agent, the gateway accepts either code for agent 43786. With the clone strategy, our agent's ASI codes must be used instead.
  • 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.