# Reports and data

This page covers the report builder (the templates a brokerage saves, and the bulk report runs that
make one PDF report for each deal) and the exports of a broker's data.

This API is in beta. Any endpoint can change in a breaking way without notice, so watch the
[changelog](/changelog). A key with the `leads_only` limit can't reach any of this; see
[Authentication](/authentication).

## Report templates

A report template is a layout from the report builder in BrokerPlus: a list of widgets, such as the
property value, the mortgage summary, or a note from the broker. A template holds no client data.
Each report fills the widgets in from its deal when the report is made.

Templates belong to the brokerage. Every broker reads them, and every broker can change one, the
same as in the report builder. `GET /v1/report-templates` lists them, and `POST`, `PATCH` and
`DELETE /v1/report-templates/{id}` write them.

```bash
curl -X POST https://api.mos.getbrokerplus.ca/v1/report-templates \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Annual review","layout":{"version":1,"widgets":[{"id":"summary","type":"deal_summary","order":0,"config":{}}]}}'
```

```json
{
  "success": true,
  "data": {
    "object": "report_template",
    "id": "cm9k2r1a0000",
    "name": "Annual review",
    "description": null,
    "layout": {
      "version": 1,
      "widgets": [{ "id": "summary", "type": "deal_summary", "order": 0, "config": {} }]
    },
    "created_by_user_id": "cm9a0b1c0000",
    "created_at": "2026-10-10T15:00:00.000Z",
    "updated_at": "2026-10-10T15:00:00.000Z"
  }
}
```

Each widget type takes its own `config` keys. BrokerPlus checks them the same way the report builder
does: a value the type cannot use gets `400 VALIDATION_ERROR`, and a key the type does not know is
dropped. A good way to start is to read a template a broker made in BrokerPlus and copy its layout.

## Bulk report runs

A bulk report run makes one PDF report for each deal you give it, from a template or a layout. It is
the same as **Bulk Reports** in BrokerPlus. BrokerPlus sends nothing to the clients: you get the
files, and you decide what to do with them.

```bash
curl -X POST https://api.mos.getbrokerplus.ca/v1/bulk-report-runs \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_id":"cm9k2r1a0000","deal_ids":["cm8d1e2f0000","cm8d1e2f0001"]}'
```

```json
{
  "success": true,
  "data": {
    "object": "bulk_report_run",
    "id": "cm9k3s2b0000",
    "template_id": "cm9k2r1a0000",
    "template_name": "Annual review",
    "status": "pending",
    "total": 2,
    "completed": 0,
    "failed": 0,
    "error": null,
    "has_zip": false,
    "created_by_user_id": "cm9a0b1c0000",
    "created_at": "2026-10-10T15:05:00.000Z",
    "updated_at": "2026-10-10T15:05:00.000Z"
  }
}
```

A run takes at most 500 deals. A deal that the key's broker cannot read is left out, and `total`
tells you how many reports the run makes. A run copies the template's layout when it starts, so a
later change to the template does not change the run.

Poll `GET /v1/bulk-report-runs/{id}` until `status` is `done` or `failed`. It lists each report with
its own `status`, and `error` says why a report failed. Then
`GET /v1/bulk-report-runs/{id}/files` gives a link to each PDF and to a ZIP file with all of them.
The links stop working after 1 hour, so get new links when you need them. A key with the
`no_documents` limit can't get the files.

`GET /v1/bulk-report-runs` lists the runs on the broker's teams, newest first.

## Data exports

A data export is a copy of the broker's records in ZIP files, the same as **Export** in BrokerPlus.
`GET /v1/data-exports/packages` lists what a broker can export: a package such as `contacts` or
`deals`, or `takeout` for everything. Each export holds what the key's broker can read, nothing more.

```bash
curl -X POST https://api.mos.getbrokerplus.ca/v1/data-exports \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"package_id":"contacts"}'
```

```json
{
  "success": true,
  "data": {
    "object": "data_export",
    "id": "cm9k4t3c0000",
    "package_id": "contacts",
    "status": "pending",
    "error": null,
    "total_steps": 0,
    "completed_steps": 0,
    "row_count": 0,
    "volumes": [],
    "total_bytes": 0,
    "created_at": "2026-10-10T15:10:00.000Z",
    "completed_at": null,
    "expires_at": "2026-11-09T15:10:00.000Z",
    "expired_at": null
  }
}
```

An export is built in the background. Poll `GET /v1/data-exports/{id}` until `status` is `ready`;
`completed_steps` and `total_steps` show how far it is. If an export of the same package is already
pending or running, the request gives you that export instead of a second one.
`POST /v1/data-exports/{id}/cancel` stops one.

The API does not give the files. An export can hold data that the API never sends, such as credit
scores, so the broker downloads it in BrokerPlus. The files stay for 30 days. `volumes` gives the
size and the SHA-256 digest of each ZIP file, so you can check a download.

Exports belong to the broker who asked for them. `GET /v1/data-exports` lists only the key's own
broker's exports, newest first.

## Imports

An import brings another system's deals into the book, as the Import page in BrokerPlus does.
`GET /v1/import-runs` lists the imports on the broker's teams, newest first, and
`GET /v1/import-runs/{id}` gives one, with each file or sync in `sources` and what was read and what
failed.

`POST /v1/import-runs/files` adds a deals export (`.csv`, `.xlsx` or `.xls`) to the team's open
import, or starts one. Send the file as `multipart/form-data` in a `file` field:

```bash
curl -X POST https://api.mos.getbrokerplus.ca/v1/import-runs/files \
  -H "Authorization: Bearer $BROKERPLUS_API_KEY" \
  -F "file=@deals.xlsx" \
  -F "provider=filogix"
```

Or send JSON with the file in `content_base64` and its name in `file_name`. BrokerPlus reads and maps
the file in the background. Nothing is added to the book until the broker reviews the import and
adds it in BrokerPlus.

### Status routes

A status route says where an import puts a deal by the status word the other system wrote, such as
`ready to close`. The routes belong to the brokerage, and every import uses them.
`GET /v1/import-status-routes` lists them. `PATCH /v1/import-status-routes` saves the routes you
send and leaves the others as they are:

```json
{
  "entries": [
    { "source_status": "Ready to close", "deal_type": "purchase", "placement": "approved" },
    { "source_status": "Funded", "board_id": "cm9k4b0a0000", "board_stage_id": "cm9k4b0s0003" }
  ]
}
```

A route names a pipeline and stage, or a `placement`. Send neither to delete the route. A route you
save is the broker's own answer, so BrokerPlus never changes it.

### Saved mappings

When a broker confirms how an import's columns map to BrokerPlus fields, BrokerPlus saves the
mapping. The next file with the same column headers is mapped the same way.
`GET /v1/import-mappings` and `GET /v1/import-mappings/{id}` read them. A key that belongs to a
brokerage admin can rename one or replace its columns with `PATCH /v1/import-mappings/{id}`, and
delete one with `DELETE /v1/import-mappings/{id}`.
