MngUpdateCustomersDeskByFilter¶
Reassigns the CRM desk for every customer matching the supplied filter. The filter accepts the same parameter set as MngGetCustomersByFilter, so a selection previewed in the customer list can be reused here unchanged.
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 desk rebalancing, team handover, and campaign reassignment. To move a single customer, use MngUpdateCustomer instead.
SEO¶
Page summary: Bulk-reassign CRM customers to a desk through the ScaleTrade manager TCP API, with a dry-run preview.
Keywords: ScaleTrade customer API, MngUpdateCustomersDeskByFilter, bulk update customers, mass desk assignment, CRM desk reassignment, manager TCP API, dry run bulk update, customer desk transfer.
Access Control¶
Allowed sessions:
SESSION_MANAGERSESSION_ADMINSESSION_DEALERSESSION_CRM_MANAGERSESSION_CRM_ADMIN
The manager 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 manager may see, and then every selected customer is checked
individually against the manager 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.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 withMngGetCustomersByFilterbut has no observable effect, because the response carries counters rather than rows.deskis normalized to uppercase, the same as in MngAddCustomer and MngUpdateCustomer.- An empty
deskis valid and clears the desk assignment for every matching customer. - The target desk is validated per customer against that customer's
brand. A desk belongs to exactly one brand, so the same target can be valid for some customers of a mixed selection and invalid for others. - Customers already on the target desk are counted in
skippedand left untouched, soupdated_timeis not disturbed and the audit trail stays clean. - Application is partial. A failure on one customer 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, so the preview reports the real outcome.
- Every call is written to the audit log with the manager id, mode, target desk, and all four counters.
Order Of Operations, Per Customer¶
- Brand and desk scope check. A failure yields
PERMISSION_DENIED. - Already on the target desk: counted in
skipped, no further work. - Target desk validated against the customer
brand. A failure yieldsINVALID_DESK. - With
confirm: false, processing stops here. - Desk written through the standard customer update path. Storage errors are recorded as failures.
Request¶
Dry run, report what would change:
{
"command": "MngUpdateCustomersDeskByFilter",
"extID": "1",
"data": {
"desk": "DESK_RETENTION",
"confirm": false,
"filter": {
"deskFilter": "*",
"limit": 100,
"offset": 0,
"where": [
["lifecycle_stage", "=", 0],
["brand", "=", "default"]
]
}
}
}
Apply, the same request with the flag flipped:
{
"command": "MngUpdateCustomersDeskByFilter",
"extID": "2",
"data": {
"desk": "DESK_RETENTION",
"confirm": true,
"filter": {
"deskFilter": "*",
"limit": 100,
"offset": 0,
"where": [
["lifecycle_stage", "=", 0],
["brand", "=", "default"]
]
}
}
}
Request Data¶
Top level:
| Field | Type | Required | Description |
|---|---|---|---|
desk |
string | Yes | Target desk code, up to 64 characters. Normalized to uppercase. An empty string clears the desk assignment |
confirm |
bool | Yes | false reports what would change without writing. true applies the change |
filter |
object | Yes | Customer selection, same parameter set as MngGetCustomersByFilter |
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 manager |
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,
"desk": "DESK_RETENTION",
"matched": 1240,
"updated": 0,
"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"
}
]
}
Applied:
{
"confirm": true,
"desk": "DESK_RETENTION",
"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"
}
]
}
| Field | Type | Description |
|---|---|---|
confirm |
bool | Echo of the request flag, so the client can tell a preview from an applied run |
desk |
string | Target desk code after normalization |
matched |
int | Customers selected by the filter, brand scope, and desk mask |
updated |
int | Customers actually moved. Always 0 when confirm is false |
skipped |
int | Customers already on the target desk. 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 move is matched - skipped - failed.
Response Statuses¶
| Status | Condition |
|---|---|
200 |
Dry run. Always, since nothing was meant to change |
200 |
Applied, at least one customer updated, no failures |
207 |
Applied, at least one customer updated, some failed |
409 |
Applied, no customer updated |
Repeating A Completed Run Returns 409¶
Once every customer sits on the target desk, 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. The message field names the offending parameter |
PERMISSION_DENIED_ACCESS |
401 |
Session type is not in the allowed list |
| — | 403 |
Manager 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 manager brand or desk scope |
INVALID_DESK |
The target desk does not exist for that 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 |
A Widespread INVALID_DESK Usually Means A Brand Mismatch¶
A desk is keyed by brand and code together, and the lookup uses the customer's
brand. If the target desk is registered under one brand while the selected
customers belong to another, or have no brand set, every one of them fails with
INVALID_DESK even though the desk exists and is active. Either narrow the
filter to the desk's brand, or register the desk in each brand that needs it.
See also MngGetCustomersByFilter for the selection, filtering, and scope rules, and MngDeleteAccountsList for the same bulk result shape.