Automate the ClaimCenter FNOL→investigation→reserve→payment→settlement→close pipeline including the failure paths — duplicate FNOL from multi-source intake, reserve-must-precede-payment ordering, supervisor-authorization tiers, premature settlement, and reopen-vs-new-claim ambiguity. Use when building claim intake from caller portals, IVR, or partner systems; automating reserve-setting jobs; or integrating settlement events with finance. Trigger with "claimcenter automation", "FNOL", "claim reserve", "claim payment", "claim settlement", "claim reopen".
89
88%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advisory
Suggest reviewing before use
Drive the ClaimCenter claims lifecycle through Cloud API and survive the failure modes that derail naive automation. This is the workflow used by FNOL portals, IVR claims intake, partner-of-record APIs, and reserve-setting jobs. Assumes guidewire-install-auth provides the bearer token and guidewire-sdk-patterns provides the retrying client with checksum round-trip.
Five production failures this skill prevents:
422 reserve-required.cc.payment.write attempts a payment above its authorization limit; API returns 422 authorization-required and the payment lands in pending-approval.guidewire-install-auth and guidewire-sdk-patternscc.claim.write, cc.reserve.write, cc.payment.write assigned per least privilege; payment authorization tier matches the integration's expected payment rangeAUTO, PROPERTY, WORKERSCOMP, etc.) and cost-category configuration (body, parts, medical, legal)policyNumber + lossDate + lossCause + reporterId)Before creating a claim, check whether one already exists for the same loss event. The dedup key combines policy, loss date, loss cause, and the reporting party.
async function findExistingClaim(policyNumber: string, lossDate: string, lossCauseCode: string): Promise<Claim | null> {
const url = `${BASE}/cc/rest/v1/claims?filter=policyNumber+eq+'${policyNumber}'+and+lossDate+eq+'${lossDate}'+and+lossCause.code+eq+'${lossCauseCode}'`;
const res = await fetch(url, { headers: { Authorization: `Bearer ${await getToken()}` } });
const body = await res.json();
return body.data[0] ?? null;
}
const existing = await findExistingClaim(policyNumber, lossDate, lossCauseCode);
if (existing) {
await addReporterToExistingClaim(existing.attributes.id, reporter); // additional reporter, same loss
return { status: "deduplicated", claimNumber: existing.attributes.claimNumber };
}
const claim = await createClaim({ policyNumber, lossDate, lossCauseCode, reporter, description });The dedup window is loss-cause-specific. For AUTO, same-day same-cause is almost certainly the same event. For PROPERTY, weather events can produce multiple legitimate same-cause same-day claims across distinct locations — extend the dedup key with the loss-location ZIP for that line.
Reserves communicate the carrier's expected outflow per cost category. The Cloud API enforces "reserve before payment" — payments without a matching reserve return 422 reserve-required.
await retryable(async () => {
const res = await fetch(`${BASE}/cc/rest/v1/claims/${claimId}/reserves`, {
method: "POST",
headers: { Authorization: `Bearer ${await getToken()}`, "Content-Type": "application/json", "Idempotency-Key": idempotencyKey },
body: JSON.stringify({
data: { attributes: {
reserveAmount: { amount: 5000, currency: "usd" },
costType: { code: "claimcost" },
costCategory: { code: "body" },
reserveLine: { exposure: { id: exposureId } },
} },
}),
});
if (!res.ok) throw await mapError(res, "POST", `/cc/rest/v1/claims/${claimId}/reserves`);
});Reserve adjustments (raise/lower) use PATCH on the reserve resource; checksum round-trip applies. Lowering a reserve below cumulative payments returns 422 — fix payments first or use a reserve transfer.
The integration's authorization tier is configured per Service Application in GCC. A payment that exceeds the tier does not error — it lands in Status: PendingApproval and waits for a supervisor:
const payment = await fetch(`${BASE}/cc/rest/v1/claims/${claimId}/payments`, {
method: "POST",
headers: { Authorization: `Bearer ${await getToken()}`, "Content-Type": "application/json", "Idempotency-Key": idempotencyKey },
body: JSON.stringify({ data: { attributes: paymentBody } }),
}).then(r => r.json());
if (payment.data.attributes.status.code === "PendingApproval") {
await emitPaymentEscalationEvent({ claimId, paymentId: payment.data.attributes.id, amount: paymentBody.amount });
return { status: "pending-approval", paymentId: payment.data.attributes.id };
}Do not poll the payment for completion. Subscribe to the App Event for payment-status-change and react asynchronously — this is covered in guidewire-webhooks-integrations.
A claim should only close when every exposure has either been paid in full, denied, or has reserves zeroed; and every required activity (subro decision, salvage decision, supervisor sign-off) is completed.
async function isReadyToSettle(claimId: string): Promise<{ ready: boolean; blockers: string[] }> {
const claim = await getClaim(claimId, "exposures,activities,reserves");
const blockers: string[] = [];
for (const reserve of claim.included.reserves) {
if (reserve.attributes.status.code === "Open" && reserve.attributes.amount.amount > 0) {
blockers.push(`reserve ${reserve.id} is ${reserve.attributes.amount.amount} open`);
}
}
for (const activity of claim.included.activities) {
if (activity.attributes.required && activity.attributes.status.code !== "Completed") {
blockers.push(`activity ${activity.id} (${activity.attributes.subject}) not completed`);
}
}
return { ready: blockers.length === 0, blockers };
}Only call the close endpoint when isReadyToSettle returns ready: true. Premature closure works but creates audit findings and forces reopens.
Late-arriving evidence on a closed claim is almost always the same loss event. Reopen the existing claim rather than creating a new one — claim numbers must remain stable for the underlying loss event so finance, analytics, and regulatory reporting continue to roll up correctly.
async function handleLateEvidence(claimNumber: string, evidence: Evidence): Promise<Result> {
const claim = await getClaimByNumber(claimNumber);
if (claim.attributes.status.code === "Closed") {
await fetch(`${BASE}/cc/rest/v1/claims/${claim.attributes.id}/reopen`, {
method: "POST",
headers: { Authorization: `Bearer ${await getToken()}`, "Idempotency-Key": idempotencyKey },
body: JSON.stringify({ data: { attributes: { reason: evidence.reason } } }),
});
}
await attachEvidence(claim.attributes.id, evidence);
return { status: "reopened", claimNumber };
}The reopen endpoint requires a reason — log the evidence type and source so the audit trail explains why the claim was reopened.
A complete ClaimCenter workflow integration ships with all of the following:
{ status: "created" | "deduplicated", claimNumber }.PendingApproval status and emits an escalation event rather than blocking the calling thread.isReadyToSettle() gate that inspects reserves and required activities before allowing the close endpoint to be called.const result = await intakeFnol({ policyNumber, lossDate, lossCauseCode, reporter, description });
if (result.status === "deduplicated") {
return { claimNumber: result.claimNumber, message: "Loss already on file; reporter added" };
}
return { claimNumber: result.claimNumber, message: "New claim opened" };await setReserve(claimId, { amount: 50000, costCategory: "medical" });
const payment = await createPayment(claimId, { amount: 35000, costCategory: "medical", payee });
if (payment.status === "pending-approval") {
await notify("supervisor", { claimId, paymentId: payment.id, amount: 35000 });
}const { ready, blockers } = await isReadyToSettle(claimId);
if (!ready) return { status: "not-ready", blockers };
await closeClaim(claimId, { reason: "settled" });| Error | Cause | Solution |
|---|---|---|
422 reserve-required on payment POST | no open reserve in the matching cost category | call reserve POST first, then retry the payment |
422 authorization-required on payment | payment exceeds the integration's authorization tier | not an error path — the payment lands in PendingApproval; subscribe to status-change event |
422 reserve-below-payments on reserve PATCH (lower) | trying to lower a reserve below the cumulative paid amount | reverse a payment first, or use a reserve transfer endpoint |
409 Conflict on claim PATCH | concurrent edits | use patchResource() from guidewire-sdk-patterns |
422 close-blocked on close endpoint | open reserves or incomplete required activities | run isReadyToSettle() before calling close |
| Two claim numbers exist for the same loss event | dedup logic is missing or the dedup key is too narrow | extend the key (add ZIP for property lines, add VIN for auto) |
| Reopen endpoint returns 422 | claim is too old (carrier-configured retention) | new claim is the only path; document the loss-event linkage in the new claim's notes |
| Late evidence created a new claim instead of reopening | code did not check existing-claim status before creating | always check getClaimByNumber() before createClaim() for late events |
For deeper coverage (subrogation tracking, salvage handling, recovery payments, multi-claimant scenarios, fraud-flagging on intake), see implementation guide and API reference.
guidewire-install-auth — bearer token and scope assignmentguidewire-sdk-patterns — retrying client, checksum round-trip, idempotencyguidewire-core-workflow-a — the equivalent submission→bind→issue pipeline in PolicyCenterguidewire-webhooks-integrations — App Events for payment-status-change, reserve-changed, claim-closedguidewire-observability-and-incident-response — interpreting reserve-balance and payment-pending signals in production3a2d27d
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.