Skip to content

Authentication

The SDK supports two ways to authenticate, and you pick one when you register it.

When to use it
API key (recommended) Programmatic access: bots, services, anything unattended
Email and password Apps acting on behalf of a person signing in, including 2FA flows

API keys are the better fit for automated callers: there is no login call, no token to expire, and no refresh cycle, so a restart or an API deployment cannot leave a client without a session.

Create a key under Account → API Keys, which gives you a key ID and an Ed25519 private key. Register the SDK with both:

using STX.Sdk;
using STX.Sdk.Auth;
using STX.Sdk.Settings;
services.ConfigureSTXServices(
STXEnvironment.OntarioProduction,
STXApiKeyCredentials.FromPemFile(keyId, "~/.stx/ontario.pem"));

FromPemFile reads the key from disk so it never has to pass through an environment variable, shell history, or a process listing. If you already hold the PEM in memory, the constructor takes it directly:

new STXApiKeyCredentials(keyId, pemContents);

There is nothing else to call. Every request is signed automatically with three headers:

Header
X-STX-ACCESS-KEY Your key ID
X-STX-ACCESS-TIMESTAMP Unix time in milliseconds
X-STX-ACCESS-SIGNATURE Base64 Ed25519 signature over the timestamp, method, and path

There is no login response under API-key auth, so the user id that WebSocket channels need has to be fetched once:

var identity = await serviceProvider.GetRequiredService<STXIdentityService>().GetMeAsync();
Console.WriteLine($"{identity.UserId} / account {identity.AccountId} / scope {identity.Scope}");

Do this at startup, before subscribing to any user-scoped channel.

A key is issued as either read_only or read_write. A read_only key can query markets, orders, trades, and settlements, but placing or cancelling an order needs read_write. STXIdentity.Scope reports which one you hold.

STXApiKeyCredentials also accepts a signing delegate, for callers who keep private keys in an HSM or a KMS rather than on disk:

new STXApiKeyCredentials(keyId, message => myHsm.SignEd25519(message));

The path still exists and keeps working: STXLoginService.LoginAsync acquires a JWT, STXSessionBackgroundService refreshes it, and everything else in the SDK behaves the same once authenticated. Nothing about it changed in 1.6.0.

It is the weaker option for a programmatic caller. A token expires, so a restart or an API deployment can leave a client without a session, and the credentials have to sit in memory for the background service to re-login. An API key has none of those properties.

  1. Create a key under Account → API Keys and choose a scope. read_only covers market, order, trade and settlement queries; placing or cancelling orders needs read_write.
  2. Swap the registration to the API-key overload shown above. Nothing else in your code changes.
  3. Replace the user id you read from the login response with one GetMeAsync() call at startup, since there is no login response to read.
  4. Drop the keepSessionAlive handling. There is no token to keep alive.

Until the current terms are accepted, authenticated calls are rejected.

To show the terms and accept only after the user has agreed, use the two explicit calls and do not call CheckTermsAndConditionsAsync at all:

var tnc = serviceProvider.GetRequiredService<STXTermsAndConditionsService>();
// Read-only: fetches the current terms without accepting anything.
var current = await tnc.GetTermsAndConditionsAsync();
// Display current.Version to the user. Only once they have agreed in your UI:
await tnc.AcceptTermsAndConditionsAsync(current.Version);

AcceptTermsAndConditionsAsync also takes no argument, in which case the server accepts the version currently in effect. Pass the version you actually displayed where you can, so what was accepted is what the user saw.

There is no server-side logout call.

With an API key there is nothing to end: requests are signed individually and no session is held. To revoke access, delete the key under Account → API Keys. Signatures made with it stop being accepted immediately.

On the legacy email/password path the session lives in STXUserStorage, which is a singleton. Stop the host to end it, and stop any channels you started with StopAsync.

Exception Cause
STXRequestFailedException Signature rejected. Check the host clock: the server allows 30 seconds of skew
STXWrongCredentialsException Bad email/password, on the legacy path
STXSessionExpiredException Token and refresh token both expired, on the legacy path
STXGeoComplyException Geo-compliance check failed; user is outside a permitted jurisdiction

See Errors & retries for the full list and recovery patterns.

v1.5.9Changelogllms.txtllms-full.txt