# Equity reports

An equity report tells a past client what their home is worth, what they owe, and how much equity
they have, on a schedule. It keeps the broker in front of the client between deals. Over the API you
can enroll clients, send a report now, pause or stop reports, and read what each client was sent.

## How enrollment works

A report is about one deal: the client is the deal's primary applicant, and the property is its
subject property. Every request on this page takes `deal_ids`, 1 to 100 of them. Find a client's
deals with `GET /v1/deals?contact_id=`.

A report's settings live on the deal's opportunity: `monthly_reports_enabled`,
`monthly_reports_paused_at`, and `report_frequency`. Read them with `GET /v1/opportunities?deal_id=`,
or list everyone enrolled with `GET /v1/opportunities?monthly_reports_enabled=true`. A deal with no
opportunity can still be enrolled; enrolling creates one.

| Request                              | What it does                                           |
| ------------------------------------ | ------------------------------------------------------ |
| `POST /v1/equity-reports/enroll`     | Starts recurring reports at a frequency.               |
| `POST /v1/equity-reports/resume`     | Starts paused reports again.                           |
| `POST /v1/equity-reports/send`       | Sends one report now, by email or SMS.                 |
| `POST /v1/equity-reports/pause`      | Pauses reports. The client stays enrolled.             |
| `POST /v1/equity-reports/unenroll`   | Stops reports.                                         |
| `POST /v1/equity-reports/frequency`  | Changes how often reports go out.                      |

Each request happens now and returns `200`, with one line in `results` for each deal you sent.
Starting reports or sending one reaches a client, so a key with the `no_sending` limit can't do it
(`403 KEY_LIMITED`), and if the broker your key acts as may not contact clients, you get
`403 FORBIDDEN`. Pausing, stopping, or changing the frequency can't send anything, so any key can do
it.

## Consent

The API follows the same rules as the reports page in BrokerPlus:

- A client who opted out of equity reports is refused with `unsubscribed`. Only the broker can enroll
  them again, in BrokerPlus.
- A client under a sanctions freeze is refused with `frozen`.
- A deal the broker dismissed from the book is refused with `dismissed`.
- Every time a report goes out, BrokerPlus checks the client's email or SMS consent again. A client
  who withdrew consent, or whose address is suppressed, doesn't get it.

These requests have no consent field, so you can't record consent or skip the check through them.
Record or withdraw a client's consent with `PATCH /v1/contacts/{id}/communication-preferences`.

## Enroll clients

Send the deals and a `frequency`: `monthly` (the default), `quarterly`, `semi_annually`, or
`annually`.

<CodeTabs syncKey="lang">

```bash title="curl"
curl -X POST "https://api.mos.getbrokerplus.ca/v1/equity-reports/enroll" \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: enroll-2026-10-07-1" \
  -d '{"deal_ids":["cmsdl1a2b","cmsdl3c4d","cmsdl5e6f"],"frequency":"quarterly"}'
```

```ts title="TypeScript"
const res = await fetch("https://api.mos.getbrokerplus.ca/v1/equity-reports/enroll", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BROKERPLUS_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "enroll-2026-10-07-1",
  },
  body: JSON.stringify({ deal_ids: ["cmsdl1a2b", "cmsdl3c4d", "cmsdl5e6f"], frequency: "quarterly" }),
});
const { data: enrollment } = await res.json();
```

```python title="Python"
import os

import requests

res = requests.post(
    "https://api.mos.getbrokerplus.ca/v1/equity-reports/enroll",
    json={"deal_ids": ["cmsdl1a2b", "cmsdl3c4d", "cmsdl5e6f"], "frequency": "quarterly"},
    headers={
        "Authorization": f"Bearer {os.environ['BROKERPLUS_API_KEY']}",
        "Idempotency-Key": "enroll-2026-10-07-1",
    },
)
enrollment = res.json()["data"]
```

</CodeTabs>

The response is `200`, with one line in `results` for each deal you sent.

```json
{
  "success": true,
  "data": {
    "results": [
      {
        "deal_id": "cmsdl1a2b",
        "opportunity_id": "cmsop7g8h",
        "contact_name": "Sam Client",
        "result": "changed",
        "reason": null
      },
      {
        "deal_id": "cmsdl3c4d",
        "opportunity_id": "cmsop9i0j",
        "contact_name": "Jordan Client",
        "result": "unchanged",
        "reason": "already_enrolled"
      },
      {
        "deal_id": "cmsdl5e6f",
        "opportunity_id": "cmsop1k2l",
        "contact_name": "Riley Client",
        "result": "refused",
        "reason": "unsubscribed"
      }
    ]
  }
}
```

The deals marked `changed` are enrolled now, and the first report goes out when it falls due.
Enrolling doesn't send a report on the spot; call `POST /v1/equity-reports/send` too if you want one
now. The other deals are not changed.

A `changed` deal can carry the reason `no_email`: the client is enrolled, but gets nothing until the
broker adds an email address. If no deal can take the action, nothing changes: the response is
`409 NOTHING_TO_REQUEST`, with each deal's reason in `error.details.results`.

## Send a report now

`POST /v1/equity-reports/send` sends one report to each client without enrolling anyone. Set
`channel` to `email` (sent from the broker's connected mailbox) or `sms` (a text with a link to the
report, with your own words in `sms_body` if you like).

```bash
curl -X POST "https://api.mos.getbrokerplus.ca/v1/equity-reports/send" \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"deal_ids":["cmsdl1a2b"],"channel":"email"}'
```

The response has the same shape as an enrollment. A deal marked `changed` got its report now. A
client with no email address is refused with `no_email`, and for `sms`, one with no phone number with
`no_phone`.

## Pause, stop, or change the frequency

Each deal comes back `changed`, `unchanged`, or `refused`, and repeating a call is safe: the second
one returns `unchanged`.

```bash
curl -X POST "https://api.mos.getbrokerplus.ca/v1/equity-reports/pause" \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"deal_ids":["cmsdl1a2b","cmsdl7m8n"]}'
```

```json
{
  "success": true,
  "data": {
    "results": [
      {
        "deal_id": "cmsdl1a2b",
        "opportunity_id": "cmsop7g8h",
        "contact_name": "Sam Client",
        "result": "changed",
        "reason": null
      },
      {
        "deal_id": "cmsdl7m8n",
        "opportunity_id": null,
        "contact_name": null,
        "result": "refused",
        "reason": "not_found"
      }
    ]
  }
}
```

- `POST /v1/equity-reports/pause` keeps the client enrolled. Restart with
  `POST /v1/equity-reports/resume`.
- `POST /v1/equity-reports/unenroll` stops reports. Reports already sent stay readable.
- `POST /v1/equity-reports/frequency` takes `frequency` with `deal_ids`. It doesn't enroll anyone.

## Change what the report shows

The report uses the figures on the deal's opportunity and property:

- Set the home's value with `PATCH /v1/opportunities/{id}` and `property_value`. It replaces the
  BrokerPlus estimate; send `null` to go back to the estimate.
- Correct the balance or rate with `PATCH /v1/opportunities/{id}` and `balance` or `interest_rate`
  (a percent, see [Units](/units)).
- Split the mortgage with `PATCH /v1/opportunities/{id}` and `segments`, for example a fixed portion
  beside a line of credit. The list replaces every segment, so send each one you want to keep, or
  `[]` to make it one loan again. The first segment that is not a `heloc` sets the opportunity's
  product, rate and term. A deal in a locked stage refuses the change with `403 DEAL_LOCKED`.
- Change the address with `PATCH /v1/deals/{id}/property`.

## Read the reports that went out

`GET /v1/equity-reports` lists the reports clients received, newest first, with the figures exactly
as the client saw them. `ltv` is a percent, like every ratio in the API. `channel` says whether it
went by `email` or `sms`, `viewed_at` is when the client opened it, and `cta_clicked_at` is when they
asked the broker to call.

## Read the send and open history

A report is made once a month for each deal, and it can go out more than once. Two lists tell you
what happened after that:

- `GET /v1/equity-reports/sends` lists each time a report went out: to which client, on which
  `channel`, at what `frequency` (null for a one-off send), and whether it left BrokerPlus (`status`
  is `sent`, `failed`, or `suppressed`). It is the same history as Outreach History in BrokerPlus.
- `GET /v1/equity-reports/interactions` lists what clients did with a report. `field` is
  `email_open`, `page_view`, or `cta_click`, or the figure the client corrected (`propertyAddress`,
  `propertyValue`, or `mortgageBalance`) with `old_value`, `new_value`, and the equity and
  loan-to-value the report showed after the correction.

Both are logs: a row never changes after it is written. To sync, send `updated_since`, and with it
`after_id` from the last row you read. The rows come oldest first.

```bash
curl "https://api.mos.getbrokerplus.ca/v1/equity-reports/interactions?field=cta_click&updated_since=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY"
```

## Answer a client's correction

A client can correct their report: the property's address, its value, or the mortgage balance. When
the brokerage reviews corrections, or the deal's client data is locked, the correction waits for the
broker, as on the report changes queue in BrokerPlus.

- `GET /v1/equity-report-changes?status=pending` lists the corrections that wait. `old_value` is
  what the report said and `new_value` is what the client says. `held_reason` says why it waited.
- `POST /v1/equity-report-changes/{id}/accept` writes the client's figure onto the deal.
- `POST /v1/equity-report-changes/{id}/decline` keeps the deal's figure. A declined property value
  is taken back off the client's report.

Neither sends anything to the client. A correction can be answered once: a second answer gets `404`.
Accepting on a deal whose client data is locked to another broker gets `403`.

```bash
curl -X POST "https://api.mos.getbrokerplus.ca/v1/equity-report-changes/cmt5e2r8k0003p3iu7h1c9d2a/accept" \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY"
```

## Retention signals

A retention signal is a reason BrokerPlus raised to reach a past client now: an `equity` signal when
their loan-to-value fell below the brokerage's line, or a date of theirs (`mortgage_anniversary`,
`birthday`, or `new_year`). `GET /v1/pulse-signals` lists them, newest first. Filter by `type`,
`status`, `contact_id`, or `deal_id`. A `drafted` signal has a draft waiting for the broker: read it
with `GET /v1/drafts/{id}` using `draft_id`, and approve it as any other draft.

`GET /v1/pulse-triggers` lists the broker's date triggers: the message for each date, how many days
before or after it they go out, and whether they send on their own (`auto_send`). The broker has at
most one trigger for each date.

`POST /v1/pulse-triggers` sets one up, switched off, with the `type` and an optional `days_offset`
from -90 to 90. `PATCH /v1/pulse-triggers/{id}` moves `days_offset`.
`POST /v1/pulse-triggers/{id}/turn-on` turns it on, and from then on BrokerPlus writes to each of the
broker's clients on that date, so a key with the `no_sending` limit is refused it.
`POST /v1/pulse-triggers/{id}/turn-off` turns it off. A message it already scheduled, up to a week
ahead, still goes out. The message and `auto_send` are set in BrokerPlus, and there is no delete:
turn the trigger off instead.

## Errors

| Status | Code                 | What to do                                                            |
| ------ | -------------------- | --------------------------------------------------------------------- |
| `400`  | `VALIDATION_ERROR`   | Send `deal_ids` as an array of 1 to 100 ids. `details` names the field. |
| `403`  | `KEY_LIMITED`        | The key has the `no_sending` limit. Use a key without it.             |
| `403`  | `POLICY_BLOCKED`     | The brokerage's automation policy doesn't allow the request.          |
| `409`  | `NOTHING_TO_REQUEST` | No deal can take the action. Read `error.details.results`.            |
