Skip to content

Customers TCP API

Customer is a CRM/KYC/AML entity above trading accounts. It does not replace account login or trading runtime identity.

A lead is not a separate server entity. It is a customer with customer_kind = 0. The same customer_id is retained while the record passes registration, KYC, first deposit, active trading, and dormancy, which are recorded as independent bits in lifecycle_flags rather than as a single stage.

customer_id is an int. A trading account is linked to a customer through AccountRecord.customer_id / Users.customer_id.

customer_id = 0 means the account is not linked to any customer. This is valid.

Access Control

All customer methods require one of:

  • SESSION_MANAGER
  • SESSION_ADMIN
  • SESSION_DEALER
  • SESSION_CRM_MANAGER
  • SESSION_CRM_ADMIN

Manager access is resolved from the cached staff record. The token is used only as a source of manager id and session type; permissions, brand, desks, and groups are not trusted from the token.

Customer visibility is scoped by manager CRM permissions and customer brand / desk:

Permission Used for
see_customers Read customer records, lists, customer accounts, and lookup by login
set_customers Create or update customer records
del_customers Delete customer records
export_customers Export customers
see_all_customers Bypass personal desk scope for customer visibility
see_customer_contacts View unmasked customer/account contact fields and check customer password
set_customer_contacts Set customer contacts or customer password

Without see_customer_contacts, customer email and phone are returned masked where the method can return contact fields. Full-contact export also requires see_customer_contacts.

The bulk list and export methods mask contacts by default even for a caller who holds the permission, and open them only when the request asks: MngGetCustomersByFilter and MngExportCustomersByFilter take contacts: "full" for that. Single-customer methods unmask automatically for anyone holding the permission.

Customer status dictionary methods use the same CRM access model: see_customers for read, set_customers for create/update, and del_customers for delete. Read methods can return global statuses with empty brand; write/delete methods require the status brand to be inside manager brand scope unless the manager has admin scope.

Customer Fields

Field Type Notes
customer_id int Primary customer identifier
external_id string Optional external CRM/PSP/reference ID
type int CUSTOMER_TYPE_INDIVIDUAL = 0, CUSTOMER_TYPE_BUSINESS = 1
status int Operational state: ACTIVE = 0, BLOCKED = 1, ARCHIVED = 2
lifecycle_flags int64 Milestone bit mask. See Lifecycle Flags
customer_kind int Lead or client: 0 lead, 1 client. See Lead and Client Kind
conversion_flags int64 Milestone mask captured when the customer became a client. Written by the server only
enable int 1 enabled, 0 disabled
enable_otp int 1 requires OTP during customer auth
deposit_allowed int Global customer deposit gate. 1 allowed, 0 blocked
withdrawal_allowed int Global customer withdrawal gate. 1 allowed, 0 blocked
first_name, last_name, middle_name, full_name string Identity fields
birth_date time_t Unix timestamp
citizenship, country_of_residence string Profile/compliance fields
address_of_residence string Residence address, free text, up to 255 characters
avatar string Optional customer avatar reference. Legacy UUID filenames may use .svg, .png, .jpg, .jpeg, .webp, or .gif; a /storage/ prefix is normalized. Legacy UUID filenames need not be registered. Newly uploaded files must use public purpose customer_avatar. Arbitrary URLs and private file references are rejected. New customers receive a random identicon derived from email and UUID when no avatar is supplied. Existing customer rows are not bulk-changed and remain empty until an avatar is set
email string Unique for non-empty values; used for customer auth
phone string Customer phone
preferred_language, timezone string Communication preferences
brand string Optional brand scope
desk string CRM desk code, for example DESK_RETENTION
manager_id int Assigned manager id
lead_source, campaign, affiliate_id, introducer_id string Acquisition and partner references
source_type int Acquisition channel: MANUAL = 0 (default), WEB = 1, AFFILIATE = 3, INTRODUCER = 5, REFERRAL = 6, LEAD_PROVIDER = 7. See Source Type
landing_url, referrer_url string Acquisition page and referrer
utm_source, utm_medium, utm_campaign, utm_content, utm_term string UTM attribution
import_batch_id, old_id string Import and legacy-system references
created_by_import int 0 created the usual way (default), 1 created by the CRM import. Accepted only on creation by MngAddClient, MngAddLead and MngAddCustomer; read-only afterwards
assigned_manager_id int CRM owner/manager id
crm_stage, segment, tags_json string CRM classification fields
email_verified, phone_verified, marketing_allowed int Boolean flags
tax_id, national_id string Compliance identifiers
pep_status int CUSTOMER_PEP_UNKNOWN = 0, CUSTOMER_PEP_CLEAR = 1, CUSTOMER_PEP_POSITIVE = 2
sanctions_status int CUSTOMER_SANCTIONS_UNKNOWN = 0, CUSTOMER_SANCTIONS_CLEAR = 1, CUSTOMER_SANCTIONS_POSITIVE = 2, CUSTOMER_SANCTIONS_REVIEW = 3
risk_level int CUSTOMER_RISK_LOW = 0, CUSTOMER_RISK_MEDIUM = 1, CUSTOMER_RISK_HIGH = 2
aml_status int CUSTOMER_AML_PENDING = 0, CUSTOMER_AML_REVIEW = 1, CUSTOMER_AML_APPROVED = 2, CUSTOMER_AML_REJECTED = 3
kyc_status int CRM status dictionary id, 0 means not assigned
sales_status int CRM status dictionary id, 0 means not assigned
risk_status int CRM status dictionary id, 0 means not assigned
finance_status int CRM status dictionary id, 0 means not assigned
kyc_updated_time, aml_updated_time time_t Status update timestamps
first_contact_time, last_contact_time time_t Manager contact timestamps. Besides manual edits, the platform records them after a manual email, SMS or connected call from CRM: last_contact_time moves forward to the event time, first_contact_time is set once when empty. This update does not publish customer.updated and does not run workflow rules. See mailer.email.sent, sms.message.sent, click2call.call.started
next_contact_time, conversion_time, last_login_time, archive_time time_t CRM/customer lifecycle timestamps
last_online_time time_t Read-only. Time when any trading account of the customer last went offline in the trading terminal; 0 means never. An account goes offline about 60 seconds after its terminal connection drops (the reconnect window). While the customer is online, the field keeps the previous offline time. The value is set only by the platform: it is ignored in Add/Update requests, does not change updated_time, does not publish customer.updated, and does not run workflow rules
created_time, updated_time time_t Record lifecycle timestamps
created_by, updated_by int Manager ids
meta_json string Optional JSON extension payload

Sensitive fields such as password hash and OTP secret are not returned in customer responses.

The server normalizes an optional /storage/ prefix and rejects arbitrary URLs and private file references. Legacy UUID references with supported extensions are accepted without upload registration; newly uploaded files must be registered public files with purpose customer_avatar. Public registration ignores a client-supplied avatar and assigns a server-generated identicon. Manager Add/Update customer, client, and lead commands accept avatar as an optional field. The customer table API appends this field after address_of_residence; existing column positions do not move.

The customer table API appends last_online_time after avatar; existing column positions do not move.

Marketing Attribution

Public registration can populate campaign, affiliate_id, source_type, landing/referrer values, and UTM fields from the marketing attribution subsystem. Attribution is resolved inside the common customer registration flow and stored as a historical snapshot.

  • Brand-page traffic is matched by a server-side fingerprint.
  • External affiliate landing pages pass public codes in the registration attribution object.
  • Mobile applications pass an installation_id previously connected through the app activation endpoint.
  • Supplied campaign and affiliate codes are stored only when they are active and valid in the customer brand.

Management of campaigns, affiliates, links, and brand applications is documented in Marketing and Affiliates TCP API. Public registration flows are documented in Marketing Attribution API.

Source Type

source_type is the acquisition channel: where the person came from. It does not describe how the record was created; a CRM import is identified by created_by_import.

Value Name Meaning
0 MANUAL Default. Added by a manager directly, or the channel is not specified
1 WEB Self-registration on the brand website or landing
3 AFFILIATE Brought by an affiliate
5 INTRODUCER Brought by an introducing broker
6 REFERRAL Invited by another client
7 LEAD_PROVIDER Purchased from a lead provider

Values 2 (former import) and 4 (former system) are retired: commands reject them with INVALID_DATA, and stored records with these values were migrated to 0. The numbers are never reused.

The server sets the channel itself only on public registration: CustomerRegistration stores 3 when an affiliate is resolved and 1 otherwise, unless the request passes source_type explicitly; CustomerRegistrationByAffiliate always stores 3. Every other path stores the value from the request or 0.

Lifecycle Flags

Milestones are stored in lifecycle_flags as an independent bit mask, not as an ordered stage. Any combination is valid; there are no forbidden pairs.

Bit Value Label
1 << 0 1 Registered
1 << 1 2 KYC pending
1 << 2 4 KYC approved
1 << 3 8 First deposit
1 << 4 16 Active trader
1 << 5 32 Dormant

The maximum mask for the six defined flags is 63.

The engine never raises or clears a flag on its own. A flag changes only through an explicit command or a workflow rule:

  • MngSetCustomerLifecycleFlags sets the mask directly and validates it against 0..63;
  • MngAddCustomer and MngUpdateCustomer accept a whole mask and check only the lower bound, so bits outside the dictionary are stored as supplied;
  • MngUpdateCustomersByFilter applies increments with lifecycle_flags_add and lifecycle_flags_remove, both bounded to 0..63;
  • the workflow action customer.set_lifecycle_flags applies the same increments.

Archiving stays separate: it is represented by status = ARCHIVED and sets archive_time, independently of the mask.

Filtering by the mask uses its own operators, described in MngGetCustomersByFilter. Sorting by lifecycle_flags is not supported, because the order of a bit mask carries no meaning.

Lifecycle Transitions

Public registration creates a customer with an empty mask and customer_kind = 0. Every later milestone is recorded explicitly, by a CRM manager or by a workflow; neither KYC processing nor a credited Cashier deposit raises a flag by itself.

Conversion from lead to client is a separate operation with its own pair of commands; see Lead and Client Kind.

Lead and Client Kind

customer_kind records whether a customer is a lead or a client, and two commands move it in either direction:

Three fields belong to this pair:

Field Type How it is written
customer_kind int 0 lead, 1 client
conversion_flags int64 Server only. Not accepted as input by any command
conversion_time int64, Unix seconds By the server on conversion, and manually through MngAddCustomer and MngUpdateCustomer

The model invariant: a lead has both conversion fields at zero, and a client has both filled.

Both commands refuse a customer that is already in the target kind, returning 400 RET_INVALID_DATA. They replace the removed MngSetCustomerKind, which took customer_kind as an argument and forced the customer to it regardless of the starting point, answering 200 even when nothing changed.

Customer Status Dictionary

CRM workflow statuses are configurable records from customer_statuses. Customer fields kyc_status, sales_status, risk_status, and finance_status store status ids. Value 0 means that a status is not assigned.

Status types:

Value Name
0 CUSTOMER_STATUS_TYPE_SALES
1 CUSTOMER_STATUS_TYPE_FINANCE
2 CUSTOMER_STATUS_TYPE_TRADE
3 CUSTOMER_STATUS_TYPE_RISK
4 CUSTOMER_STATUS_TYPE_KYC

Status record fields:

Field Type Notes
id int Status id
brand string Optional brand scope
type int Status type enum
status_name string Display name
status_description string Optional description
status_color string Optional UI color
sort int Sort order
created_time, updated_time time_t Record timestamps
created_by, updated_by int Manager ids

Customer Commands

Customer list methods require a deskFilter wildcard mask. See Desks TCP API.

How Updates Are Written

The update commands — MngUpdateCustomer, MngUpdateClient, MngUpdateLead and every ...ByFilter update — write only their own fields. The server applies them to the current customer record in one step under the customer lock. A change made at the same time to other fields — by a workflow rule, by KYC, or by another manager — is kept.

  • A single update writes the fields whose values the request changes compared with the record read when the command started.
  • A bulk update sets every field it names on each record it writes, including a field that already had the target value when the selection was read. A record whose read copy already had all the target values is counted in skipped without a write.

  • A whole lifecycle_flags mask is applied as the bits it adds and removes compared with the record read when the command started. Without concurrent changes the stored mask equals the supplied one; bits changed by someone else in the meantime stay as they are.

  • Targets are checked for the customer's brand. If the brand changes between the read and the write, the update is refused with RET_INVALID_DATA: a single update answers 400, a bulk command lists the record in failures.
  • email uniqueness is checked when the request changes email.
  • When the request changes nothing, the record is not written and no customer.update event is emitted. A bulk command counts such a record in skipped.

Unlinking From an Affiliate

MngUnlinkClientAffiliate, MngUnlinkLeadAffiliate, MngUnlinkClientsAffiliateByFilter and MngUnlinkLeadsAffiliateByFilter detach a record from its affiliate. Besides the kind permission (set_clients or set_leads) they require set_marketing.

A record counts as linked when any of these holds:

  • affiliate_id is not empty;
  • source_type is 3 (AFFILIATE);
  • the marketing attribution or a conversion event of the record still names an affiliate.

Unlinking a linked record:

  • clears affiliate_id, campaign, lead_source, utm_source, utm_medium, utm_campaign, utm_content, utm_term, landing_url and referrer_url;
  • changes source_type from 3 (AFFILIATE) to 0 (MANUAL); any other source_type is kept;
  • sets the campaign, affiliate and marketing link to none in the record's marketing attribution and in all its conversion events (registration, KYC approval, first deposit, active trader, deposits). Later deposits and lifecycle stages are recorded without an affiliate as well.

It does not change:

  • introducer_id, contacts, desk, manager, statuses and lifecycle flags;
  • deposits, withdrawals and other cashier history, or trading accounts;
  • tracking-link visits;
  • brand totals in MngGetMarketingFunnel: the record moves from the affiliate's row to the row without an affiliate.

After unlinking, the previous affiliate no longer receives the record, its transactions or its first deposit from the Affiliate API (customers, transactions, first-deposits). The record is also gone from the affiliate cabinet dashboard and conversions, from MngGetAffiliatesByFilter stats, and from the funnel and link stats filtered by that affiliate, its campaign or its links. The record is not assigned to another affiliate. Registration, deposits and restarts do not link it back automatically. The update commands do not accept affiliate_id, so the affiliate cannot be changed through them either.

The record is written through the same per-field path as the update commands and emits customer.update. Each unlinked record is logged with the manager, the time, the customer_id and the previous affiliate id and code. The command call itself goes to the journal.

Customer Status Commands

Clients and Leads Commands

Customer commands are split by customer kind. Clients and leads have their own command for every operation, and the kind is expressed by the command name instead of a request field. The pair that moves a record between the two halves is MngConvertLeadToClient and MngConvertClientToLead.

All of these commands share the session list from Access Control and the access_crm scope. What differs is the permission.

Operation Clients Leads
Read see_clients see_leads
Create and update set_clients set_leads
Delete del_clients del_leads
Export export_clients export_leads
Read contacts see_clients_contacts see_leads_contacts
Write contacts set_clients_contacts set_leads_contacts
Bypass desk scope see_all_clients see_all_leads
Terminal login link login_as_clients login_as_leads

Each of the kind-split commands requires exactly one permission of its own, with no leftovers from the customer-wide set.

The four conversion commands stand apart: MngConvertLeadToClient, MngConvertClientToLead, MngConvertLeadsToClientsByFilter, and MngConvertClientsToLeadsByFilter require both set_leads and set_clients, because the record leaves one half and appears in the other. That is not an exception to the rule but the rule itself applied to a cross-kind operation.

Rules Common to the Kind-Split Commands

  • customer_kind is not accepted on input by any of them: not on create, not on update, and not inside the fields object of a bulk update. The command name carries the kind.
  • Addressing the wrong kind is refused with 400 RET_INVALID_DATA, for example MngGetLead called with a client identifier. List commands have no such refusal: they return the records of their own kind and nothing else.
  • Lookup by login separates two cases. 404 RET_NOT_FOUND means no customer with that login exists at all; 400 RET_INVALID_DATA means one exists but is of the other kind, and the paired command has to be called.
  • For list commands count matches what is returned. The kind is selected before counting and before the page is cut, so a page never arrives short.

Extended Desk Visibility

see_all_clients opens every client of the brand, see_all_leads every lead. Neither substitutes for the other, and neither substitutes for the read permission itself: see_all_leads without see_leads grants nothing.

The brand always bounds the result. see_all_* widens the desk scope only and never reaches outside the brand.

Bulk Commands Process the Whole Selection

Every ...ByFilter write command processes every record matching the filter. limit and offset are present in the filter for uniformity with the read commands and are validated, but they do not limit the amount of work.

The guard against an accidental run is confirm. With confirm: false the command performs every check and reports what would happen, changing nothing.

Client and Lead Commands

Every operation exists once per kind. The rules these commands share are in Clients and Leads Commands.

Clients

Leads

Conversion

All four require both set_leads and set_clients.

Deprecated Commands

Eighteen customer commands are deprecated. Every one of them still works, with no change to its behavior, request, response, or required permissions. They are deprecated, not removed.

The reason is that customer commands have been split by customer kind: clients and leads now have their own commands, and the kind is expressed by the command name instead of a request field. Each old command is therefore replaced by a pair, and the permission splits along with it.

Removal is a separate task, to be carried out once frontends have moved to the new commands. No date is fixed in advance; the signal is that the old commands stop being called.

Reading

Deprecated Permission Replaced by Replacement permission
MngGetCustomer see_customers MngGetClient see_clients
MngGetLead see_leads
MngGetCustomerByLogin see_customers MngGetClientByLogin see_clients
MngGetLeadByLogin see_leads
MngGetCustomerAccounts see_customers MngGetClientAccounts see_clients
MngGetLeadAccounts see_leads
MngGetCustomersByFilter see_customers MngGetClientsByFilter see_clients
MngGetLeadsByFilter see_leads
MngGetCustomersListByDesk see_customers MngGetClientsListByDesk see_clients
MngGetLeadsListByDesk see_leads

Creating and Updating

Deprecated Permission Replaced by Replacement permission
MngAddCustomer set_customers MngAddClient set_clients
MngAddLead set_leads
MngUpdateCustomer set_customers MngUpdateClient set_clients
MngUpdateLead set_leads
MngSetCustomerLifecycleFlags set_customers MngSetClientLifecycleFlags set_clients
MngSetLeadLifecycleFlags set_leads
MngUpdateCustomersByFilter set_customers MngUpdateClientsByFilter set_clients
MngUpdateLeadsByFilter set_leads
MngUpdateCustomersDeskByFilter set_customers MngUpdateClientsDeskByFilter set_clients
MngUpdateLeadsDeskByFilter set_leads
MngUpdateCustomersAssignedManagerByFilter set_customers MngUpdateClientsAssignedManagerByFilter set_clients
MngUpdateLeadsAssignedManagerByFilter set_leads
MngUpdateCustomersStatusesByFilter set_customers MngUpdateClientsStatusesByFilter set_clients
MngUpdateLeadsStatusesByFilter set_leads

Deleting and Exporting

Deprecated Permission Replaced by Replacement permission
MngDeleteCustomer del_customers MngDeleteClient del_clients
MngDeleteLead del_leads
MngDeleteCustomersByFilter del_customers plus set_customers MngDeleteClientsByFilter del_clients
MngDeleteLeadsByFilter del_leads
MngExportCustomersByFilter export_customers MngExportClientsByFilter export_clients
MngExportLeadsByFilter export_leads

Contacts and Passwords

Deprecated Permission Replaced by Replacement permission
MngGetCustomerContacts see_customer_contacts MngGetClientContacts see_clients_contacts
MngGetLeadContacts see_leads_contacts
MngCheckCustomerPassword see_customer_contacts MngCheckClientPassword see_clients_contacts
MngCheckLeadPassword see_leads_contacts
MngSetCustomerPassword set_customer_contacts MngSetClientPassword set_clients_contacts
MngSetLeadPassword set_leads_contacts

How the Replacements Differ

These points apply to the new commands, not to the deprecated ones:

  • customer_kind is not accepted, neither on create, nor on update, nor in the fields object of a bulk update. The command name carries the kind.
  • Addressing the wrong kind is refused with RET_INVALID_DATA and status 400, for example MngGetLead called with a client identifier. List commands have no such refusal: they simply return the records of their own kind.
  • Lookup by login distinguishes two cases. 404 means no customer with that login exists at all; 400 means one exists but is of the other kind, and the paired command has to be called.
  • For list commands count matches what is returned. The kind is selected before counting and before the page is cut, so a page never arrives short.

What Is Not Deprecated

MngConvertLeadToClient and MngConvertClientToLead stay as they are, and two bulk commands join them: MngConvertLeadsToClientsByFilter and MngConvertClientsToLeadsByFilter.

All four require both set_leads and set_clients, because the record leaves one half and appears in the other.

Errors

Common errors:

  • INVALID_DATA
  • RET_NOT_FOUND
  • RET_DUPLICATE_RECORD
  • RET_INVALID_PASSWORD