> ## 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.

# Floating a rate

> Understanding floating - dispatching initial disclosures and committing to a product structure without locking the interest rate.

**Floating a rate** means committing a borrower to a specific loan product and dispatching their initial disclosures **without locking the interest rate**. A floated loan has started the [TRID](/entity-models/key-concepts/trid) disclosure clock and is committed to a product structure, but the borrower's rate can still move with the market until it is locked.

Floating is the first half of the [rate lock](/entity-models/key-concepts/rate-lock) flow—everything that happens before the rate is actually locked. It lets a borrower begin the loan in earnest (receive a Loan Estimate, sign disclosures, establish [Intent to Proceed (ITP)](/entity-models/key-concepts/itp), and trigger order-outs) while deliberately choosing **not** to lock the rate yet.

## Floating vs. locking

When a borrower selects a product structure, there are two ways to move the loan forward:

|                                | **Float** (`floatStructure`) | **Lock** (`confirmRateLock`) |
| ------------------------------ | ---------------------------- | ---------------------------- |
| Initial disclosures dispatched | ✅ Yes                        | ✅ Yes                        |
| Product structure committed    | ✅ Yes                        | ✅ Yes                        |
| Interest rate locked           | ❌ No                         | ✅ Yes                        |
| Protected from rate increases  | ❌ No                         | ✅ Yes                        |
| Can capture rate decreases     | ✅ Yes                        | ❌ No (without a float-down)  |

Both paths send initial disclosures and freeze the loan to its selected product. The difference is the rate: a floated borrower remains exposed to market movement—up or down—until they lock, whereas a locked borrower is protected from increases but can no longer benefit from decreases (unless they pay for a float-down option). See [Rate lock](/entity-models/key-concepts/rate-lock) for the locked side of this flow.

<Note>
  Floating is **not** the same as simply attaching a product structure. Attaching a product structure starts a 3-business-day timer after which initial disclosures auto-fire. Floating dispatches those initial disclosures **immediately**, on demand, without waiting for the timer and without locking the rate.
</Note>

## Why float instead of lock?

Floating is useful when a borrower is ready to move forward with the loan process but is not yet ready to commit to a rate:

* **Begin processing sooner** — Dispatching initial disclosures lets borrowers sign, establish [ITP](/entity-models/key-concepts/itp), and trigger order-outs (appraisal, title, verifications) without waiting to lock.
* **Keep rate optionality** — The borrower stays exposed to the market and can lock later if rates improve, rather than committing to today's rate.
* **Compress timelines** — Because disclosures and order-outs can start before the lock decision, the loan can be further along by the time the rate is locked.

## How floating works in Pylon

Float a product structure with the **`floatStructure`** mutation, which takes the loan ID and returns the ID of the disclosures run it dispatched:

```graphql theme={null}
mutation FloatStructure($loanId: ID!) {
  loan {
    floatStructure(input: { loanId: $loanId }) {
      loanId
      disclosuresRunId
    }
  }
}
```

`floatStructure` validates the loan, then **enqueues** the initial disclosure package for the loan's committed product structure. It deliberately does **not** lock the rate, send a lock notification, or run any post-lock change-of-circumstance workflow—it only performs the pre-lock half of the flow.

<Warning>
  **A successful `floatStructure` response does not mean the disclosures were sent.** The package is generated and delivered asynchronously, so the mutation returns as soon as the run is *enqueued*—before the send is attempted. The synchronous [preconditions](#preconditions) below are validated up front, but failures that occur *during* the send (for example, a TRID timing rejection) surface only on the disclosures run, not as a mutation error. Always confirm the outcome with [`disclosuresRunStatus`](#tracking-the-disclosure-send) using the returned `disclosuresRunId`.
</Warning>

### Preconditions

These checks run **synchronously**, before the run is enqueued. If any fails, the mutation rejects immediately with a [client-correctable error](/playground/error-handling) and nothing is dispatched:

* **AUS must pass** — The latest [AUS](/entity-models/key-concepts/aus) run must allow locking. If AUS has not been run or has not passed, the mutation rejects with `AUS_LOCK_NOT_ALLOWED` (HTTP 412).
* **The loan must not already be rate locked** — A locked loan cannot be floated; the mutation rejects with `LOAN_ALREADY_LOCKED` (HTTP 403).
* **The loan must not be frozen** — A loan whose initial disclosures have already been sent is frozen and cannot have a second disclosure package dispatched.
* **Initial disclosures must not already have been dispatched** — If an initial disclosure package is already in flight or has completed successfully, the mutation rejects with `LOAN_INITIAL_DISCLOSURES_ALREADY_DISPATCHED` (HTTP 412). This guarantees a borrower never receives duplicate initial disclosures. (A previously *failed* disclosure send does not count as dispatched, so it can be safely retried.)

### Tracking the disclosure send

Because the send is asynchronous, use the **`disclosuresRunStatus`** query—with the `disclosuresRunId` returned by `floatStructure`—to observe the outcome:

```graphql theme={null}
query DisclosuresRunStatus($disclosuresRunId: ID!) {
  disclosuresRunStatus(disclosuresRunId: $disclosuresRunId) {
    status
    stage
    errors
  }
}
```

Poll this until `status` settles. The `status` field transitions through:

| `status`     | Meaning                                                                    |
| ------------ | -------------------------------------------------------------------------- |
| `PROCESSING` | The package is still being generated and sent. Keep polling.               |
| `SUCCEEDED`  | The package was sent. The loan is now floated and frozen.                  |
| `FAILED`     | The send did not complete. `errors` contains the human-readable reason(s). |

When `status` is `FAILED`, `errors` explains why. A common cause is a **TRID timing rejection**—for example, attempting to send an initial Loan Estimate when the loan is at or near its closing date:

> `This loan is N days before closing. Initial CD must be sent or closing date extended`

This indicates the closing date is inside the window where a Closing Disclosure is required, so an initial Loan Estimate can no longer be sent. To resolve it, extend the loan's closing date to a date comfortably in the future and call `floatStructure` again (a `FAILED` run does not block a retry).

<Note>
  Right after `floatStructure`, expect `status` to be `PROCESSING`—a single immediate read will rarely show the final result. Poll every few seconds until it becomes `SUCCEEDED` or `FAILED`.
</Note>

### Checking whether a loan is floated

The `Loan` type exposes a derived **`isFloated`** boolean. A loan is floated when its initial disclosures have been sent but its rate is not yet locked:

```
isFloated = disclosuresDate IS NOT NULL && rateLockStatus !== LOCKED
```

```graphql theme={null}
query LoanFloatState($loanId: ID!) {
  node(id: $loanId) {
    ... on Loan {
      isFloated
      isFrozen
      rateLock {
        status
      }
    }
  }
}
```

Use `isFloated` as the canonical signal that a loan has begun the disclosure process but still has an open rate decision. A floated loan is also **frozen** (`isFrozen` is true), because its initial disclosures have been sent and its product structure can no longer be changed.

<Note>
  `isFloated` only flips to `true` once the disclosure package has **actually been sent**, not when `floatStructure` returns. If `floatStructure` succeeded but `isFloated` is still `false` after the run has settled, the send failed—check [`disclosuresRunStatus`](#tracking-the-disclosure-send) for the reason.
</Note>

## Lifecycle: from float to lock

A typical floated loan moves through these states:

1. **Product structure attached** — The borrower commits to a product. The 3-business-day disclosure timer starts.
2. **Float requested** — `floatStructure` enqueues the initial disclosure package and returns a `disclosuresRunId`. The send then runs asynchronously; poll [`disclosuresRunStatus`](#tracking-the-disclosure-send) until it reports `SUCCEEDED`.
3. **Floated** — Once the package is sent, the loan is frozen, `isFloated` is true, and the rate remains unlocked.
4. **ITP established** — Borrowers sign their initial disclosures, establishing [Intent to Proceed (ITP)](/entity-models/key-concepts/itp) and triggering order-outs.
5. **Locked** — When the borrower is ready, `confirmRateLock` locks the rate. `isFloated` becomes false (the rate is now `LOCKED`) and the loan proceeds through processing toward closing. Because the loan was already disclosed while floating, locking it is a [change of circumstance](/entity-models/key-concepts/change-of-circumstance)—see [Locking a floated loan](#locking-a-floated-loan) below.

### Locking a floated loan

A floated loan has already received its initial Loan Estimate, so locking it changes the disclosed terms and owes the borrower a **revised Loan Estimate** as a [change of circumstance](/entity-models/key-concepts/change-of-circumstance).

When `confirmRateLock` is called on a floated loan, Pylon files a change-of-circumstance work item for the disclosure desk **before** committing the lock. The work item records the lock terms (when it was locked, the lock period, and who initiated it) along with the changes to the loan since the last disclosure, so the revised Loan Estimate is queued as soon as the lock lands rather than depending on someone remembering to request it.

The ordering is a guarantee: the disclosure work item is filed after every lock precondition has passed but before the rate-lock write, so a locked loan can never exist without a revised-disclosure work item owning the follow-up.

The `confirmRateLock` response returns the id of the support ticket that was opened, as **`changeOfCircumstanceTicketId`**:

```graphql theme={null}
mutation ConfirmRateLock($loanId: ID!) {
  loan {
    confirmRateLock(input: { loanId: $loanId }) {
      loanId
      changeOfCircumstanceTicketId
    }
  }
}
```

`changeOfCircumstanceTicketId` is `null` when the loan was not floated—no revised Loan Estimate is due, so no ticket is opened. When the lock was a float→lock, the field is always present: the lock is refused if the ticket cannot be opened, so a successful response never leaves the id missing.

<Warning>
  If the disclosure work item cannot be filed, the lock is **refused**: `confirmRateLock` rejects with `RATE_LOCK_DISCLOSURE_TICKET_FAILED` (HTTP 503) and **nothing is persisted**—the rate is not locked. This is a transient failure: retry `confirmRateLock` once the underlying issue clears. See [Error handling](/playground/error-handling) for how error codes surface.
</Warning>

Locking a loan that was never floated (a direct `confirmRateLock` with no prior float) does not go through this path—there are no prior initial disclosures to revise.

## Related concepts

* [Rate lock](/entity-models/key-concepts/rate-lock) - The second half of the flow, where the interest rate is committed
* [Intent to Proceed (ITP)](/entity-models/key-concepts/itp) - Established when borrowers sign the disclosures that floating dispatches
* [Automated Underwriting System (AUS)](/entity-models/key-concepts/aus) - Must pass before a loan can be floated
* [TRID (TILA-RESPA Integrated Disclosure)](/entity-models/key-concepts/trid) - The regulation governing the initial disclosures that floating sends
* [Change of circumstance](/entity-models/key-concepts/change-of-circumstance) - Why locking a floated loan requires a revised Loan Estimate
* [Disclosures](/entity-models/disclosures) - How to retrieve and track the disclosures dispatched by floating
* [Loan](/entity-models/loan) - Where `isFloated` and rate lock state are managed
