CustomerRegistrationWithAccount¶
Endpoint¶
POST /customer/registration/with-account
Description¶
Combines into one request what previously took two: CustomerRegistration to create the customer, then OpenCustomerAccount to open an account. It is intended for brand affiliates who register users on their own pages and need a ready-to-use account immediately.
The endpoint accepts everything the plain registration accepts, and requires
four fields the plain registration does not: group_name, full_name, phone,
and brand. The account is opened in that group; its leverage and currency come
from the group, and if the group defines a starting deposit, that amount is
credited automatically.
The customer is created in the LEAD lifecycle stage, exactly as in
CustomerRegistration. A starting balance does not
change that: automatic promotion to FIRST_DEPOSIT is triggered by Cashier
transactions, while a group's starting deposit is credited directly.
Authorization¶
No session is required. This is a public endpoint, rate limited to 10 requests per minute per source IP address.
The brand is taken only from the body brand value, which is therefore required
in every request. The HTTP request host is not used to resolve it.
Order of Operations¶
Everything that can be checked is checked before anything is written. Once the customer record exists, no later failure can undo it.
- Schema validation.
- Rate limit by source IP.
- Request fields are read, then every field a public caller may not set is forced back to its default. See Ignored Fields.
- Brand resolved from the
brandfield. The brand must be enabled. - Group looked up by
group_nameand checked against the brand's account-opening policy. This is the last point at which the request can be refused without side effects. - Marketing attribution resolved from the request.
- Customer created.
- Attribution row written, if attribution was resolved.
- Account created and published to subscribers.
- Starting balance credited, if the group defines one.
Steps 7 to 10 are not a transaction. Customers, accounts, and balances live in independent modules with independent write queues, and there is no cross-module rollback. The order is fixed because an account needs a customer id, so the only possible partial outcome is a customer without an account, never an account without a customer. A customer created in step 7 is never rolled back; the response reports what did and did not happen. See Partial Success.
Request¶
Minimal, only the six required fields:
{
"email": "[email protected]",
"password": "s3cret-pass",
"group_name": "DEMO_EU",
"full_name": "Ivan Petrov",
"phone": "+35700000000",
"brand": "ALFA"
}
External Landing Attribution¶
{
"email": "[email protected]",
"password": "strong-password",
"group_name": "DEMO_EU",
"full_name": "John Smith",
"phone": "+35700000000",
"brand": "ALFA",
"first_name": "John",
"attribution": {
"affiliate": "AFF1024",
"campaign": "FB_CY_01",
"utm_source": "partner-landing",
"utm_medium": "affiliate",
"sub_id": "creative-7"
}
}
Required¶
| Field | Type | Limits | Description |
|---|---|---|---|
email |
string | 3-255 | Customer email. Must be unique within the installation |
password |
string | 6-128 | Customer's own password, for the client cabinet. Not the trading account password |
group_name |
string | 1-32 | Trading group the account is opened in. Must be published by the brand for self-service opening |
full_name |
string | ≤ 255 | Full display name |
phone |
string | ≤ 64 | Phone number |
brand |
string | ≤ 64 | Brand for the customer and the account. The only source of the brand; the request host is not consulted |
Optional Personal Data¶
| Field | Type | Limits | Description |
|---|---|---|---|
first_name |
string | ≤ 128 | Given name |
last_name |
string | ≤ 128 | Family name |
middle_name |
string | ≤ 128 | Middle name |
country_of_residence |
string | ≤ 64 | Resolved from the caller's IP by GeoIP when omitted |
citizenship |
string | ≤ 64 | Citizenship country |
preferred_language |
string | ≤ 16 | Interface language |
timezone |
string | ≤ 64 | Timezone label |
marketing_allowed |
int | 0-1 | Marketing consent |
Optional Brand and Attribution¶
| Field | Type | Limits | Description |
|---|---|---|---|
lead_source |
string | ≤ 128 | Acquisition source. Overwritten when attribution resolves |
introducer_id |
string | ≤ 128 | Introducer reference |
source_type |
int | 0-4 | 0 manual, 1 web, 2 import, 3 affiliate, 4 system. Forced to 3 when an affiliate is resolved |
landing_url |
string | ≤ 2048 | Landing page |
referrer_url |
string | ≤ 2048 | Referrer |
utm_source |
string | ≤ 255 | UTM parameters. Overwritten when attribution resolves |
utm_medium |
string | ≤ 255 | |
utm_campaign |
string | ≤ 255 | |
utm_content |
string | ≤ 255 | |
utm_term |
string | ≤ 255 | |
installation_id |
string | ≤ 128 | Mobile install identifier, used to match a marketing touch |
attribution |
object | — | Attribution hints for matching a marketing touch |
meta_json |
string | ≤ 4000 | Free-form JSON stored with the customer |
The attribution object and installation_id behave exactly as in
CustomerRegistration; see
Marketing Attribution API.
Ignored Fields¶
The endpoint is public, so a whole class of fields is deliberately reset after the request is read. Sending them is not an error: the values are discarded and replaced with defaults.
| Field group | Forced to |
|---|---|
kyc_status, kyc_updated_time, aml_status, aml_updated_time |
Not verified, AML pending |
risk_level, pep_status, sanctions_status |
Low risk, unknown |
email_verified, phone_verified |
0 |
customer_id, external_id |
Assigned by the server, and empty |
lifecycle_stage, status, type, enable |
Lead, active, individual, enabled |
desk, manager_id, assigned_manager_id |
Unassigned |
sales_status, risk_status, finance_status, crm_stage, segment, tags_json |
Empty |
deposit_allowed, withdrawal_allowed |
1 |
tax_id, national_id |
Empty |
enable_otp, otp_secret |
Disabled |
affiliate_id, campaign |
Empty. Only codes resolved by the marketing module are stored |
created_by, updated_by, and all lifecycle timestamps |
0 |
Resetting kyc_status is what makes the endpoint safe to expose. If a caller
could declare their own KYC as approved, they would satisfy the brand's
kyc_required_for_real gate and open a real-money account without any identity
check. Because it is always reset, a freshly registered customer never counts as
KYC-approved.
Group Requirements¶
The group is checked by the same rule that governs self-service account opening
in OpenCustomerAccount. Failing any of these refuses
the request with 403 before anything is created.
On the Group¶
| Setting | Required value | If wrong |
|---|---|---|
enable |
1 |
GROUP_DISABLED |
public_opening |
1 |
GROUP_NOT_PUBLIC |
brand |
the resolved brand | GROUP_BRAND_MISMATCH |
account_mode |
DEMO or REAL |
UNSUPPORTED_ACCOUNT_MODE |
default_leverage |
greater than 0 |
Not an error, but the account gets that leverage verbatim, and zero produces a zero-leverage account |
default_deposit |
any | Zero means no balance is credited and the account opens empty |
On the Brand¶
| Setting | Default | Effect |
|---|---|---|
can_open_demo |
1 |
Must be 1 for a DEMO group, otherwise DEMO_OPENING_DISABLED |
can_open_real |
0 |
Must be 1 for a REAL group, otherwise REAL_OPENING_DISABLED |
kyc_required_for_real |
1 |
Must be 0 for a REAL group, otherwise KYC_REQUIRED |
Real-money accounts need a deliberate brand decision
A customer created by this endpoint never has KYC. So while
kyc_required_for_real stays at its default of 1, a REAL group is refused
every time, no matter how the group itself is configured. Opening real
accounts here requires the brand to set can_open_real = 1 and
kyc_required_for_real = 0, which means real-money accounts opened with no
identity verification. That is a compliance decision rather than a settings
change. With default settings, this endpoint opens DEMO accounts.
A group's account_mode defaults to REAL. A group created without setting it
explicitly is treated as real and refused under default brand settings, which
reads as "I marked the group public and it still will not open". Check the mode
first.
Response¶
{
"registered": true,
"customer": {
"customer_id": 1042,
"email": "[email protected]",
"lifecycle_stage": 0,
"status": 0,
"enable": 1,
"brand": "ALFA",
"created_time": 1777600000,
"updated_time": 1777600000
},
"account_created": true,
"login": 500317,
"group": "DEMO_EU",
"brand": "ALFA",
"account_mode": "DEMO",
"currency": "USD",
"leverage": 100,
"temporary_password": "a3f19c02b7d4",
"must_change_password": true,
"balance_credited": true,
"balance": 10000.0
}
| Field | Type | Description |
|---|---|---|
registered |
bool | Always true once the customer exists |
customer |
object | The created customer, same shape as CustomerRegistration returns |
account_created |
bool | Whether the trading account was opened |
login |
int | Account number. Present only when the account was created |
group |
string | Group the account was opened in |
brand |
string | Brand of that group |
account_mode |
string | DEMO or REAL |
currency |
string | Account currency, taken from the group |
leverage |
int | Taken from the group's default_leverage |
temporary_password |
string | Generated password for the trading account. Returned once and never again |
must_change_password |
bool | Always true. The temporary password is meant to be replaced |
balance_credited |
bool | Whether the starting balance was credited. false when the group has no starting deposit |
balance |
number | Amount credited. Present only when it was |
The request carries two different passwords. The password in the request is
the customer's own, for the client cabinet. The temporary_password in the
response belongs to the trading account and is generated by the server. They are
unrelated, and the account password is returned exactly once; it cannot be read
back later.
Partial Success¶
Status 207 means the customer was created but something after that was not.
The customer is never rolled back, and the response always says exactly how far
the call got.
Customer created, account was not:
{
"registered": true,
"customer": { "customer_id": 1042 },
"account_created": false,
"error": "RET_ERROR",
"message": "..."
}
Customer and account created, balance was not:
{
"registered": true,
"customer": { "customer_id": 1042 },
"account_created": true,
"login": 500317,
"group": "DEMO_EU",
"balance_credited": false,
"error": "ADD_BALANCE_ERROR",
"message": "..."
}
On the first shape the customer exists and can be given an account later through
the normal account-opening flow. On the second the account exists and is usable,
and only the starting balance is missing, which a manager can credit. In both
cases customer_id is in the response, so nothing is lost.
Do not retry the same call after a 207. The email is already taken, so a retry
returns 409.
Errors¶
| HTTP | Error | Description |
|---|---|---|
400 |
INVALID_DATA |
Schema validation failed: a missing required field, a wrong type, or a value outside its limits |
400 |
INVALID_GROUP |
No group with that name exists |
403 |
GROUP_DISABLED |
The group is switched off |
403 |
GROUP_NOT_PUBLIC |
The group is not published for self-service opening |
403 |
GROUP_BRAND_MISMATCH |
The group belongs to a different brand |
403 |
DEMO_OPENING_DISABLED |
The brand does not allow self-service demo accounts |
403 |
REAL_OPENING_DISABLED |
The brand does not allow self-service real accounts |
403 |
KYC_REQUIRED |
The brand requires KYC for real accounts, which a new registration cannot have |
403 |
UNSUPPORTED_ACCOUNT_MODE |
The group's account mode is neither demo nor real |
403 |
BRAND_NOT_AVAILABLE |
The brand is disabled |
404 |
BRAND_NOT_FOUND |
The brand value does not match an existing brand |
409 |
RET_DUPLICATE_RECORD |
A customer with this email already exists |
429 |
RET_TOO_FREQUENT |
More than 10 requests in a minute from one IP address |
500 |
CORE_NOT_AVAILABLE |
The trading core is not ready to accept account creation |
All nine group and brand refusals return their own code rather than a generic
message, so an integrator can tell a misconfigured group from a brand policy
from a missing KYC. Every status up to and including 429 is returned before
anything is created: nothing was written, and the call can be corrected and
repeated.
Integration Notes¶
- Rate limit. 10 requests per minute per IP address. Exceeding it returns
429with nothing created. The limit is per source address, so several affiliates behind one gateway share the budget. - The brand comes only from the body. Unlike
CustomerRegistration, this endpoint does not derive
the brand from the request host, so
brandmust be sent explicitly and must name an enabled brand. - Country is filled from the IP. If
country_of_residenceis omitted, GeoIP resolves it from the caller's address. - Attribution overrides what you send. If the marketing module matches a
touch, by cookies, install identifier, or the
attributionobject, the resolved affiliate, campaign, source, and UTM values replace whatever the request contained, andsource_typebecomes affiliate. This is what credits the partner with the registration. - The customer is created as a lead. Promotion to a later lifecycle stage is a manager or workflow action.
- A starting balance does not change the lifecycle stage. Automatic promotion to first deposit is triggered by Cashier transactions; a group's starting deposit is credited directly and leaves the customer at lead.
- Every call is written to the audit log.