> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pylon.mortgage/llms.txt
> Use this file to discover all available pages before exploring further.

# Fee sheets

> Generate a fee sheet for a loan and retrieve it as a PDF or as structured data for your own fee sheet experience

A fee sheet summarizes the estimated rate, payment, and closing costs for a loan at a specific rate. You can generate one for the rate selected on the loan, or for any eligible rate returned by pricing without selecting that rate. Retrieve the result as a PDF download or as structured GraphQL data to render your own fee sheet.

<Steps>
  <Step title="Request a fee sheet">
    Call `feeSheet.createFeeSheetRequestForLoan` for the loan, optionally naming an eligible product structure to quote.
  </Step>

  <Step title="Poll until it is ready">
    Poll `feeSheet.feeSheetRequestStatus` until the request reaches `SUCCEEDED`.
  </Step>

  <Step title="Retrieve the fee sheet">
    Download the PDF from the signed `url`, or query `data` to build your own fee sheet.
  </Step>
</Steps>

## Request a fee sheet

Call `feeSheet.createFeeSheetRequestForLoan` with the loan and, optionally, an eligible product structure:

```graphql theme={null}
mutation RequestFeeSheetForLoan(
  $input: CreateFeeSheetRequestForLoanInput!
) {
  feeSheet {
    createFeeSheetRequestForLoan(input: $input) {
      feeSheetRequestId
      errorMessage
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "loanId": "app_5ap9raB5XUhQZLLkpg31Ve",
    "productStructureId": "pprcrate_def456"
  }
}
```

| Field                | Required | Description                                                                                                                                                                                                                  |
| -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `loanId`             | Yes      | The loan to generate the fee sheet for.                                                                                                                                                                                      |
| `productStructureId` | No       | The `EligibleProductStructure.id` to quote, as returned by [`pricing.productPricing`](/guides/getting-started/pricing-optimizations/full-rate-sheet). When omitted, the mutation uses the rate already selected on the loan. |

<Note>
  A [TBD loan](/guides/getting-started/pricing-optimizations/tbd-pricing) cannot have a selected rate, so `productStructureId` is required when generating its fee sheet.
</Note>

A successful request returns an ID to poll:

```json theme={null}
{
  "data": {
    "feeSheet": {
      "createFeeSheetRequestForLoan": {
        "feeSheetRequestId": "fsr_ghi789",
        "errorMessage": null
      }
    }
  }
}
```

If `feeSheetRequestId` is `null`, the request was not created. This mutation reports creation failures through `errorMessage` rather than `userErrors`; show or log the message, and do not start polling.

***

## Poll until the fee sheet is ready

Fee sheet generation is asynchronous. Poll `feeSheet.feeSheetRequestStatus` with the request ID:

```graphql theme={null}
query FeeSheetRequestStatus($id: ID!) {
  feeSheet {
    feeSheetRequestStatus(id: $id) {
      id
      status
      url
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "id": "fsr_ghi789"
}
```

Poll about every two seconds and enforce a client-side timeout, such as 90 seconds.

| Status       | Action                                                                                                    |
| ------------ | --------------------------------------------------------------------------------------------------------- |
| `PENDING`    | The request is queued. Continue polling.                                                                  |
| `PROCESSING` | The fee sheet is being generated. Continue polling.                                                       |
| `SUCCEEDED`  | Download the PDF from `url` and, if needed, query the structured `data`.                                  |
| `FAILED`     | Stop polling. No download URL or failure reason is returned, so show a generic error with a retry action. |

## Download the PDF

A completed request returns a signed download URL:

```json theme={null}
{
  "data": {
    "feeSheet": {
      "feeSheetRequestStatus": {
        "id": "fsr_ghi789",
        "status": "SUCCEEDED",
        "url": "https://signed-download-url.example/fee-sheet.pdf"
      }
    }
  }
}
```

The URL is scoped to your organization and expires after three days. Treat it as short-lived rather than persisting it. Query a successful request again to obtain a newly signed URL.

## Build your own fee sheet from structured data

After the request reaches `SUCCEEDED`, query `data` on the same `feeSheetRequestStatus` object. It returns the structured generated fee sheet, so clients can render their own experience without parsing the PDF.

<Note>
  `data` is `null` while the request is `PENDING`, `PROCESSING`, or `FAILED`. Poll only `status` and `url`, then request the larger structured payload after the request succeeds.
</Note>

```graphql theme={null}
query FeeSheetRequestData($id: ID!) {
  feeSheet {
    feeSheetRequestStatus(id: $id) {
      id
      status
      url
      data {
        structure {
          rate
          rateId
          loanTermYears
          principal
          apr
          totalPoints
          totalCost
          monthlyPayment
          pitia {
            principalAndInterest
            taxes
            homeownersInsurance
            mortgageInsurance
            floodInsurance
            hoaDues
          }
          ltv
          dti
          sellerCredit
        }
        fees {
          id
          feeDescription
          feeActualTotalAmount
          feePaidToType
          feeType
          integratedDisclosureSectionType
          paidTo {
            legalEntity {
              fullName
            }
          }
        }
        property {
          state
        }
        loanPurpose
        loanOfficer {
          firstName
          lastName
          email
          nmlsId
          phone
        }
        ratesEffectiveDate
        generatedAt
        isCountyLevelEstimate
        qualifyingFicoScore
        disclaimerText
      }
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "id": "fsr_ghi789"
}
```

| Field                   | Description                                                                                                                                                                                                                                       |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `structure`             | The final structure calculated for the generated sheet. Values such as annual percentage rate (APR), payment, and cost can differ from the initially selected pricing result after fee data is resolved.                                          |
| `fees`                  | Itemized fee amounts, categories, and payees. Fee amount fields are rounded to whole-dollar integers and payment splits are not populated.                                                                                                        |
| `property`              | The property state used for pricing. Other `Address` fields are currently not populated in structured fee-sheet data.                                                                                                                             |
| `loanPurpose`           | The loan purpose used to generate the fee sheet.                                                                                                                                                                                                  |
| `loanOfficer`           | The loan officer contact printed on the fee sheet, when available.                                                                                                                                                                                |
| `ratesEffectiveDate`    | The date when the rate sheet backing the quote was ingested, when available.                                                                                                                                                                      |
| `generatedAt`           | When the fee sheet finished generating, when available.                                                                                                                                                                                           |
| `isCountyLevelEstimate` | `true` when the quote was priced using a county instead of a street address, as with [TBD pricing](/guides/getting-started/pricing-optimizations/tbd-pricing). It can be `null` on older fee sheets; do not treat `null` as a street-level quote. |
| `qualifyingFicoScore`   | The qualifying credit score used for pricing, when available.                                                                                                                                                                                     |
| `disclaimerText`        | The compliance disclaimer printed on the fee sheet.                                                                                                                                                                                               |

```json theme={null}
{
  "data": {
    "feeSheet": {
      "feeSheetRequestStatus": {
        "id": "fsr_ghi789",
        "status": "SUCCEEDED",
        "url": "https://signed-download-url.example/fee-sheet.pdf",
        "data": {
          "structure": {
            "rate": 0.065,
            "rateId": "rate_abc123",
            "loanTermYears": 30,
            "principal": 450000,
            "apr": 6.72,
            "totalPoints": 0.5,
            "totalCost": 5200,
            "monthlyPayment": 3969.31,
            "pitia": {
              "principalAndInterest": 2844.31,
              "taxes": 937.5,
              "homeownersInsurance": 187.5,
              "mortgageInsurance": 0,
              "floodInsurance": 0,
              "hoaDues": 0
            },
            "ltv": 0.8,
            "dti": 0.36,
            "sellerCredit": 2500
          },
          "fees": [
            {
              "id": "fscc_jkl012",
              "feeDescription": "Appraisal fee",
              "feeActualTotalAmount": 650,
              "feePaidToType": "THIRD_PARTY_PROVIDER",
              "feeType": "APPRAISAL_FEE",
              "integratedDisclosureSectionType": "SERVICES_YOU_CANNOT_SHOP_FOR",
              "paidTo": {
                "legalEntity": {
                  "fullName": "Example Appraisal Company"
                }
              }
            }
          ],
          "property": {
            "state": "CA"
          },
          "loanPurpose": "PURCHASE",
          "loanOfficer": {
            "firstName": "Taylor",
            "lastName": "Morgan",
            "email": "taylor@example.com",
            "nmlsId": "123456",
            "phone": "415-555-0100"
          },
          "ratesEffectiveDate": "2026-08-20",
          "generatedAt": "2026-08-20T12:00:00.000Z",
          "isCountyLevelEstimate": true,
          "qualifyingFicoScore": 720,
          "disclaimerText": "Rates and fees are illustrative and subject to change."
        }
      }
    }
  }
}
```

The example selects common fields for a custom fee-sheet view. `structure` and `fees` expose additional fields in the [GraphQL API Reference](https://sandbox.pylon.mortgage/documentation/graphql/index.html#introduction). In the current response, `structure.rate`, `structure.ltv`, and `structure.dti` are decimal fractions (`0.065` means 6.5%), while `structure.apr` is a percentage.

## Retries and duplicate requests

Requests with the same loan, product structure, and server-derived pricing inputs are idempotent. A retry reuses an active or successful request instead of generating another fee sheet. A retry after `FAILED` creates a new request.

If polling times out or the client loses the request ID, call the creation mutation again with the same input and continue with the returned request ID.

## Related

* [TBD pricing](/guides/getting-started/pricing-optimizations/tbd-pricing) — Price a loan before its street address is known and quote estimated fee sheets for eligible rates.
* [Full rate sheet optimizations](/guides/getting-started/pricing-optimizations/full-rate-sheet) — Retrieve complete pricing results and the `EligibleProductStructure` IDs you can pass as `productStructureId`.
* [Pricing optimizations overview](/guides/getting-started/pricing-optimizations/overview) — Learn about optimization objectives, constraints, and pricing response types.
