Overview
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:
- Duplicate FNOL from multi-source intake — the same loss event is reported by the caller, the claimant, and the broker; without dedup logic three claims land on the same loss with three reserves.
- Payment before reserve — code creates a payment without first setting a reserve in the matching cost category; API returns
422 reserve-required. - Authorization-tier violation — an integration with payment role
cc.payment.writeattempts a payment above its authorization limit; API returns422 authorization-requiredand the payment lands in pending-approval. - Premature settlement — claim closed before all open reserves are zeroed or all open activities resolved; reopens compound work and generate audit findings.
- Reopen vs. new claim confusion — late-arriving evidence on a closed claim creates a new claim with a different number, breaking the loss-event continuity downstream finance and analytics depend on.
Prerequisites
- A working auth + SDK layer per
guidewire-install-authandguidewire-sdk-patterns - Cloud API roles
cc.claim.write,cc.reserve.write,cc.payment.writeassigned per least privilege; payment authorization tier matches the integration's expected payment range - Knowledge of the carrier's loss-cause code list (
AUTO,PROPERTY,WORKERSCOMP, etc.) and cost-category configuration (body,parts,medical,legal) - A loss-dedup key strategy (typically:
policyNumber + lossDate + lossCause + reporterId)
Instructions
1. FNOL with deduplication
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.
2. Reserve setting before any payment
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.
3. Payments with authorization-tier handling
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.
4. Settlement with completeness gates
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.
5. Reopen vs. new claim
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.
Output
A complete ClaimCenter workflow integration ships with all of the following:
- A FNOL function with a configurable dedup key (per loss-cause line) that returns
{ status: "created" | "deduplicated", claimNumber }. - Reserve-setting that runs before any payment in the same cost category, with a transfer/adjustment helper for raising or lowering reserves safely.
- Payment creation that recognizes the
PendingApprovalstatus and emits an escalation event rather than blocking the calling thread. - A
isReadyToSettle()gate that inspects reserves and required activities before allowing the close endpoint to be called. - A reopen handler that distinguishes late evidence (reopen) from a genuinely new loss event (new claim).
- A claim-event log capturing every transition with correlation_id, idempotency_key, and reason.
Examples
Example 1 — FNOL with dedup
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" };
Example 2 — Reserve, payment, escalation
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 });
}
Example 3 — Settlement readiness check
const { ready, blockers } = await isReadyToSettle(claimId);
if (!ready) return { status: "not-ready", blockers };
await closeClaim(claimId, { reason: "settled" });
Error Handling
| 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.
See Also
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 production