MngUpdateCustomersStatusesByFilter¶
Sets one or more CRM status fields, sales_status, risk_status, and
finance_status, for every customer matching the supplied filter. Only the
fields present in the statuses object are written; the others are left
untouched.
The confirm flag decides what happens. With confirm: false the command runs
every validation and access check but writes nothing, returning the counters
that describe the outcome. With confirm: true the same run also persists the
change. The response shape is identical in both modes, so a client can render
one screen for the preview and the result.
Use this method for pipeline moves, risk re-grading, and finance review sweeps. To change a single customer, use MngUpdateCustomer. This command is the third in a family that also covers desks (MngUpdateCustomersDeskByFilter) and CRM owners (MngUpdateCustomersAssignedManagerByFilter); all three share the same filter, response, and status-code contract.
SEO¶
Page summary: Bulk-update CRM sales, risk, and finance statuses through the ScaleTrade manager TCP API, with a dry-run preview.
Keywords: ScaleTrade customer API, MngUpdateCustomersStatusesByFilter, bulk update customer statuses, mass status change, CRM sales status, risk status, finance status, manager TCP API, dry run bulk update.
Access Control¶
Allowed sessions:
SESSION_MANAGERSESSION_ADMINSESSION_DEALERSESSION_CRM_MANAGERSESSION_CRM_ADMIN
The caller must have CRM access and set_customers. Session type is checked as
explicit set membership, not as a privilege ladder: a type outside the list is
rejected regardless of its numeric value.
Scope is enforced twice. The requested deskFilter is first narrowed to the
desks the caller may see, and then every selected customer is checked
individually against the caller's brand and desk scope. Customers outside that
scope are reported as failures and are never written. Admin scope widens the
brand filter; super admin bypasses the scope and permission checks, but never
the per-customer scope check.
Behavior¶
Status Fields And The Dictionary¶
All three fields reference the same dictionary, CustomerStatusRecord, and are
distinguished by the record type. A value is accepted only if the referenced
record carries the type matching the field:
| Field | Required record type | Value |
|---|---|---|
sales_status |
CUSTOMER_STATUS_TYPE_SALES (0) |
Dictionary id |
finance_status |
CUSTOMER_STATUS_TYPE_FINANCE (1) |
Dictionary id |
risk_status |
CUSTOMER_STATUS_TYPE_RISK (3) |
Dictionary id |
Dictionary ids are numbered across all types, so an id valid for one field is
usually invalid for another. Use
MngGetCustomerStatusesByFilter to read the
dictionary with id, type, and brand. The full type list is described in
Customer Status Dictionary.
Rules¶
confirmis mandatory. There is no default, so an incomplete request can never write anything.- At least one status field must be present. An empty
statusesobject is rejected with400, since it would be a no-op. - Only the fields present in
statusesare written. A request carrying justrisk_statusleaves sales and finance untouched. - A value of
0clears the status. It bypasses the dictionary lookup and the brand check entirely, since removing a status cannot conflict with a brand. - All non-zero statuses must belong to one brand. A customer belongs to exactly
one brand, so a mixed-brand set could never be satisfied by anyone; the
request is rejected with
400up front. - The set is applied whole or not at all. If the statuses do not match a customer's brand, none of them are written for that customer, so nobody is left with half a set.
- Customers where every requested field already holds the requested value are
counted in
skippedand left untouched. limitandoffsetare required by the schema but have no effect. Every customer matching the filter is processed, not just one page.orderByis accepted for schema compatibility but has no observable effect, because the response carries counters rather than rows.- Application is partial across customers. A failure on one does not stop the
run; it is recorded in
failuresand processing continues. - Both modes perform identical checks. The dry run differs only in that it does not write.
- Every call is written to the audit log with the caller id, mode, the applied field and value pairs, and all four counters.
Order Of Operations¶
- Schema validation, then the
statusesobject is parsed: each non-zero value must exist in the dictionary with the matching type, and all of them must share one brand. Any failure rejects the whole request with400. - Per customer: brand and desk scope check. A failure yields
PERMISSION_DENIED. - Per customer: every requested field already equal, counted in
skipped. - Per customer: the statuses' brand compared with the customer
brand. A mismatch yieldsINVALID_STATUS. - With
confirm: false, processing stops here. - All requested fields written together through the standard customer update path.
Request¶
Dry run, one field:
{
"command": "MngUpdateCustomersStatusesByFilter",
"extID": "1",
"data": {
"statuses": {
"risk_status": 2
},
"confirm": false,
"filter": {
"deskFilter": "*",
"limit": 100,
"offset": 0
}
}
}
Apply, two fields at once:
{
"command": "MngUpdateCustomersStatusesByFilter",
"extID": "2",
"data": {
"statuses": {
"sales_status": 3,
"risk_status": 2
},
"confirm": true,
"filter": {
"deskFilter": "DESK_EU*",
"limit": 100,
"offset": 0,
"where": [
["lifecycle_stage", "=", 0]
]
}
}
}
Clear the finance status, leave the others alone:
{
"command": "MngUpdateCustomersStatusesByFilter",
"extID": "3",
"data": {
"statuses": {
"finance_status": 0
},
"confirm": true,
"filter": {
"deskFilter": "*",
"limit": 100,
"offset": 0
}
}
}
Request Data¶
Top level:
| Field | Type | Required | Description |
|---|---|---|---|
statuses |
object | Yes | Status fields to write. At least one must be present |
confirm |
bool | Yes | false reports what would change without writing. true applies the change |
filter |
object | Yes | Customer selection, same parameter set as MngGetCustomersByFilter |
statuses object:
| Field | Type | Required | Description |
|---|---|---|---|
sales_status |
int | No | Dictionary id of a SALES status, minimum 0. 0 clears the field |
risk_status |
int | No | Dictionary id of a RISK status, minimum 0. 0 clears the field |
finance_status |
int | No | Dictionary id of a FINANCE status, minimum 0. 0 clears the field |
Each field is individually optional, but the object as a whole must carry at least one of them. Fields that are absent are not touched on any customer.
filter object:
| Field | Type | Required | Description |
|---|---|---|---|
deskFilter |
string | Yes | Desk wildcard mask, for example *, DESK_*, DESK_EU*,!DESK_EU_TEST. Narrowed to the desks visible to the caller |
limit |
int | Yes | From 1 to 50000. Required by the schema, does not limit the update |
offset |
int | Yes | Minimum 0. Required by the schema, does not limit the update |
where |
array | No | [[field, operator, value], ...], all conditions must match |
orWhere |
array | No | OR comparison group; at least one condition must match. Other filters stay mandatory |
whereNot |
array | No | [[field, value], ...] |
whereIn |
array | No | [[field, [values...]], ...] |
whereNotIn |
array | No | [[field, [values...]], ...] |
whereBetween |
array | No | [[field, [from, to]], ...] |
whereNotBetween |
array | No | [[field, [from, to]], ...] |
orderBy |
array | No | Example: [["created_time", "desc"]]. Accepted for compatibility, has no observable effect |
Supported filter fields are the customer identity, contact, CRM, acquisition,
compliance, and lifecycle fields, the same set as
MngGetCustomersByFilter. String comparisons
support =, !=, and like, where like is a case-insensitive substring
match. Common filter semantics, including grouped orWhere, are described in
Table filter syntax; desk masks are described in
Desks TCP API.
Response Data¶
Counters only, with no per-customer rows, so the payload stays the same size whether four customers are affected or forty thousand. The key set is identical in both modes.
Dry run:
{
"confirm": false,
"statuses": {
"sales_status": 3,
"risk_status": 2
},
"matched": 1240,
"updated": 0,
"skipped": 338,
"failed": 2,
"failures": [
{"customer_id": 1099, "error": "PERMISSION_DENIED"},
{
"customer_id": 1101,
"error": "INVALID_STATUS",
"message": "Statuses belong to another brand"
}
]
}
Applied:
{
"confirm": true,
"statuses": {
"sales_status": 3,
"risk_status": 2
},
"matched": 1240,
"updated": 900,
"skipped": 338,
"failed": 2,
"failures": [
{"customer_id": 1099, "error": "PERMISSION_DENIED"},
{
"customer_id": 1101,
"error": "INVALID_STATUS",
"message": "Statuses belong to another brand"
}
]
}
| Field | Type | Description |
|---|---|---|
confirm |
bool | Echo of the request flag, so the client can tell a preview from an applied run |
statuses |
object | Echo of the applied field and value pairs, exactly the fields that were present in the request |
matched |
int | Customers selected by the filter, brand scope, and desk mask |
updated |
int | Customers actually written. Always 0 when confirm is false |
skipped |
int | Customers where every requested field already held the requested value. Reported in both modes |
failed |
int | Customers that did not pass the per-customer checks. Reported in both modes |
failures |
array | One entry per failure with customer_id, error, and an optional message. Present only when non-empty |
Reading The Counters¶
After an applied run the identity matched = updated + skipped + failed holds.
On a dry run updated is 0 by definition, and the number of customers that
would be written is matched - skipped - failed.
Response Statuses¶
| Status | Condition |
|---|---|
200 |
Dry run. Always, since nothing was meant to change |
200 |
Applied, at least one customer written, no failures |
207 |
Applied, at least one customer written, some failed |
400 |
Schema validation failed, statuses was empty, or a target status is unusable |
409 |
Applied, no customer written |
Repeating A Completed Run Returns 409¶
Once every customer already holds the requested statuses, running the same
request again yields updated: 0 with skipped equal to matched and an empty
failure list, and therefore 409. This is an idempotent no-op, not a fault.
Clients must distinguish the two cases by the counters: skipped == matched
with no failures means the work was already done, while a non-zero failed
means nothing could be written.
Errors¶
Request-level:
| Error | Status | Description |
|---|---|---|
INVALID_DATA |
400 |
Schema validation failed, or statuses carried none of the three fields. Message: statuses must contain at least one of sales_status, risk_status, finance_status |
INVALID_STATUS |
400 |
A referenced status is missing from the dictionary or has the wrong type. The message names the offending field, for example risk_status does not exist or has a different status type |
INVALID_STATUS |
400 |
The referenced statuses belong to different brands. Message: Statuses belong to different brands |
PERMISSION_DENIED_ACCESS |
401 |
Session type is not in the allowed list |
| — | 403 |
Caller context could not be resolved: not found, disabled, missing CRM scope, or missing set_customers |
Per-customer, reported inside failures:
| Error | Description |
|---|---|
PERMISSION_DENIED |
The customer is outside the caller's brand or desk scope |
INVALID_STATUS |
The referenced statuses belong to a different brand than this customer. Message: Statuses belong to another brand |
RET_ERROR |
Storage or internal error while writing the customer |
RET_ERR_NOTFOUND |
The customer disappeared between selection and write |
INVALID_STATUS Appears At Three Points And Means Three Things¶
At request level it is either a bad reference, where the id is not in the
dictionary or carries the wrong type, or a mixed-brand set, where the referenced
statuses do not share one brand. Inside failures it means the statuses are
fine and consistent, but belong to a different brand than that particular
customer. The message text distinguishes all three.
Dictionary Ids Are Not Per-Field¶
Status ids are assigned across the whole dictionary, not separately per type. An
id that works for sales_status will usually fail for risk_status with a
wrong-type error. Read the dictionary with
MngGetCustomerStatusesByFilter and match on
type before building the request.
See also MngUpdateCustomersDeskByFilter and MngUpdateCustomersAssignedManagerByFilter for the same contract applied to desks and CRM owners, MngUpdateCustomersByFilter for the general form that writes any combination of editable fields, MngGetCustomersByFilter for the selection, filtering, and scope rules, and MngDeleteAccountsList for the same bulk result shape.