Skip to content

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_MANAGER
  • SESSION_ADMIN
  • SESSION_DEALER
  • SESSION_CRM_MANAGER
  • SESSION_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

  • confirm is mandatory. There is no default, so an incomplete request can never write anything.
  • At least one status field must be present. An empty statuses object is rejected with 400, since it would be a no-op.
  • Only the fields present in statuses are written. A request carrying just risk_status leaves sales and finance untouched.
  • A value of 0 clears 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 400 up 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 skipped and left untouched.
  • limit and offset are required by the schema but have no effect. Every customer matching the filter is processed, not just one page.
  • orderBy is 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 failures and 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

  1. Schema validation, then the statuses object 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 with 400.
  2. Per customer: brand and desk scope check. A failure yields PERMISSION_DENIED.
  3. Per customer: every requested field already equal, counted in skipped.
  4. Per customer: the statuses' brand compared with the customer brand. A mismatch yields INVALID_STATUS.
  5. With confirm: false, processing stops here.
  6. 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.