@model-match/api 3.19.0 → 3.20.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
@@ -215,7 +215,49 @@
215
215
  "type": "string"
216
216
  },
217
217
  "emailEnabled": {
218
- "$ref": "#/components/schemas/AlertEnabled"
218
+ "deprecated": true,
219
+ "description": "Deprecated and ignored: accepted for compatibility, but not stored and no longer controls delivery (the response always reports every flag off). Email and in-app channel preferences for alerts live in Notifications settings.",
220
+ "properties": {
221
+ "agent_sale_closed": {
222
+ "type": "boolean"
223
+ },
224
+ "area_new_listing": {
225
+ "type": "boolean"
226
+ },
227
+ "borrower_listed": {
228
+ "type": "boolean"
229
+ },
230
+ "epo_risk": {
231
+ "type": "boolean"
232
+ },
233
+ "rate_term_refi_area": {
234
+ "type": "boolean"
235
+ },
236
+ "watched_agent_pending": {
237
+ "type": "boolean"
238
+ },
239
+ "watched_loan": {
240
+ "type": "boolean"
241
+ },
242
+ "watched_property": {
243
+ "type": "boolean"
244
+ },
245
+ "watched_sale": {
246
+ "type": "boolean"
247
+ }
248
+ },
249
+ "required": [
250
+ "area_new_listing",
251
+ "watched_agent_pending",
252
+ "borrower_listed",
253
+ "epo_risk",
254
+ "agent_sale_closed",
255
+ "rate_term_refi_area",
256
+ "watched_sale",
257
+ "watched_property",
258
+ "watched_loan"
259
+ ],
260
+ "type": "object"
219
261
  },
220
262
  "enabled": {
221
263
  "$ref": "#/components/schemas/AlertEnabled"
@@ -307,6 +349,9 @@
307
349
  },
308
350
  "userId": {
309
351
  "type": "string"
352
+ },
353
+ "via": {
354
+ "$ref": "#/components/schemas/AdminAlertVia"
310
355
  }
311
356
  },
312
357
  "required": [
@@ -476,6 +521,40 @@
476
521
  ],
477
522
  "type": "object"
478
523
  },
524
+ "AdminAlertVia": {
525
+ "description": "Why the alert fired: the matched subject, the granting recipes and the match source. Absent on rows written before it existed.",
526
+ "properties": {
527
+ "matchSource": {
528
+ "description": "Which match source routed the alert: `subscriptions` for every alert fired since the legacy source was retired; `legacy` only on older rows.",
529
+ "enum": [
530
+ "legacy",
531
+ "subscriptions"
532
+ ],
533
+ "type": "string"
534
+ },
535
+ "parentKey": {
536
+ "description": "When the match went through a derived child (an auto agent, a borrower property, a manual agent's VRE), the owning subscription's subject key.",
537
+ "type": "string"
538
+ },
539
+ "recipeIds": {
540
+ "description": "The recipes whose grants enabled this alert type on the matched subscription (empty on alerts fired by the retired legacy match source).",
541
+ "items": {
542
+ "type": "string"
543
+ },
544
+ "type": "array"
545
+ },
546
+ "subjectKey": {
547
+ "description": "The watched subject key the alert matched.",
548
+ "type": "string"
549
+ }
550
+ },
551
+ "required": [
552
+ "subjectKey",
553
+ "recipeIds",
554
+ "matchSource"
555
+ ],
556
+ "type": "object"
557
+ },
479
558
  "AdminBulkJob": {
480
559
  "properties": {
481
560
  "bytes": {
@@ -1015,6 +1094,8 @@
1015
1094
  "type": "string"
1016
1095
  },
1017
1096
  "emailEnabled": {
1097
+ "deprecated": true,
1098
+ "description": "Deprecated and ignored: accepted for compatibility, but not stored and no longer controls delivery (the response always reports every flag off). Email and in-app channel preferences for alerts live in Notifications settings.",
1018
1099
  "properties": {
1019
1100
  "agent_sale_closed": {
1020
1101
  "type": "boolean"
@@ -2518,7 +2599,49 @@
2518
2599
  "$ref": "#/components/schemas/AlertConditions"
2519
2600
  },
2520
2601
  "emailEnabled": {
2521
- "$ref": "#/components/schemas/AlertEnabled"
2602
+ "deprecated": true,
2603
+ "description": "Deprecated and ignored: accepted for compatibility, but not stored and no longer controls delivery (the response always reports every flag off). Email and in-app channel preferences for alerts live in Notifications settings.",
2604
+ "properties": {
2605
+ "agent_sale_closed": {
2606
+ "type": "boolean"
2607
+ },
2608
+ "area_new_listing": {
2609
+ "type": "boolean"
2610
+ },
2611
+ "borrower_listed": {
2612
+ "type": "boolean"
2613
+ },
2614
+ "epo_risk": {
2615
+ "type": "boolean"
2616
+ },
2617
+ "rate_term_refi_area": {
2618
+ "type": "boolean"
2619
+ },
2620
+ "watched_agent_pending": {
2621
+ "type": "boolean"
2622
+ },
2623
+ "watched_loan": {
2624
+ "type": "boolean"
2625
+ },
2626
+ "watched_property": {
2627
+ "type": "boolean"
2628
+ },
2629
+ "watched_sale": {
2630
+ "type": "boolean"
2631
+ }
2632
+ },
2633
+ "required": [
2634
+ "area_new_listing",
2635
+ "watched_agent_pending",
2636
+ "borrower_listed",
2637
+ "epo_risk",
2638
+ "agent_sale_closed",
2639
+ "rate_term_refi_area",
2640
+ "watched_sale",
2641
+ "watched_property",
2642
+ "watched_loan"
2643
+ ],
2644
+ "type": "object"
2522
2645
  },
2523
2646
  "enabled": {
2524
2647
  "$ref": "#/components/schemas/AlertEnabled"
@@ -2824,16 +2947,246 @@
2824
2947
  ],
2825
2948
  "type": "object"
2826
2949
  },
2827
- "AlertsOkResponse": {
2950
+ "AlertRecipe": {
2951
+ "properties": {
2952
+ "description": {
2953
+ "type": "string"
2954
+ },
2955
+ "id": {
2956
+ "enum": [
2957
+ "past-clients",
2958
+ "agent-partners",
2959
+ "farm-area",
2960
+ "watch-record"
2961
+ ],
2962
+ "type": "string"
2963
+ },
2964
+ "inputsSchema": {
2965
+ "additionalProperties": {
2966
+ "nullable": true
2967
+ },
2968
+ "description": "JSON Schema of the inputs the recipe needs to subscribe.",
2969
+ "type": "object"
2970
+ },
2971
+ "label": {
2972
+ "type": "string"
2973
+ },
2974
+ "subscribed": {
2975
+ "description": "Whether you are subscribed: at least one of your subscriptions carries this recipe.",
2976
+ "type": "boolean"
2977
+ },
2978
+ "types": {
2979
+ "description": "The alert triggers this recipe turns on.",
2980
+ "items": {
2981
+ "enum": [
2982
+ "area_new_listing",
2983
+ "watched_agent_pending",
2984
+ "borrower_listed",
2985
+ "epo_risk",
2986
+ "agent_sale_closed",
2987
+ "rate_term_refi_area",
2988
+ "watched_sale",
2989
+ "watched_property",
2990
+ "watched_loan"
2991
+ ],
2992
+ "type": "string"
2993
+ },
2994
+ "type": "array"
2995
+ }
2996
+ },
2997
+ "required": [
2998
+ "id",
2999
+ "label",
3000
+ "description",
3001
+ "types",
3002
+ "inputsSchema",
3003
+ "subscribed"
3004
+ ],
3005
+ "type": "object"
3006
+ },
3007
+ "AlertRecipeInputs": {
3008
+ "properties": {
3009
+ "agents": {
3010
+ "description": "`agent-partners`: the agents to follow.",
3011
+ "items": {
3012
+ "properties": {
3013
+ "agentMmaId": {
3014
+ "description": "Agent id.",
3015
+ "minLength": 1,
3016
+ "type": "string"
3017
+ }
3018
+ },
3019
+ "required": [
3020
+ "agentMmaId"
3021
+ ],
3022
+ "type": "object"
3023
+ },
3024
+ "maxItems": 50,
3025
+ "type": "array"
3026
+ },
3027
+ "areas": {
3028
+ "description": "`farm-area`: areas to farm.",
3029
+ "items": {
3030
+ "$ref": "#/components/schemas/AddWatchedAreaRequest"
3031
+ },
3032
+ "maxItems": 25,
3033
+ "type": "array"
3034
+ },
3035
+ "documentId": {
3036
+ "description": "`watch-record`: the record id.",
3037
+ "minLength": 1,
3038
+ "type": "string"
3039
+ },
3040
+ "documentType": {
3041
+ "description": "`watch-record`: the record type.",
3042
+ "enum": [
3043
+ "sale",
3044
+ "property",
3045
+ "loan"
3046
+ ],
3047
+ "example": "property",
3048
+ "type": "string"
3049
+ },
3050
+ "nmlsId": {
3051
+ "description": "`past-clients`: the loan officer's NMLS id.",
3052
+ "type": "string"
3053
+ },
3054
+ "rules": {
3055
+ "description": "`watch-record`: what change to watch for. Empty or omitted applies the default for the type — property: foreclosure status changes, or sale propensity reaches 50; sale: listing status changes; loan: interest rate moves by 0.125 points or more.",
3056
+ "items": {
3057
+ "properties": {
3058
+ "match": {
3059
+ "description": "How predicates combine: all (AND, default) or any (OR).",
3060
+ "enum": [
3061
+ "all",
3062
+ "any"
3063
+ ],
3064
+ "example": "all",
3065
+ "type": "string"
3066
+ },
3067
+ "predicates": {
3068
+ "description": "One or more conditions; combined per match.",
3069
+ "items": {
3070
+ "properties": {
3071
+ "field": {
3072
+ "description": "Catalog field alias (e.g. avm, current_ltv, loan_type).",
3073
+ "example": "current_ltv",
3074
+ "type": "string"
3075
+ },
3076
+ "op": {
3077
+ "description": "Comparison / change / status / guard operator.",
3078
+ "enum": [
3079
+ "gt",
3080
+ "gte",
3081
+ "lt",
3082
+ "lte",
3083
+ "eq",
3084
+ "between",
3085
+ "pct_change_gte",
3086
+ "abs_change_gte",
3087
+ "changed",
3088
+ "changed_to"
3089
+ ],
3090
+ "example": "lte",
3091
+ "type": "string"
3092
+ },
3093
+ "stringValue": {
3094
+ "description": "Target keyword for changed_to / eq guard.",
3095
+ "type": "string"
3096
+ },
3097
+ "value": {
3098
+ "description": "Threshold, or change amount (percent for pct_change_gte).",
3099
+ "example": 0.8,
3100
+ "type": "number"
3101
+ },
3102
+ "value2": {
3103
+ "description": "Upper bound for between.",
3104
+ "type": "number"
3105
+ }
3106
+ },
3107
+ "required": [
3108
+ "field",
3109
+ "op"
3110
+ ],
3111
+ "type": "object"
3112
+ },
3113
+ "minItems": 1,
3114
+ "type": "array"
3115
+ }
3116
+ },
3117
+ "required": [
3118
+ "predicates"
3119
+ ],
3120
+ "type": "object"
3121
+ },
3122
+ "type": "array"
3123
+ }
3124
+ },
3125
+ "type": "object"
3126
+ },
3127
+ "AlertRecipePreviewResponse": {
2828
3128
  "properties": {
2829
3129
  "data": {
2830
3130
  "properties": {
2831
- "ok": {
2832
- "type": "boolean"
3131
+ "byType": {
3132
+ "additionalProperties": {
3133
+ "type": "integer"
3134
+ },
3135
+ "description": "Count per alert trigger.",
3136
+ "type": "object"
3137
+ },
3138
+ "count": {
3139
+ "description": "Alerts the recipe would have fired in the window.",
3140
+ "type": "integer"
3141
+ },
3142
+ "sample": {
3143
+ "description": "The newest few, as digest lines.",
3144
+ "items": {
3145
+ "properties": {
3146
+ "description": {
3147
+ "type": "string"
3148
+ },
3149
+ "title": {
3150
+ "type": "string"
3151
+ },
3152
+ "triggeredAt": {
3153
+ "type": "string"
3154
+ },
3155
+ "type": {
3156
+ "enum": [
3157
+ "area_new_listing",
3158
+ "watched_agent_pending",
3159
+ "borrower_listed",
3160
+ "epo_risk",
3161
+ "agent_sale_closed",
3162
+ "rate_term_refi_area",
3163
+ "watched_sale",
3164
+ "watched_property",
3165
+ "watched_loan"
3166
+ ],
3167
+ "type": "string"
3168
+ }
3169
+ },
3170
+ "required": [
3171
+ "type",
3172
+ "title",
3173
+ "description",
3174
+ "triggeredAt"
3175
+ ],
3176
+ "type": "object"
3177
+ },
3178
+ "type": "array"
3179
+ },
3180
+ "since": {
3181
+ "description": "Window start (ISO).",
3182
+ "type": "string"
2833
3183
  }
2834
3184
  },
2835
3185
  "required": [
2836
- "ok"
3186
+ "count",
3187
+ "byType",
3188
+ "sample",
3189
+ "since"
2837
3190
  ],
2838
3191
  "type": "object"
2839
3192
  }
@@ -2843,103 +3196,500 @@
2843
3196
  ],
2844
3197
  "type": "object"
2845
3198
  },
2846
- "AnalyticsCompleteness": {
2847
- "description": "Data-completeness watermark for this response. Present only when it could be measured; absence means it was not measured, NOT that the data is complete.",
3199
+ "AlertRecipeResponse": {
2848
3200
  "properties": {
2849
- "through": {
2850
- "description": "The last calendar date (`YYYY-MM-DD`) this query's matching records actually reach. Records arrive after the events they describe, so a period ending today is normally backed by data ending days or weeks earlier, and any bucket extending past this date is built from a partial period. Measured from THIS query's own matched set, not a global figure: the delay varies by geography and moves over time, so two requests with different filters can legitimately report different dates.",
2851
- "format": "date",
2852
- "type": "string"
3201
+ "data": {
3202
+ "$ref": "#/components/schemas/AlertRecipe"
2853
3203
  }
2854
3204
  },
2855
3205
  "required": [
2856
- "through"
3206
+ "data"
2857
3207
  ],
2858
3208
  "type": "object"
2859
3209
  },
2860
- "AndFilter": {
3210
+ "AlertRecipesResponse": {
2861
3211
  "properties": {
2862
- "children": {
2863
- "description": "Array of filter expressions (AND)",
3212
+ "data": {
2864
3213
  "items": {
2865
- "$ref": "#/components/schemas/FilterNode"
3214
+ "$ref": "#/components/schemas/AlertRecipe"
3215
+ },
3216
+ "type": "array"
3217
+ }
3218
+ },
3219
+ "required": [
3220
+ "data"
3221
+ ],
3222
+ "type": "object"
3223
+ },
3224
+ "AlertSubscription": {
3225
+ "properties": {
3226
+ "createdAt": {
3227
+ "type": "string"
3228
+ },
3229
+ "display": {
3230
+ "$ref": "#/components/schemas/AlertSubscriptionDisplay"
3231
+ },
3232
+ "expiresAt": {
3233
+ "description": "When the subscription stops firing (ISO 8601).",
3234
+ "type": "string"
3235
+ },
3236
+ "grants": {
3237
+ "items": {
3238
+ "$ref": "#/components/schemas/AlertSubscriptionGrant"
2866
3239
  },
2867
3240
  "type": "array"
2868
3241
  },
2869
- "type": {
3242
+ "id": {
3243
+ "description": "Opaque subscription id.",
3244
+ "type": "string"
3245
+ },
3246
+ "kinds": {
3247
+ "description": "The alert kinds that fire on this subject (empty = paused).",
3248
+ "items": {
3249
+ "enum": [
3250
+ "area_new_listing",
3251
+ "watched_agent_pending",
3252
+ "borrower_listed",
3253
+ "epo_risk",
3254
+ "agent_sale_closed",
3255
+ "rate_term_refi_area",
3256
+ "watched_sale",
3257
+ "watched_property",
3258
+ "watched_loan"
3259
+ ],
3260
+ "type": "string"
3261
+ },
3262
+ "type": "array"
3263
+ },
3264
+ "rules": {
3265
+ "items": {
3266
+ "$ref": "#/components/schemas/WatchedDocumentRule"
3267
+ },
3268
+ "type": "array"
3269
+ },
3270
+ "subjectType": {
2870
3271
  "enum": [
2871
- "and"
3272
+ "agent",
3273
+ "area",
3274
+ "nmls",
3275
+ "property",
3276
+ "loan",
3277
+ "sale",
3278
+ "company",
3279
+ "filter"
2872
3280
  ],
2873
3281
  "type": "string"
3282
+ },
3283
+ "updatedAt": {
3284
+ "type": "string"
2874
3285
  }
2875
3286
  },
2876
3287
  "required": [
2877
- "type",
2878
- "children"
3288
+ "id",
3289
+ "subjectType",
3290
+ "display",
3291
+ "kinds",
3292
+ "grants",
3293
+ "createdAt",
3294
+ "updatedAt"
2879
3295
  ],
2880
3296
  "type": "object"
2881
3297
  },
2882
- "BooleanFilterValue": {
2883
- "nullable": true,
2884
- "type": "boolean"
3298
+ "AlertSubscriptionDisplay": {
3299
+ "description": "What the subscription watches, for display.",
3300
+ "properties": {
3301
+ "agentMmaId": {
3302
+ "type": "string"
3303
+ },
3304
+ "areaType": {
3305
+ "enum": [
3306
+ "city",
3307
+ "county",
3308
+ "zip",
3309
+ "bbox",
3310
+ "radius"
3311
+ ],
3312
+ "type": "string"
3313
+ },
3314
+ "documentId": {
3315
+ "type": "string"
3316
+ },
3317
+ "documentType": {
3318
+ "enum": [
3319
+ "sale",
3320
+ "property",
3321
+ "loan"
3322
+ ],
3323
+ "type": "string"
3324
+ },
3325
+ "lat": {
3326
+ "type": "number"
3327
+ },
3328
+ "lon": {
3329
+ "type": "number"
3330
+ },
3331
+ "maxLat": {
3332
+ "type": "number"
3333
+ },
3334
+ "maxLon": {
3335
+ "type": "number"
3336
+ },
3337
+ "minLat": {
3338
+ "type": "number"
3339
+ },
3340
+ "minLon": {
3341
+ "type": "number"
3342
+ },
3343
+ "name": {
3344
+ "type": "string"
3345
+ },
3346
+ "nmlsId": {
3347
+ "type": "string"
3348
+ },
3349
+ "radiusMeters": {
3350
+ "type": "number"
3351
+ },
3352
+ "state": {
3353
+ "type": "string"
3354
+ },
3355
+ "value": {
3356
+ "type": "string"
3357
+ }
3358
+ },
3359
+ "type": "object"
2885
3360
  },
2886
- "BranchChartRequest": {
2887
- "additionalProperties": false,
3361
+ "AlertSubscriptionError": {
2888
3362
  "properties": {
2889
- "advancedFilters": {
2890
- "$ref": "#/components/schemas/FilterNode"
3363
+ "code": {
3364
+ "description": "Machine-readable reason (absent on a generic validation error).",
3365
+ "enum": [
3366
+ "invalid_kind",
3367
+ "kind_not_allowed_for_subject",
3368
+ "subject_type_not_supported",
3369
+ "rules_required",
3370
+ "rule_edge_op_on_event_kind",
3371
+ "unknown_field",
3372
+ "invalid_rule",
3373
+ "invalid_area",
3374
+ "invalid_subject",
3375
+ "invalid_subscription_id",
3376
+ "nmls_mismatch"
3377
+ ],
3378
+ "type": "string"
2891
3379
  },
2892
- "filters": {
2893
- "$ref": "#/components/schemas/FilterNode"
3380
+ "error": {
3381
+ "description": "Error message",
3382
+ "type": "string"
3383
+ }
3384
+ },
3385
+ "required": [
3386
+ "error"
3387
+ ],
3388
+ "type": "object"
3389
+ },
3390
+ "AlertSubscriptionGrant": {
3391
+ "properties": {
3392
+ "addedAt": {
3393
+ "type": "string"
2894
3394
  },
2895
- "flatFilters": {
2896
- "additionalProperties": false,
3395
+ "kinds": {
3396
+ "items": {
3397
+ "enum": [
3398
+ "area_new_listing",
3399
+ "watched_agent_pending",
3400
+ "borrower_listed",
3401
+ "epo_risk",
3402
+ "agent_sale_closed",
3403
+ "rate_term_refi_area",
3404
+ "watched_sale",
3405
+ "watched_property",
3406
+ "watched_loan"
3407
+ ],
3408
+ "type": "string"
3409
+ },
3410
+ "type": "array"
3411
+ },
3412
+ "recipeId": {
3413
+ "description": "Why the kinds are on: a recipe, `custom` (set directly), or `migration`.",
3414
+ "enum": [
3415
+ "past-clients",
3416
+ "agent-partners",
3417
+ "farm-area",
3418
+ "watch-record",
3419
+ "custom",
3420
+ "migration"
3421
+ ],
3422
+ "type": "string"
3423
+ }
3424
+ },
3425
+ "required": [
3426
+ "recipeId",
3427
+ "kinds",
3428
+ "addedAt"
3429
+ ],
3430
+ "type": "object"
3431
+ },
3432
+ "AlertSubscriptionResponse": {
3433
+ "properties": {
3434
+ "data": {
3435
+ "$ref": "#/components/schemas/AlertSubscription"
3436
+ }
3437
+ },
3438
+ "required": [
3439
+ "data"
3440
+ ],
3441
+ "type": "object"
3442
+ },
3443
+ "AlertSubscriptionSubject": {
3444
+ "properties": {
3445
+ "agentMmaId": {
3446
+ "description": "agent: the agent id.",
3447
+ "minLength": 1,
3448
+ "type": "string"
3449
+ },
3450
+ "area": {
3451
+ "description": "Area subject: the same body as POST /watched-areas.",
2897
3452
  "properties": {
2898
- "borrowerStatus": {
2899
- "$ref": "#/components/schemas/LoanBorrowerStatusFilterValue"
3453
+ "lat": {
3454
+ "description": "Center latitude (point + radius).",
3455
+ "maximum": 90,
3456
+ "minimum": -90,
3457
+ "type": "number"
2900
3458
  },
2901
- "broker": {
2902
- "allOf": [
2903
- {
2904
- "$ref": "#/components/schemas/TextFilterValue"
2905
- },
2906
- {
2907
- "description": "Broker shop NMLS id (digits only). A broker is a company — resolve a broker name to its NMLS id via instantSearch (or listCompanies)."
2908
- }
2909
- ]
3459
+ "lon": {
3460
+ "description": "Center longitude.",
3461
+ "maximum": 180,
3462
+ "minimum": -180,
3463
+ "type": "number"
2910
3464
  },
2911
- "buyerEntityType": {
2912
- "$ref": "#/components/schemas/BooleanFilterValue"
3465
+ "maxLat": {
3466
+ "description": "Box north edge.",
3467
+ "maximum": 90,
3468
+ "minimum": -90,
3469
+ "type": "number"
2913
3470
  },
2914
- "city": {
2915
- "$ref": "#/components/schemas/TextFilterValue"
3471
+ "maxLon": {
3472
+ "description": "Box east edge.",
3473
+ "maximum": 180,
3474
+ "minimum": -180,
3475
+ "type": "number"
2916
3476
  },
2917
- "conforming": {
2918
- "$ref": "#/components/schemas/BooleanFilterValue"
3477
+ "minLat": {
3478
+ "description": "Box south edge.",
3479
+ "maximum": 90,
3480
+ "minimum": -90,
3481
+ "type": "number"
2919
3482
  },
2920
- "county": {
2921
- "allOf": [
2922
- {
2923
- "$ref": "#/components/schemas/TextFilterValue"
2924
- },
2925
- {
2926
- "description": "County name (e.g. \"Broward\") or 5-digit FIPS code. Names resolve to FIPS server-side; include a `state` filter to disambiguate."
2927
- }
2928
- ]
3483
+ "minLon": {
3484
+ "description": "Box west edge.",
3485
+ "maximum": 180,
3486
+ "minimum": -180,
3487
+ "type": "number"
2929
3488
  },
2930
- "currentBalance": {
2931
- "allOf": [
2932
- {
2933
- "$ref": "#/components/schemas/NumericFilterValue"
2934
- },
2935
- {
2936
- "description": "Estimated remaining principal balance (~19% populated). Approximate current equity as `homeValue` (AVM) minus this."
2937
- }
2938
- ]
3489
+ "radius": {
3490
+ "description": "Radius around the center point.",
3491
+ "example": 10,
3492
+ "exclusiveMinimum": true,
3493
+ "minimum": 0,
3494
+ "type": "number"
2939
3495
  },
2940
- "employer": {
2941
- "allOf": [
2942
- {
3496
+ "state": {
3497
+ "description": "2-letter state. Ignored for zip + geo areas.",
3498
+ "example": "CA",
3499
+ "type": "string"
3500
+ },
3501
+ "type": {
3502
+ "description": "Named area (city/county/zip) or geo area (bbox = bounding box, radius = point + radius).",
3503
+ "enum": [
3504
+ "city",
3505
+ "county",
3506
+ "zip",
3507
+ "bbox",
3508
+ "radius"
3509
+ ],
3510
+ "type": "string"
3511
+ },
3512
+ "unit": {
3513
+ "description": "Radius unit (default mi).",
3514
+ "enum": [
3515
+ "mi",
3516
+ "km",
3517
+ "m"
3518
+ ],
3519
+ "example": "mi",
3520
+ "type": "string"
3521
+ },
3522
+ "value": {
3523
+ "description": "City / county name, or 5-digit zip. Required for named areas.",
3524
+ "example": "Los Angeles",
3525
+ "type": "string"
3526
+ }
3527
+ },
3528
+ "required": [
3529
+ "type"
3530
+ ],
3531
+ "type": "object"
3532
+ },
3533
+ "documentId": {
3534
+ "description": "sale / property / loan: the record id.",
3535
+ "minLength": 1,
3536
+ "type": "string"
3537
+ },
3538
+ "nmlsId": {
3539
+ "description": "nmls: your NMLS id.",
3540
+ "minLength": 1,
3541
+ "type": "string"
3542
+ },
3543
+ "type": {
3544
+ "description": "agent, area, nmls (your own NMLS id), sale, property or loan. company and filter are reserved.",
3545
+ "enum": [
3546
+ "agent",
3547
+ "area",
3548
+ "nmls",
3549
+ "property",
3550
+ "loan",
3551
+ "sale",
3552
+ "company",
3553
+ "filter"
3554
+ ],
3555
+ "type": "string"
3556
+ }
3557
+ },
3558
+ "required": [
3559
+ "type"
3560
+ ],
3561
+ "type": "object"
3562
+ },
3563
+ "AlertSubscriptionsResponse": {
3564
+ "properties": {
3565
+ "data": {
3566
+ "items": {
3567
+ "$ref": "#/components/schemas/AlertSubscription"
3568
+ },
3569
+ "type": "array"
3570
+ }
3571
+ },
3572
+ "required": [
3573
+ "data"
3574
+ ],
3575
+ "type": "object"
3576
+ },
3577
+ "AlertsOkResponse": {
3578
+ "properties": {
3579
+ "data": {
3580
+ "properties": {
3581
+ "ok": {
3582
+ "type": "boolean"
3583
+ }
3584
+ },
3585
+ "required": [
3586
+ "ok"
3587
+ ],
3588
+ "type": "object"
3589
+ }
3590
+ },
3591
+ "required": [
3592
+ "data"
3593
+ ],
3594
+ "type": "object"
3595
+ },
3596
+ "AnalyticsCompleteness": {
3597
+ "description": "Data-completeness watermark for this response. Present only when it could be measured; absence means it was not measured, NOT that the data is complete.",
3598
+ "properties": {
3599
+ "through": {
3600
+ "description": "The last calendar date (`YYYY-MM-DD`) this query's matching records actually reach. Records arrive after the events they describe, so a period ending today is normally backed by data ending days or weeks earlier, and any bucket extending past this date is built from a partial period. Measured from THIS query's own matched set, not a global figure: the delay varies by geography and moves over time, so two requests with different filters can legitimately report different dates.",
3601
+ "format": "date",
3602
+ "type": "string"
3603
+ }
3604
+ },
3605
+ "required": [
3606
+ "through"
3607
+ ],
3608
+ "type": "object"
3609
+ },
3610
+ "AndFilter": {
3611
+ "properties": {
3612
+ "children": {
3613
+ "description": "Array of filter expressions (AND)",
3614
+ "items": {
3615
+ "$ref": "#/components/schemas/FilterNode"
3616
+ },
3617
+ "type": "array"
3618
+ },
3619
+ "type": {
3620
+ "enum": [
3621
+ "and"
3622
+ ],
3623
+ "type": "string"
3624
+ }
3625
+ },
3626
+ "required": [
3627
+ "type",
3628
+ "children"
3629
+ ],
3630
+ "type": "object"
3631
+ },
3632
+ "BooleanFilterValue": {
3633
+ "nullable": true,
3634
+ "type": "boolean"
3635
+ },
3636
+ "BranchChartRequest": {
3637
+ "additionalProperties": false,
3638
+ "properties": {
3639
+ "advancedFilters": {
3640
+ "$ref": "#/components/schemas/FilterNode"
3641
+ },
3642
+ "filters": {
3643
+ "$ref": "#/components/schemas/FilterNode"
3644
+ },
3645
+ "flatFilters": {
3646
+ "additionalProperties": false,
3647
+ "properties": {
3648
+ "borrowerStatus": {
3649
+ "$ref": "#/components/schemas/LoanBorrowerStatusFilterValue"
3650
+ },
3651
+ "broker": {
3652
+ "allOf": [
3653
+ {
3654
+ "$ref": "#/components/schemas/TextFilterValue"
3655
+ },
3656
+ {
3657
+ "description": "Broker shop NMLS id (digits only). A broker is a company — resolve a broker name to its NMLS id via instantSearch (or listCompanies)."
3658
+ }
3659
+ ]
3660
+ },
3661
+ "buyerEntityType": {
3662
+ "$ref": "#/components/schemas/BooleanFilterValue"
3663
+ },
3664
+ "city": {
3665
+ "$ref": "#/components/schemas/TextFilterValue"
3666
+ },
3667
+ "conforming": {
3668
+ "$ref": "#/components/schemas/BooleanFilterValue"
3669
+ },
3670
+ "county": {
3671
+ "allOf": [
3672
+ {
3673
+ "$ref": "#/components/schemas/TextFilterValue"
3674
+ },
3675
+ {
3676
+ "description": "County name (e.g. \"Broward\") or 5-digit FIPS code. Names resolve to FIPS server-side; include a `state` filter to disambiguate."
3677
+ }
3678
+ ]
3679
+ },
3680
+ "currentBalance": {
3681
+ "allOf": [
3682
+ {
3683
+ "$ref": "#/components/schemas/NumericFilterValue"
3684
+ },
3685
+ {
3686
+ "description": "Estimated remaining principal balance (~19% populated). Approximate current equity as `homeValue` (AVM) minus this."
3687
+ }
3688
+ ]
3689
+ },
3690
+ "employer": {
3691
+ "allOf": [
3692
+ {
2943
3693
  "$ref": "#/components/schemas/TextFilterValue"
2944
3694
  },
2945
3695
  {
@@ -10578,6 +11328,100 @@
10578
11328
  ],
10579
11329
  "type": "object"
10580
11330
  },
11331
+ "CreateAlertSubscriptionRequest": {
11332
+ "properties": {
11333
+ "expiresAt": {
11334
+ "description": "Stop firing after this instant (ISO 8601).",
11335
+ "format": "date-time",
11336
+ "type": "string"
11337
+ },
11338
+ "kinds": {
11339
+ "description": "The alert kinds to turn on for this subject (replaces kinds set directly before; kinds granted by a recipe stay). Empty = paused.",
11340
+ "items": {
11341
+ "type": "string"
11342
+ },
11343
+ "type": "array"
11344
+ },
11345
+ "rules": {
11346
+ "description": "Rules (required for sale / property / loan subjects; same shape as POST /watched-documents).",
11347
+ "items": {
11348
+ "properties": {
11349
+ "match": {
11350
+ "description": "How predicates combine: all (AND, default) or any (OR).",
11351
+ "enum": [
11352
+ "all",
11353
+ "any"
11354
+ ],
11355
+ "example": "all",
11356
+ "type": "string"
11357
+ },
11358
+ "predicates": {
11359
+ "description": "One or more conditions; combined per match.",
11360
+ "items": {
11361
+ "properties": {
11362
+ "field": {
11363
+ "description": "Catalog field alias (e.g. avm, current_ltv, loan_type).",
11364
+ "example": "current_ltv",
11365
+ "type": "string"
11366
+ },
11367
+ "op": {
11368
+ "description": "Comparison / change / status / guard operator.",
11369
+ "enum": [
11370
+ "gt",
11371
+ "gte",
11372
+ "lt",
11373
+ "lte",
11374
+ "eq",
11375
+ "between",
11376
+ "pct_change_gte",
11377
+ "abs_change_gte",
11378
+ "changed",
11379
+ "changed_to"
11380
+ ],
11381
+ "example": "lte",
11382
+ "type": "string"
11383
+ },
11384
+ "stringValue": {
11385
+ "description": "Target keyword for changed_to / eq guard.",
11386
+ "type": "string"
11387
+ },
11388
+ "value": {
11389
+ "description": "Threshold, or change amount (percent for pct_change_gte).",
11390
+ "example": 0.8,
11391
+ "type": "number"
11392
+ },
11393
+ "value2": {
11394
+ "description": "Upper bound for between.",
11395
+ "type": "number"
11396
+ }
11397
+ },
11398
+ "required": [
11399
+ "field",
11400
+ "op"
11401
+ ],
11402
+ "type": "object"
11403
+ },
11404
+ "minItems": 1,
11405
+ "type": "array"
11406
+ }
11407
+ },
11408
+ "required": [
11409
+ "predicates"
11410
+ ],
11411
+ "type": "object"
11412
+ },
11413
+ "type": "array"
11414
+ },
11415
+ "subject": {
11416
+ "$ref": "#/components/schemas/AlertSubscriptionSubject"
11417
+ }
11418
+ },
11419
+ "required": [
11420
+ "subject",
11421
+ "kinds"
11422
+ ],
11423
+ "type": "object"
11424
+ },
10581
11425
  "CreateSavedFilterRequest": {
10582
11426
  "additionalProperties": false,
10583
11427
  "properties": {
@@ -25799,12 +26643,18 @@
25799
26643
  "lenderMatchMode": {
25800
26644
  "$ref": "#/components/schemas/LenderMatchMode"
25801
26645
  },
26646
+ "officeGeoPoint": {
26647
+ "$ref": "#/components/schemas/OriginatorOfficeGeoPoint"
26648
+ },
25802
26649
  "pagination": {
25803
26650
  "$ref": "#/components/schemas/Pagination"
25804
26651
  },
25805
26652
  "period": {
25806
26653
  "$ref": "#/components/schemas/Period"
25807
26654
  },
26655
+ "productMix": {
26656
+ "$ref": "#/components/schemas/OriginatorProductMixFilter"
26657
+ },
25808
26658
  "sort": {
25809
26659
  "items": {
25810
26660
  "properties": {
@@ -25826,7 +26676,9 @@
25826
26676
  "officeState",
25827
26677
  "officeCity",
25828
26678
  "licensedSinceDate",
25829
- "lastTransactionDate"
26679
+ "lastTransactionDate",
26680
+ "purchaseShare",
26681
+ "refinanceShare"
25830
26682
  ],
25831
26683
  "type": "string"
25832
26684
  },
@@ -26470,6 +27322,114 @@
26470
27322
  ],
26471
27323
  "type": "object"
26472
27324
  },
27325
+ "OriginatorOfficeGeoPoint": {
27326
+ "additionalProperties": false,
27327
+ "description": "Originators whose NMLS-registered OFFICE is within `radiusMiles` of an anchor — the radius form of `officeState` / `officeCity` / `officeZip`, NOT production geography (that is `state` / `city` / `county` / `zip` / `footprint`). Anchor on a coordinate (`{lat, lon, radiusMiles}`) or on another originator's office (`{nmlsId: \"7206\", radiusMiles: 25}` — 'LOs near this LO'; that originator is excluded). Originators with no geocoded office never match. 400 when neither anchor form is complete or both are given; 404 `office_geo_anchor_not_found` for an unknown `nmlsId`; 400 `office_geo_anchor_unlocated` when that originator has no geocoded office.",
27328
+ "properties": {
27329
+ "lat": {
27330
+ "description": "Anchor latitude; pair with `lon`.",
27331
+ "example": 33.4484,
27332
+ "maximum": 90,
27333
+ "minimum": -90,
27334
+ "type": "number"
27335
+ },
27336
+ "lon": {
27337
+ "description": "Anchor longitude; pair with `lat`.",
27338
+ "example": -112.074,
27339
+ "maximum": 180,
27340
+ "minimum": -180,
27341
+ "type": "number"
27342
+ },
27343
+ "nmlsId": {
27344
+ "description": "Anchor on another originator's office instead: their NMLS id (resolved server-side; they are excluded from the result). Exclusive with `lat`/`lon`.",
27345
+ "example": "7206",
27346
+ "minLength": 1,
27347
+ "type": "string"
27348
+ },
27349
+ "radiusMiles": {
27350
+ "description": "Radius in miles, > 0 and ≤ 1000.",
27351
+ "example": 50,
27352
+ "exclusiveMinimum": true,
27353
+ "maximum": 1000,
27354
+ "minimum": 0,
27355
+ "type": "number"
27356
+ }
27357
+ },
27358
+ "required": [
27359
+ "radiusMiles"
27360
+ ],
27361
+ "type": "object"
27362
+ },
27363
+ "OriginatorProductMixEntry": {
27364
+ "additionalProperties": false,
27365
+ "properties": {
27366
+ "basis": {
27367
+ "description": "`volume` (default — what the row's `fhaPct` / `vaPct` / `conventionalPct` report) or `units` (loan count).",
27368
+ "enum": [
27369
+ "volume",
27370
+ "units"
27371
+ ],
27372
+ "type": "string"
27373
+ },
27374
+ "maxShare": {
27375
+ "description": "Upper bound, 0–100 percent. An originator who wrote none of the product has a 0% share and passes.",
27376
+ "example": 60,
27377
+ "maximum": 100,
27378
+ "minimum": 0,
27379
+ "type": "number"
27380
+ },
27381
+ "minShare": {
27382
+ "description": "Lower bound on the product's share of the period book, 0–100 percent.",
27383
+ "example": 25,
27384
+ "maximum": 100,
27385
+ "minimum": 0,
27386
+ "type": "number"
27387
+ },
27388
+ "product": {
27389
+ "description": "Loan product (case-insensitive).",
27390
+ "enum": [
27391
+ "conventional",
27392
+ "fha",
27393
+ "va",
27394
+ "he",
27395
+ "heloc",
27396
+ "building",
27397
+ "commercial",
27398
+ "stand_alone_refi",
27399
+ "closed_end",
27400
+ "reverse",
27401
+ "usda",
27402
+ "stand_alone_second",
27403
+ "aggregate",
27404
+ "seller_take_back",
27405
+ "sba_participation_trust_deed",
27406
+ "balloon",
27407
+ "opened_end",
27408
+ "assumption",
27409
+ "negative_amortization",
27410
+ "land_contract",
27411
+ "trade",
27412
+ "modification",
27413
+ "unknown"
27414
+ ],
27415
+ "example": "fha",
27416
+ "type": "string"
27417
+ }
27418
+ },
27419
+ "required": [
27420
+ "product"
27421
+ ],
27422
+ "type": "object"
27423
+ },
27424
+ "OriginatorProductMixFilter": {
27425
+ "description": "Whole-book product-share bands for the selected period, ANDed: `[{product: \"fha\", minShare: 30}, {product: \"va\", maxShare: 10}]` = FHA is at least 30% of their period volume AND VA at most 10%. Shares are 0–100 percents of TOTAL period production across all markets (not scoped by any geography filter), the same per-product figures the row reports as `fhaPct` / `vaPct` / `conventionalPct`. No bucket for a product = 0% (fails any `minShare` > 0, passes any `maxShare`). Pair with `units: {gte: N}` so a 100% share off two loans does not read as a specialty.",
27426
+ "items": {
27427
+ "$ref": "#/components/schemas/OriginatorProductMixEntry"
27428
+ },
27429
+ "maxItems": 10,
27430
+ "minItems": 1,
27431
+ "type": "array"
27432
+ },
26473
27433
  "OriginatorProfileInsights": {
26474
27434
  "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.",
26475
27435
  "properties": {
@@ -26845,6 +27805,12 @@
26845
27805
  "nullable": true,
26846
27806
  "type": "number"
26847
27807
  },
27808
+ "purchaseShare": {
27809
+ "description": "Purchase loans as a 0–100 percent of the originator's period loan COUNT — the value `sort: [{field: \"purchaseShare\"}]` orders by. `null` when the originator wrote no purchase loans in the period (sorts as 0).",
27810
+ "example": 72.5,
27811
+ "nullable": true,
27812
+ "type": "number"
27813
+ },
26848
27814
  "purchaseUnits": {
26849
27815
  "nullable": true,
26850
27816
  "type": "number"
@@ -26853,6 +27819,12 @@
26853
27819
  "nullable": true,
26854
27820
  "type": "number"
26855
27821
  },
27822
+ "refinanceShare": {
27823
+ "description": "Refinance loans as a 0–100 percent of the originator's period loan COUNT — the value `sort: [{field: \"refinanceShare\"}]` orders by. `null` when none (sorts as 0).",
27824
+ "example": 18.2,
27825
+ "nullable": true,
27826
+ "type": "number"
27827
+ },
26856
27828
  "refinanceUnits": {
26857
27829
  "nullable": true,
26858
27830
  "type": "number"
@@ -32376,7 +33348,8 @@
32376
33348
  "type": "string"
32377
33349
  },
32378
33350
  "emailEnabled": {
32379
- "description": "Per-trigger email opt-ins to change (others keep their value). Off by default — turn one on to also receive an email when it fires.",
33351
+ "deprecated": true,
33352
+ "description": "Deprecated and ignored: accepted for compatibility, but not stored and no longer controls delivery (the response always reports every flag off). Email and in-app channel preferences for alerts live in Notifications settings.",
32380
33353
  "properties": {
32381
33354
  "agent_sale_closed": {
32382
33355
  "type": "boolean"
@@ -32409,7 +33382,8 @@
32409
33382
  "type": "object"
32410
33383
  },
32411
33384
  "enabled": {
32412
- "description": "Per-trigger on/off flags to change (others keep their value).",
33385
+ "deprecated": true,
33386
+ "description": "Deprecated: use the alert recipes (POST/DELETE /recipes/{id}) or /subscriptions instead. Still honoured: `true` turns the trigger on for every watched subject that supports it (and for your NMLS id when one is on file); `false` turns it off everywhere. Omitted triggers are unchanged. A trigger with nothing watched that supports it stays off.",
32413
33387
  "properties": {
32414
33388
  "agent_sale_closed": {
32415
33389
  "type": "boolean"
@@ -32767,7 +33741,7 @@
32767
33741
  "info": {
32768
33742
  "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.",
32769
33743
  "title": "ModelMatch API",
32770
- "version": "3.67.0"
33744
+ "version": "3.68.0"
32771
33745
  },
32772
33746
  "openapi": "3.1.0",
32773
33747
  "paths": {
@@ -33205,20 +34179,739 @@
33205
34179
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33206
34180
  }
33207
34181
  },
33208
- "summary": "Configurable chart (measure × slice) over the agent's sales",
34182
+ "summary": "Configurable chart (measure × slice) over the agent's sales",
34183
+ "tags": [
34184
+ "Agents"
34185
+ ]
34186
+ }
34187
+ },
34188
+ "/v1/agents/analytics/summary": {
34189
+ "post": {
34190
+ "operationId": "agentAnalyticsSummary",
34191
+ "requestBody": {
34192
+ "content": {
34193
+ "application/json": {
34194
+ "schema": {
34195
+ "$ref": "#/components/schemas/AgentSummaryRequest"
34196
+ }
34197
+ }
34198
+ }
34199
+ },
34200
+ "responses": {
34201
+ "200": {
34202
+ "content": {
34203
+ "application/json": {
34204
+ "schema": {
34205
+ "$ref": "#/components/schemas/SidedSummaryResponse"
34206
+ }
34207
+ }
34208
+ },
34209
+ "description": "Sided summary with previous-period comparison"
34210
+ },
34211
+ "400": {
34212
+ "content": {
34213
+ "application/json": {
34214
+ "schema": {
34215
+ "$ref": "#/components/schemas/ErrorResponse"
34216
+ }
34217
+ }
34218
+ },
34219
+ "description": "Validation error"
34220
+ },
34221
+ "401": {
34222
+ "content": {
34223
+ "application/json": {
34224
+ "schema": {
34225
+ "$ref": "#/components/schemas/ErrorResponse"
34226
+ }
34227
+ }
34228
+ },
34229
+ "description": "Unauthorized"
34230
+ },
34231
+ "403": {
34232
+ "content": {
34233
+ "application/json": {
34234
+ "schema": {
34235
+ "$ref": "#/components/schemas/ErrorResponse"
34236
+ }
34237
+ }
34238
+ },
34239
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
34240
+ },
34241
+ "404": {
34242
+ "content": {
34243
+ "application/json": {
34244
+ "schema": {
34245
+ "$ref": "#/components/schemas/ErrorResponse"
34246
+ }
34247
+ }
34248
+ },
34249
+ "description": "Not found"
34250
+ },
34251
+ "413": {
34252
+ "content": {
34253
+ "application/json": {
34254
+ "schema": {
34255
+ "$ref": "#/components/schemas/PayloadTooLarge"
34256
+ }
34257
+ }
34258
+ },
34259
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34260
+ }
34261
+ },
34262
+ "summary": "Production summary with buyer/seller/dual split + previous period",
34263
+ "tags": [
34264
+ "Agents"
34265
+ ]
34266
+ }
34267
+ },
34268
+ "/v1/agents/analytics/time-series": {
34269
+ "post": {
34270
+ "operationId": "agentAnalyticsTimeSeries",
34271
+ "requestBody": {
34272
+ "content": {
34273
+ "application/json": {
34274
+ "schema": {
34275
+ "$ref": "#/components/schemas/AgentTimeSeriesRequest"
34276
+ }
34277
+ }
34278
+ }
34279
+ },
34280
+ "responses": {
34281
+ "200": {
34282
+ "content": {
34283
+ "application/json": {
34284
+ "schema": {
34285
+ "$ref": "#/components/schemas/SidedTimeSeriesResponse"
34286
+ }
34287
+ }
34288
+ },
34289
+ "description": "Time-series with side splits + previous period"
34290
+ },
34291
+ "400": {
34292
+ "content": {
34293
+ "application/json": {
34294
+ "schema": {
34295
+ "$ref": "#/components/schemas/ErrorResponse"
34296
+ }
34297
+ }
34298
+ },
34299
+ "description": "Validation error"
34300
+ },
34301
+ "401": {
34302
+ "content": {
34303
+ "application/json": {
34304
+ "schema": {
34305
+ "$ref": "#/components/schemas/ErrorResponse"
34306
+ }
34307
+ }
34308
+ },
34309
+ "description": "Unauthorized"
34310
+ },
34311
+ "403": {
34312
+ "content": {
34313
+ "application/json": {
34314
+ "schema": {
34315
+ "$ref": "#/components/schemas/ErrorResponse"
34316
+ }
34317
+ }
34318
+ },
34319
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
34320
+ },
34321
+ "404": {
34322
+ "content": {
34323
+ "application/json": {
34324
+ "schema": {
34325
+ "$ref": "#/components/schemas/ErrorResponse"
34326
+ }
34327
+ }
34328
+ },
34329
+ "description": "Not found"
34330
+ },
34331
+ "413": {
34332
+ "content": {
34333
+ "application/json": {
34334
+ "schema": {
34335
+ "$ref": "#/components/schemas/PayloadTooLarge"
34336
+ }
34337
+ }
34338
+ },
34339
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34340
+ }
34341
+ },
34342
+ "summary": "Agent volume/units over time with buyer/seller/dual split",
34343
+ "tags": [
34344
+ "Agents"
34345
+ ]
34346
+ }
34347
+ },
34348
+ "/v1/agents/bulk-delivery": {
34349
+ "post": {
34350
+ "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.",
34351
+ "operationId": "submitAgentsBulkDelivery",
34352
+ "requestBody": {
34353
+ "content": {
34354
+ "application/json": {
34355
+ "schema": {
34356
+ "allOf": [
34357
+ {
34358
+ "$ref": "#/components/schemas/AgentListRequest"
34359
+ },
34360
+ {
34361
+ "additionalProperties": false,
34362
+ "properties": {
34363
+ "destination": {
34364
+ "$ref": "#/components/schemas/BulkDeliveryDestination"
34365
+ },
34366
+ "fields": {
34367
+ "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.",
34368
+ "items": {
34369
+ "minLength": 1,
34370
+ "type": "string"
34371
+ },
34372
+ "type": "array"
34373
+ },
34374
+ "format": {
34375
+ "$ref": "#/components/schemas/BulkDeliveryFormat"
34376
+ },
34377
+ "limit": {
34378
+ "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.",
34379
+ "example": 5000,
34380
+ "exclusiveMinimum": true,
34381
+ "minimum": 0,
34382
+ "type": "integer"
34383
+ }
34384
+ },
34385
+ "type": "object"
34386
+ }
34387
+ ]
34388
+ }
34389
+ }
34390
+ }
34391
+ },
34392
+ "responses": {
34393
+ "202": {
34394
+ "content": {
34395
+ "application/json": {
34396
+ "schema": {
34397
+ "$ref": "#/components/schemas/BulkDeliverySubmitResponse"
34398
+ }
34399
+ }
34400
+ },
34401
+ "description": "Job queued; ECS task launched"
34402
+ },
34403
+ "400": {
34404
+ "content": {
34405
+ "application/json": {
34406
+ "schema": {
34407
+ "anyOf": [
34408
+ {
34409
+ "$ref": "#/components/schemas/ErrorResponse"
34410
+ },
34411
+ {
34412
+ "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
34413
+ },
34414
+ {
34415
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34416
+ }
34417
+ ]
34418
+ }
34419
+ }
34420
+ },
34421
+ "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`)"
34422
+ },
34423
+ "401": {
34424
+ "content": {
34425
+ "application/json": {
34426
+ "schema": {
34427
+ "$ref": "#/components/schemas/ErrorResponse"
34428
+ }
34429
+ }
34430
+ },
34431
+ "description": "Authentication required"
34432
+ },
34433
+ "402": {
34434
+ "content": {
34435
+ "application/json": {
34436
+ "schema": {
34437
+ "anyOf": [
34438
+ {
34439
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
34440
+ },
34441
+ {
34442
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34443
+ }
34444
+ ]
34445
+ }
34446
+ }
34447
+ },
34448
+ "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
34449
+ },
34450
+ "403": {
34451
+ "content": {
34452
+ "application/json": {
34453
+ "schema": {
34454
+ "anyOf": [
34455
+ {
34456
+ "$ref": "#/components/schemas/BulkDeliveryForbidden"
34457
+ },
34458
+ {
34459
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34460
+ },
34461
+ {
34462
+ "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
34463
+ }
34464
+ ]
34465
+ }
34466
+ }
34467
+ },
34468
+ "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`)."
34469
+ },
34470
+ "404": {
34471
+ "content": {
34472
+ "application/json": {
34473
+ "schema": {
34474
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34475
+ }
34476
+ }
34477
+ },
34478
+ "description": "Push target not found (`TARGET_NOT_FOUND`)"
34479
+ },
34480
+ "409": {
34481
+ "content": {
34482
+ "application/json": {
34483
+ "schema": {
34484
+ "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
34485
+ }
34486
+ }
34487
+ },
34488
+ "description": "Push target is disabled (`TARGET_DISABLED`)"
34489
+ },
34490
+ "502": {
34491
+ "content": {
34492
+ "application/json": {
34493
+ "schema": {
34494
+ "$ref": "#/components/schemas/ErrorResponse"
34495
+ }
34496
+ }
34497
+ },
34498
+ "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
34499
+ }
34500
+ },
34501
+ "summary": "Submit a bulk-delivery job for agents",
34502
+ "tags": [
34503
+ "Agents"
34504
+ ]
34505
+ }
34506
+ },
34507
+ "/v1/agents/bulk-delivery/{jobId}": {
34508
+ "delete": {
34509
+ "description": "Cancels a queued or running agents delivery job.\n\nThe job is marked `cancelled` immediately; the container running it notices on its next page boundary and stops. Because a delivery is billed once, at the end, **a cancelled job is never charged** — no credits are debited and there is nothing to refund. No partial file is written.\n\nIdempotent-ish: cancelling a job that has already finished (or was already cancelled) returns 409 and changes nothing. Poll `GET /v1/agents/bulk-delivery/{jobId}` for the settled state.",
34510
+ "operationId": "cancelAgentsBulkDeliveryJob",
34511
+ "parameters": [
34512
+ {
34513
+ "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
34514
+ "in": "path",
34515
+ "name": "jobId",
34516
+ "required": true,
34517
+ "schema": {
34518
+ "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
34519
+ "type": "string"
34520
+ }
34521
+ }
34522
+ ],
34523
+ "responses": {
34524
+ "200": {
34525
+ "content": {
34526
+ "application/json": {
34527
+ "schema": {
34528
+ "$ref": "#/components/schemas/BulkDeliveryStatusResponse"
34529
+ }
34530
+ }
34531
+ },
34532
+ "description": "Job cancelled; body is its settled status"
34533
+ },
34534
+ "401": {
34535
+ "content": {
34536
+ "application/json": {
34537
+ "schema": {
34538
+ "$ref": "#/components/schemas/ErrorResponse"
34539
+ }
34540
+ }
34541
+ },
34542
+ "description": "Authentication required"
34543
+ },
34544
+ "403": {
34545
+ "content": {
34546
+ "application/json": {
34547
+ "schema": {
34548
+ "$ref": "#/components/schemas/ErrorResponse"
34549
+ }
34550
+ }
34551
+ },
34552
+ "description": "Caller does not own this job"
34553
+ },
34554
+ "404": {
34555
+ "content": {
34556
+ "application/json": {
34557
+ "schema": {
34558
+ "$ref": "#/components/schemas/ErrorResponse"
34559
+ }
34560
+ }
34561
+ },
34562
+ "description": "Job not found (or expired past 30d TTL)"
34563
+ },
34564
+ "409": {
34565
+ "content": {
34566
+ "application/json": {
34567
+ "schema": {
34568
+ "$ref": "#/components/schemas/ErrorResponse"
34569
+ }
34570
+ }
34571
+ },
34572
+ "description": "Job already finished — nothing to cancel"
34573
+ }
34574
+ },
34575
+ "summary": "Cancel a bulk-delivery job for agents",
34576
+ "tags": [
34577
+ "Agents"
34578
+ ]
34579
+ },
34580
+ "get": {
34581
+ "operationId": "getAgentsBulkDeliveryJob",
34582
+ "parameters": [
34583
+ {
34584
+ "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
34585
+ "in": "path",
34586
+ "name": "jobId",
34587
+ "required": true,
34588
+ "schema": {
34589
+ "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
34590
+ "type": "string"
34591
+ }
34592
+ }
34593
+ ],
34594
+ "responses": {
34595
+ "200": {
34596
+ "content": {
34597
+ "application/json": {
34598
+ "schema": {
34599
+ "$ref": "#/components/schemas/BulkDeliveryStatusResponse"
34600
+ }
34601
+ }
34602
+ },
34603
+ "description": "Job status (resultsUrl re-signed if completed)"
34604
+ },
34605
+ "401": {
34606
+ "content": {
34607
+ "application/json": {
34608
+ "schema": {
34609
+ "$ref": "#/components/schemas/ErrorResponse"
34610
+ }
34611
+ }
34612
+ },
34613
+ "description": "Authentication required"
34614
+ },
34615
+ "403": {
34616
+ "content": {
34617
+ "application/json": {
34618
+ "schema": {
34619
+ "$ref": "#/components/schemas/ErrorResponse"
34620
+ }
34621
+ }
34622
+ },
34623
+ "description": "Caller does not own this job"
34624
+ },
34625
+ "404": {
34626
+ "content": {
34627
+ "application/json": {
34628
+ "schema": {
34629
+ "$ref": "#/components/schemas/ErrorResponse"
34630
+ }
34631
+ }
34632
+ },
34633
+ "description": "Job not found (or expired past 30d TTL)"
34634
+ }
34635
+ },
34636
+ "summary": "Get bulk-delivery job status for agents",
34637
+ "tags": [
34638
+ "Agents"
34639
+ ]
34640
+ }
34641
+ },
34642
+ "/v1/agents/count": {
34643
+ "post": {
34644
+ "description": "Return the exact number of agents matching the given filters. Accepts the same request body as `listAgents` (pagination and sort are ignored). Use this when you only need a total; to get the matching agents themselves, send the same request body to `listAgents`.",
34645
+ "operationId": "countAgents",
34646
+ "requestBody": {
34647
+ "content": {
34648
+ "application/json": {
34649
+ "schema": {
34650
+ "$ref": "#/components/schemas/AgentListRequest"
34651
+ }
34652
+ }
34653
+ }
34654
+ },
34655
+ "responses": {
34656
+ "200": {
34657
+ "content": {
34658
+ "application/json": {
34659
+ "schema": {
34660
+ "$ref": "#/components/schemas/CountResponse"
34661
+ }
34662
+ }
34663
+ },
34664
+ "description": "Exact count of matching agents"
34665
+ },
34666
+ "400": {
34667
+ "content": {
34668
+ "application/json": {
34669
+ "schema": {
34670
+ "$ref": "#/components/schemas/ErrorResponse"
34671
+ }
34672
+ }
34673
+ },
34674
+ "description": "Validation error"
34675
+ },
34676
+ "401": {
34677
+ "content": {
34678
+ "application/json": {
34679
+ "schema": {
34680
+ "$ref": "#/components/schemas/ErrorResponse"
34681
+ }
34682
+ }
34683
+ },
34684
+ "description": "Unauthorized"
34685
+ },
34686
+ "403": {
34687
+ "content": {
34688
+ "application/json": {
34689
+ "schema": {
34690
+ "$ref": "#/components/schemas/ErrorResponse"
34691
+ }
34692
+ }
34693
+ },
34694
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
34695
+ },
34696
+ "404": {
34697
+ "content": {
34698
+ "application/json": {
34699
+ "schema": {
34700
+ "$ref": "#/components/schemas/ErrorResponse"
34701
+ }
34702
+ }
34703
+ },
34704
+ "description": "Not found"
34705
+ },
34706
+ "413": {
34707
+ "content": {
34708
+ "application/json": {
34709
+ "schema": {
34710
+ "$ref": "#/components/schemas/PayloadTooLarge"
34711
+ }
34712
+ }
34713
+ },
34714
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34715
+ },
34716
+ "503": {
34717
+ "content": {
34718
+ "application/json": {
34719
+ "schema": {
34720
+ "$ref": "#/components/schemas/ErrorResponse"
34721
+ }
34722
+ }
34723
+ },
34724
+ "description": "Location dictionary temporarily unavailable. Only returned for a NEGATED location filter (`neq`, or `not`), which cannot be honoured without resolving the excluded value — answering anyway would return the records you asked to exclude. Positive location filters are unaffected. Retry shortly; the filter value itself was not rejected."
34725
+ }
34726
+ },
34727
+ "summary": "Count agents matching filters",
34728
+ "tags": [
34729
+ "Agents"
34730
+ ],
34731
+ "x-mm-records-via": "listAgents"
34732
+ }
34733
+ },
34734
+ "/v1/agents/{id}": {
34735
+ "get": {
34736
+ "operationId": "getAgent",
34737
+ "parameters": [
34738
+ {
34739
+ "description": "Agent document ID",
34740
+ "in": "path",
34741
+ "name": "id",
34742
+ "required": true,
34743
+ "schema": {
34744
+ "description": "Agent document ID",
34745
+ "type": "string"
34746
+ }
34747
+ },
34748
+ {
34749
+ "description": "Period for volume/units (default: last12Months)",
34750
+ "in": "query",
34751
+ "name": "period",
34752
+ "required": false,
34753
+ "schema": {
34754
+ "allOf": [
34755
+ {
34756
+ "$ref": "#/components/schemas/Period"
34757
+ },
34758
+ {
34759
+ "description": "Period for volume/units (default: last12Months)"
34760
+ }
34761
+ ]
34762
+ }
34763
+ }
34764
+ ],
34765
+ "responses": {
34766
+ "200": {
34767
+ "content": {
34768
+ "application/json": {
34769
+ "schema": {
34770
+ "$ref": "#/components/schemas/AgentDetailResponse"
34771
+ }
34772
+ }
34773
+ },
34774
+ "description": "Agent detail"
34775
+ },
34776
+ "404": {
34777
+ "content": {
34778
+ "application/json": {
34779
+ "schema": {
34780
+ "$ref": "#/components/schemas/ErrorResponse"
34781
+ }
34782
+ }
34783
+ },
34784
+ "description": "Agent not found"
34785
+ },
34786
+ "413": {
34787
+ "content": {
34788
+ "application/json": {
34789
+ "schema": {
34790
+ "$ref": "#/components/schemas/PayloadTooLarge"
34791
+ }
34792
+ }
34793
+ },
34794
+ "description": "Response payload too large"
34795
+ }
34796
+ },
34797
+ "summary": "Get agent by ID",
34798
+ "tags": [
34799
+ "Agents"
34800
+ ]
34801
+ }
34802
+ },
34803
+ "/v1/agents/{id}/breakdowns/cities": {
34804
+ "post": {
34805
+ "operationId": "agentCities",
34806
+ "parameters": [
34807
+ {
34808
+ "description": "Agent document ID",
34809
+ "in": "path",
34810
+ "name": "id",
34811
+ "required": true,
34812
+ "schema": {
34813
+ "description": "Agent document ID",
34814
+ "type": "string"
34815
+ }
34816
+ }
34817
+ ],
34818
+ "requestBody": {
34819
+ "content": {
34820
+ "application/json": {
34821
+ "schema": {
34822
+ "$ref": "#/components/schemas/BreakdownRequest"
34823
+ }
34824
+ }
34825
+ }
34826
+ },
34827
+ "responses": {
34828
+ "200": {
34829
+ "content": {
34830
+ "application/json": {
34831
+ "schema": {
34832
+ "$ref": "#/components/schemas/BreakdownResponse"
34833
+ }
34834
+ }
34835
+ },
34836
+ "description": "Breakdown of the agent's sales by cities"
34837
+ },
34838
+ "400": {
34839
+ "content": {
34840
+ "application/json": {
34841
+ "schema": {
34842
+ "$ref": "#/components/schemas/ErrorResponse"
34843
+ }
34844
+ }
34845
+ },
34846
+ "description": "Validation error"
34847
+ },
34848
+ "401": {
34849
+ "content": {
34850
+ "application/json": {
34851
+ "schema": {
34852
+ "$ref": "#/components/schemas/ErrorResponse"
34853
+ }
34854
+ }
34855
+ },
34856
+ "description": "Unauthorized"
34857
+ },
34858
+ "403": {
34859
+ "content": {
34860
+ "application/json": {
34861
+ "schema": {
34862
+ "$ref": "#/components/schemas/ErrorResponse"
34863
+ }
34864
+ }
34865
+ },
34866
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
34867
+ },
34868
+ "404": {
34869
+ "content": {
34870
+ "application/json": {
34871
+ "schema": {
34872
+ "$ref": "#/components/schemas/ErrorResponse"
34873
+ }
34874
+ }
34875
+ },
34876
+ "description": "Not found"
34877
+ },
34878
+ "413": {
34879
+ "content": {
34880
+ "application/json": {
34881
+ "schema": {
34882
+ "$ref": "#/components/schemas/PayloadTooLarge"
34883
+ }
34884
+ }
34885
+ },
34886
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34887
+ }
34888
+ },
34889
+ "summary": "City volume breakdown across the agent's sales",
33209
34890
  "tags": [
33210
34891
  "Agents"
33211
34892
  ]
33212
34893
  }
33213
34894
  },
33214
- "/v1/agents/analytics/summary": {
34895
+ "/v1/agents/{id}/breakdowns/companies": {
33215
34896
  "post": {
33216
- "operationId": "agentAnalyticsSummary",
34897
+ "operationId": "agentCompanies",
34898
+ "parameters": [
34899
+ {
34900
+ "description": "Agent document ID",
34901
+ "in": "path",
34902
+ "name": "id",
34903
+ "required": true,
34904
+ "schema": {
34905
+ "description": "Agent document ID",
34906
+ "type": "string"
34907
+ }
34908
+ }
34909
+ ],
33217
34910
  "requestBody": {
33218
34911
  "content": {
33219
34912
  "application/json": {
33220
34913
  "schema": {
33221
- "$ref": "#/components/schemas/AgentSummaryRequest"
34914
+ "$ref": "#/components/schemas/ScoredBreakdownRequest"
33222
34915
  }
33223
34916
  }
33224
34917
  }
@@ -33228,11 +34921,11 @@
33228
34921
  "content": {
33229
34922
  "application/json": {
33230
34923
  "schema": {
33231
- "$ref": "#/components/schemas/SidedSummaryResponse"
34924
+ "$ref": "#/components/schemas/BreakdownResponse"
33232
34925
  }
33233
34926
  }
33234
34927
  },
33235
- "description": "Sided summary with previous-period comparison"
34928
+ "description": "Pre-aggregated companies breakdown from the agent's scored doc"
33236
34929
  },
33237
34930
  "400": {
33238
34931
  "content": {
@@ -33285,20 +34978,32 @@
33285
34978
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33286
34979
  }
33287
34980
  },
33288
- "summary": "Production summary with buyer/seller/dual split + previous period",
34981
+ "summary": "Mortgage companies the agent dealt with (pre-aggregated)",
33289
34982
  "tags": [
33290
34983
  "Agents"
33291
34984
  ]
33292
34985
  }
33293
34986
  },
33294
- "/v1/agents/analytics/time-series": {
34987
+ "/v1/agents/{id}/breakdowns/counties": {
33295
34988
  "post": {
33296
- "operationId": "agentAnalyticsTimeSeries",
34989
+ "operationId": "agentCounties",
34990
+ "parameters": [
34991
+ {
34992
+ "description": "Agent document ID",
34993
+ "in": "path",
34994
+ "name": "id",
34995
+ "required": true,
34996
+ "schema": {
34997
+ "description": "Agent document ID",
34998
+ "type": "string"
34999
+ }
35000
+ }
35001
+ ],
33297
35002
  "requestBody": {
33298
35003
  "content": {
33299
35004
  "application/json": {
33300
35005
  "schema": {
33301
- "$ref": "#/components/schemas/AgentTimeSeriesRequest"
35006
+ "$ref": "#/components/schemas/BreakdownRequest"
33302
35007
  }
33303
35008
  }
33304
35009
  }
@@ -33308,11 +35013,11 @@
33308
35013
  "content": {
33309
35014
  "application/json": {
33310
35015
  "schema": {
33311
- "$ref": "#/components/schemas/SidedTimeSeriesResponse"
35016
+ "$ref": "#/components/schemas/BreakdownResponse"
33312
35017
  }
33313
35018
  }
33314
35019
  },
33315
- "description": "Time-series with side splits + previous period"
35020
+ "description": "Breakdown of the agent's sales by counties"
33316
35021
  },
33317
35022
  "400": {
33318
35023
  "content": {
@@ -33365,86 +35070,56 @@
33365
35070
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33366
35071
  }
33367
35072
  },
33368
- "summary": "Agent volume/units over time with buyer/seller/dual split",
35073
+ "summary": "County (FIPS) volume breakdown",
33369
35074
  "tags": [
33370
35075
  "Agents"
33371
35076
  ]
33372
35077
  }
33373
35078
  },
33374
- "/v1/agents/bulk-delivery": {
35079
+ "/v1/agents/{id}/breakdowns/lenders": {
33375
35080
  "post": {
33376
- "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.",
33377
- "operationId": "submitAgentsBulkDelivery",
35081
+ "operationId": "agentLenders",
35082
+ "parameters": [
35083
+ {
35084
+ "description": "Agent document ID",
35085
+ "in": "path",
35086
+ "name": "id",
35087
+ "required": true,
35088
+ "schema": {
35089
+ "description": "Agent document ID",
35090
+ "type": "string"
35091
+ }
35092
+ }
35093
+ ],
33378
35094
  "requestBody": {
33379
35095
  "content": {
33380
35096
  "application/json": {
33381
35097
  "schema": {
33382
- "allOf": [
33383
- {
33384
- "$ref": "#/components/schemas/AgentListRequest"
33385
- },
33386
- {
33387
- "additionalProperties": false,
33388
- "properties": {
33389
- "destination": {
33390
- "$ref": "#/components/schemas/BulkDeliveryDestination"
33391
- },
33392
- "fields": {
33393
- "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.",
33394
- "items": {
33395
- "minLength": 1,
33396
- "type": "string"
33397
- },
33398
- "type": "array"
33399
- },
33400
- "format": {
33401
- "$ref": "#/components/schemas/BulkDeliveryFormat"
33402
- },
33403
- "limit": {
33404
- "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.",
33405
- "example": 5000,
33406
- "exclusiveMinimum": true,
33407
- "minimum": 0,
33408
- "type": "integer"
33409
- }
33410
- },
33411
- "type": "object"
33412
- }
33413
- ]
35098
+ "$ref": "#/components/schemas/ScoredBreakdownRequest"
33414
35099
  }
33415
35100
  }
33416
35101
  }
33417
35102
  },
33418
35103
  "responses": {
33419
- "202": {
35104
+ "200": {
33420
35105
  "content": {
33421
35106
  "application/json": {
33422
35107
  "schema": {
33423
- "$ref": "#/components/schemas/BulkDeliverySubmitResponse"
35108
+ "$ref": "#/components/schemas/BreakdownResponse"
33424
35109
  }
33425
35110
  }
33426
35111
  },
33427
- "description": "Job queued; ECS task launched"
35112
+ "description": "Pre-aggregated lenders breakdown from the agent's scored doc"
33428
35113
  },
33429
35114
  "400": {
33430
35115
  "content": {
33431
35116
  "application/json": {
33432
35117
  "schema": {
33433
- "anyOf": [
33434
- {
33435
- "$ref": "#/components/schemas/ErrorResponse"
33436
- },
33437
- {
33438
- "$ref": "#/components/schemas/BulkDeliveryPreflightExceeded"
33439
- },
33440
- {
33441
- "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
33442
- }
33443
- ]
35118
+ "$ref": "#/components/schemas/ErrorResponse"
33444
35119
  }
33445
35120
  }
33446
35121
  },
33447
- "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`)"
35122
+ "description": "Validation error"
33448
35123
  },
33449
35124
  "401": {
33450
35125
  "content": {
@@ -33454,108 +35129,89 @@
33454
35129
  }
33455
35130
  }
33456
35131
  },
33457
- "description": "Authentication required"
33458
- },
33459
- "402": {
33460
- "content": {
33461
- "application/json": {
33462
- "schema": {
33463
- "anyOf": [
33464
- {
33465
- "$ref": "#/components/schemas/BulkDeliveryInsufficientCredits"
33466
- },
33467
- {
33468
- "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
33469
- }
33470
- ]
33471
- }
33472
- }
33473
- },
33474
- "description": "Insufficient credits (balance < estimatedTotal, at 1 credit per row), OR the plan does not include push targets (`PLAN_REQUIRED`)"
35132
+ "description": "Unauthorized"
33475
35133
  },
33476
35134
  "403": {
33477
35135
  "content": {
33478
35136
  "application/json": {
33479
35137
  "schema": {
33480
- "anyOf": [
33481
- {
33482
- "$ref": "#/components/schemas/BulkDeliveryForbidden"
33483
- },
33484
- {
33485
- "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
33486
- },
33487
- {
33488
- "$ref": "#/components/schemas/BulkDeliveryInsufficientScope"
33489
- }
33490
- ]
35138
+ "$ref": "#/components/schemas/ErrorResponse"
33491
35139
  }
33492
35140
  }
33493
35141
  },
33494
- "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`)."
35142
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
33495
35143
  },
33496
35144
  "404": {
33497
35145
  "content": {
33498
35146
  "application/json": {
33499
35147
  "schema": {
33500
- "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
33501
- }
33502
- }
33503
- },
33504
- "description": "Push target not found (`TARGET_NOT_FOUND`)"
33505
- },
33506
- "409": {
33507
- "content": {
33508
- "application/json": {
33509
- "schema": {
33510
- "$ref": "#/components/schemas/BulkDeliveryPushTargetError"
35148
+ "$ref": "#/components/schemas/ErrorResponse"
33511
35149
  }
33512
35150
  }
33513
35151
  },
33514
- "description": "Push target is disabled (`TARGET_DISABLED`)"
35152
+ "description": "Not found"
33515
35153
  },
33516
- "502": {
35154
+ "413": {
33517
35155
  "content": {
33518
35156
  "application/json": {
33519
35157
  "schema": {
33520
- "$ref": "#/components/schemas/ErrorResponse"
35158
+ "$ref": "#/components/schemas/PayloadTooLarge"
33521
35159
  }
33522
35160
  }
33523
35161
  },
33524
- "description": "Preflight / S3 / DDB / ECS launch failure, or the push-target check could not reach the auth server"
35162
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33525
35163
  }
33526
35164
  },
33527
- "summary": "Submit a bulk-delivery job for agents",
35165
+ "summary": "Raw-name lenders encountered (pre-aggregated)",
33528
35166
  "tags": [
33529
35167
  "Agents"
33530
35168
  ]
33531
35169
  }
33532
35170
  },
33533
- "/v1/agents/bulk-delivery/{jobId}": {
33534
- "delete": {
33535
- "description": "Cancels a queued or running agents delivery job.\n\nThe job is marked `cancelled` immediately; the container running it notices on its next page boundary and stops. Because a delivery is billed once, at the end, **a cancelled job is never charged** — no credits are debited and there is nothing to refund. No partial file is written.\n\nIdempotent-ish: cancelling a job that has already finished (or was already cancelled) returns 409 and changes nothing. Poll `GET /v1/agents/bulk-delivery/{jobId}` for the settled state.",
33536
- "operationId": "cancelAgentsBulkDeliveryJob",
35171
+ "/v1/agents/{id}/breakdowns/offices": {
35172
+ "post": {
35173
+ "operationId": "agentOffices",
33537
35174
  "parameters": [
33538
35175
  {
33539
- "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
35176
+ "description": "Agent document ID",
33540
35177
  "in": "path",
33541
- "name": "jobId",
35178
+ "name": "id",
33542
35179
  "required": true,
33543
35180
  "schema": {
33544
- "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
35181
+ "description": "Agent document ID",
33545
35182
  "type": "string"
33546
35183
  }
33547
35184
  }
33548
35185
  ],
35186
+ "requestBody": {
35187
+ "content": {
35188
+ "application/json": {
35189
+ "schema": {
35190
+ "$ref": "#/components/schemas/BreakdownRequest"
35191
+ }
35192
+ }
35193
+ }
35194
+ },
33549
35195
  "responses": {
33550
35196
  "200": {
33551
35197
  "content": {
33552
35198
  "application/json": {
33553
35199
  "schema": {
33554
- "$ref": "#/components/schemas/BulkDeliveryStatusResponse"
35200
+ "$ref": "#/components/schemas/BreakdownResponse"
33555
35201
  }
33556
35202
  }
33557
35203
  },
33558
- "description": "Job cancelled; body is its settled status"
35204
+ "description": "Breakdown of the agent's sales by offices"
35205
+ },
35206
+ "400": {
35207
+ "content": {
35208
+ "application/json": {
35209
+ "schema": {
35210
+ "$ref": "#/components/schemas/ErrorResponse"
35211
+ }
35212
+ }
35213
+ },
35214
+ "description": "Validation error"
33559
35215
  },
33560
35216
  "401": {
33561
35217
  "content": {
@@ -33565,7 +35221,7 @@
33565
35221
  }
33566
35222
  }
33567
35223
  },
33568
- "description": "Authentication required"
35224
+ "description": "Unauthorized"
33569
35225
  },
33570
35226
  "403": {
33571
35227
  "content": {
@@ -33575,7 +35231,7 @@
33575
35231
  }
33576
35232
  }
33577
35233
  },
33578
- "description": "Caller does not own this job"
35234
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
33579
35235
  },
33580
35236
  "404": {
33581
35237
  "content": {
@@ -33585,48 +35241,69 @@
33585
35241
  }
33586
35242
  }
33587
35243
  },
33588
- "description": "Job not found (or expired past 30d TTL)"
35244
+ "description": "Not found"
33589
35245
  },
33590
- "409": {
35246
+ "413": {
33591
35247
  "content": {
33592
35248
  "application/json": {
33593
35249
  "schema": {
33594
- "$ref": "#/components/schemas/ErrorResponse"
35250
+ "$ref": "#/components/schemas/PayloadTooLarge"
33595
35251
  }
33596
35252
  }
33597
35253
  },
33598
- "description": "Job already finished — nothing to cancel"
35254
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33599
35255
  }
33600
35256
  },
33601
- "summary": "Cancel a bulk-delivery job for agents",
35257
+ "summary": "Listing offices the agent has closed through",
33602
35258
  "tags": [
33603
35259
  "Agents"
33604
35260
  ]
33605
- },
33606
- "get": {
33607
- "operationId": "getAgentsBulkDeliveryJob",
35261
+ }
35262
+ },
35263
+ "/v1/agents/{id}/breakdowns/originators": {
35264
+ "post": {
35265
+ "operationId": "agentOriginators",
33608
35266
  "parameters": [
33609
35267
  {
33610
- "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
35268
+ "description": "Agent document ID",
33611
35269
  "in": "path",
33612
- "name": "jobId",
35270
+ "name": "id",
33613
35271
  "required": true,
33614
35272
  "schema": {
33615
- "description": "Job ID returned by `POST /v1/agents/bulk-delivery`",
35273
+ "description": "Agent document ID",
33616
35274
  "type": "string"
33617
35275
  }
33618
35276
  }
33619
35277
  ],
35278
+ "requestBody": {
35279
+ "content": {
35280
+ "application/json": {
35281
+ "schema": {
35282
+ "$ref": "#/components/schemas/ScoredBreakdownRequest"
35283
+ }
35284
+ }
35285
+ }
35286
+ },
33620
35287
  "responses": {
33621
35288
  "200": {
33622
35289
  "content": {
33623
35290
  "application/json": {
33624
35291
  "schema": {
33625
- "$ref": "#/components/schemas/BulkDeliveryStatusResponse"
35292
+ "$ref": "#/components/schemas/BreakdownResponse"
33626
35293
  }
33627
35294
  }
33628
35295
  },
33629
- "description": "Job status (resultsUrl re-signed if completed)"
35296
+ "description": "Pre-aggregated originators breakdown from the agent's scored doc"
35297
+ },
35298
+ "400": {
35299
+ "content": {
35300
+ "application/json": {
35301
+ "schema": {
35302
+ "$ref": "#/components/schemas/ErrorResponse"
35303
+ }
35304
+ }
35305
+ },
35306
+ "description": "Validation error"
33630
35307
  },
33631
35308
  "401": {
33632
35309
  "content": {
@@ -33636,7 +35313,7 @@
33636
35313
  }
33637
35314
  }
33638
35315
  },
33639
- "description": "Authentication required"
35316
+ "description": "Unauthorized"
33640
35317
  },
33641
35318
  "403": {
33642
35319
  "content": {
@@ -33646,7 +35323,7 @@
33646
35323
  }
33647
35324
  }
33648
35325
  },
33649
- "description": "Caller does not own this job"
35326
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
33650
35327
  },
33651
35328
  "404": {
33652
35329
  "content": {
@@ -33656,24 +35333,45 @@
33656
35333
  }
33657
35334
  }
33658
35335
  },
33659
- "description": "Job not found (or expired past 30d TTL)"
35336
+ "description": "Not found"
35337
+ },
35338
+ "413": {
35339
+ "content": {
35340
+ "application/json": {
35341
+ "schema": {
35342
+ "$ref": "#/components/schemas/PayloadTooLarge"
35343
+ }
35344
+ }
35345
+ },
35346
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33660
35347
  }
33661
35348
  },
33662
- "summary": "Get bulk-delivery job status for agents",
35349
+ "summary": "Loan officers the agent worked with (pre-aggregated)",
33663
35350
  "tags": [
33664
35351
  "Agents"
33665
35352
  ]
33666
35353
  }
33667
35354
  },
33668
- "/v1/agents/count": {
35355
+ "/v1/agents/{id}/breakdowns/states": {
33669
35356
  "post": {
33670
- "description": "Return the exact number of agents matching the given filters. Accepts the same request body as `listAgents` (pagination and sort are ignored). Use this when you only need a total; to get the matching agents themselves, send the same request body to `listAgents`.",
33671
- "operationId": "countAgents",
35357
+ "operationId": "agentStates",
35358
+ "parameters": [
35359
+ {
35360
+ "description": "Agent document ID",
35361
+ "in": "path",
35362
+ "name": "id",
35363
+ "required": true,
35364
+ "schema": {
35365
+ "description": "Agent document ID",
35366
+ "type": "string"
35367
+ }
35368
+ }
35369
+ ],
33672
35370
  "requestBody": {
33673
35371
  "content": {
33674
35372
  "application/json": {
33675
35373
  "schema": {
33676
- "$ref": "#/components/schemas/AgentListRequest"
35374
+ "$ref": "#/components/schemas/BreakdownRequest"
33677
35375
  }
33678
35376
  }
33679
35377
  }
@@ -33683,11 +35381,11 @@
33683
35381
  "content": {
33684
35382
  "application/json": {
33685
35383
  "schema": {
33686
- "$ref": "#/components/schemas/CountResponse"
35384
+ "$ref": "#/components/schemas/BreakdownResponse"
33687
35385
  }
33688
35386
  }
33689
35387
  },
33690
- "description": "Exact count of matching agents"
35388
+ "description": "Breakdown of the agent's sales by states"
33691
35389
  },
33692
35390
  "400": {
33693
35391
  "content": {
@@ -33738,28 +35436,17 @@
33738
35436
  }
33739
35437
  },
33740
35438
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33741
- },
33742
- "503": {
33743
- "content": {
33744
- "application/json": {
33745
- "schema": {
33746
- "$ref": "#/components/schemas/ErrorResponse"
33747
- }
33748
- }
33749
- },
33750
- "description": "Location dictionary temporarily unavailable. Only returned for a NEGATED location filter (`neq`, or `not`), which cannot be honoured without resolving the excluded value — answering anyway would return the records you asked to exclude. Positive location filters are unaffected. Retry shortly; the filter value itself was not rejected."
33751
35439
  }
33752
35440
  },
33753
- "summary": "Count agents matching filters",
35441
+ "summary": "State volume breakdown",
33754
35442
  "tags": [
33755
35443
  "Agents"
33756
- ],
33757
- "x-mm-records-via": "listAgents"
35444
+ ]
33758
35445
  }
33759
35446
  },
33760
- "/v1/agents/{id}": {
33761
- "get": {
33762
- "operationId": "getAgent",
35447
+ "/v1/agents/{id}/breakdowns/zip-codes": {
35448
+ "post": {
35449
+ "operationId": "agentZipCodes",
33763
35450
  "parameters": [
33764
35451
  {
33765
35452
  "description": "Agent document ID",
@@ -33770,34 +35457,57 @@
33770
35457
  "description": "Agent document ID",
33771
35458
  "type": "string"
33772
35459
  }
33773
- },
33774
- {
33775
- "description": "Period for volume/units (default: last12Months)",
33776
- "in": "query",
33777
- "name": "period",
33778
- "required": false,
33779
- "schema": {
33780
- "allOf": [
33781
- {
33782
- "$ref": "#/components/schemas/Period"
33783
- },
33784
- {
33785
- "description": "Period for volume/units (default: last12Months)"
33786
- }
33787
- ]
33788
- }
33789
35460
  }
33790
35461
  ],
35462
+ "requestBody": {
35463
+ "content": {
35464
+ "application/json": {
35465
+ "schema": {
35466
+ "$ref": "#/components/schemas/BreakdownRequest"
35467
+ }
35468
+ }
35469
+ }
35470
+ },
33791
35471
  "responses": {
33792
35472
  "200": {
33793
35473
  "content": {
33794
35474
  "application/json": {
33795
35475
  "schema": {
33796
- "$ref": "#/components/schemas/AgentDetailResponse"
35476
+ "$ref": "#/components/schemas/BreakdownResponse"
33797
35477
  }
33798
35478
  }
33799
35479
  },
33800
- "description": "Agent detail"
35480
+ "description": "Breakdown of the agent's sales by zip-codes"
35481
+ },
35482
+ "400": {
35483
+ "content": {
35484
+ "application/json": {
35485
+ "schema": {
35486
+ "$ref": "#/components/schemas/ErrorResponse"
35487
+ }
35488
+ }
35489
+ },
35490
+ "description": "Validation error"
35491
+ },
35492
+ "401": {
35493
+ "content": {
35494
+ "application/json": {
35495
+ "schema": {
35496
+ "$ref": "#/components/schemas/ErrorResponse"
35497
+ }
35498
+ }
35499
+ },
35500
+ "description": "Unauthorized"
35501
+ },
35502
+ "403": {
35503
+ "content": {
35504
+ "application/json": {
35505
+ "schema": {
35506
+ "$ref": "#/components/schemas/ErrorResponse"
35507
+ }
35508
+ }
35509
+ },
35510
+ "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
33801
35511
  },
33802
35512
  "404": {
33803
35513
  "content": {
@@ -33807,7 +35517,7 @@
33807
35517
  }
33808
35518
  }
33809
35519
  },
33810
- "description": "Agent not found"
35520
+ "description": "Not found"
33811
35521
  },
33812
35522
  "413": {
33813
35523
  "content": {
@@ -33817,26 +35527,27 @@
33817
35527
  }
33818
35528
  }
33819
35529
  },
33820
- "description": "Response payload too large"
35530
+ "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33821
35531
  }
33822
35532
  },
33823
- "summary": "Get agent by ID",
35533
+ "summary": "Zip-code volume breakdown",
33824
35534
  "tags": [
33825
35535
  "Agents"
33826
35536
  ]
33827
35537
  }
33828
35538
  },
33829
- "/v1/agents/{id}/breakdowns/cities": {
35539
+ "/v1/agents/{id}/markets": {
33830
35540
  "post": {
33831
- "operationId": "agentCities",
35541
+ "description": "Rank the ZIP markets one real-estate agent works, with volume, transaction counts and mean sale price per market, plus a weighted summary across the whole footprint. Optionally narrow to a ZIP list or a date window. REACH, NOT MARKET SHARE: an agent belongs to every market they touch — where their office is AND everywhere they have transacted — so one agent is counted in several markets at once. Per-market agent counts therefore over-sum: they cannot be added together, and dividing one into a total does not produce a market share. The only percentage returned, `shareOfScope`, is each market's share of this agent's volume across the scope requested and sums to 100 over that scope; there is no share-of-whole-book figure, because the transaction records carry no per-agent total to divide by. TWO COUNTS, ON PURPOSE: `transactions` counts transaction records; `units` counts the ones carrying a price, which are the records behind `volume` and `avgSalePrice`. Roughly half of an agent's records are priced, so dividing volume by `transactions` understates the average by about 2×. Figures are computed live from the agent's transaction records — an agent record carries no stored market rollup — and are ranked across their busiest ZIP markets. When a ZIP scope returns no markets, `reach.presentInScope` distinguishes 'works there, nothing closed in the window' from 'not present at all'. The summary also carries a Community Lending mix: what kind of neighborhood these markets are, weighted by the agent's activity in each. These are U.S. Census tract-level aggregates describing whole neighborhoods — never an individual, household or client attribute — and on this surface the basis is always the markets in scope, since an agent record carries no stored neighborhood rollup.",
35542
+ "operationId": "agentMarkets",
33832
35543
  "parameters": [
33833
35544
  {
33834
- "description": "Agent document ID",
35545
+ "description": "Agent ID",
33835
35546
  "in": "path",
33836
35547
  "name": "id",
33837
35548
  "required": true,
33838
35549
  "schema": {
33839
- "description": "Agent document ID",
35550
+ "description": "Agent ID",
33840
35551
  "type": "string"
33841
35552
  }
33842
35553
  }
@@ -33845,7 +35556,7 @@
33845
35556
  "content": {
33846
35557
  "application/json": {
33847
35558
  "schema": {
33848
- "$ref": "#/components/schemas/BreakdownRequest"
35559
+ "$ref": "#/components/schemas/AgentMarketsRequest"
33849
35560
  }
33850
35561
  }
33851
35562
  }
@@ -33855,11 +35566,11 @@
33855
35566
  "content": {
33856
35567
  "application/json": {
33857
35568
  "schema": {
33858
- "$ref": "#/components/schemas/BreakdownResponse"
35569
+ "$ref": "#/components/schemas/AgentMarketsResponse"
33859
35570
  }
33860
35571
  }
33861
35572
  },
33862
- "description": "Breakdown of the agent's sales by cities"
35573
+ "description": "The agent's blended market footprint"
33863
35574
  },
33864
35575
  "400": {
33865
35576
  "content": {
@@ -33912,15 +35623,15 @@
33912
35623
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
33913
35624
  }
33914
35625
  },
33915
- "summary": "City volume breakdown across the agent's sales",
35626
+ "summary": "The ZIP markets this agent works, blended",
33916
35627
  "tags": [
33917
35628
  "Agents"
33918
35629
  ]
33919
35630
  }
33920
35631
  },
33921
- "/v1/agents/{id}/breakdowns/companies": {
35632
+ "/v1/agents/{id}/properties": {
33922
35633
  "post": {
33923
- "operationId": "agentCompanies",
35634
+ "operationId": "agentProperties",
33924
35635
  "parameters": [
33925
35636
  {
33926
35637
  "description": "Agent document ID",
@@ -33937,7 +35648,7 @@
33937
35648
  "content": {
33938
35649
  "application/json": {
33939
35650
  "schema": {
33940
- "$ref": "#/components/schemas/ScoredBreakdownRequest"
35651
+ "$ref": "#/components/schemas/AgentRelationRequest"
33941
35652
  }
33942
35653
  }
33943
35654
  }
@@ -33947,11 +35658,11 @@
33947
35658
  "content": {
33948
35659
  "application/json": {
33949
35660
  "schema": {
33950
- "$ref": "#/components/schemas/BreakdownResponse"
35661
+ "$ref": "#/components/schemas/AgentPropertiesResponse"
33951
35662
  }
33952
35663
  }
33953
35664
  },
33954
- "description": "Pre-aggregated companies breakdown from the agent's scored doc"
35665
+ "description": "Paginated list of properties for this agent"
33955
35666
  },
33956
35667
  "400": {
33957
35668
  "content": {
@@ -34004,46 +35715,64 @@
34004
35715
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34005
35716
  }
34006
35717
  },
34007
- "summary": "Mortgage companies the agent dealt with (pre-aggregated)",
35718
+ "summary": "Properties this agent has transacted on",
34008
35719
  "tags": [
34009
35720
  "Agents"
34010
35721
  ]
34011
35722
  }
34012
35723
  },
34013
- "/v1/agents/{id}/breakdowns/counties": {
34014
- "post": {
34015
- "operationId": "agentCounties",
35724
+ "/v1/agents/{id}/related": {
35725
+ "get": {
35726
+ "description": "Return the ids of records of another type that are linked to this record. Links resolve by identifier, strongest identifier first, and equally-trusted identifiers are combined — a company's linked loans cover every role it holds on a loan, not just the first one found. The response reports which strength was used: `primary` is the canonical identifier for that pair, `secondary` is a real but weaker alternate used only when the record carries nothing better. This is a lightweight index: it returns ids and a count, not full records. For filtering, sorting, pagination and full detail, use the named endpoint for the pair (for example the originator loans endpoint) and fetch records by id.",
35727
+ "operationId": "agentRelated",
34016
35728
  "parameters": [
34017
35729
  {
34018
- "description": "Agent document ID",
35730
+ "description": "Agent id",
34019
35731
  "in": "path",
34020
35732
  "name": "id",
34021
35733
  "required": true,
34022
35734
  "schema": {
34023
- "description": "Agent document ID",
35735
+ "description": "Agent id",
34024
35736
  "type": "string"
34025
35737
  }
34026
- }
34027
- ],
34028
- "requestBody": {
34029
- "content": {
34030
- "application/json": {
34031
- "schema": {
34032
- "$ref": "#/components/schemas/BreakdownRequest"
34033
- }
35738
+ },
35739
+ {
35740
+ "description": "Type of record to link to",
35741
+ "in": "query",
35742
+ "name": "to",
35743
+ "required": true,
35744
+ "schema": {
35745
+ "description": "Type of record to link to",
35746
+ "enum": [
35747
+ "loans",
35748
+ "sales"
35749
+ ],
35750
+ "type": "string"
35751
+ }
35752
+ },
35753
+ {
35754
+ "description": "Maximum ids to return (default 25, max 200)",
35755
+ "in": "query",
35756
+ "name": "limit",
35757
+ "required": false,
35758
+ "schema": {
35759
+ "description": "Maximum ids to return (default 25, max 200)",
35760
+ "maximum": 200,
35761
+ "minimum": 1,
35762
+ "type": "integer"
34034
35763
  }
34035
35764
  }
34036
- },
35765
+ ],
34037
35766
  "responses": {
34038
35767
  "200": {
34039
35768
  "content": {
34040
35769
  "application/json": {
34041
35770
  "schema": {
34042
- "$ref": "#/components/schemas/BreakdownResponse"
35771
+ "$ref": "#/components/schemas/RelatedResponse"
34043
35772
  }
34044
35773
  }
34045
35774
  },
34046
- "description": "Breakdown of the agent's sales by counties"
35775
+ "description": "Linked record ids"
34047
35776
  },
34048
35777
  "400": {
34049
35778
  "content": {
@@ -34083,7 +35812,7 @@
34083
35812
  }
34084
35813
  }
34085
35814
  },
34086
- "description": "Not found"
35815
+ "description": "Record not found"
34087
35816
  },
34088
35817
  "413": {
34089
35818
  "content": {
@@ -34096,15 +35825,15 @@
34096
35825
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34097
35826
  }
34098
35827
  },
34099
- "summary": "County (FIPS) volume breakdown",
35828
+ "summary": "Records linked to this one",
34100
35829
  "tags": [
34101
- "Agents"
35830
+ "Related"
34102
35831
  ]
34103
35832
  }
34104
35833
  },
34105
- "/v1/agents/{id}/breakdowns/lenders": {
35834
+ "/v1/agents/{id}/sales": {
34106
35835
  "post": {
34107
- "operationId": "agentLenders",
35836
+ "operationId": "agentSales",
34108
35837
  "parameters": [
34109
35838
  {
34110
35839
  "description": "Agent document ID",
@@ -34121,7 +35850,7 @@
34121
35850
  "content": {
34122
35851
  "application/json": {
34123
35852
  "schema": {
34124
- "$ref": "#/components/schemas/ScoredBreakdownRequest"
35853
+ "$ref": "#/components/schemas/AgentRelationRequest"
34125
35854
  }
34126
35855
  }
34127
35856
  }
@@ -34131,11 +35860,11 @@
34131
35860
  "content": {
34132
35861
  "application/json": {
34133
35862
  "schema": {
34134
- "$ref": "#/components/schemas/BreakdownResponse"
35863
+ "$ref": "#/components/schemas/AgentSalesResponse"
34135
35864
  }
34136
35865
  }
34137
35866
  },
34138
- "description": "Pre-aggregated lenders breakdown from the agent's scored doc"
35867
+ "description": "Paginated list of sales for this agent"
34139
35868
  },
34140
35869
  "400": {
34141
35870
  "content": {
@@ -34188,46 +35917,25 @@
34188
35917
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34189
35918
  }
34190
35919
  },
34191
- "summary": "Raw-name lenders encountered (pre-aggregated)",
35920
+ "summary": "Sales transactions where this agent was listing or selling side",
34192
35921
  "tags": [
34193
35922
  "Agents"
34194
35923
  ]
34195
35924
  }
34196
35925
  },
34197
- "/v1/agents/{id}/breakdowns/offices": {
34198
- "post": {
34199
- "operationId": "agentOffices",
34200
- "parameters": [
34201
- {
34202
- "description": "Agent document ID",
34203
- "in": "path",
34204
- "name": "id",
34205
- "required": true,
34206
- "schema": {
34207
- "description": "Agent document ID",
34208
- "type": "string"
34209
- }
34210
- }
34211
- ],
34212
- "requestBody": {
34213
- "content": {
34214
- "application/json": {
34215
- "schema": {
34216
- "$ref": "#/components/schemas/BreakdownRequest"
34217
- }
34218
- }
34219
- }
34220
- },
35926
+ "/v1/alerts/conditions": {
35927
+ "get": {
35928
+ "operationId": "getAlertConditions",
34221
35929
  "responses": {
34222
35930
  "200": {
34223
35931
  "content": {
34224
35932
  "application/json": {
34225
35933
  "schema": {
34226
- "$ref": "#/components/schemas/BreakdownResponse"
35934
+ "$ref": "#/components/schemas/AlertConditionsResponse"
34227
35935
  }
34228
35936
  }
34229
35937
  },
34230
- "description": "Breakdown of the agent's sales by offices"
35938
+ "description": "Per-trigger flags + tunables (defaults if none saved)"
34231
35939
  },
34232
35940
  "400": {
34233
35941
  "content": {
@@ -34280,32 +35988,19 @@
34280
35988
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34281
35989
  }
34282
35990
  },
34283
- "summary": "Listing offices the agent has closed through",
35991
+ "summary": "Get your alert settings",
34284
35992
  "tags": [
34285
- "Agents"
35993
+ "Alerts"
34286
35994
  ]
34287
- }
34288
- },
34289
- "/v1/agents/{id}/breakdowns/originators": {
34290
- "post": {
34291
- "operationId": "agentOriginators",
34292
- "parameters": [
34293
- {
34294
- "description": "Agent document ID",
34295
- "in": "path",
34296
- "name": "id",
34297
- "required": true,
34298
- "schema": {
34299
- "description": "Agent document ID",
34300
- "type": "string"
34301
- }
34302
- }
34303
- ],
35995
+ },
35996
+ "put": {
35997
+ "description": "Saves your NMLS id, contact identity and tunables. The per-trigger `enabled` flags are deprecated: subscribe to alert recipes or manage subscriptions instead. While still accepted, `enabled[type]: true` turns the trigger on for every watched subject that supports it, and `false` turns it off everywhere.",
35998
+ "operationId": "updateAlertConditions",
34304
35999
  "requestBody": {
34305
36000
  "content": {
34306
36001
  "application/json": {
34307
36002
  "schema": {
34308
- "$ref": "#/components/schemas/ScoredBreakdownRequest"
36003
+ "$ref": "#/components/schemas/UpdateAlertConditionsRequest"
34309
36004
  }
34310
36005
  }
34311
36006
  }
@@ -34315,11 +36010,11 @@
34315
36010
  "content": {
34316
36011
  "application/json": {
34317
36012
  "schema": {
34318
- "$ref": "#/components/schemas/BreakdownResponse"
36013
+ "$ref": "#/components/schemas/AlertConditionsResponse"
34319
36014
  }
34320
36015
  }
34321
36016
  },
34322
- "description": "Pre-aggregated originators breakdown from the agent's scored doc"
36017
+ "description": "The saved settings"
34323
36018
  },
34324
36019
  "400": {
34325
36020
  "content": {
@@ -34372,46 +36067,49 @@
34372
36067
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34373
36068
  }
34374
36069
  },
34375
- "summary": "Loan officers the agent worked with (pre-aggregated)",
36070
+ "summary": "Update your alert settings",
34376
36071
  "tags": [
34377
- "Agents"
36072
+ "Alerts"
34378
36073
  ]
34379
36074
  }
34380
36075
  },
34381
- "/v1/agents/{id}/breakdowns/states": {
34382
- "post": {
34383
- "operationId": "agentStates",
36076
+ "/v1/alerts/digests": {
36077
+ "get": {
36078
+ "operationId": "listAlertDigests",
34384
36079
  "parameters": [
34385
36080
  {
34386
- "description": "Agent document ID",
34387
- "in": "path",
34388
- "name": "id",
34389
- "required": true,
36081
+ "description": "Max items to return (default 25).",
36082
+ "in": "query",
36083
+ "name": "limit",
36084
+ "required": false,
34390
36085
  "schema": {
34391
- "description": "Agent document ID",
36086
+ "description": "Max items to return (default 25).",
36087
+ "maximum": 100,
36088
+ "minimum": 1,
36089
+ "type": "integer"
36090
+ }
36091
+ },
36092
+ {
36093
+ "description": "Pagination cursor from a previous response.",
36094
+ "in": "query",
36095
+ "name": "cursor",
36096
+ "required": false,
36097
+ "schema": {
36098
+ "description": "Pagination cursor from a previous response.",
34392
36099
  "type": "string"
34393
36100
  }
34394
36101
  }
34395
36102
  ],
34396
- "requestBody": {
34397
- "content": {
34398
- "application/json": {
34399
- "schema": {
34400
- "$ref": "#/components/schemas/BreakdownRequest"
34401
- }
34402
- }
34403
- }
34404
- },
34405
36103
  "responses": {
34406
36104
  "200": {
34407
36105
  "content": {
34408
36106
  "application/json": {
34409
36107
  "schema": {
34410
- "$ref": "#/components/schemas/BreakdownResponse"
36108
+ "$ref": "#/components/schemas/AlertDigestResponse"
34411
36109
  }
34412
36110
  }
34413
36111
  },
34414
- "description": "Breakdown of the agent's sales by states"
36112
+ "description": "Paginated digest feed"
34415
36113
  },
34416
36114
  "400": {
34417
36115
  "content": {
@@ -34464,46 +36162,37 @@
34464
36162
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34465
36163
  }
34466
36164
  },
34467
- "summary": "State volume breakdown",
36165
+ "summary": "List your alert digests (newest first)",
34468
36166
  "tags": [
34469
- "Agents"
36167
+ "Alerts"
34470
36168
  ]
34471
36169
  }
34472
36170
  },
34473
- "/v1/agents/{id}/breakdowns/zip-codes": {
36171
+ "/v1/alerts/digests/{id}/read": {
34474
36172
  "post": {
34475
- "operationId": "agentZipCodes",
36173
+ "operationId": "markAlertDigestRead",
34476
36174
  "parameters": [
34477
36175
  {
34478
- "description": "Agent document ID",
36176
+ "description": "Digest id (from the feed).",
34479
36177
  "in": "path",
34480
36178
  "name": "id",
34481
36179
  "required": true,
34482
36180
  "schema": {
34483
- "description": "Agent document ID",
36181
+ "description": "Digest id (from the feed).",
34484
36182
  "type": "string"
34485
36183
  }
34486
36184
  }
34487
36185
  ],
34488
- "requestBody": {
34489
- "content": {
34490
- "application/json": {
34491
- "schema": {
34492
- "$ref": "#/components/schemas/BreakdownRequest"
34493
- }
34494
- }
34495
- }
34496
- },
34497
36186
  "responses": {
34498
36187
  "200": {
34499
36188
  "content": {
34500
36189
  "application/json": {
34501
36190
  "schema": {
34502
- "$ref": "#/components/schemas/BreakdownResponse"
36191
+ "$ref": "#/components/schemas/AlertsOkResponse"
34503
36192
  }
34504
36193
  }
34505
36194
  },
34506
- "description": "Breakdown of the agent's sales by zip-codes"
36195
+ "description": "Marked read (idempotent)"
34507
36196
  },
34508
36197
  "400": {
34509
36198
  "content": {
@@ -34556,47 +36245,49 @@
34556
36245
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34557
36246
  }
34558
36247
  },
34559
- "summary": "Zip-code volume breakdown",
36248
+ "summary": "Mark one digest read",
34560
36249
  "tags": [
34561
- "Agents"
36250
+ "Alerts"
34562
36251
  ]
34563
36252
  }
34564
36253
  },
34565
- "/v1/agents/{id}/markets": {
34566
- "post": {
34567
- "description": "Rank the ZIP markets one real-estate agent works, with volume, transaction counts and mean sale price per market, plus a weighted summary across the whole footprint. Optionally narrow to a ZIP list or a date window. REACH, NOT MARKET SHARE: an agent belongs to every market they touch — where their office is AND everywhere they have transacted — so one agent is counted in several markets at once. Per-market agent counts therefore over-sum: they cannot be added together, and dividing one into a total does not produce a market share. The only percentage returned, `shareOfScope`, is each market's share of this agent's volume across the scope requested and sums to 100 over that scope; there is no share-of-whole-book figure, because the transaction records carry no per-agent total to divide by. TWO COUNTS, ON PURPOSE: `transactions` counts transaction records; `units` counts the ones carrying a price, which are the records behind `volume` and `avgSalePrice`. Roughly half of an agent's records are priced, so dividing volume by `transactions` understates the average by about 2×. Figures are computed live from the agent's transaction records — an agent record carries no stored market rollup — and are ranked across their busiest ZIP markets. When a ZIP scope returns no markets, `reach.presentInScope` distinguishes 'works there, nothing closed in the window' from 'not present at all'. The summary also carries a Community Lending mix: what kind of neighborhood these markets are, weighted by the agent's activity in each. These are U.S. Census tract-level aggregates describing whole neighborhoods — never an individual, household or client attribute — and on this surface the basis is always the markets in scope, since an agent record carries no stored neighborhood rollup.",
34568
- "operationId": "agentMarkets",
36254
+ "/v1/alerts/log": {
36255
+ "get": {
36256
+ "operationId": "listAlertLog",
34569
36257
  "parameters": [
34570
36258
  {
34571
- "description": "Agent ID",
34572
- "in": "path",
34573
- "name": "id",
34574
- "required": true,
36259
+ "description": "Max items to return (default 25).",
36260
+ "in": "query",
36261
+ "name": "limit",
36262
+ "required": false,
34575
36263
  "schema": {
34576
- "description": "Agent ID",
36264
+ "description": "Max items to return (default 25).",
36265
+ "maximum": 100,
36266
+ "minimum": 1,
36267
+ "type": "integer"
36268
+ }
36269
+ },
36270
+ {
36271
+ "description": "Pagination cursor from a previous response.",
36272
+ "in": "query",
36273
+ "name": "cursor",
36274
+ "required": false,
36275
+ "schema": {
36276
+ "description": "Pagination cursor from a previous response.",
34577
36277
  "type": "string"
34578
36278
  }
34579
36279
  }
34580
36280
  ],
34581
- "requestBody": {
34582
- "content": {
34583
- "application/json": {
34584
- "schema": {
34585
- "$ref": "#/components/schemas/AgentMarketsRequest"
34586
- }
34587
- }
34588
- }
34589
- },
34590
36281
  "responses": {
34591
36282
  "200": {
34592
36283
  "content": {
34593
36284
  "application/json": {
34594
36285
  "schema": {
34595
- "$ref": "#/components/schemas/AgentMarketsResponse"
36286
+ "$ref": "#/components/schemas/AlertLogResponse"
34596
36287
  }
34597
36288
  }
34598
36289
  },
34599
- "description": "The agent's blended market footprint"
36290
+ "description": "Paginated alert feed"
34600
36291
  },
34601
36292
  "400": {
34602
36293
  "content": {
@@ -34649,46 +36340,25 @@
34649
36340
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34650
36341
  }
34651
36342
  },
34652
- "summary": "The ZIP markets this agent works, blended",
36343
+ "summary": "List your alerts (newest first)",
34653
36344
  "tags": [
34654
- "Agents"
36345
+ "Alerts"
34655
36346
  ]
34656
36347
  }
34657
36348
  },
34658
- "/v1/agents/{id}/properties": {
36349
+ "/v1/alerts/log/mark-all-read": {
34659
36350
  "post": {
34660
- "operationId": "agentProperties",
34661
- "parameters": [
34662
- {
34663
- "description": "Agent document ID",
34664
- "in": "path",
34665
- "name": "id",
34666
- "required": true,
34667
- "schema": {
34668
- "description": "Agent document ID",
34669
- "type": "string"
34670
- }
34671
- }
34672
- ],
34673
- "requestBody": {
34674
- "content": {
34675
- "application/json": {
34676
- "schema": {
34677
- "$ref": "#/components/schemas/AgentRelationRequest"
34678
- }
34679
- }
34680
- }
34681
- },
36351
+ "operationId": "markAllAlertsRead",
34682
36352
  "responses": {
34683
36353
  "200": {
34684
36354
  "content": {
34685
36355
  "application/json": {
34686
36356
  "schema": {
34687
- "$ref": "#/components/schemas/AgentPropertiesResponse"
36357
+ "$ref": "#/components/schemas/AlertsOkResponse"
34688
36358
  }
34689
36359
  }
34690
36360
  },
34691
- "description": "Paginated list of properties for this agent"
36361
+ "description": "Unread count reset to 0"
34692
36362
  },
34693
36363
  "400": {
34694
36364
  "content": {
@@ -34741,52 +36411,25 @@
34741
36411
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34742
36412
  }
34743
36413
  },
34744
- "summary": "Properties this agent has transacted on",
36414
+ "summary": "Clear the unread badge",
34745
36415
  "tags": [
34746
- "Agents"
36416
+ "Alerts"
34747
36417
  ]
34748
36418
  }
34749
36419
  },
34750
- "/v1/agents/{id}/related": {
34751
- "get": {
34752
- "description": "Return the ids of records of another type that are linked to this record. Links resolve by identifier, strongest identifier first, and equally-trusted identifiers are combined — a company's linked loans cover every role it holds on a loan, not just the first one found. The response reports which strength was used: `primary` is the canonical identifier for that pair, `secondary` is a real but weaker alternate used only when the record carries nothing better. This is a lightweight index: it returns ids and a count, not full records. For filtering, sorting, pagination and full detail, use the named endpoint for the pair (for example the originator loans endpoint) and fetch records by id.",
34753
- "operationId": "agentRelated",
36420
+ "/v1/alerts/log/{id}/read": {
36421
+ "post": {
36422
+ "operationId": "markAlertRead",
34754
36423
  "parameters": [
34755
36424
  {
34756
- "description": "Agent id",
36425
+ "description": "Alert id (from the feed).",
34757
36426
  "in": "path",
34758
36427
  "name": "id",
34759
36428
  "required": true,
34760
36429
  "schema": {
34761
- "description": "Agent id",
34762
- "type": "string"
34763
- }
34764
- },
34765
- {
34766
- "description": "Type of record to link to",
34767
- "in": "query",
34768
- "name": "to",
34769
- "required": true,
34770
- "schema": {
34771
- "description": "Type of record to link to",
34772
- "enum": [
34773
- "loans",
34774
- "sales"
34775
- ],
36430
+ "description": "Alert id (from the feed).",
34776
36431
  "type": "string"
34777
36432
  }
34778
- },
34779
- {
34780
- "description": "Maximum ids to return (default 25, max 200)",
34781
- "in": "query",
34782
- "name": "limit",
34783
- "required": false,
34784
- "schema": {
34785
- "description": "Maximum ids to return (default 25, max 200)",
34786
- "maximum": 200,
34787
- "minimum": 1,
34788
- "type": "integer"
34789
- }
34790
36433
  }
34791
36434
  ],
34792
36435
  "responses": {
@@ -34794,11 +36437,11 @@
34794
36437
  "content": {
34795
36438
  "application/json": {
34796
36439
  "schema": {
34797
- "$ref": "#/components/schemas/RelatedResponse"
36440
+ "$ref": "#/components/schemas/AlertsOkResponse"
34798
36441
  }
34799
36442
  }
34800
36443
  },
34801
- "description": "Linked record ids"
36444
+ "description": "Marked read (idempotent)"
34802
36445
  },
34803
36446
  "400": {
34804
36447
  "content": {
@@ -34838,7 +36481,7 @@
34838
36481
  }
34839
36482
  }
34840
36483
  },
34841
- "description": "Record not found"
36484
+ "description": "Not found"
34842
36485
  },
34843
36486
  "413": {
34844
36487
  "content": {
@@ -34851,46 +36494,36 @@
34851
36494
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34852
36495
  }
34853
36496
  },
34854
- "summary": "Records linked to this one",
36497
+ "summary": "Mark one alert read",
34855
36498
  "tags": [
34856
- "Related"
36499
+ "Alerts"
34857
36500
  ]
34858
36501
  }
34859
36502
  },
34860
- "/v1/agents/{id}/sales": {
36503
+ "/v1/alerts/preview": {
34861
36504
  "post": {
34862
- "operationId": "agentSales",
34863
- "parameters": [
34864
- {
34865
- "description": "Agent document ID",
34866
- "in": "path",
34867
- "name": "id",
34868
- "required": true,
34869
- "schema": {
34870
- "description": "Agent document ID",
34871
- "type": "string"
34872
- }
34873
- }
34874
- ],
36505
+ "description": "Runs all six detectors scoped to your own token, in-process and synchronously, as a dry-run: returns the alerts that WOULD fire (per detector, with the decision payloads) without writing the feed, publishing events, or sending email.",
36506
+ "operationId": "previewAlerts",
34875
36507
  "requestBody": {
34876
36508
  "content": {
34877
36509
  "application/json": {
34878
36510
  "schema": {
34879
- "$ref": "#/components/schemas/AgentRelationRequest"
36511
+ "$ref": "#/components/schemas/PreviewAlertsRequest"
34880
36512
  }
34881
36513
  }
34882
- }
36514
+ },
36515
+ "required": false
34883
36516
  },
34884
36517
  "responses": {
34885
36518
  "200": {
34886
36519
  "content": {
34887
36520
  "application/json": {
34888
36521
  "schema": {
34889
- "$ref": "#/components/schemas/AgentSalesResponse"
36522
+ "$ref": "#/components/schemas/RunUserDetectorResponse"
34890
36523
  }
34891
36524
  }
34892
36525
  },
34893
- "description": "Paginated list of sales for this agent"
36526
+ "description": "Per-detector dry-run summary (the decisions that would fire)"
34894
36527
  },
34895
36528
  "400": {
34896
36529
  "content": {
@@ -34943,25 +36576,26 @@
34943
36576
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
34944
36577
  }
34945
36578
  },
34946
- "summary": "Sales transactions where this agent was listing or selling side",
36579
+ "summary": "Preview the alerts that would fire for you",
34947
36580
  "tags": [
34948
- "Agents"
36581
+ "Alerts"
34949
36582
  ]
34950
36583
  }
34951
36584
  },
34952
- "/v1/alerts/conditions": {
36585
+ "/v1/alerts/recipes": {
34953
36586
  "get": {
34954
- "operationId": "getAlertConditions",
36587
+ "description": "The alert recipes — named bundles of alert triggers — with the inputs each needs and whether you are subscribed.",
36588
+ "operationId": "listAlertRecipes",
34955
36589
  "responses": {
34956
36590
  "200": {
34957
36591
  "content": {
34958
36592
  "application/json": {
34959
36593
  "schema": {
34960
- "$ref": "#/components/schemas/AlertConditionsResponse"
36594
+ "$ref": "#/components/schemas/AlertRecipesResponse"
34961
36595
  }
34962
36596
  }
34963
36597
  },
34964
- "description": "Per-trigger flags + tunables (defaults if none saved)"
36598
+ "description": "Recipes"
34965
36599
  },
34966
36600
  "400": {
34967
36601
  "content": {
@@ -35014,32 +36648,45 @@
35014
36648
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35015
36649
  }
35016
36650
  },
35017
- "summary": "Get your alert settings",
36651
+ "summary": "List alert recipes",
35018
36652
  "tags": [
35019
36653
  "Alerts"
35020
36654
  ]
35021
- },
35022
- "put": {
35023
- "operationId": "updateAlertConditions",
35024
- "requestBody": {
35025
- "content": {
35026
- "application/json": {
35027
- "schema": {
35028
- "$ref": "#/components/schemas/UpdateAlertConditionsRequest"
35029
- }
36655
+ }
36656
+ },
36657
+ "/v1/alerts/recipes/{id}": {
36658
+ "delete": {
36659
+ "description": "Turns the recipe's alert triggers off. Saved agents, areas and records stay; remove them with the watch-list endpoints.",
36660
+ "operationId": "unsubscribeAlertRecipe",
36661
+ "parameters": [
36662
+ {
36663
+ "description": "Recipe id.",
36664
+ "in": "path",
36665
+ "name": "id",
36666
+ "required": true,
36667
+ "schema": {
36668
+ "description": "Recipe id.",
36669
+ "enum": [
36670
+ "past-clients",
36671
+ "agent-partners",
36672
+ "farm-area",
36673
+ "watch-record"
36674
+ ],
36675
+ "example": "farm-area",
36676
+ "type": "string"
35030
36677
  }
35031
36678
  }
35032
- },
36679
+ ],
35033
36680
  "responses": {
35034
36681
  "200": {
35035
36682
  "content": {
35036
36683
  "application/json": {
35037
36684
  "schema": {
35038
- "$ref": "#/components/schemas/AlertConditionsResponse"
36685
+ "$ref": "#/components/schemas/AlertRecipeResponse"
35039
36686
  }
35040
36687
  }
35041
36688
  },
35042
- "description": "The saved settings"
36689
+ "description": "Unsubscribed"
35043
36690
  },
35044
36691
  "400": {
35045
36692
  "content": {
@@ -35092,49 +36739,53 @@
35092
36739
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35093
36740
  }
35094
36741
  },
35095
- "summary": "Update your alert settings",
36742
+ "summary": "Unsubscribe from an alert recipe",
35096
36743
  "tags": [
35097
36744
  "Alerts"
35098
36745
  ]
35099
- }
35100
- },
35101
- "/v1/alerts/digests": {
35102
- "get": {
35103
- "operationId": "listAlertDigests",
36746
+ },
36747
+ "post": {
36748
+ "description": "Turns on the recipe's alert triggers, saves its inputs (NMLS id, agents, areas or a record), and backfills recent matches. For watch-record, empty rules apply the default for the record type: property — foreclosure status changes, or sale propensity reaches 50; sale — listing status changes; loan — interest rate moves by 0.125 points or more.",
36749
+ "operationId": "subscribeAlertRecipe",
35104
36750
  "parameters": [
35105
36751
  {
35106
- "description": "Max items to return (default 25).",
35107
- "in": "query",
35108
- "name": "limit",
35109
- "required": false,
35110
- "schema": {
35111
- "description": "Max items to return (default 25).",
35112
- "maximum": 100,
35113
- "minimum": 1,
35114
- "type": "integer"
35115
- }
35116
- },
35117
- {
35118
- "description": "Pagination cursor from a previous response.",
35119
- "in": "query",
35120
- "name": "cursor",
35121
- "required": false,
36752
+ "description": "Recipe id.",
36753
+ "in": "path",
36754
+ "name": "id",
36755
+ "required": true,
35122
36756
  "schema": {
35123
- "description": "Pagination cursor from a previous response.",
36757
+ "description": "Recipe id.",
36758
+ "enum": [
36759
+ "past-clients",
36760
+ "agent-partners",
36761
+ "farm-area",
36762
+ "watch-record"
36763
+ ],
36764
+ "example": "farm-area",
35124
36765
  "type": "string"
35125
36766
  }
35126
36767
  }
35127
36768
  ],
36769
+ "requestBody": {
36770
+ "content": {
36771
+ "application/json": {
36772
+ "schema": {
36773
+ "$ref": "#/components/schemas/AlertRecipeInputs"
36774
+ }
36775
+ }
36776
+ },
36777
+ "required": false
36778
+ },
35128
36779
  "responses": {
35129
36780
  "200": {
35130
36781
  "content": {
35131
36782
  "application/json": {
35132
36783
  "schema": {
35133
- "$ref": "#/components/schemas/AlertDigestResponse"
36784
+ "$ref": "#/components/schemas/AlertRecipeResponse"
35134
36785
  }
35135
36786
  }
35136
36787
  },
35137
- "description": "Paginated digest feed"
36788
+ "description": "Subscribed"
35138
36789
  },
35139
36790
  "400": {
35140
36791
  "content": {
@@ -35187,37 +36838,55 @@
35187
36838
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35188
36839
  }
35189
36840
  },
35190
- "summary": "List your alert digests (newest first)",
36841
+ "summary": "Subscribe to an alert recipe",
35191
36842
  "tags": [
35192
36843
  "Alerts"
35193
36844
  ]
35194
36845
  }
35195
36846
  },
35196
- "/v1/alerts/digests/{id}/read": {
36847
+ "/v1/alerts/recipes/{id}/preview": {
35197
36848
  "post": {
35198
- "operationId": "markAlertDigestRead",
36849
+ "description": "Dry-run the recipe over the last 30 days for you, with the given inputs applied on top of your saved ones. Writes nothing.",
36850
+ "operationId": "previewAlertRecipe",
35199
36851
  "parameters": [
35200
36852
  {
35201
- "description": "Digest id (from the feed).",
36853
+ "description": "Recipe id.",
35202
36854
  "in": "path",
35203
36855
  "name": "id",
35204
36856
  "required": true,
35205
36857
  "schema": {
35206
- "description": "Digest id (from the feed).",
36858
+ "description": "Recipe id.",
36859
+ "enum": [
36860
+ "past-clients",
36861
+ "agent-partners",
36862
+ "farm-area",
36863
+ "watch-record"
36864
+ ],
36865
+ "example": "farm-area",
35207
36866
  "type": "string"
35208
36867
  }
35209
36868
  }
35210
36869
  ],
36870
+ "requestBody": {
36871
+ "content": {
36872
+ "application/json": {
36873
+ "schema": {
36874
+ "$ref": "#/components/schemas/AlertRecipeInputs"
36875
+ }
36876
+ }
36877
+ },
36878
+ "required": false
36879
+ },
35211
36880
  "responses": {
35212
36881
  "200": {
35213
36882
  "content": {
35214
36883
  "application/json": {
35215
36884
  "schema": {
35216
- "$ref": "#/components/schemas/AlertsOkResponse"
36885
+ "$ref": "#/components/schemas/AlertRecipePreviewResponse"
35217
36886
  }
35218
36887
  }
35219
36888
  },
35220
- "description": "Marked read (idempotent)"
36889
+ "description": "What the recipe would have fired"
35221
36890
  },
35222
36891
  "400": {
35223
36892
  "content": {
@@ -35270,35 +36939,55 @@
35270
36939
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35271
36940
  }
35272
36941
  },
35273
- "summary": "Mark one digest read",
36942
+ "summary": "Preview an alert recipe",
35274
36943
  "tags": [
35275
36944
  "Alerts"
35276
36945
  ]
35277
36946
  }
35278
36947
  },
35279
- "/v1/alerts/log": {
36948
+ "/v1/alerts/subscriptions": {
35280
36949
  "get": {
35281
- "operationId": "listAlertLog",
36950
+ "description": "Every subject you watch — agents, areas, your NMLS id, records — with the alert kinds that fire on it and the recipes that turned them on.",
36951
+ "operationId": "listAlertSubscriptions",
35282
36952
  "parameters": [
35283
36953
  {
35284
- "description": "Max items to return (default 25).",
36954
+ "description": "Only subscriptions of this subject type.",
35285
36955
  "in": "query",
35286
- "name": "limit",
36956
+ "name": "subjectType",
35287
36957
  "required": false,
35288
36958
  "schema": {
35289
- "description": "Max items to return (default 25).",
35290
- "maximum": 100,
35291
- "minimum": 1,
35292
- "type": "integer"
36959
+ "description": "Only subscriptions of this subject type.",
36960
+ "enum": [
36961
+ "agent",
36962
+ "area",
36963
+ "nmls",
36964
+ "property",
36965
+ "loan",
36966
+ "sale",
36967
+ "company",
36968
+ "filter"
36969
+ ],
36970
+ "type": "string"
35293
36971
  }
35294
36972
  },
35295
36973
  {
35296
- "description": "Pagination cursor from a previous response.",
36974
+ "description": "Only subscriptions carrying this alert kind.",
35297
36975
  "in": "query",
35298
- "name": "cursor",
36976
+ "name": "kind",
35299
36977
  "required": false,
35300
36978
  "schema": {
35301
- "description": "Pagination cursor from a previous response.",
36979
+ "description": "Only subscriptions carrying this alert kind.",
36980
+ "enum": [
36981
+ "area_new_listing",
36982
+ "watched_agent_pending",
36983
+ "borrower_listed",
36984
+ "epo_risk",
36985
+ "agent_sale_closed",
36986
+ "rate_term_refi_area",
36987
+ "watched_sale",
36988
+ "watched_property",
36989
+ "watched_loan"
36990
+ ],
35302
36991
  "type": "string"
35303
36992
  }
35304
36993
  }
@@ -35308,11 +36997,11 @@
35308
36997
  "content": {
35309
36998
  "application/json": {
35310
36999
  "schema": {
35311
- "$ref": "#/components/schemas/AlertLogResponse"
37000
+ "$ref": "#/components/schemas/AlertSubscriptionsResponse"
35312
37001
  }
35313
37002
  }
35314
37003
  },
35315
- "description": "Paginated alert feed"
37004
+ "description": "Subscriptions"
35316
37005
  },
35317
37006
  "400": {
35318
37007
  "content": {
@@ -35365,35 +37054,43 @@
35365
37054
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35366
37055
  }
35367
37056
  },
35368
- "summary": "List your alerts (newest first)",
37057
+ "summary": "List your alert subscriptions",
35369
37058
  "tags": [
35370
37059
  "Alerts"
35371
37060
  ]
35372
- }
35373
- },
35374
- "/v1/alerts/log/mark-all-read": {
37061
+ },
35375
37062
  "post": {
35376
- "operationId": "markAllAlertsRead",
37063
+ "description": "Watch a subject (an agent, an area, your NMLS id, or a sale / property / loan with rules) for the given alert kinds. Re-posting the same subject replaces the kinds you set directly; kinds a recipe turned on stay. Recent matches are backfilled when kinds are added.",
37064
+ "operationId": "createAlertSubscription",
37065
+ "requestBody": {
37066
+ "content": {
37067
+ "application/json": {
37068
+ "schema": {
37069
+ "$ref": "#/components/schemas/CreateAlertSubscriptionRequest"
37070
+ }
37071
+ }
37072
+ }
37073
+ },
35377
37074
  "responses": {
35378
37075
  "200": {
35379
37076
  "content": {
35380
37077
  "application/json": {
35381
37078
  "schema": {
35382
- "$ref": "#/components/schemas/AlertsOkResponse"
37079
+ "$ref": "#/components/schemas/AlertSubscriptionResponse"
35383
37080
  }
35384
37081
  }
35385
37082
  },
35386
- "description": "Unread count reset to 0"
37083
+ "description": "The subscription as saved"
35387
37084
  },
35388
37085
  "400": {
35389
37086
  "content": {
35390
37087
  "application/json": {
35391
37088
  "schema": {
35392
- "$ref": "#/components/schemas/ErrorResponse"
37089
+ "$ref": "#/components/schemas/AlertSubscriptionError"
35393
37090
  }
35394
37091
  }
35395
37092
  },
35396
- "description": "Validation error"
37093
+ "description": "Invalid subscription (see `code`)"
35397
37094
  },
35398
37095
  "401": {
35399
37096
  "content": {
@@ -35436,23 +37133,24 @@
35436
37133
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35437
37134
  }
35438
37135
  },
35439
- "summary": "Clear the unread badge",
37136
+ "summary": "Subscribe to alerts on a subject",
35440
37137
  "tags": [
35441
37138
  "Alerts"
35442
37139
  ]
35443
37140
  }
35444
37141
  },
35445
- "/v1/alerts/log/{id}/read": {
35446
- "post": {
35447
- "operationId": "markAlertRead",
37142
+ "/v1/alerts/subscriptions/{id}": {
37143
+ "delete": {
37144
+ "description": "Stops watching the subject (idempotent). For a record watch this also forgets its baseline, so watching it again starts fresh.",
37145
+ "operationId": "deleteAlertSubscription",
35448
37146
  "parameters": [
35449
37147
  {
35450
- "description": "Alert id (from the feed).",
37148
+ "description": "Opaque subscription id.",
35451
37149
  "in": "path",
35452
37150
  "name": "id",
35453
37151
  "required": true,
35454
37152
  "schema": {
35455
- "description": "Alert id (from the feed).",
37153
+ "description": "Opaque subscription id.",
35456
37154
  "type": "string"
35457
37155
  }
35458
37156
  }
@@ -35466,17 +37164,17 @@
35466
37164
  }
35467
37165
  }
35468
37166
  },
35469
- "description": "Marked read (idempotent)"
37167
+ "description": "Removed"
35470
37168
  },
35471
37169
  "400": {
35472
37170
  "content": {
35473
37171
  "application/json": {
35474
37172
  "schema": {
35475
- "$ref": "#/components/schemas/ErrorResponse"
37173
+ "$ref": "#/components/schemas/AlertSubscriptionError"
35476
37174
  }
35477
37175
  }
35478
37176
  },
35479
- "description": "Validation error"
37177
+ "description": "Invalid subscription (see `code`)"
35480
37178
  },
35481
37179
  "401": {
35482
37180
  "content": {
@@ -35519,89 +37217,7 @@
35519
37217
  "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35520
37218
  }
35521
37219
  },
35522
- "summary": "Mark one alert read",
35523
- "tags": [
35524
- "Alerts"
35525
- ]
35526
- }
35527
- },
35528
- "/v1/alerts/preview": {
35529
- "post": {
35530
- "description": "Runs all six detectors scoped to your own token, in-process and synchronously, as a dry-run: returns the alerts that WOULD fire (per detector, with the decision payloads) without writing the feed, publishing events, or sending email.",
35531
- "operationId": "previewAlerts",
35532
- "requestBody": {
35533
- "content": {
35534
- "application/json": {
35535
- "schema": {
35536
- "$ref": "#/components/schemas/PreviewAlertsRequest"
35537
- }
35538
- }
35539
- },
35540
- "required": false
35541
- },
35542
- "responses": {
35543
- "200": {
35544
- "content": {
35545
- "application/json": {
35546
- "schema": {
35547
- "$ref": "#/components/schemas/RunUserDetectorResponse"
35548
- }
35549
- }
35550
- },
35551
- "description": "Per-detector dry-run summary (the decisions that would fire)"
35552
- },
35553
- "400": {
35554
- "content": {
35555
- "application/json": {
35556
- "schema": {
35557
- "$ref": "#/components/schemas/ErrorResponse"
35558
- }
35559
- }
35560
- },
35561
- "description": "Validation error"
35562
- },
35563
- "401": {
35564
- "content": {
35565
- "application/json": {
35566
- "schema": {
35567
- "$ref": "#/components/schemas/ErrorResponse"
35568
- }
35569
- }
35570
- },
35571
- "description": "Unauthorized"
35572
- },
35573
- "403": {
35574
- "content": {
35575
- "application/json": {
35576
- "schema": {
35577
- "$ref": "#/components/schemas/ErrorResponse"
35578
- }
35579
- }
35580
- },
35581
- "description": "Forbidden (authenticated, but missing required context — e.g. no active organization)"
35582
- },
35583
- "404": {
35584
- "content": {
35585
- "application/json": {
35586
- "schema": {
35587
- "$ref": "#/components/schemas/ErrorResponse"
35588
- }
35589
- }
35590
- },
35591
- "description": "Not found"
35592
- },
35593
- "413": {
35594
- "content": {
35595
- "application/json": {
35596
- "schema": {
35597
- "$ref": "#/components/schemas/PayloadTooLarge"
35598
- }
35599
- }
35600
- },
35601
- "description": "Response payload too large — detail endpoint capped to fit Lambda response limits"
35602
- }
35603
- },
35604
- "summary": "Preview the alerts that would fire for you",
37220
+ "summary": "Remove an alert subscription",
35605
37221
  "tags": [
35606
37222
  "Alerts"
35607
37223
  ]
@@ -35832,12 +37448,12 @@
35832
37448
  "operationId": "removeWatchedAgent",
35833
37449
  "parameters": [
35834
37450
  {
35835
- "description": "Agent id to remove.",
37451
+ "description": "Agent id to remove (a hand-added agent; auto-derived agents follow your NMLS id).",
35836
37452
  "in": "path",
35837
37453
  "name": "id",
35838
37454
  "required": true,
35839
37455
  "schema": {
35840
- "description": "Agent id to remove.",
37456
+ "description": "Agent id to remove (a hand-added agent; auto-derived agents follow your NMLS id).",
35841
37457
  "type": "string"
35842
37458
  }
35843
37459
  }