> For the complete documentation index, see [llms.txt](https://api-docs.realfinity.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://api-docs.realfinity.io/mcp/tool-reference/loan-lookup.md).

# Loan Lookup

Tools for finding a loan and reading it back: two that load one known loan, and two that find loans across your pipeline.

{% hint style="info" %}
**Results are scoped to the calling user.** Every tool on this page returns only the loans the authenticated user may see. Scope is resolved from SQL on every request, from the caller's roles — never from anything in the request itself.
{% endhint %}

**Scope by role**

| Role                      | Scope                                                                                                         |
| ------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Admin                     | All loans                                                                                                     |
| Processor                 | All loans, org-wide — **not** limited to their own company. One exception on this page: `query_loans`, below. |
| ConciergeKey              | Loans of managed agents                                                                                       |
| Agent / DualLicensedAgent | Loans they originate (self, assisted, ConciergeKey) or referred                                               |
| Pre-licensed              | Referred loans only                                                                                           |

{% hint style="warning" %}
**A Processor is not company-bounded for per-loan access.** `get_loan_application`, `get_loan_application_contacts`, and `search_loan_applications` give a Processor the same reach as an Admin: every loan in the organization, whichever company the loan belongs to. The company-bounded branch in the loan authorization check belongs to the separate **CompanyAdmin** role, not to Processor.

The aggregate path narrows a Processor differently. `query_loans` on this page — and the caller-scoped reporting tools on Reporting — resolve scope through the accessible-users set, which for a Processor is the users of their own company. So a Processor can open any loan by id, but a `query_loans` result set covers their company only.
{% endhint %}

Officer, processor, and referrer **name** fields require Admin, Processor, or ConciergeKey. An empty result is normally scoping, not an error.

**Which tool to reach for**

| You have                                                 | You want                                               | Tool                            |
| -------------------------------------------------------- | ------------------------------------------------------ | ------------------------------- |
| A loan id (`externalId` or GUID)                         | The whole loan, including borrower-wizard completion   | `get_loan_application`          |
| A loan id                                                | Just the agents, escrow, attorneys, insurance contacts | `get_loan_application_contacts` |
| A borrower name, email, loan number, or street           | To find which loan it is                               | `search_loan_applications`      |
| A structured question ("all FHA loans over $500k in TX") | One row per loan, chosen fields                        | `query_loans`                   |

**Prerequisites:** `query_loans` — call `get_loan_query_fields` first for the field whitelist.

***

### get\_loan\_application

Retrieve a Realfinity loan application by either its MLM `externalId` (preferred when the loan is in MLM) or its internal Realfinity GUID, including a per-section completion report for the borrower wizard.

* **Backing store:** SQL, merged with MLM/POS data when `externalId` is set on the row
* **Roles:** Admin, Processor, DualLicensedAgent, ConciergeKey, Agent

**Parameters**

| Name                | Type   | Required                                  | Description                                                                                                                                                                                                                                                                                            |
| ------------------- | ------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `externalId`        | string | Optional — but one of the two is required | PREFERRED when available. The MLM ExternalId of the loan (string, up to 108 chars). Use this whenever the loan already exists in MLM — it is the stable identifier humans see in MLM UIs and is what webhooks surface. If both ids are supplied, `externalId` wins and `loanApplicationId` is ignored. |
| `loanApplicationId` | GUID   | Optional — but one of the two is required | Fallback identifier: the Realfinity internal loan application ID (GUID). Use this only when no `externalId` is known — typically because the loan has not yet been pushed to MLM.                                                                                                                      |

Both parameters are individually optional, but the loan must be identified somehow. The loan is resolved by external id or internal id and access-checked before any data is returned. The server:

1. Looks the loan up by `externalId` among non-deleted loans, and raises `Not found:` when nothing matches.
2. Falls back to `loanApplicationId` when no `externalId` was supplied.
3. Raises `Bad request: Provide either externalId or loanApplicationId.` when neither was supplied.
4. Checks the caller's **read** access to the resolved loan, raising `Unauthorized:` if the loan is outside the caller's scope.

**Returns**

`GetLoanApplicationToolResponse` — the full loan snapshot plus a completion report. Top-level shape:

| Field                                                                                                                                 | Contents                                                                                                                        |
| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                                                                                                                  | Internal loan application GUID                                                                                                  |
| `loanInfo`                                                                                                                            | The loan itself: borrowers, subject property address, loan status, purpose, amount, rate, compensation, and the merged POS data |
| `loanTimeline`                                                                                                                        | Milestone timeline for the loan                                                                                                 |
| `losType`                                                                                                                             | Which LOS/application the loan belongs to                                                                                       |
| `createdTimestampUtc`, `updatedTimestampUtc`, `updatedBy`, `updatedByEmail`, `updatedByFirstName`, `updatedByLastName`, `updatedByIp` | Audit fields                                                                                                                    |
| `isDeleted`, `isLoanEditable`, `isSetupComplete`, `skipStatedIncomePreapproval`, `startStatedIncomePreapproval`, `timestamp`          | State flags                                                                                                                     |
| `referredBy`                                                                                                                          | The referring contact                                                                                                           |
| `sectionCompletion`                                                                                                                   | Borrower-wizard completion report (below)                                                                                       |

`tasks` and `taskSummaries` are always returned as `null` by this tool — use the tools on Tasks & Conditions for task detail.

`sectionCompletion` is a `LoanAppSectionCompletionReport`:

* `sections[]` — one entry per wizard section, in sidebar order: About You, Loan & Property, Work & Income, Assets, Liabilities, Real Estate, Additional Details. Each carries `sectionName`, `taskCode`, `taskName`, `status` (`NotStarted`, `InProgress`, `Submitted`, `Completed`, `Accepted`), `rawTaskStatusName`, `isValid`, `fields[]`, `missingRequiredFields[]`, `ruleErrors[]`, and `notes[]`.
* `allSectionsComplete` — true when every section is `Completed` or `Accepted`.
* `isReadyToSubmit` — true when the loan can be submitted to the LOS right now.
* `submitBlockers[]` — human-readable reasons it cannot be submitted yet.

No example response is published for this tool. The response is a deeply nested loan snapshot built almost entirely from borrower and property data, so a realistic example cannot be shown on a public page, and a redacted one large enough to be useful would be more likely to mislead than to help. Read the shape from the field tables above.

**Notes**

* Prefer `externalId`. It is stable, human-visible in MLM, and what webhooks carry.
* Use `sectionCompletion.missingRequiredFields` and `submitBlockers` to answer "what is left before this loan can be submitted?" — they apply the same rules the borrower UI uses to gate **Complete Section** and submission.
* `isValid` is always true for Assets and Liabilities; those sections have no field-level validation here, so read their `status` instead.
* Deleted loans are not resolvable by `externalId`.

***

### get\_loan\_application\_contacts

Retrieve the contacts saved for a Realfinity loan application, synced from MLM: buyer and listing agents, attorneys, escrow, loan officer, CPA/tax preparer, homeowner insurance, verifications, and other.

* **Backing store:** SQL
* **Roles:** Admin, Agent, Processor, ConciergeKey, DualLicensedAgent

**Parameters**

| Name                | Type         | Required                                      | Description                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------- | ------------ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `externalId`        | string       | Optional — but one of the two ids is required | PREFERRED when available. The MLM ExternalId of the loan. If both ids are supplied, `externalId` wins and `loanApplicationId` is ignored.                                                                                                                                                                                                                                                                                            |
| `loanApplicationId` | GUID         | Optional — but one of the two ids is required | Fallback identifier: the Realfinity internal loan application ID (GUID). Use only when no `externalId` is known.                                                                                                                                                                                                                                                                                                                     |
| `roles`             | string array | Optional                                      | Contact role names to filter by, e.g. `["BuyerAgent", "Escrow"]`. Only these roles are synced from MLM, so they are the only ones that can be returned: `BuyerAgent`, `ListingAgent`, `Attorney`, `BuyerAttorney`, `SellerAttorney`, `Escrow`, `LoanOfficer`, `CPATaxPreparer`, `HomeOwnerInsurance`, `HazardInsurance`, `Verifications`, `Other`. Case-insensitive. Omit or leave empty to return every saved contact for the loan. |

The loan is resolved by external id or internal id and access-checked before any data is returned, exactly as in `get_loan_application`, with the same errors.

**Returns**

A flat array of `LoanAppContact`. Each entry carries `externalId`, `role`, `roleName`, `name`, `email`, `phone`, `companyName`, `companyPhone`, `agentNMLS`, `companyNMLS`, `street`, `city`, `state`, and `zip`.

<details>

<summary>Example response</summary>

All identifying values below are placeholders.

```json
[
  {
    "externalId": "00000000-0000-0000-0000-000000000000",
    "role": "BuyerAgent",
    "roleName": "Buyer Agent",
    "name": "Alex Example",
    "email": "alex@example.com",
    "phone": "555-0100",
    "companyName": "Example Realty",
    "companyPhone": "555-0101",
    "agentNMLS": null,
    "companyNMLS": null,
    "street": "123 Example St",
    "city": "Exampleville",
    "state": "TX",
    "zip": "00000"
  },
  {
    "externalId": "00000000-0000-0000-0000-000000000000",
    "role": "Escrow",
    "roleName": "Escrow",
    "name": "Sam Example",
    "email": "sam@example.com",
    "phone": "555-0102",
    "companyName": "Example Title & Escrow",
    "companyPhone": "555-0103",
    "agentNMLS": null,
    "companyNMLS": null,
    "street": "456 Example Ave",
    "city": "Exampleville",
    "state": "TX",
    "zip": "00000"
  }
]
```

</details>

**Notes**

* The returnable role set is driven by the `LoanApplicationConfig:LoanAppContactRoles` setting, which decides what the MLM sync saves. A role outside that set can never come back, even if MLM holds it.
* Each entry in `roles` must be a valid `ContactRole` name. An unrecognized value raises `Bad request: Unknown contact role '<value>'.` rather than being ignored.
* `HomeOwnerInsurance` is retained for compatibility; MLM labels `HazardInsurance` as "Homeowner Insurance".
* An empty array means the loan has no synced contacts, not that access was denied — an access failure raises `Unauthorized:` instead.

***

### search\_loan\_applications

Search the loan applications you have access to by free text (borrower first/last name or email, loan number, subject property street) and/or structured filters, returning lightweight loan summaries rather than full loans.

* **Backing store:** SQL
* **Roles:** Admin, Processor, DualLicensedAgent, ConciergeKey, Agent

{% hint style="warning" %}
Loan **type** (FHA / VA / Conventional) is **not filterable here.** This search runs against SQL, where loan type is not a queryable column. For any loan-type breakdown or filter, use `report_loans` (aggregates) or `query_loans` (one row per loan) instead.
{% endhint %}

**Parameters**

Supply `searchText`, at least one structured filter, or both. Supplying none raises `Bad request:`.

| Name               | Type         | Required             | Description                                                                                                                                                                                                                                                                                                   |
| ------------------ | ------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `searchText`       | string       | Optional (see above) | Free-text search. Matches (case-insensitive, partial) against a borrower's first name, last name, and email, and also against the loan number and subject property street. Whitespace-separated tokens must all match, so `"jane doe"` matches a loan whose borrower first name is Jane and last name is Doe. |
| `statuses`         | string array | Optional             | Filter to these loan statuses (exact MLM status names, OR'd), e.g. `"Loan Funded"`, `"Loan Closed"`, `"Loan Underwriting"`. Matches each loan's CURRENT status only.                                                                                                                                          |
| `purposes`         | string array | Optional             | Filter to these loan purposes (exact values), e.g. `"Purchase"`, `"Refinance"`.                                                                                                                                                                                                                               |
| `referringAgentId` | GUID         | Optional             | Restrict to loans referred by this user id.                                                                                                                                                                                                                                                                   |
| `statusDateFrom`   | date-time    | Optional             | Include loans whose CURRENT status was set on/after this date (ISO 8601). This is when the loan last changed status — NOT when it closed/funded.                                                                                                                                                              |
| `statusDateTo`     | date-time    | Optional             | Include loans whose CURRENT status was set on/before this date (ISO 8601). Use end-of-day to include a full day.                                                                                                                                                                                              |
| `closingDateFrom`  | date-time    | Optional             | Include loans with a closing date on/after this date (ISO 8601). CAUTION: `closingDate` is sparsely populated (estimated closing date, unset on most loans) — a 0-loan result usually means missing data, not that nothing closed.                                                                            |
| `closingDateTo`    | date-time    | Optional             | Include loans with a closing date on/before this date (ISO 8601). Same caution as `closingDateFrom`.                                                                                                                                                                                                          |
| `loanAmountMin`    | number       | Optional             | Minimum loan amount in USD (inclusive).                                                                                                                                                                                                                                                                       |
| `loanAmountMax`    | number       | Optional             | Maximum loan amount in USD (inclusive).                                                                                                                                                                                                                                                                       |
| `commissionMin`    | number       | Optional             | Minimum locked compensation (commission) in USD (inclusive).                                                                                                                                                                                                                                                  |
| `commissionMax`    | number       | Optional             | Maximum locked compensation (commission) in USD (inclusive).                                                                                                                                                                                                                                                  |
| `skip`             | integer      | Optional             | Number of matching loans to skip before returning the page. Defaults to 0. Keep `searchText` the same and request skip 0, then 5, then 10, one page size at a time.                                                                                                                                           |
| `limit`            | integer      | Optional             | Page size. Defaults to 5, capped at 10. Page through additional matches with `skip` rather than raising this.                                                                                                                                                                                                 |
| `orderBy`          | string       | Optional             | Sort order. Defaults to `"createdtimestamp desc"` (newest first). Supported fields, each with `asc` or `desc`: `createdtimestamp`, `status`, `loannumber`, `loanofficer`, `referredbyagent`, `address`, `borrower`. An unsupported value raises `Bad request:` listing the accepted ones.                     |

**Returns**

`SearchLoanApplicationsResponse` — `count` (loans in this page), `totalCount` (matches across all pages), `skip`, `hasMore`, and `loanApplications[]`. Each result carries `loanApplicationId`, `externalId`, `borrowers[]` (`name`, `email`, primary first), `propertyAddress` (single line), `loanStatus`, `referringAgentId`, `referringAgentName`, and `createdTimestamp`.

| Field                | Description                                                                                                                                                                                                                                                 |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `referringAgentId`   | Realfinity user id of the referring agent — the real-estate agent or partner who referred the borrower — when one is assigned; otherwise null. Returned to every role. Pass it to `get_user_profile` for details, or back as the `referringAgentId` filter. |
| `referringAgentName` | The referring agent's display name. **Admin, Processor and ConciergeKey only**; null for Agent / DualLicensedAgent callers even when `referringAgentId` is set — the same rule as the User-scope fields in `query_loans`.                                   |

<details>

<summary>Example response</summary>

All identifying values below are placeholders.

```json
{
  "count": 2,
  "totalCount": 7,
  "skip": 0,
  "hasMore": true,
  "loanApplications": [
    {
      "loanApplicationId": "00000000-0000-0000-0000-000000000000",
      "externalId": "LN-000000",
      "borrowers": [
        { "name": "Jane Borrower", "email": "jane@example.com" },
        { "name": "John Borrower", "email": "john@example.com" }
      ],
      "propertyAddress": "123 Example St, Exampleville, TX, 00000",
      "loanStatus": "Loan Underwriting",
      "referringAgentId": "00000000-0000-0000-0000-000000000000",
      "referringAgentName": "Ray Referrer",
      "createdTimestamp": "2026-01-15T14:32:07Z"
    },
    {
      "loanApplicationId": "00000000-0000-0000-0000-000000000000",
      "externalId": null,
      "borrowers": [
        { "name": "Jane Borrower", "email": "jane@example.com" }
      ],
      "propertyAddress": "456 Example Ave, Exampleville, TX, 00000",
      "loanStatus": "Lead",
      "referringAgentId": null,
      "referringAgentName": null,
      "createdTimestamp": "2026-01-09T09:04:51Z"
    }
  ]
}
```

</details>

**Notes**

* This returns summaries, not loans. Follow up with `get_loan_application`, passing `externalId` when present and `loanApplicationId` otherwise.
* `externalId` is `null` for loans that have not been pushed to MLM.
* Status filters match the **current** status only. Closed loans usually move on to Sold / Purchased / Archived, so do not use `statuses` to count what closed or funded in a period — use `report_loan_milestones`, which reads historical status crossings.
* Admin and Processor callers search across all loans; every other role is limited to loans whose loan officer or referrer falls within their accessible-users set (self, assisted users, ConciergeKey users).
* Test loans are excluded unless the calling agent is flagged to include test data.

***

### query\_loans

Run a custom filtered query returning **one row per loan**: supply WHERE conditions (whitelisted field + operator + value) and optionally which fields to return.

* **Backing store:** Cosmos loan snapshot
* **Roles:** Admin, Processor, DualLicensedAgent, ConciergeKey, Agent

{% hint style="success" %}
**This is not raw SQL, and injection is not possible.** Four independent properties make that true:

1. Every `field` key must resolve against the fixed `LoanQuerySchema` whitelist — an unknown key is rejected, never interpolated.
2. `operator` is an enum, so only the nine listed comparisons can be expressed.
3. Every `value` is bound as a Cosmos query parameter, never concatenated into the query text.
4. The Cosmos query API used is read-only; there is no statement that could mutate or delete data.

The caller's RBAC scope is applied on the server from the resolved scope, never from request input, and caller-supplied loan-officer ids can only narrow that scope, never widen it.
{% endhint %}

**Parameters**

| Name              | Type                       | Required                                        | Description                                                                                                                                                                   |
| ----------------- | -------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conditions`      | array of condition objects | Optional (defaults to empty — scope-only query) | WHERE conditions, all AND'd together. Each condition is a whitelisted field + operator + parameterized value. Results are always additionally scoped to loans you can access. |
| `select`          | string array               | Optional                                        | Field keys to return per loan. Defaults to a standard loan summary. Borrower fields are filter-only; user-name fields require an admin/processor/concierge role.              |
| `top`             | integer                    | Optional                                        | Max loans to return (1–50, default 20).                                                                                                                                       |
| `skip`            | integer                    | Optional                                        | Number of loans to skip for paging (default 0).                                                                                                                               |
| `orderBy`         | string                     | Optional                                        | Field key to sort by (default `"updatedDate"`).                                                                                                                               |
| `orderDescending` | boolean                    | Optional                                        | Sort descending (default `true`).                                                                                                                                             |

Each entry in `conditions` is:

| Name       | Type         | Required                                | Description                                                                                                                                                                               |
| ---------- | ------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `field`    | string       | Required                                | Whitelisted field key (call `get_loan_query_fields` for the list), e.g. `"status"`, `"loanAmount"`, `"borrowerLastName"`, `"clientId"`.                                                   |
| `operator` | enum         | Required                                | One of `Eq`, `Ne`, `Gt`, `Gte`, `Lt`, `Lte`, `Contains`, `StartsWith`, `In`. `Contains`/`StartsWith` require a text field; `Gt`/`Gte`/`Lt`/`Lte` require number/date; `In` uses `values`. |
| `value`    | string       | Required for every operator except `In` | Comparison value in string form; numbers, dates, and GUIDs are parsed and bound as parameters.                                                                                            |
| `values`   | string array | Required for `In`                       | Values for the `In` operator.                                                                                                                                                             |

**The field whitelist**

Captured live from `get_loan_query_fields`. Fields with scope `User` require Admin, Processor, or ConciergeKey (`requiresUserDataAccess: true`); `Borrower` fields are filter-only and can never be projected.

| Field                   | Type    | Scope    | Selectable       | Requires elevated access |
| ----------------------- | ------- | -------- | ---------------- | ------------------------ |
| `closingDate`           | Date    | Loan     | yes              | no                       |
| `createdDate`           | Date    | Loan     | yes              | no                       |
| `estimatedCompensation` | Number  | Loan     | yes              | no                       |
| `isDscrLoan`            | Boolean | Loan     | yes              | no                       |
| `loanAmount`            | Number  | Loan     | yes              | no                       |
| `loanApplicationId`     | Guid    | Loan     | yes              | no                       |
| `loanNumber`            | String  | Loan     | yes              | no                       |
| `loanOfficerId`         | Guid    | Loan     | yes              | no                       |
| `loanTerm`              | Number  | Loan     | yes              | no                       |
| `loanType`              | String  | Loan     | yes              | no                       |
| `lockedCompensation`    | Number  | Loan     | yes              | no                       |
| `mortgageLoanType`      | String  | Loan     | yes              | no                       |
| `noteRate`              | Number  | Loan     | yes              | no                       |
| `processorId`           | Guid    | Loan     | yes              | no                       |
| `propertyCity`          | String  | Loan     | yes              | no                       |
| `propertyState`         | String  | Loan     | yes              | no                       |
| `purpose`               | String  | Loan     | yes              | no                       |
| `referringAgentId`      | Guid    | Loan     | yes              | no                       |
| `status`                | String  | Loan     | yes              | no                       |
| `statusDate`            | Date    | Loan     | yes              | no                       |
| `updatedDate`           | Date    | Loan     | yes              | no                       |
| `borrowerEmail`         | String  | Borrower | no (filter-only) | no                       |
| `borrowerFico`          | Number  | Borrower | no (filter-only) | no                       |
| `borrowerFirstName`     | String  | Borrower | no (filter-only) | no                       |
| `borrowerLastName`      | String  | Borrower | no (filter-only) | no                       |
| `clientId`              | Guid    | Borrower | no (filter-only) | no                       |
| `loanOfficerEmail`      | String  | User     | yes              | **yes**                  |
| `loanOfficerName`       | String  | User     | yes              | **yes**                  |
| `processorName`         | String  | User     | yes              | **yes**                  |
| `referringAgentEmail`   | String  | User     | yes              | **yes**                  |
| `referringAgentName`    | String  | User     | yes              | **yes**                  |

<details>

<summary>Example response — get_loan_query_fields (real output, truncated)</summary>

This is genuine live output from `get_loan_query_fields`, which returns schema metadata only and no borrower or user data. The `fields` array is **truncated** here to one entry per scope; the table above lists all 31 fields.

```json
{
  "operators": ["Eq", "Ne", "Gt", "Gte", "Lt", "Lte", "Contains", "StartsWith", "In"],
  "fields": [
    {
      "field": "loanAmount",
      "type": "Number",
      "scope": "Loan",
      "canSelect": true,
      "filterOnly": false,
      "requiresUserDataAccess": false
    },
    {
      "field": "borrowerLastName",
      "type": "String",
      "scope": "Borrower",
      "canSelect": false,
      "filterOnly": true,
      "requiresUserDataAccess": false
    },
    {
      "field": "loanOfficerName",
      "type": "String",
      "scope": "User",
      "canSelect": true,
      "filterOnly": false,
      "requiresUserDataAccess": true
    }
  ]
}
```

</details>

**Example request**

Every FHA loan over $500,000 in Texas, newest first, returning four columns:

```json
{
  "conditions": [
    { "field": "loanType", "operator": "Eq", "value": "FHA" },
    { "field": "loanAmount", "operator": "Gt", "value": "500000" },
    { "field": "propertyState", "operator": "Eq", "value": "TX" }
  ],
  "select": ["loanNumber", "loanAmount", "status", "propertyCity"],
  "orderBy": "statusDate",
  "orderDescending": true,
  "top": 20
}
```

**Returns**

`QueryLoansResponse` — `totalCount`, `skip`, `top`, `fields[]` (the column keys actually returned, after any access filtering), and `rows[]`, one dictionary per loan keyed by those field names.

<details>

<summary>Example response</summary>

All identifying values below are placeholders.

```json
{
  "totalCount": 2,
  "skip": 0,
  "top": 20,
  "fields": ["loanNumber", "loanAmount", "status", "propertyCity"],
  "rows": [
    {
      "loanNumber": "LN-000000",
      "loanAmount": 612000,
      "status": "Loan Underwriting",
      "propertyCity": "Exampleville"
    },
    {
      "loanNumber": "LN-000000",
      "loanAmount": 548500,
      "status": "Clear to Close",
      "propertyCity": "Exampleville"
    }
  ]
}
```

</details>

**Notes**

* Call `get_loan_query_fields` before building a request. The whitelist is authoritative and can change; an unknown `field` key is rejected outright.
* User-attribute fields — `loanOfficerName`, `loanOfficerEmail`, `processorName`, `referringAgentName`, `referringAgentEmail` — require **Admin, Processor, or ConciergeKey**. Callers without one of those roles do not get these columns back; check `fields[]` in the response to see what was actually returned.
* Borrower fields (`borrowerFirstName`, `borrowerLastName`, `borrowerEmail`, `borrowerFico`, `clientId`) can be filtered on but never selected, so a `query_loans` result never carries borrower identity.
* For aggregated counts and sums rather than one row per loan, use `report_loans`. For a snapshot of what is in the pipeline now, use `loan_pipeline_report`.
* Because this runs on the Cosmos loan snapshot, loan **type** is queryable here — unlike `search_loan_applications`.

***

**Related:** Authentication · Troubleshooting · Overview
