Provision users & accounts
Your system is the source of truth for who a client is. This guide creates the client in Octolit under your own id, opens their trading accounts, and covers the day-two operations: pausing, signing out, keeping records in sync and honouring deletion requests.
#The model
external_id (your id)brand_id), never changesYour id is stored on the client as external_user_id and comes back in every webhook as data.client.external_id. It must be 1 - 191 characters from letters, digits and . _ @ : + -. Choose an id that never changes for a person - a database key, not an e-mail.
#Before you start
- The brand switch Partner API is on; your key carries
users:write,clients:readandaccounts:write(plusaccounts:read,trading:control,sso:issuefor the later sections). - You know the
brand_idof the brand the client belongs to (shown in the Octolit CRM; every client the API returns carries it asbrand.id). - You know the
account_type_idvalues you want to open accounts with (the CRM's account types; the company default is used when you omit it).
#Onboarding flow
- your siteUser registers (or passes your KYC) on your side
- step 1
PUT …/users/cust_10042withbrand_id, names, e-mail, phone, country →201 - step 2
POST …/users/cust_10042/accountsfor the live account (and one more call for a demo) →201 - step 3fund it with money posting, or let the client deposit in the trader
- step 4sign the user in with single sign-on; the client is trading
-
Create the user
One idempotent call. If no client with this
external_idexists in your company it is created in the brand named bybrand_idand the answer is201.countryis ISO 3166-1 alpha-2;phonecarries the country code.PUT /api/external/v1/users/cust_10042curl -X PUT "https://api.your-brand.com/api/external/v1/users/cust_10042" \ -H "X-Api-Key: $OCTOLIT_PARTNER_KEY" \ -H "Content-Type: application/json" \ -d '{ "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE" }'const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042", { method: "PUT", headers: { "X-Api-Key": process.env.OCTOLIT_PARTNER_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE" }), }); if (!res.ok) throw new Error(`PUT failed: ${res.status} ${await res.text()}`); const data = await res.json(); console.log(data);import os, requests r = requests.request( "PUT", "https://api.your-brand.com/api/external/v1/users/cust_10042", headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]}, json={ "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE", }, timeout=10, ) r.raise_for_status() print(r.json())<?php $ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042"); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "PUT", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"), "Content-Type: " . "application/json", ], CURLOPT_POSTFIELDS => json_encode([ "brand_id" => 13, "email" => "anna@example.com", "first_name" => "Anna", "last_name" => "Lindqvist", "phone" => "+46 70 123 45 67", "country" => "SE", ]), ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); print_r($data);using var http = new HttpClient(); using var req = new HttpRequestMessage(HttpMethod.Put, "https://api.your-brand.com/api/external/v1/users/cust_10042"); req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY")); req.Content = new StringContent(""" { "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE" } """, Encoding.UTF8, "application/json"); using var res = await http.SendAsync(req); res.EnsureSuccessStatusCode(); Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient(); var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042")) .header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY")) .header("Content-Type", "application/json") .method("PUT", HttpRequest.BodyPublishers.ofString(""" { "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE" } """)) .build(); var res = client.send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.statusCode() + " " + res.body());body := strings.NewReader(`{ "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE" }`) req, err := http.NewRequest("PUT", "https://api.your-brand.com/api/external/v1/users/cust_10042", body) if err != nil { log.Fatal(err) } req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY")) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() out, _ := io.ReadAll(res.Body) fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042")!) req.httpMethod = "PUT" req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key") req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = """ { "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE" } """.data(using: .utf8) let (data, response) = try await URLSession.shared.data(for: req) print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient() val req = Request.Builder() .url("https://api.your-brand.com/api/external/v1/users/cust_10042") .header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY")) .method("PUT", """{ "brand_id": 13, "email": "anna@example.com", "first_name": "Anna", "last_name": "Lindqvist", "phone": "+46 70 123 45 67", "country": "SE" }""".toRequestBody("application/json".toMediaType())) .build() client.newCall(req).execute().use { res -> println("${res.code} ${res.body?.string()}") }201 Created{ "id": 10042, "external_id": "cust_10042", "first_name": "Anna", "last_name": "Lindqvist", "full_name": "Anna Lindqvist", "email": "a***@example.com", "phone": "+46 70 *** ** 67", "brand": { "id": 13, "name": "Your brand" }, "country": "SE", "active": true, "is_lead": false, "deleted": false, "login_enabled": true, "trading_enabled": true, "accounts": 1, "kyc": { "id_status": "approved", "billing_status": "pending" }, "created_at": "2026-11-11T09:30:00Z", "updated_at": "2026-11-11T09:30:00Z" }emailandphonecome back masked unless the key also carriesclients:read_email/clients:read_phone. -
Open the live account
The account type decides live or demo and which pricing applies. The brand's maximum number of live accounts per client is enforced (
409 limit_reached). The new account starts at balance 0.POST /api/external/v1/users/cust_10042/accountscurl -X POST "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts" \ -H "X-Api-Key: $OCTOLIT_PARTNER_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_type_id": 4, "currency": "EUR", "name": "Main EUR" }'const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", { method: "POST", headers: { "X-Api-Key": process.env.OCTOLIT_PARTNER_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "account_type_id": 4, "currency": "EUR", "name": "Main EUR" }), }); if (!res.ok) throw new Error(`POST failed: ${res.status} ${await res.text()}`); const data = await res.json(); console.log(data);import os, requests r = requests.request( "POST", "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]}, json={ "account_type_id": 4, "currency": "EUR", "name": "Main EUR", }, timeout=10, ) r.raise_for_status() print(r.json())<?php $ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts"); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"), "Content-Type: " . "application/json", ], CURLOPT_POSTFIELDS => json_encode([ "account_type_id" => 4, "currency" => "EUR", "name" => "Main EUR", ]), ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); print_r($data);using var http = new HttpClient(); using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts"); req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY")); req.Content = new StringContent(""" { "account_type_id": 4, "currency": "EUR", "name": "Main EUR" } """, Encoding.UTF8, "application/json"); using var res = await http.SendAsync(req); res.EnsureSuccessStatusCode(); Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient(); var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts")) .header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY")) .header("Content-Type", "application/json") .method("POST", HttpRequest.BodyPublishers.ofString(""" { "account_type_id": 4, "currency": "EUR", "name": "Main EUR" } """)) .build(); var res = client.send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.statusCode() + " " + res.body());body := strings.NewReader(`{ "account_type_id": 4, "currency": "EUR", "name": "Main EUR" }`) req, err := http.NewRequest("POST", "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", body) if err != nil { log.Fatal(err) } req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY")) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() out, _ := io.ReadAll(res.Body) fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts")!) req.httpMethod = "POST" req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key") req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = """ { "account_type_id": 4, "currency": "EUR", "name": "Main EUR" } """.data(using: .utf8) let (data, response) = try await URLSession.shared.data(for: req) print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient() val req = Request.Builder() .url("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts") .header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY")) .method("POST", """{ "account_type_id": 4, "currency": "EUR", "name": "Main EUR" }""".toRequestBody("application/json".toMediaType())) .build() client.newCall(req).execute().use { res -> println("${res.code} ${res.body?.string()}") }201 Created{ "account": { "id": 88213, "name": "Main EUR", "currency": "EUR", "demo": false, "type": { "id": 4, "name": "Standard", "provider": "internal" }, "balance": "0", "equity": "0", "margin": "0", "free_margin": "0", "margin_level": null, "open_pl": "0", "closed_pl": "0", "credit": "0", "mt_login": null, "created_at": "2026-11-11T09:31:00Z" } } -
Open a demo account (optional)
A demo account type always gives a demo account;
demo: trueon the default live type gives a demo account on live pricing. Demo accounts have their own per-client maximum.POST /api/external/v1/users/cust_10042/accountscurl -X POST "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts" \ -H "X-Api-Key: $OCTOLIT_PARTNER_KEY" \ -H "Content-Type: application/json" \ -d '{ "demo": true, "currency": "USD", "name": "Practice" }'const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", { method: "POST", headers: { "X-Api-Key": process.env.OCTOLIT_PARTNER_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "demo": true, "currency": "USD", "name": "Practice" }), }); if (!res.ok) throw new Error(`POST failed: ${res.status} ${await res.text()}`); const data = await res.json(); console.log(data);import os, requests r = requests.request( "POST", "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]}, json={ "demo": True, "currency": "USD", "name": "Practice", }, timeout=10, ) r.raise_for_status() print(r.json())<?php $ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts"); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"), "Content-Type: " . "application/json", ], CURLOPT_POSTFIELDS => json_encode([ "demo" => true, "currency" => "USD", "name" => "Practice", ]), ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); print_r($data);using var http = new HttpClient(); using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts"); req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY")); req.Content = new StringContent(""" { "demo": true, "currency": "USD", "name": "Practice" } """, Encoding.UTF8, "application/json"); using var res = await http.SendAsync(req); res.EnsureSuccessStatusCode(); Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient(); var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts")) .header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY")) .header("Content-Type", "application/json") .method("POST", HttpRequest.BodyPublishers.ofString(""" { "demo": true, "currency": "USD", "name": "Practice" } """)) .build(); var res = client.send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.statusCode() + " " + res.body());body := strings.NewReader(`{ "demo": true, "currency": "USD", "name": "Practice" }`) req, err := http.NewRequest("POST", "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", body) if err != nil { log.Fatal(err) } req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY")) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() out, _ := io.ReadAll(res.Body) fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts")!) req.httpMethod = "POST" req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key") req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = """ { "demo": true, "currency": "USD", "name": "Practice" } """.data(using: .utf8) let (data, response) = try await URLSession.shared.data(for: req) print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient() val req = Request.Builder() .url("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts") .header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY")) .method("POST", """{ "demo": true, "currency": "USD", "name": "Practice" }""".toRequestBody("application/json".toMediaType())) .build() client.newCall(req).execute().use { res -> println("${res.code} ${res.body?.string()}") } -
Fund and sign in
Book the first deposit from your cashier with money posting (or let the client use the trader's own deposit page), then hand the user over with a one-time login link. The platform sends
account.created,deposit.statusandclient.loginto your webhook along the way.
#Users: update and read
The same PUT updates an existing user (200). Send only the fields that changed - every field is optional. Values are added, never removed: a new e-mail joins the client's emails list, a new phone the phones list. There is no delete; see export & erase.
curl -X PUT "https://api.your-brand.com/api/external/v1/users/cust_10042" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+46 70 765 43 21"
}'const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042", {
method: "PUT",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"phone": "+46 70 765 43 21"
}),
});
if (!res.ok) throw new Error(`PUT failed: ${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);import os, requests
r = requests.request(
"PUT",
"https://api.your-brand.com/api/external/v1/users/cust_10042",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
json={
"phone": "+46 70 765 43 21",
},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"),
"Content-Type: " . "application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"phone" => "+46 70 765 43 21",
]),
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Put, "https://api.your-brand.com/api/external/v1/users/cust_10042");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
req.Content = new StringContent("""
{
"phone": "+46 70 765 43 21"
}
""", Encoding.UTF8, "application/json");
using var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.header("Content-Type", "application/json")
.method("PUT", HttpRequest.BodyPublishers.ofString("""
{
"phone": "+46 70 765 43 21"
}
"""))
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());body := strings.NewReader(`{
"phone": "+46 70 765 43 21"
}`)
req, err := http.NewRequest("PUT", "https://api.your-brand.com/api/external/v1/users/cust_10042", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042")!)
req.httpMethod = "PUT"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = """
{
"phone": "+46 70 765 43 21"
}
""".data(using: .utf8)
let (data, response) = try await URLSession.shared.data(for: req)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient()
val req = Request.Builder()
.url("https://api.your-brand.com/api/external/v1/users/cust_10042")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("PUT", """{
"phone": "+46 70 765 43 21"
}""".toRequestBody("application/json".toMediaType()))
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} | Field | Rule |
|---|---|
first_name, last_name, full_name | Up to 120 characters each. Send either the pair or full_name. |
email | Up to 254 characters. An address that belongs to another client of your company answers 409 email_taken. |
phone | With the country code, e.g. +357 99 123456. |
country | ISO 3166-1 alpha-2 (SE, DE, …). |
brand_id | Read only when the user is created. A later call with another brand answers 409 brand_fixed. |
Read the user back with GET …/users/{external_id}. Three fields matter for your own UI: login_enabled (the client may sign in), trading_enabled (false after a pause, see below) and kyc.id_status / kyc.billing_status.
curl "https://api.your-brand.com/api/external/v1/users/cust_10042" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY"const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042", {
method: "GET",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
},
});
if (!res.ok) throw new Error(`GET failed: ${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);import os, requests
r = requests.request(
"GET",
"https://api.your-brand.com/api/external/v1/users/cust_10042",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"),
],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Get, "https://api.your-brand.com/api/external/v1/users/cust_10042");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
using var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());var body io.Reader
req, err := http.NewRequest("GET", "https://api.your-brand.com/api/external/v1/users/cust_10042", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042")!)
req.httpMethod = "GET"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
let (data, response) = try await URLSession.shared.data(for: req)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient()
val req = Request.Builder()
.url("https://api.your-brand.com/api/external/v1/users/cust_10042")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.get()
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} #Trading accounts
List a user's accounts with their live figures, or read one combined balance across all accounts with GET …/balance. Each account carries type.provider: internal for accounts the platform executes, mt for accounts on your MetaTrader server (then mt_login is set - see the MT4 / MT5 guide).
curl "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY"const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", {
method: "GET",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
},
});
if (!res.ok) throw new Error(`GET failed: ${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);import os, requests
r = requests.request(
"GET",
"https://api.your-brand.com/api/external/v1/users/cust_10042/accounts",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"),
],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Get, "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
using var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());var body io.Reader
req, err := http.NewRequest("GET", "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042/accounts")!)
req.httpMethod = "GET"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
let (data, response) = try await URLSession.shared.data(for: req)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient()
val req = Request.Builder()
.url("https://api.your-brand.com/api/external/v1/users/cust_10042/accounts")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.get()
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} currencyon creation is a 3-letter code (USD,EUR,GBP, …) or its id; defaultUSD. It must be one the platform supports (422otherwise).name(up to 64 characters) is what the client sees in the account switcher.- Leverage, spreads and margin rules come from the account type and the symbol settings in the CRM, not from the API call. Pick the type that carries the leverage you want.
- Accounts are not deleted through the API. Stop using one by not funding it, or pause the whole user.
#Pause and resume trading
Pausing is the compliance lever: new positions, balance changes and transfers are refused for the user until you resume. Open positions stay open and are still managed - stop loss, take profit and stop-out keep working. The pause is recorded in the client's CRM history with the key that did it.
curl -X POST "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY"const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable", {
method: "POST",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
},
});
if (!res.ok) throw new Error(`POST failed: ${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);import os, requests
r = requests.request(
"POST",
"https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"),
],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
using var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());var body io.Reader
req, err := http.NewRequest("POST", "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable")!)
req.httpMethod = "POST"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
let (data, response) = try await URLSession.shared.data(for: req)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient()
val req = Request.Builder()
.url("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/disable")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("POST", null)
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} curl -X POST "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY"const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable", {
method: "POST",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
},
});
if (!res.ok) throw new Error(`POST failed: ${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);import os, requests
r = requests.request(
"POST",
"https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"),
],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
using var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());var body io.Reader
req, err := http.NewRequest("POST", "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable")!)
req.httpMethod = "POST"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
let (data, response) = try await URLSession.shared.data(for: req)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient()
val req = Request.Builder()
.url("https://api.your-brand.com/api/external/v1/users/cust_10042/trading/enable")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("POST", null)
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} Both answer 200; the user's trading_enabled flips. Calling disable twice is harmless. Typical triggers: a failed KYC re-check, a chargeback, a self-exclusion request, a sanctions hit on your side.
#Sessions
A pause does not sign the user out. When the account must go dark at once, end the sessions as well: the user is signed out of the trader and the dashboard everywhere.
curl -X DELETE "https://api.your-brand.com/api/external/v1/users/cust_10042/sessions" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY"const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/sessions", {
method: "DELETE",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
},
});
if (!res.ok) throw new Error(`DELETE failed: ${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);import os, requests
r = requests.request(
"DELETE",
"https://api.your-brand.com/api/external/v1/users/cust_10042/sessions",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042/sessions");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"),
],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Delete, "https://api.your-brand.com/api/external/v1/users/cust_10042/sessions");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
using var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/users/cust_10042/sessions"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("DELETE", HttpRequest.BodyPublishers.noBody())
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());var body io.Reader
req, err := http.NewRequest("DELETE", "https://api.your-brand.com/api/external/v1/users/cust_10042/sessions", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/users/cust_10042/sessions")!)
req.httpMethod = "DELETE"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
let (data, response) = try await URLSession.shared.data(for: req)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient()
val req = Request.Builder()
.url("https://api.your-brand.com/api/external/v1/users/cust_10042/sessions")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("DELETE", null)
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} List sessions first if you want to show "signed in on 2 devices" in your own UI. Both need the sso:issue scope.
#Keep both systems in sync
Changes made in the Octolit CRM by your staff (a new e-mail, a KYC decision, an account opened by an agent) do not go through your API calls. Two ways to pick them up:
- Webhooks -
client.created,client.updated,client.deleted,account.createdand the rest arrive within seconds. See the webhooks guide. - Polling by change time -
GET /clients?updated_since=…pages through every client changed since your last run (cursor based, up to 100 per page). Run it nightly as a safety net, andGET /clients/deletedfor clients erased in the CRM.
curl "https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY"const res = await fetch("https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100", {
method: "GET",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
},
});
if (!res.ok) throw new Error(`GET failed: ${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);import os, requests
r = requests.request(
"GET",
"https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: " . getenv("OCTOLIT_PARTNER_KEY"),
],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Get, "https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
using var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());var body io.Reader
req, err := http.NewRequest("GET", "https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("X-Api-Key", os.Getenv("OCTOLIT_PARTNER_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(out))var req = URLRequest(url: URL(string: "https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100")!)
req.httpMethod = "GET"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
let (data, response) = try await URLSession.shared.data(for: req)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self))val client = OkHttpClient()
val req = Request.Builder()
.url("https://api.your-brand.com/api/external/v1/clients?updated_since=2026-11-10T00:00:00Z&per_page=100")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.get()
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} external_user_id. A PUT under a new id with that client's e-mail does not link the two - it answers 409 email_taken. Let your system create every client, and when a hand-made client shows up in the change feed with an e-mail you know, settle it in the CRM first (your staff decide which record stays), then PUT again. #Data export and erasure
Subject-access and deletion requests are handled in the Octolit CRM by your staff; the API does not delete clients. What you need to know when your own process runs:
- Export: the CRM produces a per-client data export as a zip (client page > Data management). Your own records are yours to add.
- Erasure of personal data is refused while the client has open positions, pending money or a balance. Close, settle and withdraw first (pausing alone is not enough). The client row stays as "Erased client <id>".
- After erasure the client is listed by
GET /clients/deletedas{ id, deleted_at }andclient.deletedis sent - so store the CRMidfrom thePUTanswer next to yourexternal_id. A laterPUTwith the sameexternal_idcreates a fresh client. - Reduced-data mode is a per-brand setting: clients created through single sign-on get no name or e-mail, and sign-in rows keep only the network part of the IP. Set it before go-live if your contract requires it.
- Retention of the sign-in history and the other logs is a CRM setting; the webhook delivery log keeps 30 days.
#Idempotency and retries
PUT …/users/{external_id}is idempotent by design: the same body twice gives the same client. Retry freely on a network error.POST …/accountsis not: two calls open two accounts (until the brand's limit stops you). On a timeout, list the accounts and look for thenameyou sent before you retry.- Pause, resume and session calls are safe to repeat.
- Money posting has a required idempotency key - see that guide.
- On
429wait forRetry-After; on5xxretry with backoff (1 s, 2 s, 4 s, three times).
#Go-live checklist
external_idis a stable internal key; a test shows the same id maps to the same client after a secondPUT.brand_idis sent on creation and the right account types are used for live and demo.- Your UI reads
login_enabledandtrading_enabledand explains a pause to the client. - A compliance action on your side calls
trading/disableandDELETE …/sessions. - A nightly
updated_sincerun (or webhooks) keeps e-mails and KYC status in sync. - Your deletion process checks
GET /clients/deletedand removes your copy.
Reference: create or update a user, open an account, list accounts, pause / resume trading, end sessions, login link.