@model-match/api 3.16.0 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.json CHANGED
@@ -3189,6 +3189,16 @@
3189
3189
  "period": {
3190
3190
  "$ref": "#/components/schemas/Period"
3191
3191
  },
3192
+ "segment": {
3193
+ "allOf": [
3194
+ {
3195
+ "$ref": "#/components/schemas/BranchSlice"
3196
+ },
3197
+ {
3198
+ "description": "Optional second dimension. Each bucket of `slice` then carries `segments`: the same `measure` broken down by this dimension (e.g. `slice: \"loanType\", segment: \"lender\"` is loan type × lender in one call), top 25 per bucket ranked by `measure`, with the remainder counted in `segmentsOmittedCount`. Omit it and the response is the one-dimensional chart, unchanged. Must differ from `slice`, and cannot be combined with `size` / `after` paging (400)."
3199
+ }
3200
+ ]
3201
+ },
3192
3202
  "slice": {
3193
3203
  "$ref": "#/components/schemas/BranchSlice"
3194
3204
  }
@@ -4144,7 +4154,24 @@
4144
4154
  ],
4145
4155
  "type": "string"
4146
4156
  },
4157
+ "BranchSegment": {
4158
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`. Time-series only: `titleCompany` groups on the title company named on the deed, with spellings of one company merged per bucket (`label` = most-used spelling across the response, `id` = normalized name — match on `id`) and \"none available\"-style placeholders excluded.",
4159
+ "enum": [
4160
+ "transactionType",
4161
+ "loanType",
4162
+ "city",
4163
+ "county",
4164
+ "state",
4165
+ "zip",
4166
+ "lender",
4167
+ "propertyType",
4168
+ "conforming",
4169
+ "titleCompany"
4170
+ ],
4171
+ "type": "string"
4172
+ },
4147
4173
  "BranchSlice": {
4174
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`.",
4148
4175
  "enum": [
4149
4176
  "transactionType",
4150
4177
  "loanType",
@@ -4152,7 +4179,9 @@
4152
4179
  "county",
4153
4180
  "state",
4154
4181
  "zip",
4155
- "lender"
4182
+ "lender",
4183
+ "propertyType",
4184
+ "conforming"
4156
4185
  ],
4157
4186
  "type": "string"
4158
4187
  },
@@ -4918,7 +4947,7 @@
4918
4947
  "$ref": "#/components/schemas/Period"
4919
4948
  },
4920
4949
  "segment": {
4921
- "$ref": "#/components/schemas/BranchSlice"
4950
+ "$ref": "#/components/schemas/BranchSegment"
4922
4951
  }
4923
4952
  },
4924
4953
  "required": [
@@ -5353,6 +5382,35 @@
5353
5382
  },
5354
5383
  "type": "array"
5355
5384
  },
5385
+ "BulkDeliveryBilling": {
5386
+ "description": "`credits` = billed at 1 credit per delivered row. `none` = not billed (push-target deliveries from the ModelMatch web app).",
5387
+ "enum": [
5388
+ "credits",
5389
+ "none"
5390
+ ],
5391
+ "type": "string"
5392
+ },
5393
+ "BulkDeliveryDestination": {
5394
+ "description": "Where the finished delivery goes. Omit for the default — a file you download from the status endpoint. `push-target`: when the job completes, the rows are handed to the named push target, which sends them to its configured HTTPS destination using its own field mapping and signing. The target is checked at submit (it must exist, be enabled, accept this entity, and be usable by you on your plan) — a failed check rejects the submit with the push target's error `code`. `format` is ignored (the handoff is always gzipped NDJSON), and when the target's mapping reads only some fields, only those fields (plus `id`) are delivered. Row limits, entitlements, `limit`, and billing (1 credit / row) are unchanged, except that push-target deliveries submitted from the ModelMatch web app are not billed. Poll the job for `pushBatchId`; a handoff that cannot be completed fails the job with error `push_handoff_failed` and is not billed.",
5395
+ "properties": {
5396
+ "targetId": {
5397
+ "description": "Id of a push target configured on the active workspace (or your own member push targets).",
5398
+ "minLength": 1,
5399
+ "type": "string"
5400
+ },
5401
+ "type": {
5402
+ "enum": [
5403
+ "push-target"
5404
+ ],
5405
+ "type": "string"
5406
+ }
5407
+ },
5408
+ "required": [
5409
+ "type",
5410
+ "targetId"
5411
+ ],
5412
+ "type": "object"
5413
+ },
5356
5414
  "BulkDeliveryForbidden": {
5357
5415
  "properties": {
5358
5416
  "entitlementKey": {
@@ -5390,7 +5448,7 @@
5390
5448
  "type": "object"
5391
5449
  },
5392
5450
  "BulkDeliveryFormat": {
5393
- "description": "Output file format for the delivered dataset. Every format contains the same rows — the entity's list/summary DTO (e.g. `LoanSummary`), one record per row. `ndjson`: gzipped newline-delimited JSON (`application/x-ndjson`, `.ndjson.gz`). `json`: a single JSON array (`application/json`, `.json`). `csv`: RFC 4180 (`text/csv`, `.csv`). `parquet`: typed columnar Apache Parquet — scalar fields keep their DTO types, nested fields are JSON-encoded strings (`application/vnd.apache.parquet`, `.parquet`).",
5451
+ "description": "Output file format for the delivered dataset. Every format contains the same rows — the entity's list/summary DTO (e.g. `LoanSummary`), one record per row. `ndjson`: gzipped newline-delimited JSON (`application/x-ndjson`, `.ndjson.gz`). `json`: a single JSON array (`application/json`, `.json`). `csv`: RFC 4180 (`text/csv`, `.csv`). `parquet`: typed columnar Apache Parquet — scalar fields keep their DTO types, nested fields are JSON-encoded strings (`application/vnd.apache.parquet`, `.parquet`). Required on submit unless `destination` is a push target, which always delivers gzipped NDJSON (a missing `format` on a file delivery is a 400 `format_required`).",
5394
5452
  "enum": [
5395
5453
  "ndjson",
5396
5454
  "json",
@@ -5436,6 +5494,31 @@
5436
5494
  ],
5437
5495
  "type": "object"
5438
5496
  },
5497
+ "BulkDeliveryInsufficientScope": {
5498
+ "properties": {
5499
+ "error": {
5500
+ "enum": [
5501
+ "insufficient_scope"
5502
+ ],
5503
+ "type": "string"
5504
+ },
5505
+ "message": {
5506
+ "type": "string"
5507
+ },
5508
+ "requiredScope": {
5509
+ "enum": [
5510
+ "push-targets:send"
5511
+ ],
5512
+ "type": "string"
5513
+ }
5514
+ },
5515
+ "required": [
5516
+ "error",
5517
+ "requiredScope",
5518
+ "message"
5519
+ ],
5520
+ "type": "object"
5521
+ },
5439
5522
  "BulkDeliveryPreflightExceeded": {
5440
5523
  "properties": {
5441
5524
  "error": {
@@ -5461,8 +5544,52 @@
5461
5544
  ],
5462
5545
  "type": "object"
5463
5546
  },
5547
+ "BulkDeliveryPushTargetError": {
5548
+ "description": "The push-target destination was refused: `TARGET_NOT_FOUND` (404), `FORBIDDEN` (403), `PLAN_REQUIRED` (402), `TARGET_DISABLED` (409), `ENTITY_MISMATCH` (400).",
5549
+ "properties": {
5550
+ "code": {
5551
+ "enum": [
5552
+ "TARGET_NOT_FOUND",
5553
+ "FORBIDDEN",
5554
+ "PLAN_REQUIRED",
5555
+ "TARGET_DISABLED",
5556
+ "ENTITY_MISMATCH"
5557
+ ],
5558
+ "type": "string"
5559
+ },
5560
+ "error": {
5561
+ "enum": [
5562
+ "TARGET_NOT_FOUND",
5563
+ "FORBIDDEN",
5564
+ "PLAN_REQUIRED",
5565
+ "TARGET_DISABLED",
5566
+ "ENTITY_MISMATCH"
5567
+ ],
5568
+ "type": "string"
5569
+ },
5570
+ "message": {
5571
+ "type": "string"
5572
+ }
5573
+ },
5574
+ "required": [
5575
+ "error",
5576
+ "code",
5577
+ "message"
5578
+ ],
5579
+ "type": "object"
5580
+ },
5464
5581
  "BulkDeliveryStatusResponse": {
5465
5582
  "properties": {
5583
+ "billing": {
5584
+ "$ref": "#/components/schemas/BulkDeliveryBilling"
5585
+ },
5586
+ "billingReason": {
5587
+ "description": "Why `billing` is `none`: `first_party_push_target` = a push-target delivery submitted from the ModelMatch web app.",
5588
+ "enum": [
5589
+ "first_party_push_target"
5590
+ ],
5591
+ "type": "string"
5592
+ },
5466
5593
  "bytes": {
5467
5594
  "minimum": 0,
5468
5595
  "type": "integer"
@@ -5484,6 +5611,9 @@
5484
5611
  "minimum": 0,
5485
5612
  "type": "number"
5486
5613
  },
5614
+ "destination": {
5615
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
5616
+ },
5487
5617
  "downloadUrl": {
5488
5618
  "description": "Stable, shareable download link for the completed dataset. Forces a file download (with a friendly filename) and stays valid for 7 days after completion. Present once `status` is `completed`. This is the link to hand to a user or send by email.",
5489
5619
  "type": "string"
@@ -5492,6 +5622,7 @@
5492
5622
  "type": "string"
5493
5623
  },
5494
5624
  "error": {
5625
+ "description": "Why the job failed. `push_handoff_failed` = the rows were exported but could not be handed to the push target (not billed).",
5495
5626
  "type": "string"
5496
5627
  },
5497
5628
  "estimatedTotal": {
@@ -5508,6 +5639,10 @@
5508
5639
  "minimum": 0,
5509
5640
  "type": "integer"
5510
5641
  },
5642
+ "pushBatchId": {
5643
+ "description": "Push-target deliveries only: the batch the push target is sending, set once the completed file was handed off. Per-record send progress is tracked by the push target, not this job.",
5644
+ "type": "string"
5645
+ },
5511
5646
  "resultsUrl": {
5512
5647
  "description": "Direct download URL for the completed dataset. Long-lived per request but changes on each poll; prefer `downloadUrl` for a stable link to share.",
5513
5648
  "type": "string"
@@ -5534,6 +5669,12 @@
5534
5669
  },
5535
5670
  "BulkDeliverySubmitResponse": {
5536
5671
  "properties": {
5672
+ "billing": {
5673
+ "$ref": "#/components/schemas/BulkDeliveryBilling"
5674
+ },
5675
+ "destination": {
5676
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
5677
+ },
5537
5678
  "entityType": {
5538
5679
  "type": "string"
5539
5680
  },
@@ -5589,18 +5730,23 @@
5589
5730
  "type": "number"
5590
5731
  },
5591
5732
  "id": {
5592
- "description": "Machine value behind `label` when the bucket key is an id rather than a name (today: the `lender` slice/segment, where `label` is the dictionary display name). Pass `id` — not `label` — back as a `lender` filter value. Absent on slices whose `label` is already the machine value.",
5733
+ "description": "Machine value behind `label` when the bucket key is not itself the label. `lender`: `label` is the dictionary display name and `id` the normalized lender key — pass `id`, not `label`, back as a `lender` filter value. `conforming`: `label` is `conforming` / `nonConforming` and `id` the raw `true` / `false` flag. `titleCompany` (time-series segment): `label` is the most-used spelling and `id` the normalized name every spelling was merged under — match segments across buckets on `id`. Absent on slices whose `label` is already the machine value.",
5593
5734
  "type": "string"
5594
5735
  },
5595
5736
  "label": {
5596
5737
  "type": "string"
5597
5738
  },
5598
5739
  "segments": {
5740
+ "description": "Present only when the request named a `segment`: this bucket broken down by that second dimension, ranked by the same `measure` and capped at 25 per bucket (the rest is counted in `segmentsOmittedCount`). Documents with no value for the segment are not grouped and appear nowhere here, so segment `count`s sum to at most the bucket's `count`.",
5599
5741
  "items": {
5600
5742
  "$ref": "#/components/schemas/ChartSegment"
5601
5743
  },
5602
5744
  "type": "array"
5603
5745
  },
5746
+ "segmentsOmittedCount": {
5747
+ "description": "Present only with `segments`: documents in this bucket whose segment value fell past the 25-per-bucket cap. A document COUNT, never a dollar figure. `0` means `segments` is the complete breakdown of every document that carries a segment value.",
5748
+ "type": "number"
5749
+ },
5604
5750
  "unattributed": {
5605
5751
  "description": "Present and `true` on at most one bucket, which is NOT a real category: it collects the documents this breakdown could not attribute to any value of the slice, and its `label` (`\"(unknown)\"`) is prose, not an identifier. Filter on THIS field, never on the label — a consumer plotting a map or joining the label to a real code must drop this row. It is always the LAST element of `data`, appended after ranking rather than placed by value, so it is the one bucket that does not obey the ordering `measure` describes. It exists so the bars account for the same population the summary reports: a document whose slice field holds no indexed term is absent from a terms breakdown entirely rather than forming a small bucket, so without it a chart silently omits part of its own corpus. That reconciliation is exact for `units`; on an average it is not, because buckets under the five-document floor are dropped and a slice with more than 200 distinct values still truncates — neither loss is collected in this row (the truncated tail is reported response-wide as `omittedCount`; the floor is reported nowhere). Absent when there is nothing unattributed, never `false` and never a zero-valued row.",
5606
5752
  "enum": [
@@ -5653,6 +5799,7 @@
5653
5799
  "type": "number"
5654
5800
  },
5655
5801
  "segment": {
5802
+ "description": "The second dimension echoed from the request, or `null` for a one-dimensional chart (then no bucket carries `segments`).",
5656
5803
  "nullable": true,
5657
5804
  "type": "string"
5658
5805
  },
@@ -5678,16 +5825,26 @@
5678
5825
  },
5679
5826
  "ChartSegment": {
5680
5827
  "properties": {
5828
+ "count": {
5829
+ "description": "Documents in this segment of the parent bucket — the denominator behind `value`, whatever the measure.",
5830
+ "type": "number"
5831
+ },
5832
+ "id": {
5833
+ "description": "Machine value behind `label` when the bucket key is not itself the label. `lender`: `label` is the dictionary display name and `id` the normalized lender key — pass `id`, not `label`, back as a `lender` filter value. `conforming`: `label` is `conforming` / `nonConforming` and `id` the raw `true` / `false` flag. `titleCompany` (time-series segment): `label` is the most-used spelling and `id` the normalized name every spelling was merged under — match segments across buckets on `id`. Absent on slices whose `label` is already the machine value.",
5834
+ "type": "string"
5835
+ },
5681
5836
  "label": {
5682
5837
  "type": "string"
5683
5838
  },
5684
5839
  "value": {
5840
+ "description": "The requested `measure` over the documents in BOTH this segment and its parent bucket.",
5685
5841
  "type": "number"
5686
5842
  }
5687
5843
  },
5688
5844
  "required": [
5689
5845
  "label",
5690
- "value"
5846
+ "value",
5847
+ "count"
5691
5848
  ],
5692
5849
  "type": "object"
5693
5850
  },
@@ -6514,6 +6671,16 @@
6514
6671
  "period": {
6515
6672
  "$ref": "#/components/schemas/Period"
6516
6673
  },
6674
+ "segment": {
6675
+ "allOf": [
6676
+ {
6677
+ "$ref": "#/components/schemas/CompanySlice"
6678
+ },
6679
+ {
6680
+ "description": "Optional second dimension. Each bucket of `slice` then carries `segments`: the same `measure` broken down by this dimension (e.g. `slice: \"loanType\", segment: \"lender\"` is loan type × lender in one call), top 25 per bucket ranked by `measure`, with the remainder counted in `segmentsOmittedCount`. Omit it and the response is the one-dimensional chart, unchanged. Must differ from `slice`, and cannot be combined with `size` / `after` paging (400)."
6681
+ }
6682
+ ]
6683
+ },
6517
6684
  "slice": {
6518
6685
  "$ref": "#/components/schemas/CompanySlice"
6519
6686
  }
@@ -8828,7 +8995,24 @@
8828
8995
  },
8829
8996
  "type": "object"
8830
8997
  },
8998
+ "CompanySegment": {
8999
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`. Time-series only: `titleCompany` groups on the title company named on the deed, with spellings of one company merged per bucket (`label` = most-used spelling across the response, `id` = normalized name — match on `id`) and \"none available\"-style placeholders excluded.",
9000
+ "enum": [
9001
+ "transactionType",
9002
+ "loanType",
9003
+ "city",
9004
+ "county",
9005
+ "state",
9006
+ "zip",
9007
+ "lender",
9008
+ "propertyType",
9009
+ "conforming",
9010
+ "titleCompany"
9011
+ ],
9012
+ "type": "string"
9013
+ },
8831
9014
  "CompanySlice": {
9015
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`.",
8832
9016
  "enum": [
8833
9017
  "transactionType",
8834
9018
  "loanType",
@@ -8836,7 +9020,9 @@
8836
9020
  "county",
8837
9021
  "state",
8838
9022
  "zip",
8839
- "lender"
9023
+ "lender",
9024
+ "propertyType",
9025
+ "conforming"
8840
9026
  ],
8841
9027
  "type": "string"
8842
9028
  },
@@ -10308,7 +10494,7 @@
10308
10494
  "$ref": "#/components/schemas/Period"
10309
10495
  },
10310
10496
  "segment": {
10311
- "$ref": "#/components/schemas/CompanySlice"
10497
+ "$ref": "#/components/schemas/CompanySegment"
10312
10498
  }
10313
10499
  },
10314
10500
  "type": "object"
@@ -10572,6 +10758,10 @@
10572
10758
  },
10573
10759
  "type": "array"
10574
10760
  },
10761
+ "occurredAt": {
10762
+ "nullable": true,
10763
+ "type": "number"
10764
+ },
10575
10765
  "parentId": {
10576
10766
  "nullable": true,
10577
10767
  "type": "string"
@@ -10601,7 +10791,8 @@
10601
10791
  "actorEmail",
10602
10792
  "actorImage",
10603
10793
  "createdAt",
10604
- "editedAt"
10794
+ "editedAt",
10795
+ "occurredAt"
10605
10796
  ],
10606
10797
  "type": "object"
10607
10798
  },
@@ -11738,6 +11929,25 @@
11738
11929
  ],
11739
11930
  "type": "object"
11740
11931
  },
11932
+ "CrmEditLoggedActivityResult": {
11933
+ "properties": {
11934
+ "data": {
11935
+ "properties": {
11936
+ "updated": {
11937
+ "type": "boolean"
11938
+ }
11939
+ },
11940
+ "required": [
11941
+ "updated"
11942
+ ],
11943
+ "type": "object"
11944
+ }
11945
+ },
11946
+ "required": [
11947
+ "data"
11948
+ ],
11949
+ "type": "object"
11950
+ },
11741
11951
  "CrmEditNoteResult": {
11742
11952
  "properties": {
11743
11953
  "data": {
@@ -13032,6 +13242,34 @@
13032
13242
  ],
13033
13243
  "type": "object"
13034
13244
  },
13245
+ "CrmLogActivityResult": {
13246
+ "properties": {
13247
+ "data": {
13248
+ "properties": {
13249
+ "id": {
13250
+ "type": "string"
13251
+ }
13252
+ },
13253
+ "required": [
13254
+ "id"
13255
+ ],
13256
+ "type": "object"
13257
+ }
13258
+ },
13259
+ "required": [
13260
+ "data"
13261
+ ],
13262
+ "type": "object"
13263
+ },
13264
+ "CrmLoggedActivityType": {
13265
+ "enum": [
13266
+ "call",
13267
+ "meeting",
13268
+ "email",
13269
+ "task"
13270
+ ],
13271
+ "type": "string"
13272
+ },
13035
13273
  "CrmMailProvider": {
13036
13274
  "enum": [
13037
13275
  "google",
@@ -18965,6 +19203,16 @@
18965
19203
  "period": {
18966
19204
  "$ref": "#/components/schemas/DatedPeriod"
18967
19205
  },
19206
+ "segment": {
19207
+ "allOf": [
19208
+ {
19209
+ "$ref": "#/components/schemas/LoanSlice"
19210
+ },
19211
+ {
19212
+ "description": "Optional second dimension. Each bucket of `slice` then carries `segments`: the same `measure` broken down by this dimension (e.g. `slice: \"loanType\", segment: \"lender\"` is loan type × lender in one call), top 25 per bucket ranked by `measure`, with the remainder counted in `segmentsOmittedCount`. Omit it and the response is the one-dimensional chart, unchanged. Must differ from `slice`, and cannot be combined with `size` / `after` paging (400)."
19213
+ }
19214
+ ]
19215
+ },
18968
19216
  "size": {
18969
19217
  "description": "PARTY slices only (`originator`, `broker`, `loanCompany`; 400 on any other slice). Page size for the ranking, 1–1000. Passing `size` (or `after`) turns on paging: the response gains `total` / `totalApproximate` and, when more buckets remain, `nextCursor`, and drops `omittedCount`. Buckets stay ranked by `measure` descending (ties broken by key), and bucket labels stay NMLS ids. Default when only `after` is sent: 200, the fixed chart's bucket count. Paging reaches the top 10,000 ranked buckets.",
18970
19218
  "maximum": 1000,
@@ -19448,7 +19696,27 @@
19448
19696
  ],
19449
19697
  "type": "object"
19450
19698
  },
19699
+ "LoanSegment": {
19700
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`. Time-series only: `titleCompany` groups on the title company named on the deed, with spellings of one company merged per bucket (`label` = most-used spelling across the response, `id` = normalized name — match on `id`) and \"none available\"-style placeholders excluded.",
19701
+ "enum": [
19702
+ "transactionType",
19703
+ "loanType",
19704
+ "city",
19705
+ "county",
19706
+ "state",
19707
+ "zip",
19708
+ "lender",
19709
+ "propertyType",
19710
+ "conforming",
19711
+ "broker",
19712
+ "loanCompany",
19713
+ "originator",
19714
+ "titleCompany"
19715
+ ],
19716
+ "type": "string"
19717
+ },
19451
19718
  "LoanSlice": {
19719
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`.",
19452
19720
  "enum": [
19453
19721
  "transactionType",
19454
19722
  "loanType",
@@ -19457,6 +19725,8 @@
19457
19725
  "state",
19458
19726
  "zip",
19459
19727
  "lender",
19728
+ "propertyType",
19729
+ "conforming",
19460
19730
  "broker",
19461
19731
  "loanCompany",
19462
19732
  "originator"
@@ -20220,7 +20490,7 @@
20220
20490
  "$ref": "#/components/schemas/DatedPeriod"
20221
20491
  },
20222
20492
  "segment": {
20223
- "$ref": "#/components/schemas/LoanSlice"
20493
+ "$ref": "#/components/schemas/LoanSegment"
20224
20494
  }
20225
20495
  },
20226
20496
  "type": "object"
@@ -23816,6 +24086,16 @@
23816
24086
  "period": {
23817
24087
  "$ref": "#/components/schemas/Period"
23818
24088
  },
24089
+ "segment": {
24090
+ "allOf": [
24091
+ {
24092
+ "$ref": "#/components/schemas/OriginatorSlice"
24093
+ },
24094
+ {
24095
+ "description": "Optional second dimension. Each bucket of `slice` then carries `segments`: the same `measure` broken down by this dimension (e.g. `slice: \"loanType\", segment: \"lender\"` is loan type × lender in one call), top 25 per bucket ranked by `measure`, with the remainder counted in `segmentsOmittedCount`. Omit it and the response is the one-dimensional chart, unchanged. Must differ from `slice`, and cannot be combined with `size` / `after` paging (400)."
24096
+ }
24097
+ ]
24098
+ },
23819
24099
  "slice": {
23820
24100
  "$ref": "#/components/schemas/OriginatorSlice"
23821
24101
  }
@@ -24232,6 +24512,16 @@
24232
24512
  "nullable": true,
24233
24513
  "type": "number"
24234
24514
  },
24515
+ "careerYears": {
24516
+ "description": "Years since the earliest employer start on `records`, to 0.1 — the career length the recruiting signal is judged on. Null when no record carries a start date.",
24517
+ "nullable": true,
24518
+ "type": "number"
24519
+ },
24520
+ "currentTenureYears": {
24521
+ "description": "Years at the current employer (the first employer with an active record), from its earliest record start to today, to 0.1. Null when no employer is active or its start date is unknown. A 365-day year, matching the profile's recruiting signal.",
24522
+ "nullable": true,
24523
+ "type": "number"
24524
+ },
24235
24525
  "employerChanges": {
24236
24526
  "nullable": true,
24237
24527
  "type": "number"
@@ -24242,6 +24532,37 @@
24242
24532
  },
24243
24533
  "type": "array"
24244
24534
  },
24535
+ "recruitingSignal": {
24536
+ "description": "One-line read of this career for recruiting, rule-based over `records` (employers grouped by company NMLS id): no records → `none`; over half the employers missing a start date → `incompleteData`; under 2 career years with one employer → `limitedHistory`; one employer for 2+ years → `singleEmployerCareer`; 5+ years at the current employer with ≤3 employers → `longTermStability`; 3+ employers at ≥0.6 per career year → `highMobility`; under 1 year at the current employer with >1 employer → `recentMove`; 3-4 employers over 5+ years → `multiSponsorships`; otherwise `establishedAtCurrent`. First matching rule wins. `label`/`subtext` are display copy.",
24537
+ "properties": {
24538
+ "code": {
24539
+ "enum": [
24540
+ "singleEmployerCareer",
24541
+ "longTermStability",
24542
+ "establishedAtCurrent",
24543
+ "recentMove",
24544
+ "highMobility",
24545
+ "multiSponsorships",
24546
+ "limitedHistory",
24547
+ "incompleteData",
24548
+ "none"
24549
+ ],
24550
+ "type": "string"
24551
+ },
24552
+ "label": {
24553
+ "type": "string"
24554
+ },
24555
+ "subtext": {
24556
+ "type": "string"
24557
+ }
24558
+ },
24559
+ "required": [
24560
+ "code",
24561
+ "label",
24562
+ "subtext"
24563
+ ],
24564
+ "type": "object"
24565
+ },
24245
24566
  "totalEmployers": {
24246
24567
  "type": "number"
24247
24568
  },
@@ -24284,6 +24605,9 @@
24284
24605
  "nullable": true,
24285
24606
  "type": "array"
24286
24607
  },
24608
+ "profileInsights": {
24609
+ "$ref": "#/components/schemas/OriginatorProfileInsights"
24610
+ },
24287
24611
  "registration": {
24288
24612
  "$ref": "#/components/schemas/OriginatorRegistration"
24289
24613
  },
@@ -26077,6 +26401,41 @@
26077
26401
  ],
26078
26402
  "type": "object"
26079
26403
  },
26404
+ "OriginatorProfileInsights": {
26405
+ "description": "Profile read-outs computed server-side from the LO's production in the requested `period` (default `last12Months`) — the same `volume` / `units` this response reports. Labels and descriptions are display copy; branch on the enum fields.",
26406
+ "properties": {
26407
+ "producerTier": {
26408
+ "description": "From the period's units and volume, first match wins: `topProducer` ≥ 60 units OR ≥ $25M; `strongProducer` ≥ 36 units OR ≥ $12M; `midTier` ≥ 18 units OR ≥ $5M; else `lowVolume`.",
26409
+ "properties": {
26410
+ "description": {
26411
+ "type": "string"
26412
+ },
26413
+ "label": {
26414
+ "type": "string"
26415
+ },
26416
+ "tier": {
26417
+ "enum": [
26418
+ "topProducer",
26419
+ "strongProducer",
26420
+ "midTier",
26421
+ "lowVolume"
26422
+ ],
26423
+ "type": "string"
26424
+ }
26425
+ },
26426
+ "required": [
26427
+ "tier",
26428
+ "label",
26429
+ "description"
26430
+ ],
26431
+ "type": "object"
26432
+ }
26433
+ },
26434
+ "required": [
26435
+ "producerTier"
26436
+ ],
26437
+ "type": "object"
26438
+ },
26080
26439
  "OriginatorRegistration": {
26081
26440
  "nullable": true,
26082
26441
  "properties": {
@@ -26149,7 +26508,24 @@
26149
26508
  },
26150
26509
  "type": "object"
26151
26510
  },
26511
+ "OriginatorSegment": {
26512
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`. Time-series only: `titleCompany` groups on the title company named on the deed, with spellings of one company merged per bucket (`label` = most-used spelling across the response, `id` = normalized name — match on `id`) and \"none available\"-style placeholders excluded.",
26513
+ "enum": [
26514
+ "transactionType",
26515
+ "loanType",
26516
+ "city",
26517
+ "county",
26518
+ "state",
26519
+ "zip",
26520
+ "lender",
26521
+ "propertyType",
26522
+ "conforming",
26523
+ "titleCompany"
26524
+ ],
26525
+ "type": "string"
26526
+ },
26152
26527
  "OriginatorSlice": {
26528
+ "description": "Dimension to group by. Most are the loan's own field (`loanType`, `transactionType`, `state`, …). `lender` groups on the normalized lender (label = display name, `id` = filter key). `propertyType` is the recorder's land-use code of the collateral (`SFR` single-family, `PUD` planned unit development, `CND` condo, `RES` residential-other, `MFD` multi-family dwelling, `2ND` second home, `MFG` manufactured, `TWN` townhouse, `LAN` land, `COM` commercial, …); loans with no code are not grouped (≈20% of recent loans). `conforming` splits on the conforming-loan-limit flag: `conforming` vs `nonConforming` (above the county conforming limit — i.e. jumbo, not Non-QM), raw `true`/`false` on `id`.",
26153
26529
  "enum": [
26154
26530
  "transactionType",
26155
26531
  "loanType",
@@ -26157,7 +26533,9 @@
26157
26533
  "county",
26158
26534
  "state",
26159
26535
  "zip",
26160
- "lender"
26536
+ "lender",
26537
+ "propertyType",
26538
+ "conforming"
26161
26539
  ],
26162
26540
  "type": "string"
26163
26541
  },
@@ -27080,7 +27458,7 @@
27080
27458
  "$ref": "#/components/schemas/Period"
27081
27459
  },
27082
27460
  "segment": {
27083
- "$ref": "#/components/schemas/OriginatorSlice"
27461
+ "$ref": "#/components/schemas/OriginatorSegment"
27084
27462
  }
27085
27463
  },
27086
27464
  "type": "object"
@@ -31445,7 +31823,7 @@
31445
31823
  "SimpleTimeSeriesSegment": {
31446
31824
  "properties": {
31447
31825
  "id": {
31448
- "description": "Machine value behind `label` when the bucket key is an id rather than a name (today: the `lender` slice/segment, where `label` is the dictionary display name). Pass `id` — not `label` — back as a `lender` filter value. Absent on slices whose `label` is already the machine value.",
31826
+ "description": "Machine value behind `label` when the bucket key is not itself the label. `lender`: `label` is the dictionary display name and `id` the normalized lender key — pass `id`, not `label`, back as a `lender` filter value. `conforming`: `label` is `conforming` / `nonConforming` and `id` the raw `true` / `false` flag. `titleCompany` (time-series segment): `label` is the most-used spelling and `id` the normalized name every spelling was merged under — match segments across buckets on `id`. Absent on slices whose `label` is already the machine value.",
31449
31827
  "type": "string"
31450
31828
  },
31451
31829
  "label": {
@@ -32308,9 +32686,9 @@
32308
32686
  }
32309
32687
  },
32310
32688
  "info": {
32311
- "description": "ModelMatch API — mortgage and real-estate market data.\n\n## Bulk delivery limits\n\nThe `POST /{entity}/bulk-delivery` endpoints export a full result set to\nS3 as NDJSON, JSON, CSV, or Parquet.\n\n- **Up to 1,000 rows per delivery: no entitlement required.** Available to\n every authenticated caller.\n- **Above 1,000 rows: requires the entity's `bulk-delivery.{entity}`\n entitlement** (e.g. `bulk-delivery.loans`). The grant's scope sets your\n row ceiling and may also restrict which rows and fields you receive.\n\nA delivery that would exceed the limit available to you is rejected with\n`403 entitlement_missing`, reporting the matched row count and the ceiling\nthat applied. To stay under the limit, pass `limit` — the job then delivers\n(and bills for) only the first `limit` rows of a broader query.\n\n**Need a higher limit?** Email support@modelmatch.com to request the\nentitlement. Limits are raised per organization and per entity.\n\nBulk delivery is billed at 1 credit per delivered row whether or not\nan entitlement is held.",
32689
+ "description": "ModelMatch API — mortgage and real-estate market data.\n\n## Bulk delivery limits\n\nThe `POST /{entity}/bulk-delivery` endpoints export a full result set to\nS3 as NDJSON, JSON, CSV, or Parquet.\n\n- **Up to 5,000 rows per delivery: no entitlement required.** Available to\n every authenticated caller.\n- **Above 5,000 rows: requires the entity's `bulk-delivery.{entity}`\n entitlement** (e.g. `bulk-delivery.loans`). The grant's scope sets your\n row ceiling and may also restrict which rows and fields you receive.\n\nA delivery that would exceed the limit available to you is rejected with\n`403 entitlement_missing`, reporting the matched row count and the ceiling\nthat applied. To stay under the limit, pass `limit` — the job then delivers\n(and bills for) only the first `limit` rows of a broader query.\n\n**Need a higher limit?** Email support@modelmatch.com to request the\nentitlement. Limits are raised per organization and per entity.\n\nBulk delivery is billed at 1 credit per delivered row whether or not\nan entitlement is held.\n\nInstead of a download, a delivery can be sent to a push target configured\non your workspace: pass `destination: { type: \"push-target\", targetId }`.\nThe same limits and billing apply.",
32312
32690
  "title": "ModelMatch API",
32313
- "version": "3.62.0"
32691
+ "version": "3.64.0"
32314
32692
  },
32315
32693
  "openapi": "3.1.0",
32316
32694
  "paths": {
@@ -32647,7 +33025,7 @@
32647
33025
  },
32648
33026
  "/agents/bulk-delivery": {
32649
33027
  "post": {
32650
- "description": "Queues an ECS task that dumps every matching agents document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/agents/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.agents` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
33028
+ "description": "Queues an ECS task that dumps every matching agents document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/agents/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.agents` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
32651
33029
  "operationId": "submitAgentsBulkDelivery",
32652
33030
  "requestBody": {
32653
33031
  "content": {
@@ -32660,8 +33038,11 @@
32660
33038
  {
32661
33039
  "additionalProperties": false,
32662
33040
  "properties": {
33041
+ "destination": {
33042
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
33043
+ },
32663
33044
  "fields": {
32664
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
33045
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
32665
33046
  "items": {
32666
33047
  "minLength": 1,
32667
33048
  "type": "string"
@@ -32672,16 +33053,13 @@
32672
33053
  "$ref": "#/components/schemas/BulkDeliveryFormat"
32673
33054
  },
32674
33055
  "limit": {
32675
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
32676
- "example": 1000,
33056
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
33057
+ "example": 5000,
32677
33058
  "exclusiveMinimum": true,
32678
33059
  "minimum": 0,
32679
33060
  "type": "integer"
32680
33061
  }
32681
33062
  },
32682
- "required": [
32683
- "format"
32684
- ],
32685
33063
  "type": "object"
32686
33064
  }
32687
33065
  ]
@@ -32710,12 +33088,15 @@
32710
33088
  },
32711
33089
  {
32712
33090
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
33091
+ },
33092
+ {
33093
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
32713
33094
  }
32714
33095
  ]
32715
33096
  }
32716
33097
  }
32717
33098
  },
32718
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
33099
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
32719
33100
  },
32720
33101
  "401": {
32721
33102
  "content": {
@@ -32731,21 +33112,58 @@
32731
33112
  "content": {
32732
33113
  "application/json": {
32733
33114
  "schema": {
32734
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
33115
+ "anyOf": [
33116
+ {
33117
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
33118
+ },
33119
+ {
33120
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
33121
+ }
33122
+ ]
32735
33123
  }
32736
33124
  }
32737
33125
  },
32738
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
33126
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
32739
33127
  },
32740
33128
  "403": {
32741
33129
  "content": {
32742
33130
  "application/json": {
32743
33131
  "schema": {
32744
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
33132
+ "anyOf": [
33133
+ {
33134
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
33135
+ },
33136
+ {
33137
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
33138
+ },
33139
+ {
33140
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
33141
+ }
33142
+ ]
33143
+ }
33144
+ }
33145
+ },
33146
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.agents` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
33147
+ },
33148
+ "404": {
33149
+ "content": {
33150
+ "application/json": {
33151
+ "schema": {
33152
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
33153
+ }
33154
+ }
33155
+ },
33156
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
33157
+ },
33158
+ "409": {
33159
+ "content": {
33160
+ "application/json": {
33161
+ "schema": {
33162
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
32745
33163
  }
32746
33164
  }
32747
33165
  },
32748
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.agents` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
33166
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
32749
33167
  },
32750
33168
  "502": {
32751
33169
  "content": {
@@ -32755,7 +33173,7 @@
32755
33173
  }
32756
33174
  }
32757
33175
  },
32758
- "description": "Preflight / S3 / DDB / ECS launch failure"
33176
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
32759
33177
  }
32760
33178
  },
32761
33179
  "summary": "Submit a bulk-delivery job for agents",
@@ -34396,7 +34814,7 @@
34396
34814
  },
34397
34815
  "/originators/bulk-delivery": {
34398
34816
  "post": {
34399
- "description": "Queues an ECS task that dumps every matching originators document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/originators/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.originators` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
34817
+ "description": "Queues an ECS task that dumps every matching originators document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/originators/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.originators` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
34400
34818
  "operationId": "submitOriginatorsBulkDelivery",
34401
34819
  "requestBody": {
34402
34820
  "content": {
@@ -34409,8 +34827,11 @@
34409
34827
  {
34410
34828
  "additionalProperties": false,
34411
34829
  "properties": {
34830
+ "destination": {
34831
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
34832
+ },
34412
34833
  "fields": {
34413
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
34834
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
34414
34835
  "items": {
34415
34836
  "minLength": 1,
34416
34837
  "type": "string"
@@ -34421,16 +34842,13 @@
34421
34842
  "$ref": "#/components/schemas/BulkDeliveryFormat"
34422
34843
  },
34423
34844
  "limit": {
34424
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
34425
- "example": 1000,
34845
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
34846
+ "example": 5000,
34426
34847
  "exclusiveMinimum": true,
34427
34848
  "minimum": 0,
34428
34849
  "type": "integer"
34429
34850
  }
34430
34851
  },
34431
- "required": [
34432
- "format"
34433
- ],
34434
34852
  "type": "object"
34435
34853
  }
34436
34854
  ]
@@ -34459,12 +34877,15 @@
34459
34877
  },
34460
34878
  {
34461
34879
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
34880
+ },
34881
+ {
34882
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34462
34883
  }
34463
34884
  ]
34464
34885
  }
34465
34886
  }
34466
34887
  },
34467
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
34888
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
34468
34889
  },
34469
34890
  "401": {
34470
34891
  "content": {
@@ -34480,21 +34901,58 @@
34480
34901
  "content": {
34481
34902
  "application/json": {
34482
34903
  "schema": {
34483
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
34904
+ "anyOf": [
34905
+ {
34906
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
34907
+ },
34908
+ {
34909
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34910
+ }
34911
+ ]
34484
34912
  }
34485
34913
  }
34486
34914
  },
34487
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
34915
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
34488
34916
  },
34489
34917
  "403": {
34490
34918
  "content": {
34491
34919
  "application/json": {
34492
34920
  "schema": {
34493
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
34921
+ "anyOf": [
34922
+ {
34923
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
34924
+ },
34925
+ {
34926
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34927
+ },
34928
+ {
34929
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
34930
+ }
34931
+ ]
34932
+ }
34933
+ }
34934
+ },
34935
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.originators` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
34936
+ },
34937
+ "404": {
34938
+ "content": {
34939
+ "application/json": {
34940
+ "schema": {
34941
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34942
+ }
34943
+ }
34944
+ },
34945
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
34946
+ },
34947
+ "409": {
34948
+ "content": {
34949
+ "application/json": {
34950
+ "schema": {
34951
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34494
34952
  }
34495
34953
  }
34496
34954
  },
34497
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.originators` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
34955
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
34498
34956
  },
34499
34957
  "502": {
34500
34958
  "content": {
@@ -34504,7 +34962,7 @@
34504
34962
  }
34505
34963
  }
34506
34964
  },
34507
- "description": "Preflight / S3 / DDB / ECS launch failure"
34965
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
34508
34966
  }
34509
34967
  },
34510
34968
  "summary": "Submit a bulk-delivery job for originators",
@@ -35628,6 +36086,99 @@
35628
36086
  ]
35629
36087
  }
35630
36088
  },
36089
+ "/originators/{nmlsId}/breakdowns/title-companies": {
36090
+ "post": {
36091
+ "description": "Ranks the title companies on this entity's loans (recorded deeds, `recording_date` window — the same population and period axis as every other loans breakdown). Spellings of one company are MERGED: `label` is its most-used spelling, `id` the normalized name every spelling was grouped under (legal-form and filler words such as `inc`, `llc`, `co`, `company`, `title`, `escrow` removed; `natl` → `national`). Placeholder entries meaning \"no title company recorded\" (`none available` and its misspellings, `unknown`, `n/a`, …) and names shorter than 3 characters are excluded rather than ranked. `attorney` and `accommodation` are kept: they say who closed. `pctUnits` / `pctVolume` are shares of the entity's WHOLE loan population in the window, including loans with no title company, so they do not sum to 100 across the page. Pages of up to 100 via `pagination`.",
36092
+ "operationId": "originatorTitleCompanies",
36093
+ "parameters": [
36094
+ {
36095
+ "description": "Originator NMLS ID",
36096
+ "in": "path",
36097
+ "name": "nmlsId",
36098
+ "required": true,
36099
+ "schema": {
36100
+ "description": "Originator NMLS ID",
36101
+ "type": "string"
36102
+ }
36103
+ }
36104
+ ],
36105
+ "requestBody": {
36106
+ "content": {
36107
+ "application/json": {
36108
+ "schema": {
36109
+ "$ref": "#/components/schemas/BreakdownRequest"
36110
+ }
36111
+ }
36112
+ }
36113
+ },
36114
+ "responses": {
36115
+ "200": {
36116
+ "content": {
36117
+ "application/json": {
36118
+ "schema": {
36119
+ "$ref": "#/components/schemas/BreakdownResponse"
36120
+ }
36121
+ }
36122
+ },
36123
+ "description": "Breakdown of the entity's loans by title company"
36124
+ },
36125
+ "400": {
36126
+ "content": {
36127
+ "application/json": {
36128
+ "schema": {
36129
+ "$ref": "#/components/schemas/ErrorResponse"
36130
+ }
36131
+ }
36132
+ },
36133
+ "description": "Validation error"
36134
+ },
36135
+ "401": {
36136
+ "content": {
36137
+ "application/json": {
36138
+ "schema": {
36139
+ "$ref": "#/components/schemas/ErrorResponse"
36140
+ }
36141
+ }
36142
+ },
36143
+ "description": "Unauthorized"
36144
+ },
36145
+ "403": {
36146
+ "content": {
36147
+ "application/json": {
36148
+ "schema": {
36149
+ "$ref": "#/components/schemas/ErrorResponse"
36150
+ }
36151
+ }
36152
+ },
36153
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
36154
+ },
36155
+ "404": {
36156
+ "content": {
36157
+ "application/json": {
36158
+ "schema": {
36159
+ "$ref": "#/components/schemas/ErrorResponse"
36160
+ }
36161
+ }
36162
+ },
36163
+ "description": "Not found"
36164
+ },
36165
+ "413": {
36166
+ "content": {
36167
+ "application/json": {
36168
+ "schema": {
36169
+ "$ref": "#/components/schemas/PayloadTooLarge"
36170
+ }
36171
+ }
36172
+ },
36173
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
36174
+ }
36175
+ },
36176
+ "summary": "Title companies on this LO's loans (spellings merged)",
36177
+ "tags": [
36178
+ "Originators"
36179
+ ]
36180
+ }
36181
+ },
35631
36182
  "/originators/{nmlsId}/breakdowns/zip-codes": {
35632
36183
  "post": {
35633
36184
  "operationId": "originatorZipCodes",
@@ -36600,7 +37151,7 @@
36600
37151
  },
36601
37152
  "/v1/agents/bulk-delivery": {
36602
37153
  "post": {
36603
- "description": "Queues an ECS task that dumps every matching agents document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/agents/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.agents` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
37154
+ "description": "Queues an ECS task that dumps every matching agents document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/agents/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.agents` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
36604
37155
  "operationId": "submitAgentsBulkDelivery",
36605
37156
  "requestBody": {
36606
37157
  "content": {
@@ -36613,8 +37164,11 @@
36613
37164
  {
36614
37165
  "additionalProperties": false,
36615
37166
  "properties": {
37167
+ "destination": {
37168
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
37169
+ },
36616
37170
  "fields": {
36617
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
37171
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
36618
37172
  "items": {
36619
37173
  "minLength": 1,
36620
37174
  "type": "string"
@@ -36625,16 +37179,13 @@
36625
37179
  "$ref": "#/components/schemas/BulkDeliveryFormat"
36626
37180
  },
36627
37181
  "limit": {
36628
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
36629
- "example": 1000,
37182
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
37183
+ "example": 5000,
36630
37184
  "exclusiveMinimum": true,
36631
37185
  "minimum": 0,
36632
37186
  "type": "integer"
36633
37187
  }
36634
37188
  },
36635
- "required": [
36636
- "format"
36637
- ],
36638
37189
  "type": "object"
36639
37190
  }
36640
37191
  ]
@@ -36663,12 +37214,15 @@
36663
37214
  },
36664
37215
  {
36665
37216
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
37217
+ },
37218
+ {
37219
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
36666
37220
  }
36667
37221
  ]
36668
37222
  }
36669
37223
  }
36670
37224
  },
36671
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
37225
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
36672
37226
  },
36673
37227
  "401": {
36674
37228
  "content": {
@@ -36684,21 +37238,58 @@
36684
37238
  "content": {
36685
37239
  "application/json": {
36686
37240
  "schema": {
36687
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
37241
+ "anyOf": [
37242
+ {
37243
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
37244
+ },
37245
+ {
37246
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
37247
+ }
37248
+ ]
36688
37249
  }
36689
37250
  }
36690
37251
  },
36691
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
37252
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
36692
37253
  },
36693
37254
  "403": {
36694
37255
  "content": {
36695
37256
  "application/json": {
36696
37257
  "schema": {
36697
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
37258
+ "anyOf": [
37259
+ {
37260
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
37261
+ },
37262
+ {
37263
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
37264
+ },
37265
+ {
37266
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
37267
+ }
37268
+ ]
37269
+ }
37270
+ }
37271
+ },
37272
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.agents` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
37273
+ },
37274
+ "404": {
37275
+ "content": {
37276
+ "application/json": {
37277
+ "schema": {
37278
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
36698
37279
  }
36699
37280
  }
36700
37281
  },
36701
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.agents` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
37282
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
37283
+ },
37284
+ "409": {
37285
+ "content": {
37286
+ "application/json": {
37287
+ "schema": {
37288
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
37289
+ }
37290
+ }
37291
+ },
37292
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
36702
37293
  },
36703
37294
  "502": {
36704
37295
  "content": {
@@ -36708,7 +37299,7 @@
36708
37299
  }
36709
37300
  }
36710
37301
  },
36711
- "description": "Preflight / S3 / DDB / ECS launch failure"
37302
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
36712
37303
  }
36713
37304
  },
36714
37305
  "summary": "Submit a bulk-delivery job for agents",
@@ -39902,7 +40493,7 @@
39902
40493
  },
39903
40494
  "/v1/branches/bulk-delivery": {
39904
40495
  "post": {
39905
- "description": "Queues an ECS task that dumps every matching branches document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/branches/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.branches` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
40496
+ "description": "Queues an ECS task that dumps every matching branches document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/branches/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.branches` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
39906
40497
  "operationId": "submitBranchesBulkDelivery",
39907
40498
  "requestBody": {
39908
40499
  "content": {
@@ -39915,8 +40506,11 @@
39915
40506
  {
39916
40507
  "additionalProperties": false,
39917
40508
  "properties": {
40509
+ "destination": {
40510
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
40511
+ },
39918
40512
  "fields": {
39919
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
40513
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
39920
40514
  "items": {
39921
40515
  "minLength": 1,
39922
40516
  "type": "string"
@@ -39927,16 +40521,13 @@
39927
40521
  "$ref": "#/components/schemas/BulkDeliveryFormat"
39928
40522
  },
39929
40523
  "limit": {
39930
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
39931
- "example": 1000,
40524
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
40525
+ "example": 5000,
39932
40526
  "exclusiveMinimum": true,
39933
40527
  "minimum": 0,
39934
40528
  "type": "integer"
39935
40529
  }
39936
40530
  },
39937
- "required": [
39938
- "format"
39939
- ],
39940
40531
  "type": "object"
39941
40532
  }
39942
40533
  ]
@@ -39965,12 +40556,15 @@
39965
40556
  },
39966
40557
  {
39967
40558
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
40559
+ },
40560
+ {
40561
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
39968
40562
  }
39969
40563
  ]
39970
40564
  }
39971
40565
  }
39972
40566
  },
39973
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
40567
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
39974
40568
  },
39975
40569
  "401": {
39976
40570
  "content": {
@@ -39986,21 +40580,58 @@
39986
40580
  "content": {
39987
40581
  "application/json": {
39988
40582
  "schema": {
39989
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
40583
+ "anyOf": [
40584
+ {
40585
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
40586
+ },
40587
+ {
40588
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
40589
+ }
40590
+ ]
39990
40591
  }
39991
40592
  }
39992
40593
  },
39993
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
40594
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
39994
40595
  },
39995
40596
  "403": {
39996
40597
  "content": {
39997
40598
  "application/json": {
39998
40599
  "schema": {
39999
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
40600
+ "anyOf": [
40601
+ {
40602
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
40603
+ },
40604
+ {
40605
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
40606
+ },
40607
+ {
40608
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
40609
+ }
40610
+ ]
40611
+ }
40612
+ }
40613
+ },
40614
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.branches` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
40615
+ },
40616
+ "404": {
40617
+ "content": {
40618
+ "application/json": {
40619
+ "schema": {
40620
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
40000
40621
  }
40001
40622
  }
40002
40623
  },
40003
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.branches` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
40624
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
40625
+ },
40626
+ "409": {
40627
+ "content": {
40628
+ "application/json": {
40629
+ "schema": {
40630
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
40631
+ }
40632
+ }
40633
+ },
40634
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
40004
40635
  },
40005
40636
  "502": {
40006
40637
  "content": {
@@ -40010,7 +40641,7 @@
40010
40641
  }
40011
40642
  }
40012
40643
  },
40013
- "description": "Preflight / S3 / DDB / ECS launch failure"
40644
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
40014
40645
  }
40015
40646
  },
40016
40647
  "summary": "Submit a bulk-delivery job for branches",
@@ -40761,10 +41392,10 @@
40761
41392
  ]
40762
41393
  }
40763
41394
  },
40764
- "/v1/branches/{nmlsId}/loans": {
41395
+ "/v1/branches/{nmlsId}/breakdowns/title-companies": {
40765
41396
  "post": {
40766
- "description": "Returns the individual loans attributed to this branch office — the branch's own book, not its parent company's. Accepts the same filters, sorts and pagination as the main loan search. Attribution comes from public record, so this is the branch's recorded footprint and can be smaller than the production totals on `GET /v1/branches/{nmlsId}`.",
40767
- "operationId": "branchLoans",
41397
+ "description": "Ranks the title companies on this entity's loans (recorded deeds, `recording_date` window — the same population and period axis as every other loans breakdown). Spellings of one company are MERGED: `label` is its most-used spelling, `id` the normalized name every spelling was grouped under (legal-form and filler words such as `inc`, `llc`, `co`, `company`, `title`, `escrow` removed; `natl` → `national`). Placeholder entries meaning \"no title company recorded\" (`none available` and its misspellings, `unknown`, `n/a`, …) and names shorter than 3 characters are excluded rather than ranked. `attorney` and `accommodation` are kept: they say who closed. `pctUnits` / `pctVolume` are shares of the entity's WHOLE loan population in the window, including loans with no title company, so they do not sum to 100 across the page. Pages of up to 100 via `pagination`.",
41398
+ "operationId": "branchTitleCompanies",
40768
41399
  "parameters": [
40769
41400
  {
40770
41401
  "description": "Branch NMLS ID",
@@ -40781,7 +41412,7 @@
40781
41412
  "content": {
40782
41413
  "application/json": {
40783
41414
  "schema": {
40784
- "$ref": "#/components/schemas/LoanListRequest"
41415
+ "$ref": "#/components/schemas/BreakdownRequest"
40785
41416
  }
40786
41417
  }
40787
41418
  }
@@ -40791,11 +41422,11 @@
40791
41422
  "content": {
40792
41423
  "application/json": {
40793
41424
  "schema": {
40794
- "$ref": "#/components/schemas/LoanListResponse"
41425
+ "$ref": "#/components/schemas/BreakdownResponse"
40795
41426
  }
40796
41427
  }
40797
41428
  },
40798
- "description": "Paginated list of loans written by this branch"
41429
+ "description": "Breakdown of the entity's loans by title company"
40799
41430
  },
40800
41431
  "400": {
40801
41432
  "content": {
@@ -40848,16 +41479,16 @@
40848
41479
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
40849
41480
  }
40850
41481
  },
40851
- "summary": "Loans written by this branch",
41482
+ "summary": "Title companies on this branch's loans (spellings merged)",
40852
41483
  "tags": [
40853
41484
  "Branches"
40854
41485
  ]
40855
41486
  }
40856
41487
  },
40857
- "/v1/branches/{nmlsId}/roster": {
41488
+ "/v1/branches/{nmlsId}/loans": {
40858
41489
  "post": {
40859
- "description": "The loan officers currently registered to this branch location who hold an active sponsorship or registration — one row per person, with current employer, tenure, years in the industry, manager flags and collected contact info. This is the LICENSING roster; for the loan officers whose loans public record attributes to the branch, use `branchOriginators`.",
40860
- "operationId": "branchRoster",
41490
+ "description": "Returns the individual loans attributed to this branch office — the branch's own book, not its parent company's. Accepts the same filters, sorts and pagination as the main loan search. Attribution comes from public record, so this is the branch's recorded footprint and can be smaller than the production totals on `GET /v1/branches/{nmlsId}`.",
41491
+ "operationId": "branchLoans",
40861
41492
  "parameters": [
40862
41493
  {
40863
41494
  "description": "Branch NMLS ID",
@@ -40874,7 +41505,7 @@
40874
41505
  "content": {
40875
41506
  "application/json": {
40876
41507
  "schema": {
40877
- "$ref": "#/components/schemas/BranchRosterRequest"
41508
+ "$ref": "#/components/schemas/LoanListRequest"
40878
41509
  }
40879
41510
  }
40880
41511
  }
@@ -40884,11 +41515,11 @@
40884
41515
  "content": {
40885
41516
  "application/json": {
40886
41517
  "schema": {
40887
- "$ref": "#/components/schemas/BranchRosterResponse"
41518
+ "$ref": "#/components/schemas/LoanListResponse"
40888
41519
  }
40889
41520
  }
40890
41521
  },
40891
- "description": "Paginated roster"
41522
+ "description": "Paginated list of loans written by this branch"
40892
41523
  },
40893
41524
  "400": {
40894
41525
  "content": {
@@ -40928,7 +41559,7 @@
40928
41559
  }
40929
41560
  }
40930
41561
  },
40931
- "description": "Branch not found"
41562
+ "description": "Not found"
40932
41563
  },
40933
41564
  "413": {
40934
41565
  "content": {
@@ -40941,21 +41572,33 @@
40941
41572
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
40942
41573
  }
40943
41574
  },
40944
- "summary": "Loan officers on this branch's current roster",
41575
+ "summary": "Loans written by this branch",
40945
41576
  "tags": [
40946
41577
  "Branches"
40947
41578
  ]
40948
41579
  }
40949
41580
  },
40950
- "/v1/companies": {
41581
+ "/v1/branches/{nmlsId}/roster": {
40951
41582
  "post": {
40952
- "description": "Search and list individual mortgage companies with filters — one row per company. Filter by name, NMLS ID, location (state, county, city, zip), and production metrics (loan volume, units, market share) by period and geography, with sorting and cursor pagination. This is the per-company record search: use it to find specific mortgage lenders/companies or build a filtered company list. (For a single company by ID use the company detail endpoint.) If you only need how many companies match and not the records themselves, send the same request body to `countCompanies`.",
40953
- "operationId": "listCompanies",
41583
+ "description": "The loan officers currently registered to this branch location who hold an active sponsorship or registration — one row per person, with current employer, tenure, years in the industry, manager flags and collected contact info. This is the LICENSING roster; for the loan officers whose loans public record attributes to the branch, use `branchOriginators`.",
41584
+ "operationId": "branchRoster",
41585
+ "parameters": [
41586
+ {
41587
+ "description": "Branch NMLS ID",
41588
+ "in": "path",
41589
+ "name": "nmlsId",
41590
+ "required": true,
41591
+ "schema": {
41592
+ "description": "Branch NMLS ID",
41593
+ "type": "string"
41594
+ }
41595
+ }
41596
+ ],
40954
41597
  "requestBody": {
40955
41598
  "content": {
40956
41599
  "application/json": {
40957
41600
  "schema": {
40958
- "$ref": "#/components/schemas/CompanyListRequest"
41601
+ "$ref": "#/components/schemas/BranchRosterRequest"
40959
41602
  }
40960
41603
  }
40961
41604
  }
@@ -40965,11 +41608,11 @@
40965
41608
  "content": {
40966
41609
  "application/json": {
40967
41610
  "schema": {
40968
- "$ref": "#/components/schemas/CompanyListResponse"
41611
+ "$ref": "#/components/schemas/BranchRosterResponse"
40969
41612
  }
40970
41613
  }
40971
41614
  },
40972
- "description": "Paginated list of companies"
41615
+ "description": "Paginated roster"
40973
41616
  },
40974
41617
  "400": {
40975
41618
  "content": {
@@ -40979,7 +41622,88 @@
40979
41622
  }
40980
41623
  }
40981
41624
  },
40982
- "description": "Validation error. Includes unresolvable location filters: a `city`/`county`/`state`/`zip` value that does not match a recognized location returns 400 rather than an empty result set. Resolve the name with `instantSearch` (use an id from its `results.locations[]`) or `suggestLocations` (use the `id` of a `source: \"dictionary\"` result), then pass that id back as the filter value."
41625
+ "description": "Validation error"
41626
+ },
41627
+ "401": {
41628
+ "content": {
41629
+ "application/json": {
41630
+ "schema": {
41631
+ "$ref": "#/components/schemas/ErrorResponse"
41632
+ }
41633
+ }
41634
+ },
41635
+ "description": "Unauthorized"
41636
+ },
41637
+ "403": {
41638
+ "content": {
41639
+ "application/json": {
41640
+ "schema": {
41641
+ "$ref": "#/components/schemas/ErrorResponse"
41642
+ }
41643
+ }
41644
+ },
41645
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
41646
+ },
41647
+ "404": {
41648
+ "content": {
41649
+ "application/json": {
41650
+ "schema": {
41651
+ "$ref": "#/components/schemas/ErrorResponse"
41652
+ }
41653
+ }
41654
+ },
41655
+ "description": "Branch not found"
41656
+ },
41657
+ "413": {
41658
+ "content": {
41659
+ "application/json": {
41660
+ "schema": {
41661
+ "$ref": "#/components/schemas/PayloadTooLarge"
41662
+ }
41663
+ }
41664
+ },
41665
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
41666
+ }
41667
+ },
41668
+ "summary": "Loan officers on this branch's current roster",
41669
+ "tags": [
41670
+ "Branches"
41671
+ ]
41672
+ }
41673
+ },
41674
+ "/v1/companies": {
41675
+ "post": {
41676
+ "description": "Search and list individual mortgage companies with filters — one row per company. Filter by name, NMLS ID, location (state, county, city, zip), and production metrics (loan volume, units, market share) by period and geography, with sorting and cursor pagination. This is the per-company record search: use it to find specific mortgage lenders/companies or build a filtered company list. (For a single company by ID use the company detail endpoint.) If you only need how many companies match and not the records themselves, send the same request body to `countCompanies`.",
41677
+ "operationId": "listCompanies",
41678
+ "requestBody": {
41679
+ "content": {
41680
+ "application/json": {
41681
+ "schema": {
41682
+ "$ref": "#/components/schemas/CompanyListRequest"
41683
+ }
41684
+ }
41685
+ }
41686
+ },
41687
+ "responses": {
41688
+ "200": {
41689
+ "content": {
41690
+ "application/json": {
41691
+ "schema": {
41692
+ "$ref": "#/components/schemas/CompanyListResponse"
41693
+ }
41694
+ }
41695
+ },
41696
+ "description": "Paginated list of companies"
41697
+ },
41698
+ "400": {
41699
+ "content": {
41700
+ "application/json": {
41701
+ "schema": {
41702
+ "$ref": "#/components/schemas/ErrorResponse"
41703
+ }
41704
+ }
41705
+ },
41706
+ "description": "Validation error. Includes unresolvable location filters: a `city`/`county`/`state`/`zip` value that does not match a recognized location returns 400 rather than an empty result set. Resolve the name with `instantSearch` (use an id from its `results.locations[]`) or `suggestLocations` (use the `id` of a `source: \"dictionary\"` result), then pass that id back as the filter value."
40983
41707
  },
40984
41708
  "401": {
40985
41709
  "content": {
@@ -41270,7 +41994,7 @@
41270
41994
  },
41271
41995
  "/v1/companies/bulk-delivery": {
41272
41996
  "post": {
41273
- "description": "Queues an ECS task that dumps every matching companies document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/companies/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.companies` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
41997
+ "description": "Queues an ECS task that dumps every matching companies document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/companies/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.companies` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
41274
41998
  "operationId": "submitCompaniesBulkDelivery",
41275
41999
  "requestBody": {
41276
42000
  "content": {
@@ -41283,8 +42007,11 @@
41283
42007
  {
41284
42008
  "additionalProperties": false,
41285
42009
  "properties": {
42010
+ "destination": {
42011
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
42012
+ },
41286
42013
  "fields": {
41287
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
42014
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
41288
42015
  "items": {
41289
42016
  "minLength": 1,
41290
42017
  "type": "string"
@@ -41295,16 +42022,13 @@
41295
42022
  "$ref": "#/components/schemas/BulkDeliveryFormat"
41296
42023
  },
41297
42024
  "limit": {
41298
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
41299
- "example": 1000,
42025
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
42026
+ "example": 5000,
41300
42027
  "exclusiveMinimum": true,
41301
42028
  "minimum": 0,
41302
42029
  "type": "integer"
41303
42030
  }
41304
42031
  },
41305
- "required": [
41306
- "format"
41307
- ],
41308
42032
  "type": "object"
41309
42033
  }
41310
42034
  ]
@@ -41333,12 +42057,15 @@
41333
42057
  },
41334
42058
  {
41335
42059
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
42060
+ },
42061
+ {
42062
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
41336
42063
  }
41337
42064
  ]
41338
42065
  }
41339
42066
  }
41340
42067
  },
41341
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
42068
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
41342
42069
  },
41343
42070
  "401": {
41344
42071
  "content": {
@@ -41354,21 +42081,58 @@
41354
42081
  "content": {
41355
42082
  "application/json": {
41356
42083
  "schema": {
41357
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
42084
+ "anyOf": [
42085
+ {
42086
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
42087
+ },
42088
+ {
42089
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
42090
+ }
42091
+ ]
41358
42092
  }
41359
42093
  }
41360
42094
  },
41361
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
42095
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
41362
42096
  },
41363
42097
  "403": {
41364
42098
  "content": {
41365
42099
  "application/json": {
41366
42100
  "schema": {
41367
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
42101
+ "anyOf": [
42102
+ {
42103
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
42104
+ },
42105
+ {
42106
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
42107
+ },
42108
+ {
42109
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
42110
+ }
42111
+ ]
42112
+ }
42113
+ }
42114
+ },
42115
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.companies` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
42116
+ },
42117
+ "404": {
42118
+ "content": {
42119
+ "application/json": {
42120
+ "schema": {
42121
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
42122
+ }
42123
+ }
42124
+ },
42125
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
42126
+ },
42127
+ "409": {
42128
+ "content": {
42129
+ "application/json": {
42130
+ "schema": {
42131
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
41368
42132
  }
41369
42133
  }
41370
42134
  },
41371
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.companies` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
42135
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
41372
42136
  },
41373
42137
  "502": {
41374
42138
  "content": {
@@ -41378,7 +42142,7 @@
41378
42142
  }
41379
42143
  }
41380
42144
  },
41381
- "description": "Preflight / S3 / DDB / ECS launch failure"
42145
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
41382
42146
  }
41383
42147
  },
41384
42148
  "summary": "Submit a bulk-delivery job for companies",
@@ -42694,6 +43458,99 @@
42694
43458
  ]
42695
43459
  }
42696
43460
  },
43461
+ "/v1/companies/{nmlsId}/breakdowns/title-companies": {
43462
+ "post": {
43463
+ "description": "Ranks the title companies on this entity's loans (recorded deeds, `recording_date` window — the same population and period axis as every other loans breakdown). Spellings of one company are MERGED: `label` is its most-used spelling, `id` the normalized name every spelling was grouped under (legal-form and filler words such as `inc`, `llc`, `co`, `company`, `title`, `escrow` removed; `natl` → `national`). Placeholder entries meaning \"no title company recorded\" (`none available` and its misspellings, `unknown`, `n/a`, …) and names shorter than 3 characters are excluded rather than ranked. `attorney` and `accommodation` are kept: they say who closed. `pctUnits` / `pctVolume` are shares of the entity's WHOLE loan population in the window, including loans with no title company, so they do not sum to 100 across the page. Pages of up to 100 via `pagination`.",
43464
+ "operationId": "companyTitleCompanies",
43465
+ "parameters": [
43466
+ {
43467
+ "description": "Company NMLS ID",
43468
+ "in": "path",
43469
+ "name": "nmlsId",
43470
+ "required": true,
43471
+ "schema": {
43472
+ "description": "Company NMLS ID",
43473
+ "type": "string"
43474
+ }
43475
+ }
43476
+ ],
43477
+ "requestBody": {
43478
+ "content": {
43479
+ "application/json": {
43480
+ "schema": {
43481
+ "$ref": "#/components/schemas/BreakdownRequest"
43482
+ }
43483
+ }
43484
+ }
43485
+ },
43486
+ "responses": {
43487
+ "200": {
43488
+ "content": {
43489
+ "application/json": {
43490
+ "schema": {
43491
+ "$ref": "#/components/schemas/BreakdownResponse"
43492
+ }
43493
+ }
43494
+ },
43495
+ "description": "Breakdown of the entity's loans by title company"
43496
+ },
43497
+ "400": {
43498
+ "content": {
43499
+ "application/json": {
43500
+ "schema": {
43501
+ "$ref": "#/components/schemas/ErrorResponse"
43502
+ }
43503
+ }
43504
+ },
43505
+ "description": "Validation error"
43506
+ },
43507
+ "401": {
43508
+ "content": {
43509
+ "application/json": {
43510
+ "schema": {
43511
+ "$ref": "#/components/schemas/ErrorResponse"
43512
+ }
43513
+ }
43514
+ },
43515
+ "description": "Unauthorized"
43516
+ },
43517
+ "403": {
43518
+ "content": {
43519
+ "application/json": {
43520
+ "schema": {
43521
+ "$ref": "#/components/schemas/ErrorResponse"
43522
+ }
43523
+ }
43524
+ },
43525
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
43526
+ },
43527
+ "404": {
43528
+ "content": {
43529
+ "application/json": {
43530
+ "schema": {
43531
+ "$ref": "#/components/schemas/ErrorResponse"
43532
+ }
43533
+ }
43534
+ },
43535
+ "description": "Not found"
43536
+ },
43537
+ "413": {
43538
+ "content": {
43539
+ "application/json": {
43540
+ "schema": {
43541
+ "$ref": "#/components/schemas/PayloadTooLarge"
43542
+ }
43543
+ }
43544
+ },
43545
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
43546
+ }
43547
+ },
43548
+ "summary": "Title companies on this company's loans (spellings merged)",
43549
+ "tags": [
43550
+ "Companies"
43551
+ ]
43552
+ }
43553
+ },
42697
43554
  "/v1/companies/{nmlsId}/breakdowns/zip-codes": {
42698
43555
  "post": {
42699
43556
  "operationId": "companyZipCodes",
@@ -44215,32 +45072,328 @@
44215
45072
  ]
44216
45073
  }
44217
45074
  },
44218
- "/v1/crm/activity/referenced": {
44219
- "get": {
44220
- "operationId": "listCrmReferencedActivity",
44221
- "parameters": [
44222
- {
44223
- "description": "The CRM record whose referenced (trickled-up) activity to read.",
44224
- "in": "query",
44225
- "name": "entityId",
44226
- "required": true,
44227
- "schema": {
44228
- "description": "The CRM record whose referenced (trickled-up) activity to read.",
44229
- "minLength": 1,
44230
- "type": "string"
45075
+ "/v1/crm/activity/log": {
45076
+ "patch": {
45077
+ "operationId": "editCrmLoggedActivity",
45078
+ "requestBody": {
45079
+ "content": {
45080
+ "application/json": {
45081
+ "schema": {
45082
+ "properties": {
45083
+ "activityType": {
45084
+ "$ref": "#/components/schemas/CrmLoggedActivityType"
45085
+ },
45086
+ "body": {
45087
+ "type": "string"
45088
+ },
45089
+ "bodyHtml": {
45090
+ "nullable": true,
45091
+ "type": "string"
45092
+ },
45093
+ "direction": {
45094
+ "description": "call + email only.",
45095
+ "enum": [
45096
+ "inbound",
45097
+ "outbound",
45098
+ null
45099
+ ],
45100
+ "nullable": true,
45101
+ "type": "string"
45102
+ },
45103
+ "durationMinutes": {
45104
+ "description": "call + meeting only, in minutes.",
45105
+ "maximum": 1440,
45106
+ "minimum": 0,
45107
+ "nullable": true,
45108
+ "type": "integer"
45109
+ },
45110
+ "grantRecordAccessUserIds": {
45111
+ "items": {
45112
+ "type": "string"
45113
+ },
45114
+ "type": "array"
45115
+ },
45116
+ "id": {
45117
+ "minLength": 1,
45118
+ "type": "string"
45119
+ },
45120
+ "listId": {
45121
+ "nullable": true,
45122
+ "type": "string"
45123
+ },
45124
+ "occurredAt": {
45125
+ "description": "Epoch ms the activity actually happened. Must be ≤ now + 5 minutes.",
45126
+ "exclusiveMinimum": true,
45127
+ "minimum": 0,
45128
+ "type": "integer"
45129
+ },
45130
+ "outcome": {
45131
+ "description": "call: connected | voicemail | no_answer | busy. meeting: held | no_show. Not allowed for email/task.",
45132
+ "nullable": true,
45133
+ "type": "string"
45134
+ },
45135
+ "recordName": {
45136
+ "type": "string"
45137
+ },
45138
+ "title": {
45139
+ "description": "Meeting title / email subject / task title. Not allowed for a call; required for a task.",
45140
+ "maxLength": 200,
45141
+ "nullable": true,
45142
+ "type": "string"
45143
+ }
45144
+ },
45145
+ "required": [
45146
+ "id",
45147
+ "activityType",
45148
+ "occurredAt",
45149
+ "body"
45150
+ ],
45151
+ "type": "object"
45152
+ }
44231
45153
  }
44232
45154
  }
44233
- ],
45155
+ },
44234
45156
  "responses": {
44235
45157
  "200": {
44236
45158
  "content": {
44237
45159
  "application/json": {
44238
45160
  "schema": {
44239
- "$ref": "#/components/schemas/CrmReferencedActivityList"
45161
+ "$ref": "#/components/schemas/CrmEditLoggedActivityResult"
44240
45162
  }
44241
45163
  }
44242
45164
  },
44243
- "description": "Notes authored on other records that reference this record, newest first."
45165
+ "description": "Whether the logged entry was updated."
45166
+ },
45167
+ "400": {
45168
+ "content": {
45169
+ "application/json": {
45170
+ "schema": {
45171
+ "$ref": "#/components/schemas/ErrorResponse"
45172
+ }
45173
+ }
45174
+ },
45175
+ "description": "Validation error"
45176
+ },
45177
+ "401": {
45178
+ "content": {
45179
+ "application/json": {
45180
+ "schema": {
45181
+ "$ref": "#/components/schemas/ErrorResponse"
45182
+ }
45183
+ }
45184
+ },
45185
+ "description": "Unauthorized"
45186
+ },
45187
+ "403": {
45188
+ "content": {
45189
+ "application/json": {
45190
+ "schema": {
45191
+ "$ref": "#/components/schemas/ErrorResponse"
45192
+ }
45193
+ }
45194
+ },
45195
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
45196
+ },
45197
+ "404": {
45198
+ "content": {
45199
+ "application/json": {
45200
+ "schema": {
45201
+ "$ref": "#/components/schemas/ErrorResponse"
45202
+ }
45203
+ }
45204
+ },
45205
+ "description": "No live logged entry with that id — either it doesn't exist, was deleted, or isn't a logged entry (this route can't edit a plain note)."
45206
+ },
45207
+ "413": {
45208
+ "content": {
45209
+ "application/json": {
45210
+ "schema": {
45211
+ "$ref": "#/components/schemas/PayloadTooLarge"
45212
+ }
45213
+ }
45214
+ },
45215
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
45216
+ }
45217
+ },
45218
+ "summary": "Edit a logged activity entry",
45219
+ "tags": [
45220
+ "CRM"
45221
+ ]
45222
+ },
45223
+ "post": {
45224
+ "operationId": "logCrmActivity",
45225
+ "requestBody": {
45226
+ "content": {
45227
+ "application/json": {
45228
+ "schema": {
45229
+ "properties": {
45230
+ "activityType": {
45231
+ "$ref": "#/components/schemas/CrmLoggedActivityType"
45232
+ },
45233
+ "body": {
45234
+ "type": "string"
45235
+ },
45236
+ "bodyHtml": {
45237
+ "nullable": true,
45238
+ "type": "string"
45239
+ },
45240
+ "direction": {
45241
+ "description": "call + email only.",
45242
+ "enum": [
45243
+ "inbound",
45244
+ "outbound",
45245
+ null
45246
+ ],
45247
+ "nullable": true,
45248
+ "type": "string"
45249
+ },
45250
+ "durationMinutes": {
45251
+ "description": "call + meeting only, in minutes.",
45252
+ "maximum": 1440,
45253
+ "minimum": 0,
45254
+ "nullable": true,
45255
+ "type": "integer"
45256
+ },
45257
+ "entityId": {
45258
+ "minLength": 1,
45259
+ "type": "string"
45260
+ },
45261
+ "grantRecordAccessUserIds": {
45262
+ "items": {
45263
+ "type": "string"
45264
+ },
45265
+ "type": "array"
45266
+ },
45267
+ "listId": {
45268
+ "nullable": true,
45269
+ "type": "string"
45270
+ },
45271
+ "occurredAt": {
45272
+ "description": "Epoch ms the activity actually happened. Must be ≤ now + 5 minutes.",
45273
+ "exclusiveMinimum": true,
45274
+ "minimum": 0,
45275
+ "type": "integer"
45276
+ },
45277
+ "outcome": {
45278
+ "description": "call: connected | voicemail | no_answer | busy. meeting: held | no_show. Not allowed for email/task.",
45279
+ "nullable": true,
45280
+ "type": "string"
45281
+ },
45282
+ "recordName": {
45283
+ "type": "string"
45284
+ },
45285
+ "title": {
45286
+ "description": "Meeting title / email subject / task title. Not allowed for a call; required for a task.",
45287
+ "maxLength": 200,
45288
+ "nullable": true,
45289
+ "type": "string"
45290
+ }
45291
+ },
45292
+ "required": [
45293
+ "entityId",
45294
+ "activityType",
45295
+ "occurredAt",
45296
+ "body"
45297
+ ],
45298
+ "type": "object"
45299
+ }
45300
+ }
45301
+ }
45302
+ },
45303
+ "responses": {
45304
+ "200": {
45305
+ "content": {
45306
+ "application/json": {
45307
+ "schema": {
45308
+ "$ref": "#/components/schemas/CrmLogActivityResult"
45309
+ }
45310
+ }
45311
+ },
45312
+ "description": "The new logged entry's id."
45313
+ },
45314
+ "400": {
45315
+ "content": {
45316
+ "application/json": {
45317
+ "schema": {
45318
+ "$ref": "#/components/schemas/ErrorResponse"
45319
+ }
45320
+ }
45321
+ },
45322
+ "description": "Validation error"
45323
+ },
45324
+ "401": {
45325
+ "content": {
45326
+ "application/json": {
45327
+ "schema": {
45328
+ "$ref": "#/components/schemas/ErrorResponse"
45329
+ }
45330
+ }
45331
+ },
45332
+ "description": "Unauthorized"
45333
+ },
45334
+ "403": {
45335
+ "content": {
45336
+ "application/json": {
45337
+ "schema": {
45338
+ "$ref": "#/components/schemas/ErrorResponse"
45339
+ }
45340
+ }
45341
+ },
45342
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
45343
+ },
45344
+ "404": {
45345
+ "content": {
45346
+ "application/json": {
45347
+ "schema": {
45348
+ "$ref": "#/components/schemas/ErrorResponse"
45349
+ }
45350
+ }
45351
+ },
45352
+ "description": "Not found"
45353
+ },
45354
+ "413": {
45355
+ "content": {
45356
+ "application/json": {
45357
+ "schema": {
45358
+ "$ref": "#/components/schemas/PayloadTooLarge"
45359
+ }
45360
+ }
45361
+ },
45362
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
45363
+ }
45364
+ },
45365
+ "summary": "Log a Call/Meeting/Email/Task that happened outside Model Match",
45366
+ "tags": [
45367
+ "CRM"
45368
+ ]
45369
+ }
45370
+ },
45371
+ "/v1/crm/activity/referenced": {
45372
+ "get": {
45373
+ "operationId": "listCrmReferencedActivity",
45374
+ "parameters": [
45375
+ {
45376
+ "description": "The CRM record whose referenced (trickled-up) activity to read.",
45377
+ "in": "query",
45378
+ "name": "entityId",
45379
+ "required": true,
45380
+ "schema": {
45381
+ "description": "The CRM record whose referenced (trickled-up) activity to read.",
45382
+ "minLength": 1,
45383
+ "type": "string"
45384
+ }
45385
+ }
45386
+ ],
45387
+ "responses": {
45388
+ "200": {
45389
+ "content": {
45390
+ "application/json": {
45391
+ "schema": {
45392
+ "$ref": "#/components/schemas/CrmReferencedActivityList"
45393
+ }
45394
+ }
45395
+ },
45396
+ "description": "Notes authored on other records that reference this record, newest first."
44244
45397
  },
44245
45398
  "400": {
44246
45399
  "content": {
@@ -56768,7 +57921,7 @@
56768
57921
  },
56769
57922
  "/v1/loans/bulk-delivery": {
56770
57923
  "post": {
56771
- "description": "Queues an ECS task that dumps every matching loans document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/loans/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.loans` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
57924
+ "description": "Queues an ECS task that dumps every matching loans document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/loans/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.loans` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
56772
57925
  "operationId": "submitLoansBulkDelivery",
56773
57926
  "requestBody": {
56774
57927
  "content": {
@@ -56781,8 +57934,11 @@
56781
57934
  {
56782
57935
  "additionalProperties": false,
56783
57936
  "properties": {
57937
+ "destination": {
57938
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
57939
+ },
56784
57940
  "fields": {
56785
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
57941
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
56786
57942
  "items": {
56787
57943
  "minLength": 1,
56788
57944
  "type": "string"
@@ -56793,16 +57949,13 @@
56793
57949
  "$ref": "#/components/schemas/BulkDeliveryFormat"
56794
57950
  },
56795
57951
  "limit": {
56796
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
56797
- "example": 1000,
57952
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
57953
+ "example": 5000,
56798
57954
  "exclusiveMinimum": true,
56799
57955
  "minimum": 0,
56800
57956
  "type": "integer"
56801
57957
  }
56802
57958
  },
56803
- "required": [
56804
- "format"
56805
- ],
56806
57959
  "type": "object"
56807
57960
  }
56808
57961
  ]
@@ -56831,12 +57984,15 @@
56831
57984
  },
56832
57985
  {
56833
57986
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
57987
+ },
57988
+ {
57989
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
56834
57990
  }
56835
57991
  ]
56836
57992
  }
56837
57993
  }
56838
57994
  },
56839
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
57995
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
56840
57996
  },
56841
57997
  "401": {
56842
57998
  "content": {
@@ -56852,21 +58008,58 @@
56852
58008
  "content": {
56853
58009
  "application/json": {
56854
58010
  "schema": {
56855
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
58011
+ "anyOf": [
58012
+ {
58013
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
58014
+ },
58015
+ {
58016
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
58017
+ }
58018
+ ]
56856
58019
  }
56857
58020
  }
56858
58021
  },
56859
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
58022
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
56860
58023
  },
56861
58024
  "403": {
56862
58025
  "content": {
56863
58026
  "application/json": {
56864
58027
  "schema": {
56865
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
58028
+ "anyOf": [
58029
+ {
58030
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
58031
+ },
58032
+ {
58033
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
58034
+ },
58035
+ {
58036
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
58037
+ }
58038
+ ]
56866
58039
  }
56867
58040
  }
56868
58041
  },
56869
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.loans` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
58042
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.loans` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
58043
+ },
58044
+ "404": {
58045
+ "content": {
58046
+ "application/json": {
58047
+ "schema": {
58048
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
58049
+ }
58050
+ }
58051
+ },
58052
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
58053
+ },
58054
+ "409": {
58055
+ "content": {
58056
+ "application/json": {
58057
+ "schema": {
58058
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
58059
+ }
58060
+ }
58061
+ },
58062
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
56870
58063
  },
56871
58064
  "502": {
56872
58065
  "content": {
@@ -56876,7 +58069,7 @@
56876
58069
  }
56877
58070
  }
56878
58071
  },
56879
- "description": "Preflight / S3 / DDB / ECS launch failure"
58072
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
56880
58073
  }
56881
58074
  },
56882
58075
  "summary": "Submit a bulk-delivery job for loans",
@@ -60378,7 +61571,7 @@
60378
61571
  },
60379
61572
  "/v1/originators/bulk-delivery": {
60380
61573
  "post": {
60381
- "description": "Queues an ECS task that dumps every matching originators document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/originators/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.originators` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
61574
+ "description": "Queues an ECS task that dumps every matching originators document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/originators/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.originators` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
60382
61575
  "operationId": "submitOriginatorsBulkDelivery",
60383
61576
  "requestBody": {
60384
61577
  "content": {
@@ -60391,8 +61584,11 @@
60391
61584
  {
60392
61585
  "additionalProperties": false,
60393
61586
  "properties": {
61587
+ "destination": {
61588
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
61589
+ },
60394
61590
  "fields": {
60395
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
61591
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
60396
61592
  "items": {
60397
61593
  "minLength": 1,
60398
61594
  "type": "string"
@@ -60403,16 +61599,13 @@
60403
61599
  "$ref": "#/components/schemas/BulkDeliveryFormat"
60404
61600
  },
60405
61601
  "limit": {
60406
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
60407
- "example": 1000,
61602
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
61603
+ "example": 5000,
60408
61604
  "exclusiveMinimum": true,
60409
61605
  "minimum": 0,
60410
61606
  "type": "integer"
60411
61607
  }
60412
61608
  },
60413
- "required": [
60414
- "format"
60415
- ],
60416
61609
  "type": "object"
60417
61610
  }
60418
61611
  ]
@@ -60441,12 +61634,15 @@
60441
61634
  },
60442
61635
  {
60443
61636
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
61637
+ },
61638
+ {
61639
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
60444
61640
  }
60445
61641
  ]
60446
61642
  }
60447
61643
  }
60448
61644
  },
60449
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
61645
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
60450
61646
  },
60451
61647
  "401": {
60452
61648
  "content": {
@@ -60462,21 +61658,58 @@
60462
61658
  "content": {
60463
61659
  "application/json": {
60464
61660
  "schema": {
60465
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
61661
+ "anyOf": [
61662
+ {
61663
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
61664
+ },
61665
+ {
61666
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
61667
+ }
61668
+ ]
60466
61669
  }
60467
61670
  }
60468
61671
  },
60469
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
61672
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
60470
61673
  },
60471
61674
  "403": {
60472
61675
  "content": {
60473
61676
  "application/json": {
60474
61677
  "schema": {
60475
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
61678
+ "anyOf": [
61679
+ {
61680
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
61681
+ },
61682
+ {
61683
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
61684
+ },
61685
+ {
61686
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
61687
+ }
61688
+ ]
61689
+ }
61690
+ }
61691
+ },
61692
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.originators` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
61693
+ },
61694
+ "404": {
61695
+ "content": {
61696
+ "application/json": {
61697
+ "schema": {
61698
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
61699
+ }
61700
+ }
61701
+ },
61702
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
61703
+ },
61704
+ "409": {
61705
+ "content": {
61706
+ "application/json": {
61707
+ "schema": {
61708
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
60476
61709
  }
60477
61710
  }
60478
61711
  },
60479
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.originators` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
61712
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
60480
61713
  },
60481
61714
  "502": {
60482
61715
  "content": {
@@ -60486,7 +61719,7 @@
60486
61719
  }
60487
61720
  }
60488
61721
  },
60489
- "description": "Preflight / S3 / DDB / ECS launch failure"
61722
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
60490
61723
  }
60491
61724
  },
60492
61725
  "summary": "Submit a bulk-delivery job for originators",
@@ -61721,6 +62954,99 @@
61721
62954
  ]
61722
62955
  }
61723
62956
  },
62957
+ "/v1/originators/{nmlsId}/breakdowns/title-companies": {
62958
+ "post": {
62959
+ "description": "Ranks the title companies on this entity's loans (recorded deeds, `recording_date` window — the same population and period axis as every other loans breakdown). Spellings of one company are MERGED: `label` is its most-used spelling, `id` the normalized name every spelling was grouped under (legal-form and filler words such as `inc`, `llc`, `co`, `company`, `title`, `escrow` removed; `natl` → `national`). Placeholder entries meaning \"no title company recorded\" (`none available` and its misspellings, `unknown`, `n/a`, …) and names shorter than 3 characters are excluded rather than ranked. `attorney` and `accommodation` are kept: they say who closed. `pctUnits` / `pctVolume` are shares of the entity's WHOLE loan population in the window, including loans with no title company, so they do not sum to 100 across the page. Pages of up to 100 via `pagination`.",
62960
+ "operationId": "originatorTitleCompanies",
62961
+ "parameters": [
62962
+ {
62963
+ "description": "Originator NMLS ID",
62964
+ "in": "path",
62965
+ "name": "nmlsId",
62966
+ "required": true,
62967
+ "schema": {
62968
+ "description": "Originator NMLS ID",
62969
+ "type": "string"
62970
+ }
62971
+ }
62972
+ ],
62973
+ "requestBody": {
62974
+ "content": {
62975
+ "application/json": {
62976
+ "schema": {
62977
+ "$ref": "#/components/schemas/BreakdownRequest"
62978
+ }
62979
+ }
62980
+ }
62981
+ },
62982
+ "responses": {
62983
+ "200": {
62984
+ "content": {
62985
+ "application/json": {
62986
+ "schema": {
62987
+ "$ref": "#/components/schemas/BreakdownResponse"
62988
+ }
62989
+ }
62990
+ },
62991
+ "description": "Breakdown of the entity's loans by title company"
62992
+ },
62993
+ "400": {
62994
+ "content": {
62995
+ "application/json": {
62996
+ "schema": {
62997
+ "$ref": "#/components/schemas/ErrorResponse"
62998
+ }
62999
+ }
63000
+ },
63001
+ "description": "Validation error"
63002
+ },
63003
+ "401": {
63004
+ "content": {
63005
+ "application/json": {
63006
+ "schema": {
63007
+ "$ref": "#/components/schemas/ErrorResponse"
63008
+ }
63009
+ }
63010
+ },
63011
+ "description": "Unauthorized"
63012
+ },
63013
+ "403": {
63014
+ "content": {
63015
+ "application/json": {
63016
+ "schema": {
63017
+ "$ref": "#/components/schemas/ErrorResponse"
63018
+ }
63019
+ }
63020
+ },
63021
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
63022
+ },
63023
+ "404": {
63024
+ "content": {
63025
+ "application/json": {
63026
+ "schema": {
63027
+ "$ref": "#/components/schemas/ErrorResponse"
63028
+ }
63029
+ }
63030
+ },
63031
+ "description": "Not found"
63032
+ },
63033
+ "413": {
63034
+ "content": {
63035
+ "application/json": {
63036
+ "schema": {
63037
+ "$ref": "#/components/schemas/PayloadTooLarge"
63038
+ }
63039
+ }
63040
+ },
63041
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
63042
+ }
63043
+ },
63044
+ "summary": "Title companies on this LO's loans (spellings merged)",
63045
+ "tags": [
63046
+ "Originators"
63047
+ ]
63048
+ }
63049
+ },
61724
63050
  "/v1/originators/{nmlsId}/breakdowns/zip-codes": {
61725
63051
  "post": {
61726
63052
  "operationId": "originatorZipCodes",
@@ -62334,7 +63660,7 @@
62334
63660
  },
62335
63661
  "/v1/properties/bulk-delivery": {
62336
63662
  "post": {
62337
- "description": "Queues an ECS task that dumps every matching properties document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/properties/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 1,000 rows need no entitlement. Above that, the org's `bulk-delivery.properties` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.",
63663
+ "description": "Queues an ECS task that dumps every matching properties document to S3 in the requested format. Returns 202 with a jobId; poll `GET /v1/properties/bulk-delivery/{jobId}` for status + the signed download URL.\n\n**Row limit.** Deliveries of up to 5,000 rows need no entitlement. Above that, the org's `bulk-delivery.properties` entitlement is required and its scope (row ceiling, row filters, allowed fields) governs the delivery; without it the request is rejected with 403 `entitlement_missing`. Pass `limit` at or below the ceiling to deliver a capped subset of a broader query. Email support@modelmatch.com to request a limit increase.\n\nDelivery is billed at 1 credit per row regardless of entitlement.\n\n**Push-target destination.** Pass `destination: { type: \"push-target\", targetId }` to have the finished rows sent to a configured push target instead of downloaded. The target is checked before anything is queued; a refusal returns the push target's status and `code` (`TARGET_NOT_FOUND` 404, `FORBIDDEN` 403, `PLAN_REQUIRED` 402, `TARGET_DISABLED` 409, `ENTITY_MISMATCH` 400). Limits and billing are the same as a file delivery, except that push-target deliveries from the ModelMatch web app are not billed.",
62338
63664
  "operationId": "submitPropertiesBulkDelivery",
62339
63665
  "requestBody": {
62340
63666
  "content": {
@@ -62347,8 +63673,11 @@
62347
63673
  {
62348
63674
  "additionalProperties": false,
62349
63675
  "properties": {
63676
+ "destination": {
63677
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
63678
+ },
62350
63679
  "fields": {
62351
- "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects.",
63680
+ "description": "Optional output column projection, named by the response DTO fields (the same camelCase fields the list endpoint returns, e.g. `city`, `interestRate`). Intersected with the entitlement scope's allowedFields and the entity's full column set. If empty after intersection, falls back to all columns. It also SELECTS the opt-in columns, which are not part of the list DTO and are never delivered unless named here: `communityLending` (properties, originators, companies, branches) — the Community Lending neighborhood profile, identical to the block on the entity's detail endpoint; `parties` (properties) — the mortgage parties attributed to the parcel, by attribution scope; and `marketCount` / `countyCount` / `stateCount` / `zipCount` / `lenderCount` (originators) — the market-breadth rollups you can already filter on. In `csv` and `parquet` the two block columns are JSON-encoded cells; in `json` and `ndjson` they are nested objects. Windowed production (originators, agents, companies, branches): name `<field>_<n>mo` for n in 3, 6, 12, 14, 18, 24 to get a production field for that trailing window regardless of `period` — e.g. `volume_3mo`, `units_6mo`, `purchaseVolume_24mo`. Available for originators `volume`, `units`, `avgLoanAmount`, `purchaseVolume`, `purchaseUnits`, `refinanceVolume`, `refinanceUnits`, `conventionalPct`, `fhaPct`, `vaPct`; agents `volume`, `units`, `avgSoldPrice`, `buyerVolume`, `buyerUnits`, `sellerVolume`, `sellerUnits`, `totalOriginatorsWorkedWith`, `totalCompaniesWorkedWith`, `totalLendersWorkedWith`; companies `volume`, `units`, `avgLoanAmount`, `loCount`; branches `volume`, `units`, `avgLoanAmount`, `purchasePct`, `refinancePct`, `loCount`. Numbers (null when the window has no data); an entitlement that allows a field allows every window of it.",
62352
63681
  "items": {
62353
63682
  "minLength": 1,
62354
63683
  "type": "string"
@@ -62359,16 +63688,13 @@
62359
63688
  "$ref": "#/components/schemas/BulkDeliveryFormat"
62360
63689
  },
62361
63690
  "limit": {
62362
- "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 1,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 1,000-row allowance.",
62363
- "example": 1000,
63691
+ "description": "Optional hard cap on the number of rows delivered — and therefore billed (1 credit / row). The job delivers the first `limit` rows in sort order (default newest-first) and bills only for those. If the query matches fewer than `limit`, all matches are delivered. Distinct from the page-size `size` field, which is ignored on this endpoint. The row ceiling still applies — 5,000 without the entity's bulk-delivery entitlement, or the entitlement's `maxRows` with it. A `limit` above the ceiling is rejected, but a `limit` at or below it lets you export a capped top-N even when the full match exceeds the ceiling — which is the intended way to run a broad query under the 5,000-row allowance.",
63692
+ "example": 5000,
62364
63693
  "exclusiveMinimum": true,
62365
63694
  "minimum": 0,
62366
63695
  "type": "integer"
62367
63696
  }
62368
63697
  },
62369
- "required": [
62370
- "format"
62371
- ],
62372
63698
  "type": "object"
62373
63699
  }
62374
63700
  ]
@@ -62397,12 +63723,15 @@
62397
63723
  },
62398
63724
  {
62399
63725
  "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
63726
+ },
63727
+ {
63728
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
62400
63729
  }
62401
63730
  ]
62402
63731
  }
62403
63732
  }
62404
63733
  },
62405
- "description": "Validation failure OR delivery would exceed the entitlement's maxRows"
63734
+ "description": "Validation failure (incl. `format_required`: a file delivery needs `format`), delivery would exceed the entitlement's maxRows, OR the push target does not accept this entity (`ENTITY_MISMATCH`)"
62406
63735
  },
62407
63736
  "401": {
62408
63737
  "content": {
@@ -62418,21 +63747,58 @@
62418
63747
  "content": {
62419
63748
  "application/json": {
62420
63749
  "schema": {
62421
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
63750
+ "anyOf": [
63751
+ {
63752
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
63753
+ },
63754
+ {
63755
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
63756
+ }
63757
+ ]
62422
63758
  }
62423
63759
  }
62424
63760
  },
62425
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row)"
63761
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
62426
63762
  },
62427
63763
  "403": {
62428
63764
  "content": {
62429
63765
  "application/json": {
62430
63766
  "schema": {
62431
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
63767
+ "anyOf": [
63768
+ {
63769
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
63770
+ },
63771
+ {
63772
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
63773
+ },
63774
+ {
63775
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
63776
+ }
63777
+ ]
63778
+ }
63779
+ }
63780
+ },
63781
+ "description": "Delivery exceeds the 5,000-row limit available without the `bulk-delivery.properties` entitlement (lower `limit`, narrow the filters, or contact support for a limit increase), OR you may not use the push target (`FORBIDDEN`), OR a scoped token named a push-target destination without `push-targets:send` (`insufficient_scope`)."
63782
+ },
63783
+ "404": {
63784
+ "content": {
63785
+ "application/json": {
63786
+ "schema": {
63787
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
63788
+ }
63789
+ }
63790
+ },
63791
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
63792
+ },
63793
+ "409": {
63794
+ "content": {
63795
+ "application/json": {
63796
+ "schema": {
63797
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
62432
63798
  }
62433
63799
  }
62434
63800
  },
62435
- "description": "Delivery exceeds the 1,000-row limit available without the `bulk-delivery.properties` entitlement. Lower `limit`, narrow the filters, or contact support for a limit increase."
63801
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
62436
63802
  },
62437
63803
  "502": {
62438
63804
  "content": {
@@ -62442,7 +63808,7 @@
62442
63808
  }
62443
63809
  }
62444
63810
  },
62445
- "description": "Preflight / S3 / DDB / ECS launch failure"
63811
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
62446
63812
  }
62447
63813
  },
62448
63814
  "summary": "Submit a bulk-delivery job for properties",