> 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/users-and-metadata.md).

# Users & Metadata

{% hint style="info" %}
**Results are scoped to the calling user.** These tools return only the records the authenticated user may see — for the user-facing tools that means the users within the caller's access scope. Scope is resolved from SQL on every request, from the caller's roles — never from anything in the request itself.
{% endhint %}

**Scope by role — `search_users` and `report_users`**

This ladder describes `search_users` and `report_users`. It does **not** describe `get_user_licenses` or `get_user_profile`, whose per-user access check has no Processor branch — see those tools' Notes below. `get_current_user` is self-only by construction, and the remaining tools on this page return no user records and are not scoped at all.

| Role                      | Scope                                                                  |
| ------------------------- | ---------------------------------------------------------------------- |
| Admin                     | All records                                                            |
| Processor                 | All records in the user's company                                      |
| ConciergeKey              | Records of managed agents                                              |
| Agent / DualLicensedAgent | Themselves, their assisted users, and their ConciergeKey-managed users |

An empty result is normally scoping, not an error. Separately, the officer, processor, and referrer **name** fields on the loan tools require Admin, Processor, or ConciergeKey — which is what `get_loan_query_fields` reports as `requiresUserDataAccess`.

These eight tools are the supporting cast: they resolve **who the caller is**, **who** a user is and how they are **configured**, **where** they are licensed, **what** fields a loan query may reference, whether the server is **alive**, and how to obtain a short-lived token for the handful of Private API calls the MCP tools do not cover.

The user-facing tools return real people's records, so each is governed by an access scope — see each tool's Notes. They are **not** all governed by the same rule: `search_users` and `report_users` use the broader accessible-users set above, while `get_user_licenses` and `get_user_profile` use a narrower per-user access check. Each tool's Notes state its own rule.

### search\_users

Search Realfinity users you have access to by free-text matching their first name, last name, or email. Returns lightweight user records; pass the returned `id` as `userId` to other tools, such as `search_pricing` when the user is the pricing agent, or `get_user_profile` for full metadata.

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

**Parameters**

| Name         | Type   | Required | Description                                                                                                                                                                                                         |
| ------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `searchText` | string | Yes      | Free-text search. Matches (case-insensitive, partial) against a user's first name, last name, and email. Whitespace-separated tokens must **all** match, so `"jane doe"` matches first name Jane and last name Doe. |

**Returns**

A `SearchUsersResponse`: a `count` and a `users` array of `SearchUserResult` (`id`, `firstName`, `lastName`, `email`, `jobTitle`, `lastLoggedInTimestamp`). Results are ordered by last name then first name and **capped at 5** — narrow `searchText` rather than expecting to page through matches; for a full list use `report_users`.

<details>

<summary>Example response (synthetic, redacted)</summary>

```json
{
  "count": 1,
  "users": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "firstName": "Jane",
      "lastName": "Officer",
      "email": "jane@example.com",
      "jobTitle": "Loan Officer",
      "lastLoggedInTimestamp": "2026-08-28T13:10:44Z"
    }
  ]
}
```

</details>

**Notes**

* **Who you can see.** Admin callers search every enabled user. Every other role is restricted to their accessible-user set: themselves, their assisted users, and the users they manage as ConciergeKey. A Processor additionally sees all users in their own company. This mirrors the Private API's accessible-user rule exactly.
* Test users are excluded by default. A caller who is themselves flagged as a test user sees test users too.
* `searchText` is required; an empty or whitespace-only value is a bad request.
* An empty `users` array normally means the match fell outside your scope, not that the person does not exist.
* `lastLoggedInTimestamp` is coarse (recorded at most once per 5 hours) and null for users who have never signed in.

### get\_user\_licenses

Return the U.S. states a Realfinity user is currently licensed in — active, unexpired licenses only. Useful to confirm a loan officer can be priced under for a given property state before calling `search_pricing`.

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

**Parameters**

| Name     | Type | Required | Description                                                                                                                                                                                        |
| -------- | ---- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `userId` | GUID | No       | Realfinity user ID whose state licenses to return. Obtain it from `search_users`, or from a loan application's loan officer via `get_loan_application`. Defaults to the calling user when omitted. |

**Returns**

A `GetUserLicensesResponse`: the resolved `userId`, a `count`, and a `licenses` array of `UserLicenseInfo` (`state` as a 2-letter code, `stateLicenseNumber`, `expirationYear`), ordered by state.

<details>

<summary>Example response (synthetic, redacted)</summary>

```json
{
  "userId": "00000000-0000-0000-0000-000000000000",
  "count": 2,
  "licenses": [
    { "state": "AZ", "stateLicenseNumber": "0000000", "expirationYear": "2026" },
    { "state": "CA", "stateLicenseNumber": "0000000", "expirationYear": "2027" }
  ]
}
```

</details>

**Notes**

* **Who you can query.** The target user is access-checked before anything is returned. Admin can query any enabled user; otherwise the caller may query themselves, an agent they are bound to as an assistant, or an agent they manage as ConciergeKey. A target outside that set is rejected as unauthorized rather than returned empty.
* **This is narrower than `search_users`.** `search_users` additionally includes all company users when the caller is a Processor; this tool's per-user access check has no company-wide branch and no Processor branch at all. So a Processor can find a colleague with `search_users` and still be refused their licenses here. Omit `userId` to read your own.
* Expired and inactive licenses are filtered out server-side, so `count` is a count of licenses usable **today**. A state absent from the list is a state the user cannot currently be priced under.
* `state` is normalized to the 2-letter code, matching the property state expected by `search_pricing`.

### get\_current\_user

Return the identity of the signed-in caller: their Realfinity `userId`, name, email, roles, and how the connection was authenticated. Call this first when another tool needs the caller's own `userId` — for example as `loanOfficerId` in a `query_loans` filter or as `userId` on `search_pricing`.

* **Backing store:** SQL (name/email lookup); identity comes from the token
* **Roles:** Admin, Processor, DualLicensedAgent, ConciergeKey, Agent

**Parameters**

None. The identity is always the caller's own, read from the authenticated request — there is no way to look up someone else here.

**Returns**

A `GetCurrentUserResponse`:

| Field                  | Type      | Description                                                                                                                                     |
| ---------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `userId`               | GUID      | The caller's Realfinity user ID.                                                                                                                |
| `firstName`            | string    | May be null if the user record cannot be loaded.                                                                                                |
| `lastName`             | string    |                                                                                                                                                 |
| `email`                | string    |                                                                                                                                                 |
| `roles`                | string\[] | Roles on the caller's token, e.g. `Admin`, `Agent`, `Processor`.                                                                                |
| `authenticationSource` | string    | `Auth0` when the client connected directly (Claude, MCP Inspector); `RealfinityAiApi` when the request came via the Realfinity AI chat backend. |

<details>

<summary>Example response (synthetic, redacted)</summary>

```json
{
  "userId": "00000000-0000-0000-0000-000000000000",
  "firstName": "Jane",
  "lastName": "Officer",
  "email": "jane@example.com",
  "roles": ["Agent"],
  "authenticationSource": "Auth0"
}
```

</details>

**Notes**

* `userId` is taken from the token's Realfinity user-id claim, so it is returned even when the SQL user record cannot be loaded; in that case the name and email fields are null.
* `roles` reflects the token, which is what every `[Authorize]` check on the server evaluates — use it to predict which tools will accept the caller.

### get\_user\_profile

Return full metadata for one Realfinity user: identity, roles, company and organization, ConciergeKey owner, NMLS ID, enabled/test flags, created and enrollment dates, last sign-in, and Optimal Blue pricing configuration. Defaults to the calling user when `userId` is omitted.

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

**Parameters**

| Name     | Type | Required | Description                                                                                        |
| -------- | ---- | -------- | -------------------------------------------------------------------------------------------------- |
| `userId` | GUID | No       | Realfinity user ID. Obtain it from `search_users` or `report_users`. Defaults to the calling user. |

**Returns**

A `UserProfile`:

| Field                                        | Type         | Description                                                                                                                       |
| -------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                         | GUID         |                                                                                                                                   |
| `firstName`, `lastName`, `email`, `jobTitle` | string       |                                                                                                                                   |
| `nmlsId`                                     | string       | The user's NMLS identifier.                                                                                                       |
| `roles`                                      | string\[]    | Roles assigned to the user.                                                                                                       |
| `companyId`, `companyName`                   | GUID, string |                                                                                                                                   |
| `organizationId`, `organizationName`         | GUID, string |                                                                                                                                   |
| `ckUserId`                                   | GUID         | Realfinity user ID of the ConciergeKey (CK) owner this user is managed by. Null when the user is not under a ConciergeKey.        |
| `ckUserName`                                 | string       | Display name of the ConciergeKey owner, when set.                                                                                 |
| `isDisabled`                                 | boolean      |                                                                                                                                   |
| `isTest`                                     | boolean      |                                                                                                                                   |
| `createdTimestamp`                           | datetime     | When the user record was created.                                                                                                 |
| `enrollmentDate`                             | datetime     |                                                                                                                                   |
| `lastLoggedInTimestamp`                      | datetime     | Most recent sign-in to the Realfinity app. **Coarse:** recorded at most once per 5 hours. Null when the user has never signed in. |
| `obChannelIndex`                             | int          | Optimal Blue business channel index — the OB **Channel ID**. Null when the user prices under the company default account.         |
| `obOriginatorIndex`                          | int          | Optimal Blue originator index — the OB **User Index**. Null when the user prices under the company default account.               |
| `hasOwnObAccount`                            | boolean      | True when both OB indices are set, i.e. the user prices under their own Optimal Blue originator rather than the company default.  |
| `isReadyForPricing`                          | boolean      |                                                                                                                                   |

<details>

<summary>Example response (synthetic, redacted)</summary>

```json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "firstName": "Jane",
  "lastName": "Officer",
  "email": "jane@example.com",
  "jobTitle": "Loan Officer",
  "nmlsId": "0000000",
  "roles": ["Agent"],
  "companyId": "00000000-0000-0000-0000-000000000000",
  "companyName": "Realfinity",
  "organizationId": null,
  "organizationName": null,
  "ckUserId": "00000000-0000-0000-0000-000000000000",
  "ckUserName": "Connie Key",
  "isDisabled": false,
  "isTest": false,
  "createdTimestamp": "2025-03-01T14:02:11Z",
  "enrollmentDate": "2025-03-02T00:00:00Z",
  "lastLoggedInTimestamp": "2026-08-28T13:10:44Z",
  "obChannelIndex": 7,
  "obOriginatorIndex": 42,
  "hasOwnObAccount": true,
  "isReadyForPricing": true
}
```

</details>

**Notes**

* **Who you can query — same rule as `get_user_licenses`.** The target is access-checked per user: Admin can read any enabled user; otherwise the caller may read themselves, an agent they are bound to as an assistant, or an agent they manage as ConciergeKey. There is **no Processor branch** — a Processor who needs company-wide metadata should use `report_users` instead. A target outside the set is rejected as unauthorized; an unknown ID is a not-found error.
* **"Pricing account" is the channel/originator pair.** Optimal Blue has no separate account field on the user; the OB account a user prices under is `obChannelIndex` + `obOriginatorIndex`. When either is null, pricing falls back to the company default account (`hasOwnObAccount` = false).
* **Last sign-in is not a login history.** The timestamp is written when the app loads the user's snapshot, and only if the previous value is more than 5 hours old. Nothing records individual sessions or logins.
* `ckUserId` is the same relationship that defines the ConciergeKey scope ladder above: a ConciergeKey caller's "managed agents" are exactly the users whose `ckUserId` is the caller.

### report\_users

Paged report of every Realfinity user within the caller's access scope, with the same per-user metadata as `get_user_profile`. Built for user-configuration and activity reporting — which users have their own Optimal Blue account, who has not signed in since a date, who is disabled, who sits under a given ConciergeKey.

* **Backing store:** SQL
* **Roles:** **Admin, Processor** only

**Parameters**

| Name                   | Type     | Required | Description                                                                                                           |
| ---------------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `companyId`            | GUID     | No       | Restrict to users in this company.                                                                                    |
| `ckUserId`             | GUID     | No       | Restrict to users managed by this ConciergeKey owner.                                                                 |
| `role`                 | string   | No       | Restrict to users holding this role, e.g. `Agent`, `Processor`, `Admin`. Case-insensitive.                            |
| `isDisabled`           | boolean  | No       | `true` = disabled users only; `false` = enabled only. Omit for both.                                                  |
| `hasOwnObAccount`      | boolean  | No       | `true` = only users with their own OB channel/originator; `false` = only users on the company default. Omit for both. |
| `lastLoginAfter`       | datetime | No       | Only users whose last sign-in is on or after this UTC instant.                                                        |
| `lastLoginBefore`      | datetime | No       | Only users whose last sign-in is before this UTC instant.                                                             |
| `includeNeverLoggedIn` | boolean  | No       | With `lastLoginBefore`, also return users who have never signed in. Default `false`.                                  |
| `page`                 | int      | No       | 1-based page number. Default 1.                                                                                       |
| `pageSize`             | int      | No       | Rows per page, 1–200. Default 50.                                                                                     |

**Returns**

A `ReportUsersResponse`: `totalCount`, `page`, `pageSize`, `totalPages`, and a `users` array of `UserProfile` (see `get_user_profile`), ordered by last name then first name.

<details>

<summary>Example response (synthetic, redacted, one row)</summary>

```json
{
  "totalCount": 137,
  "page": 1,
  "pageSize": 50,
  "totalPages": 3,
  "users": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "firstName": "Jane",
      "lastName": "Officer",
      "email": "jane@example.com",
      "roles": ["Agent"],
      "companyName": "Realfinity",
      "ckUserId": "00000000-0000-0000-0000-000000000000",
      "ckUserName": "Connie Key",
      "isDisabled": false,
      "lastLoggedInTimestamp": "2026-08-28T13:10:44Z",
      "obChannelIndex": 7,
      "obOriginatorIndex": 42,
      "hasOwnObAccount": true
    }
  ]
}
```

</details>

**Notes**

* **Scope follows `search_users`, not `get_user_profile`.** Admin sees every enabled user; a Processor sees every user in their own company. This is the scope ladder at the top of this page.
* Test users are excluded unless the caller is themselves a test user.
* `lastLoginAfter` always excludes users with no recorded sign-in. `lastLoginBefore` does too unless `includeNeverLoggedIn` is `true` — set it when the question is "who has been inactive," since never-signed-in users are the most inactive of all.
* `page` below 1, or `pageSize` outside 1–200, is a bad request.
* The report answers "what is configured / when did they last sign in." It cannot answer "how often do they sign in" — see the last-sign-in caveat under `get_user_profile`.

### get\_loan\_query\_fields

Return the whitelist of fields available to `query_loans`: each field's key, type, scope (Loan / Borrower / User), whether it can be selected (vs. filter-only), and whether it requires elevated access. Call this **before** building a `query_loans` request.

* **Backing store:** none — static schema metadata
* **Roles:** Admin, Processor, DualLicensedAgent, ConciergeKey, Agent

**Parameters**

None.

**Returns**

A `LoanQueryFieldsResponse` with two arrays: `operators` (the full operator enum) and `fields`. Each field carries `field`, `type`, `scope`, `canSelect`, `filterOnly`, and `requiresUserDataAccess`.

Valid operators: `Eq`, `Ne`, `Gt`, `Gte`, `Lt`, `Lte`, `Contains`, `StartsWith`, `In`.

The whitelist as returned by the live server:

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

<details>

<summary>Example response (live output, abridged to three fields)</summary>

```json
{
  "operators": ["Eq", "Ne", "Gt", "Gte", "Lt", "Lte", "Contains", "StartsWith", "In"],
  "fields": [
    {
      "field": "closingDate",
      "type": "Date",
      "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>

**Notes**

* The whitelist is the reason `query_loans` is not raw SQL: a field key that is not in this list is rejected before any query is built.
* `filterOnly` fields (the Borrower scope) can appear in a `where` condition but cannot be projected into the result — filter on `borrowerLastName`, return `loanNumber`.
* `requiresUserDataAccess` marks the `User`-scope name and email fields. Those require Admin, Processor, or ConciergeKey; other roles must leave them out.
* The response is static metadata derived from the loan query schema, so it is cheap to call and safe to call on every session. It contains no loan or borrower data.

### ping

Health check for the MCP server: returns a static greeting plus the server's current UTC timestamp, with no downstream dependencies. Use it to confirm the server is reachable and the latest build is live.

* **Backing store:** none
* **Roles:** no role restriction — but the `/mcp` transport itself requires a valid token, so `ping` only answers an authenticated caller

**Parameters**

None.

**Returns**

A single string.

<details>

<summary>Example response (live output)</summary>

```
pong — Realfinity MCP server is up at 2026-08-07T21:22:53.4165120Z
```

</details>

**Notes**

* Because `ping` runs behind authentication, a failure tells you either the server is down **or** your token is bad. To answer only "is the server up?", use the unauthenticated `GET https://ai.realfinity.io/health` probe instead — see Troubleshooting.

### get\_api\_access\_token

Mint a short-lived Bearer token for the Realfinity Private API so an external script can call it on behalf of the current user — for example to upload or download loan-application task documents that are too large to pass through MCP.

* **Backing store:** none — signs a JWT
* **Roles:** Admin, Agent, Processor, ConciergeKey, DualLicensedAgent

**Parameters**

None. The token is always minted for the calling user, taken from the request's authenticated identity; there is no way to mint one for someone else.

**Returns**

A `GetApiAccessTokenResponse`:

| Field               | Type         | Description                                                                 |
| ------------------- | ------------ | --------------------------------------------------------------------------- |
| `token`             | string       | The Bearer token. Send as `Authorization: Bearer <token>`.                  |
| `tokenType`         | string       | Always `Bearer`.                                                            |
| `expiryTimestamp`   | UTC datetime | The instant the token stops working.                                        |
| `privateApiBaseUrl` | string       | Base URL of the Private API for this environment, without a trailing slash. |

No example response is shown: any realistic example would contain a token value.

**Notes**

* **Lifetime is 900 seconds — 15 minutes.** The value comes from `AppSettings:McpApiTokenConfig:TokenLifetimeSeconds` on the MCP server and is not negotiable per request. Treat the token as single-task credentials: mint it, use it immediately, discard it. Do not cache it beyond its life, do not persist it to disk, and do not reuse it across sessions — mint a fresh one instead.
* **Never print the token.** Pass it directly to the script that needs it. A token echoed into a conversation, a log, or a document is a live credential for the rest of its 15 minutes.
* **Audience and issuer.** Tokens are stamped with the audience `https://api.realfinity.io/internal-api/mcp`, deliberately distinct from every Auth0 audience so no audience-routed auth branch can match one by accident, and with a dedicated MCP issuer identifier that the Private API uses to route the token to its MCP-token validation branch. They are HMAC-signed with a key shared between the MCP server and the Private API via Key Vault, and additionally carry a token-source claim, so a token signed with that key for any other purpose is rejected.
* **Narrow by design.** Only Private API endpoints explicitly enabled for MCP tokens accept one — currently the loan-task document upload and download endpoints. Every other endpoint returns `403`, and normal loan-access rules still apply on the endpoints that do accept it. The token is a transport for the file flows MCP cannot carry, not a general-purpose API key.
* `privateApiBaseUrl` is environment-specific (production returns `https://api.realfinity.io`, UAT returns `https://uat-api.realfinity.io`). Always use the value from the response rather than hard-coding a host.
* If the server returns "MCP API token minting is not configured", the environment is missing its token configuration — this is a server-side condition, not something the caller can fix by retrying.

***

**Related pages:** Overview · Authentication · Loan Lookup · Tasks & Conditions · Reporting · Pricing · Troubleshooting
