Skip to main content

What it is

Every authenticated request to Ekso carries a client marker that distinguishes how the call arrived:
ClientHow the request arrived
WebBrowser session, webapp authorization-code flow. Default when no claim is present.
CliCLI device-flow login (ekso auth login).
SdkAPI-key auth — every ApiKeyAuth call.
The backend exposes this as UserContext.IsCliSdk (true for Cli or Sdk; false for Web). Controllers can read it to gate operations that should not be scriptable.

Why it exists

The webapp and the API are the same surface — there’s one set of HTTP routes, used by both browsers and SDK clients. That’s good for symmetry but creates a problem: some operations are safe in a UI but dangerous from a script. Concrete example — system field updates. An admin in the webapp legitimately renames the “Priority” field’s prompt or tweaks its description. The same request from a renegade script could brick a tenant’s field setup. Same route, different threat surface. The client marker lets the controller distinguish them: if the request arrived via SDK or CLI, apply the lockdown; otherwise let the webapp through.

What’s currently gated

OperationWebappCLI / SDK
Create a custom field (any type)
Update a custom field (IsCore=false)
Update a system field’s metadata (name, description, …)❌ — 403
Update a system list field’s data values (e.g. add Critical to Priority)✅ — via PUT /api/field/list/data
The list-data escape hatch is important: even an automation needs to be able to add Critical to Priority or new statuses to the workflow. That’s a legitimate scripted operation and stays open. What’s blocked is scripted renaming or deletion of system fields.

How it gets stamped

Three points stamp the marker:
  1. Webapp authorization-code grant — issues a JWT with no EksoClient claim. Validation maps absent → Web.
  2. CLI device-flow grant — issues a JWT with EksoClient=Cli. The marker is preserved through token refreshes via DataOAuthRefreshToken.ClientType, so a refreshed CLI token doesn’t silently downgrade to Web.
  3. API-key validation — sets IsCliSdk=true unconditionally. API keys are by definition non-interactive, so Sdk is the right marker.
The flag lives on UserContext.IsCliSdk and is set per request by the auth middleware after token validation. Controllers never need to parse JWT claims directly.

How to react in your code

If you’re calling the SDK and you hit a 403 on a system-field update, you’ve found this gate:
try
{
    var systemField = await GetField("priority");  // IsCore=true
    systemField.Description = "Updated description";
    await client.Api.Field.List.PutAsync(systemField);
}
catch (EksoApiException ex) when (ex.StatusCode == 403)
{
    // Two options:
    // 1. Don't try this from CLI/SDK at all — change the field via the webapp.
    // 2. If you only need to change list values, use the dedicated endpoint:
    await client.Api.Field.List.Data.PutAsync(new UpdateFieldListDataRequest
    {
        Id = systemField.Id,
        Data = systemField.Data,           // mutated values
        DefaultValue = systemField.DefaultValue,
    });
}
The CLI surfaces this directly — ekso field update-list <id> 403s on system lists, but ekso field update-list-data <id> works for both system and custom lists.

When to expect more gates

The CLI/SDK marker is a structural primitive — any operation we don’t want scripted gets the same if (helper.UserContext.IsCliSdk) ... guard. Today only system fields are gated. Likely future additions:
  • Bulk delete operations (already require --confirm flags; may also gate from SDK).
  • Tenant-shape-changing operations (workspace renames, admin email changes).
  • Anything where “someone left an API key in a Lambda” is a credible blast radius.
Each new gate will be called out in the operation’s API Reference page when it lands.

See also