Single sign-on
Two directions: your backend asks for a one-time login link (POST …/sso), or your platform signs a short JWT and sends the browser to /api/sso/brands/{brand_id}/login. Sessions can be listed and ended.
#A one-time login link for a user (your backend asks)
Scope sso:issue. Answers a link to the brand's trader or client dashboard that signs the user in once. The link lives login_ttl_seconds of the brand (default 60 s) and works a single time; the client's two-factor login (when switched on) still asks for the code. Open it in the user's browser (redirect) or hand the token to your page, which calls the app's /login?token= itself.
Path parameters
Your client id (1-191 letters, digits, . _ @ : + -). Stored on the client as external_user_id.
Request body (optional) · application/json
Responses
The link.
sso_disabled (brand switch), login_disabled (the user may not sign in), insufficient_scope.
not_found / user_not_found / account_not_found / symbol_not_found.
no_brand or no_app_domain (the brand has no trader / dashboard domain yet).
invalid_request with details.errors = { field: reason }.
curl -X POST "https://api.your-brand.com/api/external/v1/users/cust_10042/sso" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{
"target": "trader"
}'const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/sso", {
method: "POST",
headers: {
"X-Api-Key": process.env.OCTOLIT_PARTNER_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"target": "trader"
}),
});
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/sso",
headers={"X-Api-Key": os.environ["OCTOLIT_PARTNER_KEY"]},
json={
"target": "trader",
},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/external/v1/users/cust_10042/sso");
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([
"target" => "trader",
]),
]);
$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/sso");
req.Headers.Add("X-Api-Key", Environment.GetEnvironmentVariable("OCTOLIT_PARTNER_KEY"));
req.Content = new StringContent("""
{
"target": "trader"
}
""", 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/sso"))
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"target": "trader"
}
"""))
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());body := strings.NewReader(`{
"target": "trader"
}`)
req, err := http.NewRequest("POST", "https://api.your-brand.com/api/external/v1/users/cust_10042/sso", 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/sso")!)
req.httpMethod = "POST"
req.setValue(ProcessInfo.processInfo.environment["OCTOLIT_PARTNER_KEY"], forHTTPHeaderField: "X-Api-Key")
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = """
{
"target": "trader"
}
""".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/sso")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.method("POST", """{
"target": "trader"
}""".toRequestBody("application/json".toMediaType()))
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} {
"login_url": "https://trade.your-brand.com/login?token=otp_...",
"token": "otp_8f3c...",
"target": "trader",
"expires_at": "2026-11-11T09:31:00Z",
"client_id": 1042
}#The user's live sessions
Scope sso:issue. Newest first (at most 100).
Path parameters
Your client id (1-191 letters, digits, . _ @ : + -). Stored on the client as external_user_id.
Responses
The sessions.
curl "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: "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/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 => "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/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("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/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 = "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/sessions")
.header("X-Api-Key", System.getenv("OCTOLIT_PARTNER_KEY"))
.get()
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} {
"sessions": [
{
"id": 1042,
"started_at": "2026-11-11T08:00:00Z",
"agent_session": true,
"expires_at": "2026-11-11T09:31:00Z"
}
]
}#End every session of the user
Scope sso:issue. The user is signed out of the trader and the dashboard everywhere.
Path parameters
Your client id (1-191 letters, digits, . _ @ : + -). Stored on the client as external_user_id.
Responses
How many sessions ended.
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()}")
} {
"ended": 1
}#End one session
Path parameters
Your client id (1-191 letters, digits, . _ @ : + -). Stored on the client as external_user_id.
Responses
Ended.
not_found / user_not_found / account_not_found / symbol_not_found.
curl -X DELETE "https://api.your-brand.com/api/external/v1/users/cust_10042/sessions/9001" \
-H "X-Api-Key: $OCTOLIT_PARTNER_KEY"const res = await fetch("https://api.your-brand.com/api/external/v1/users/cust_10042/sessions/9001", {
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/9001",
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/9001");
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/9001");
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/9001"))
.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/9001", 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/9001")!)
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/9001")
.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()}")
} {
"ended": 1
}#Sign a client in with a token your system signed (browser link)
No API key: the signed token is the credential. Your system signs a short JWT for the client and sends the browser here; we verify it and redirect to the brand's trader or client dashboard with a one-time login token. On a refusal the browser is sent to the app's /login?sso_error=<code> (or JSON with ?format=json).
Token (JWT, RS256 with the public key / JWKS URL you gave us, or HS256 with the shared secret):
| claim | meaning |
|---|---|
iss | exactly the issuer saved for the brand |
aud | exactly the audience saved for the brand |
sub | your client id (external_id, 1-191 letters, digits, . _ @ : + -) |
iat | issued at (unix seconds); older than max_age_seconds (default 300) is refused |
exp | expiry (unix seconds); at most max_age_seconds after iat |
jti | unique token id - a token presented twice is refused (token_replayed) |
nbf, email, name, given_name, family_name | optional; the names / e-mail fill a client created by auto-create |
header kid | the key id when a JWKS URL is used |
Clock skew of clock_skew_seconds (default 60) is tolerated. Unknown client id: a new client is created when the brand's "create unknown clients" is on, else 404 user_not_found.
Path parameters
The brand id shown in CRM Settings > API & Webhooks > SSO.
Query parameters
traderdashboardjsonResponses
Redirect to https://<brand app>/login?token=<one-time token> (or /login?sso_error=<code>).
token_invalid (not a JWT / bad header or claims), token_signature, token_alg (the algorithm is not the one configured for the brand), token_issuer, token_audience, token_expired, token_age (issued more than the allowed age ago, or not yet valid: iat / nbf outside the window), token_jti (missing jti), token_subject (missing sub), token_key (no key found for the token's kid), token_replayed (the jti was already used).
sso_disabled, sso_not_configured, login_disabled, user_other_brand, company_inactive, brand_inactive.
brand_not_found, user_not_found.
no_app_domain.
invalid_target, invalid_subject, create_failed.
curl "https://api.your-brand.com/api/sso/brands/13/login?token=otp_8f3c..."const res = await fetch("https://api.your-brand.com/api/sso/brands/13/login?token=otp_8f3c...", {
method: "GET",
headers: {
},
});
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/sso/brands/13/login?token=otp_8f3c...",
headers={},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/sso/brands/13/login?token=otp_8f3c...");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
],
]);
$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/sso/brands/13/login?token=otp_8f3c...");
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/sso/brands/13/login?token=otp_8f3c..."))
.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/sso/brands/13/login?token=otp_8f3c...", body)
if err != nil {
log.Fatal(err)
}
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/sso/brands/13/login?token=otp_8f3c...")!)
req.httpMethod = "GET"
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/sso/brands/13/login?token=otp_8f3c...")
.get()
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} Redirect to `https://<brand app>/login?token=<one-time token>` (or `/login?sso_error=<code>`).#Sign a client in with a token your system signed (server to server)
The same check as GET; answers the login link as JSON for your backend or page script.
Path parameters
The brand id shown in CRM Settings > API & Webhooks > SSO.
Request body · application/json
Responses
The link.
No key, an unknown / expired / revoked key, or a refused token.
insufficient_scope, ip_not_allowed, api_disabled, company_inactive, brand_disabled, sso_disabled, trending_disabled (trading on behalf also: brand_inactive, client_inactive, trading_inactive, trading_denied).
not_found / user_not_found / account_not_found / symbol_not_found.
invalid_request with details.errors = { field: reason }.
curl -X POST "https://api.your-brand.com/api/sso/brands/13/login" \
-H "Content-Type: application/json" \
-d '{
"token": "otp_8f3c...",
"target": "trader"
}'const res = await fetch("https://api.your-brand.com/api/sso/brands/13/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"token": "otp_8f3c...",
"target": "trader"
}),
});
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/sso/brands/13/login",
headers={},
json={
"token": "otp_8f3c...",
"target": "trader",
},
timeout=10,
)
r.raise_for_status()
print(r.json())<?php
$ch = curl_init("https://api.your-brand.com/api/sso/brands/13/login");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: " . "application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"token" => "otp_8f3c...",
"target" => "trader",
]),
]);
$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/sso/brands/13/login");
req.Content = new StringContent("""
{
"token": "otp_8f3c...",
"target": "trader"
}
""", 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/sso/brands/13/login"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"token": "otp_8f3c...",
"target": "trader"
}
"""))
.build();
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());body := strings.NewReader(`{
"token": "otp_8f3c...",
"target": "trader"
}`)
req, err := http.NewRequest("POST", "https://api.your-brand.com/api/sso/brands/13/login", body)
if err != nil {
log.Fatal(err)
}
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/sso/brands/13/login")!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = """
{
"token": "otp_8f3c...",
"target": "trader"
}
""".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/sso/brands/13/login")
.method("POST", """{
"token": "otp_8f3c...",
"target": "trader"
}""".toRequestBody("application/json".toMediaType()))
.build()
client.newCall(req).execute().use { res ->
println("${res.code} ${res.body?.string()}")
} {
"login_url": "https://trade.your-brand.com/login?token=otp_...",
"token": "otp_8f3c...",
"target": "trader",
"expires_at": "2026-11-11T09:31:00Z",
"client_id": 1042,
"created": true
}