MngUpdateCustomersByFilter¶
Writes an arbitrary combination of editable fields to every customer matching
the supplied filter. Only the fields present in the fields object are written;
everything else on the record is 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.
This is the general form of a family that also includes three narrow commands: MngUpdateCustomersDeskByFilter, MngUpdateCustomersAssignedManagerByFilter, and MngUpdateCustomersStatusesByFilter. All four share the same filter, response shape, and status codes. The narrow commands remain the better choice when a UI edits exactly one thing, because their request is simpler and their intent is explicit in the audit log.
The fields those narrow commands handle, desk, assigned_manager_id, and the
three statuses, are validated here by the same shared logic they use, not by
calling them. A request carrying all five therefore performs one selection and
one database write per customer, rather than five.
SEO¶
Page summary: Bulk-edit any combination of CRM customer fields through the ScaleTrade manager TCP API, with a dry-run preview.
Keywords: ScaleTrade customer API, MngUpdateCustomersByFilter, bulk update customers, mass edit customer fields, CRM bulk edit, manager TCP API, dry run bulk update, customer field whitelist.
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.
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¶
confirmis mandatory. There is no default, so an incomplete request can never write anything.- At least one field must be present in
fields. An empty object is rejected with400. - Unsupported keys are rejected, not ignored. Any key outside the whitelist
fails the whole request with
400naming the offending field. In a bulk edit a silently dropped field is worse than a refusal. - Only the fields present are written. Everything else on the customer record stays as it was.
- A value of
0or""clears a target field. That applies todesk,assigned_manager_id, and the three statuses; clearing bypasses dictionary and brand checks. - All brand-bound targets must share one brand. The assigned manager and the
statuses are resolved up front and must belong to the same brand, otherwise no
customer could satisfy the set and the request is rejected with
400. - The set is applied whole or not at all. If any target check fails for a customer, none of the fields are written for that customer.
- Customers where every requested field already holds the requested value are
counted in
skipped. limitandoffsetare required by the schema but have no effect. Every customer matching the filter is processed.orderByis accepted for schema compatibility but has no observable effect.- Application is partial across customers. A failure on one is recorded in
failuresand the run continues. - Every call is written to the audit log with the caller id, mode, the list of field names, and all four counters.
Order Of Operations¶
- Schema validation, then every key in
fieldsis matched against the whitelist. - Brand-bound targets are resolved once: the manager must exist, be enabled,
and hold CRM access; each non-zero status must exist with the matching type;
all of them must share one brand. Any failure rejects the request with
400. - 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 targets' brand compared with the customer
brand. A mismatch yieldsINVALID_MANAGER. - Per customer:
deskchecked against that customer's brand. A failure yieldsINVALID_DESK. - With
confirm: false, processing stops here. - All requested fields written together in one update.
Why Desk Is Checked Per Customer While The Others Are Checked Once¶
A desk is keyed by brand and code together, so its brand cannot be derived from the code alone: the lookup needs a brand to start from. Managers and statuses are keyed by id, so their brand is known up front. This is why a bad manager id fails the whole request immediately, while a desk that is missing in one brand only fails the customers of that brand.
Request¶
Dry run, mixed field types in one call:
{
"command": "MngUpdateCustomersByFilter",
"extID": "1",
"data": {
"fields": {
"desk": "DESK_EU",
"assigned_manager_id": 10,
"risk_status": 2,
"segment": "vip",
"marketing_allowed": 1
},
"confirm": false,
"filter": {
"deskFilter": "*",
"limit": 100,
"offset": 0,
"where": [
["lifecycle_stage", "=", 0]
]
}
}
}
Apply, a single simple field:
{
"command": "MngUpdateCustomersByFilter",
"extID": "2",
"data": {
"fields": {
"lifecycle_stage": 1
},
"confirm": true,
"filter": {
"deskFilter": "DESK_EU*",
"limit": 100,
"offset": 0
}
}
}
Clear the desk and the CRM owner together:
{
"command": "MngUpdateCustomersByFilter",
"extID": "3",
"data": {
"fields": {
"desk": "",
"assigned_manager_id": 0
},
"confirm": true,
"filter": {
"deskFilter": "*",
"limit": 100,
"offset": 0
}
}
}
Request Data¶
Top level:
| Field | Type | Required | Description |
|---|---|---|---|
fields |
object | Yes | Fields to write. At least one, all keys must be in the whitelist |
confirm |
bool | Yes | false reports what would change without writing. true applies |
filter |
object | Yes | Customer selection, same parameter set as MngGetCustomersByFilter |
The fields object is described in Editable Fields, the
filter object in Filter.
Editable Fields¶
Thirty-three fields are accepted. Five are brand-bound targets with dictionary validation; the remaining twenty-eight are plain scalars validated by type and range only.
Brand-Bound Targets¶
| Field | Type | Rule | Clearing value |
|---|---|---|---|
desk |
string | Up to 64 characters, uppercased. Must exist, be enabled, and be active in the customer's brand | "" |
assigned_manager_id |
int | 0 or greater. Manager must exist, be enabled, hold CRM access, and not be the recovery manager |
0 |
sales_status |
int | 0 or greater. Dictionary id of type SALES |
0 |
risk_status |
int | 0 or greater. Dictionary id of type RISK |
0 |
finance_status |
int | 0 or greater. Dictionary id of type FINANCE |
0 |
Status types and the dictionary record are described in Customer Status Dictionary; read the dictionary with MngGetCustomerStatusesByFilter.
Integer Fields¶
| Field | Range | Meaning |
|---|---|---|
enable |
0..1 |
Customer enabled |
status |
0..2 |
0 active, 1 blocked, 2 archived |
type |
0..1 |
0 individual, 1 business |
lifecycle_stage |
0..6 |
0 lead through 6 dormant |
deposit_allowed |
0..1 |
Deposits allowed |
withdrawal_allowed |
0..1 |
Withdrawals allowed |
manager_id |
0 or greater |
Legacy responsible manager |
source_type |
0..4 |
0 manual, 1 web, 2 import, 3 affiliate, 4 system |
pep_status |
0..2 |
0 unknown, 1 clear, 2 positive |
sanctions_status |
0..3 |
0 unknown, 1 clear, 2 positive, 3 review |
risk_level |
0..2 |
0 low, 1 medium, 2 high |
aml_status |
0..3 |
0 pending, 1 review, 2 approved, 3 rejected |
marketing_allowed |
0..1 |
Marketing consent |
email_verified |
0..1 |
Email verified |
phone_verified |
0..1 |
Phone verified |
Text Fields¶
| Field | Max length | Meaning |
|---|---|---|
crm_stage |
255 | CRM pipeline stage |
segment |
255 | Customer segment |
lead_source |
255 | Acquisition source |
campaign |
255 | Campaign identifier |
affiliate_id |
255 | Affiliate reference |
introducer_id |
255 | Introducer or IB reference |
preferred_language |
64 | Language preference |
timezone |
64 | Timezone label |
citizenship |
64 | Citizenship country code |
country_of_residence |
64 | Residence country code |
Timestamp Fields¶
| Field | Type | Meaning |
|---|---|---|
next_contact_time |
int64, 0 or greater |
Next planned CRM contact |
last_contact_time |
int64, 0 or greater |
Last CRM contact |
first_contact_time |
int64, 0 or greater |
First CRM contact |
Fields Deliberately Excluded¶
| Field | Reason |
|---|---|
customer_id |
Record identity |
password, otp_secret, enable_otp |
Authentication material, never acceptable in bulk |
email, phone |
Unique per record; a mass write would collide. Also gated by set_customer_contacts |
first_name, last_name, middle_name, full_name, birth_date, tax_id, national_id |
Personal data, meaningless to set identically across many people |
external_id, old_id, import_batch_id |
Per-record identifiers |
created_time, updated_time, created_by, updated_by |
System-maintained |
conversion_time, archive_time, last_login_time |
Derived from events, not set by hand |
brand |
Changing the brand would orphan the desk, manager, and status references. Needs a dedicated command that migrates them |
tags_json, meta_json |
Free-form JSON; a mass write would erase per-customer data |
kyc_status |
A fourth dictionary status, excluded by product decision |
Filter¶
| 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 |
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, 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.
Applied:
{
"confirm": true,
"fields": {
"desk": "DESK_EU",
"assigned_manager_id": 10,
"risk_status": 2,
"segment": "vip",
"marketing_allowed": 1
},
"matched": 1240,
"updated": 900,
"skipped": 338,
"failed": 2,
"failures": [
{"customer_id": 1099, "error": "PERMISSION_DENIED"},
{
"customer_id": 1101,
"error": "INVALID_DESK",
"message": "Desk does not exist, is disabled, archived, or belongs to another brand"
}
]
}
A dry run returns the same keys with confirm: false and updated: 0.
| Field | Type | Description |
|---|---|---|
confirm |
bool | Echo of the request flag |
fields |
object | Echo of the applied fields, exactly as supplied |
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 |
failed |
int | Customers that did not pass the per-customer checks |
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, fields was empty or carried an unsupported key, or a brand-bound target is unusable |
409 |
Applied, no customer written |
Repeating A Completed Run Returns 409¶
Once every customer already holds the requested values, 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 |
INVALID_DATA |
400 |
fields carried no editable field. Message: fields must contain at least one editable field |
INVALID_DATA |
400 |
fields carried a key outside the whitelist. Message: fields contains an unsupported field: <name> |
INVALID_MANAGER |
400 |
The target manager does not exist, is disabled, has no CRM access, or is the recovery manager |
INVALID_STATUS |
400 |
A referenced status is missing or has the wrong type, and the message names the field; or the manager and 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_MANAGER |
The manager or statuses belong to a different brand than this customer. Message: Target manager or statuses belong to another brand |
INVALID_DESK |
The target desk does not exist for this customer's brand, is disabled, or is archived |
RET_ERROR |
Storage or internal error while writing the customer |
RET_ERR_NOTFOUND |
The customer disappeared between selection and write |
Brand Is The Usual Cause Of Mass Failures¶
Desks, managers, and statuses all belong to exactly one brand, and so does every
customer. A selection spanning several brands will therefore fail for everyone
outside the targets' brand, even though the targets themselves are perfectly
valid. Narrow the filter with ["brand", "=", "..."], or run the command once
per brand.
See also MngUpdateCustomersDeskByFilter, MngUpdateCustomersAssignedManagerByFilter, and MngUpdateCustomersStatusesByFilter for the narrow commands of the same family, MngUpdateCustomer for a single record, MngGetCustomersByFilter for the selection, filtering, and scope rules, and MngDeleteAccountsList for the same bulk result shape.