SDK · Client
The client object, its options and configuration, the error type every refusal arrives as, the token store an app implements to persist a session, and the details shapes of the refusals a caller branches on.
Generated from the SDK’s own source. The SDK Reference is the narrative; this page is every member, in the order the SDK declares them.
createClient
createClient(options: FleetlessClientOptions): FleetlessClient
Builds a client for one app. Pass tokenStore (or nothing — the default
keeps the session in memory) for an app-user client that signs in with
auth.login or a federated provider; pass serverKey for a server-side
caller that never holds a user session; pass credentials when the
embedder already holds the bearer and refreshes it itself. Passing more
than one throws, because each is a different identity and a client acts
as exactly one.
Nothing is fetched here: the realtime channel opens on the first
subscription and closes on close() or auth.logout().
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
FleetlessClientOptions |
yes |
Returns FleetlessClient.
References: FleetlessClientOptions, FleetlessClient
FleetlessClient
One app’s client, returned by createClient. Every API the SDK offers is
a property on it, and all of them share this client’s identity, its
single realtime channel and its token refresh.
| Property | Type | Required | Description |
|---|---|---|---|
config |
FleetlessClientConfig |
yes |
The settled configuration, including the defaults createClient filled in. |
auth |
AuthApi |
yes |
The whole client auth API: registration, verification, login, logout, password reset, invitations, the app’s federated sign-in providers, the MCP consent screen, and the app user’s own standing MCP grants. |
datapoints |
DatapointsApi |
yes |
A topic’s latest value, a live subscription to it, and its recorded history. |
actions |
ActionsApi |
yes |
Long-running work on the robot: invoke, cancel, and watch a job as it runs. |
services |
ServicesApi |
yes |
Request/response calls to the robot that answer once and are done. |
publishers |
PublishersApi |
yes |
One-way messages to a robot’s publisher, such as a velocity command. |
cameras |
CamerasApi |
yes |
Camera snapshots, their age, and live video sessions. |
jobs |
JobsApi |
yes |
Robot-wide job reads that do not fit under actions/services because they are not addressed by slug — see JobsApi.list. |
assets |
AssetsApi |
yes |
URDF and mesh reads — list/get/urdf, plus the urdf-loader mesh callback. |
robots |
RobotsApi |
yes |
Which robots this caller reaches, and what their role lets them do on each — the calls every screen starts from. |
References: FleetlessClientConfig, AuthApi, DatapointsApi, ActionsApi, ServicesApi, PublishersApi, CamerasApi, JobsApi, AssetsApi, RobotsApi
close()
close(): void
Closes the realtime channel and stops it from reconnecting. Safe with
no subscription ever made, and safe to call twice. A Node script (the
exact use case serverKey is for) that never calls this after
subscribing will not exit on its own — an open WebSocket keeps the
event loop alive. auth.logout() calls this automatically; call it
yourself if the process should exit without logging out (e.g. a
server-side shutdown).
Returns void.
FleetlessClientOptions
Everything createClient accepts. apiUrl and appIdentifier are
required; the rest either select the kind of caller (tokenStore versus
serverKey) or replace a global the SDK would otherwise reach for.
| Property | Type | Required | Description |
|---|---|---|---|
apiUrl |
string |
yes |
Base URL of the Fleetless REST API, e.g. https://api.fleetless.dev. |
appIdentifier |
string |
yes |
The app’s identifier (the slug shown in the console), sent on every login. |
tokenStore |
TokenStore |
no |
Where refresh/access tokens live between calls. Defaults to in-memory — pass your own (localStorage, a cookie, a native keystore) to persist a session across reloads. The SDK never assumes a browser exists. |
serverKey |
string |
no |
A server key (flk_...) for server-side callers with full app rights. Mutually exclusive with tokenStore-based login: a client constructed with a server key never calls auth.login/auth.logout. |
credentials |
CredentialSource |
no |
A credential this client does not own. The caller answers both questions a bearer raises: what the token is right now (token()), and what to do when the server says it expired (handleExpired()). For an embedder that already holds a session and refreshes it itself — the Fleetless console is the case this exists for. Without it such a caller had to impersonate a TokenStore, and a cloud-side token_expired arriving while its own clock still read live posted one empty refresh whose validation_error had to be translated back into a session message. A source that answers handleExpired: false never builds a refresh request at all. Mutually exclusive with tokenStore and serverKey, which each own a credential of their own; a client acts as exactly one identity. auth.login/auth.logout refuse here for the same reason they refuse on a server key: there is no session for this client to start or end. |
fetch |
(input: RequestInfo | URL, init?: RequestInit) => Promise<Response> |
no |
Injectable for tests, or a non-global fetch implementation. |
WebSocket |
(url: string | URL, protocols?: string | string[]) => WebSocket |
no |
Injectable for tests, or a non-global WebSocket implementation. |
realtimeUrl |
string |
no |
Defaults to apiUrl with http(s) swapped for ws(s) and /realtime appended. |
References: TokenStore, CredentialSource
FleetlessClientConfig
The settled configuration of a client, reachable as client.config. It
is frozen and reflects the defaults createClient filled in, which is
what makes it worth reading: realtimeUrl is usually derived rather than
passed.
| Property | Type | Required | Description |
|---|---|---|---|
apiUrl |
string |
yes |
The REST base URL this client calls, exactly as passed to createClient. |
appIdentifier |
string |
yes |
The app this client acts as, exactly as passed to createClient. |
realtimeUrl |
string |
yes |
The realtime WebSocket URL in use, derived from apiUrl unless one was passed. |
FleetlessError
The one error type the SDK throws for a refused API call: a stable
machine-readable code a caller can branch on (forbidden versus
token_expired) plus a human message for logs and debugging. Never
parse message — it is not part of the contract, only code is.
Catch it by shape rather than by class where you can (err.code), since
a bundler that ends up with two copies of the SDK also ends up with two
classes and instanceof then answers false for a genuine one.
| Property | Type | Required | Description |
|---|---|---|---|
code |
FleetlessErrorCode |
yes |
What went wrong, as a stable string — the field to branch on. |
details |
unknown |
no |
Structured detail the server sent with the refusal, if any: the violations behind parameter_invalid, the running job behind busy, retry_after_ms behind rate_limited. Parse it rather than assume its shape — parameterInvalidDetails is exported for exactly that. |
status |
number |
no |
The HTTP status of the response that produced this error, if it came from one. |
References: FleetlessErrorCode
new FleetlessError()
new FleetlessError(code: FleetlessErrorCode, message: string, options?: FleetlessErrorOptions): FleetlessError
Builds an error. code is what a caller branches on and message is
for a human; anything else the refusal carried goes in options.
| Parameter | Type | Required | Description |
|---|---|---|---|
code |
FleetlessErrorCode |
yes |
|
message |
string |
yes |
|
options |
FleetlessErrorOptions |
no |
Returns FleetlessError.
References: FleetlessErrorCode, FleetlessErrorOptions, FleetlessError
Inherited from Error: cause, name, message, stack.
FleetlessErrorOptions
The third argument of FleetlessError’s constructor: what a thrower can
attach beyond the code and the message. Both fields are optional, and
both are absent on the codes the SDK raises before any request is sent.
| Property | Type | Required | Description |
|---|---|---|---|
details |
unknown |
no |
Field-level detail for validation errors, passed through verbatim. |
status |
number |
no |
The HTTP status of the response that produced this error, if any. |
FleetlessErrorCode
A stable code a caller can branch on: a server-defined code (open-ended —
see ErrorCode’s own doc comment), one of SdkErrorCode’s client-side
codes, or, since neither list is exhaustive, any other string.
(string & {}) is the standard trick to keep autocomplete on the known
values while still accepting an arbitrary one.
type FleetlessErrorCode = ErrorCode | SdkErrorCode | string & {}
References: SdkErrorCode
SdkErrorCode
The union of SDK_ERROR_CODES — the SDK’s own client-side error
vocabulary. A FleetlessError whose code is one of these was raised by
this SDK rather than relayed from the server.
type SdkErrorCode = typeof SDK_ERROR_CODES[number]
References: SDK_ERROR_CODES
SDK_ERROR_CODES
Codes the SDK produces itself rather than relaying from the server. Kept
out of @fleetless/contracts’ ERROR_CODES deliberately — that list is
the wire vocabulary, every entry something a server may actually send,
and none of these are.
no_session/no_websocket: a client-side refusal before a request ever reaches the network (not logged in; no WebSocket implementation available). Named apart from the server’s ownunauthorizedso a caller can tell “the server refused me” from “the SDK refused before asking” by the code alone.unparseable_error: the opposite direction — a real response did arrive, its body just was not shaped like the platform’s error format. Not a refusal at all, just “we do not know what the server said.”command_timeout: a realtime command (invoke/cancel/publish) got nocommand_resultwithin its timeout. The server may still answer later on the same socket — nobody knows — but the caller cannot be made to wait forever for that.command_outcome_unknown: worse than a timeout, and told apart from it on purpose — the connection that carried the command was replaced by a new one (a reconnect) before any reply arrived. The server, if it answered at all, answered a socket that no longer exists, so the command may or may not have run. Never retried automatically — that could run an action twice — the caller recovers by reading the job (e.g.actions.subscribe), since state is observed by slug regardless of which connection asked for it.unexpected_response: the server answeredok:truebut left out something the command is defined to always return (e.g. nojobon a successfulinvoke) — a contract violation the SDK noticed, not a refusal.invalid_option: the caller passed an SDK-level argument or option that cannot mean what it looks like it means. Three cases so far:timeoutMs < patienceMsoninvoke/call— the SDK would give up locally before the platform’s own patience runs out, and reportcommand_timeoutfor a call the platform never actually refused; a non-string, non-null, non-omittedjobIdoncancel— almost always a caller who upgraded past the oldercancel(robotId, slug, options?)signature and is still passing an options object third; and aconcurrencyonassets.prepareUrdfScenethat is not a positive integer, which would otherwise fetch nothing and return a scene that renders blank with no error to explain why. A fourth since 3.0.0: everyauthmethod needing an app user’s own session, called on a client built with aserverKey—register,login,logout, the password and invitation calls, both OIDC calls, the two MCP decisions and the two grant calls. Those threw a bareErrorbefore, which a caller could only catch by message. All of them are refused before any request is sent — as a rejection, since every one of those methods isasync. A client-side mistake to fix, not something a server response could ever produce, which is why this code belongs here and not in@fleetless/contracts’ERROR_CODES.untrusted_absolute_url: the SDK refused to fetch an absolute URL whose origin does not match this client’s ownapiUrl— thrown before the request is ever sent, so noAuthorizationheader is ever built for it, let alone attached. The one caller that fetches an absolute URL at all isassets.createMeshLoader, following a URDF’s rewritten mesh URIs — and a URDF is ROS graph input, not first-party data, so an app rendering one must not silently trust wherever it points.assets.createMeshLoader’sonCompletesurfaces this the same way it surfaces a network failure:(null, err).no_urdf_synced:assets.prepareUrdfScenelooked for akind: 'urdf'row inassets.list()and found none. Thrown before any asset fetch, rather than left to surface as a confusing downstream failure fromURDFLoader.parse(undefined)or similar — the caller’s fix is “sync a URDF first”, which this error can say directly.aborted:assets.prepareUrdfScene()was given anAbortSignaland it fired — either already-aborted before the call started, or mid-flight while a fetch was in progress. Normalized to this one code regardless of which stage the abort landed in, rather than surfacing whatever shape the underlyingfetch()rejects an aborted request with (aDOMExceptionnamedAbortErrorin a browser, anErrornamedAbortErrorunder Node’sfetch— two different shapes a caller would otherwise have to detect themselves to tell “I cancelled this” from “the network actually failed”). Every partial resource this call had already created (blob:URLs) is revoked before this throws — an aborted load must not leak what it fetched before the signal fired, the same guarantee a failed load already had.state_mismatch:auth.completeOidcLogin()was called with astatethat does not match theexpectedStateits ownbeginOidcLogin()returned for this attempt — or with nostateat all (beginOidcLoginalways sets one, so a callback carrying none does not look like a reply to a flow this client started), or with an emptyexpectedState, meaning nothing was persisted for this attempt at all. That last case is folded in rather than given its own code: two empty strings compare equal, so it has to be checked explicitly or the comparison defends nothing for exactly the callers most likely to hit it — but a caller branching on the code has the same next step either way, which is to start the sign-in again. Which of the two happened is in the message, because the developer’s remedies do differ (“check how your app persisted the value” versus “this response belongs to a sign-in you did not start”). Thrown before/api/client/oidc/exchangeis ever called: RFC 6749 section 10.12’s whole point is that a caller must not complete an authorization response it did not itself request, so this check happens client-side, first, rather than being left to the server to catch — by which point a one-time code would already have been spent for a flow this client never started.
const SDK_ERROR_CODES: readonly ["no_session", "no_websocket", "unparseable_error", "command_timeout", "command_outcome_unknown", "unexpected_response", "invalid_option", "untrusted_absolute_url", "state_mismatch", "no_urdf_synced", "aborted"]
TokenStore
Where the SDK keeps a session. A developer implements this to persist a login (localStorage, a cookie, a native keystore) — the SDK itself never assumes a browser, or any storage, exists.
load()
load(): { access_token: string; refresh_token: string; expires_in: number } | Promise<{ access_token: string; refresh_token: string; expires_in: number } | null> | null
Returns the stored session, or null when nobody is logged in. May be
async, so a store backed by a native keystore or an IndexedDB read
works without a synchronous cache in front of it.
Returns { access_token: string; refresh_token: string; expires_in: number } | Promise<{ access_token: string; refresh_token: string; expires_in: number } | null> | null.
save()
save(session: { access_token: string; refresh_token: string; expires_in: number } | null): void | Promise<void>
Writes the session, or clears it when passed null. Called after a
login, after every silent refresh, and on logout — so an implementation
that persists must expect to be called often, not once.
| Parameter | Type | Required | Description |
|---|---|---|---|
session |
{ access_token: string; refresh_token: string; expires_in: number } | null |
yes |
Returns void | Promise<void>.
StoredSession
What is kept between calls to stay logged in: exactly the wire shape
sessionTokens returns, no derived fields. Refresh is reactive (a call
that meets an expired access token refreshes and retries) rather than
proactive, so there is no expires_at to compute or drift out of sync.
type StoredSession = SessionTokens
InMemoryTokenStore
The default store: works out of the box, forgets the session on reload.
new InMemoryTokenStore()
new InMemoryTokenStore(): InMemoryTokenStore
Nothing is loaded from anywhere — a client built with it starts logged out.
Returns InMemoryTokenStore.
References: InMemoryTokenStore
load()
load(): { access_token: string; refresh_token: string; expires_in: number } | null
Returns the session held in memory, or null if there is none.
Returns { access_token: string; refresh_token: string; expires_in: number } | null.
save()
save(session: { access_token: string; refresh_token: string; expires_in: number } | null): void
Replaces the session held in memory; null clears it.
| Parameter | Type | Required | Description |
|---|---|---|---|
session |
{ access_token: string; refresh_token: string; expires_in: number } | null |
yes |
Returns void.
CredentialSource
Supplies the Authorization header value for a request, and knows what to
do when the server says the token has expired. HttpClient is agnostic to
what is authenticating it — an end-user session with silent refresh, or
a static server key — so both can share one request path.
token()
token(): Promise<string | null>
The current raw bearer credential — a JWT or a server key (flk_...) —
or null if not authenticated. Raw, not Bearer <token>: REST forms the
Authorization header from it, and the realtime auth frame
carries the very same value unprefixed, so there is exactly one place
that knows what “the token” currently is.
Returns Promise<string | null>.
handleExpired()
handleExpired(): Promise<boolean>
Called once when a request comes back token_expired. Three outcomes:
- resolves
true— a fresh credential is ready, retry the request; - resolves
false— nothing to do (e.g. a server key, which cannot be refreshed at all), propagate the originaltoken_expired; - throws a
FleetlessError— refreshing itself failed for a specific, more useful reason (e.g.token_revoked, a reused refresh token); that error propagates instead of the originaltoken_expired, so the caller learns what actually happened, not just that a retry was tried.
Returns Promise<boolean>.
RateLimitDetails
The rate-limit-details wire schema, re-exported so app code and the SDK agree on the shape — see API Schemas for its fields.
BusyDetails
The busy-details wire schema, re-exported so app code and the SDK agree on the shape — see API Schemas for its fields.
ParameterInvalidDetails
The parameter-invalid-details wire schema, re-exported so app code and the SDK agree on the shape — see API Schemas for its fields.
ParameterViolation
The parameter-violation wire schema, re-exported so app code and the SDK agree on the shape — see API Schemas for its fields.
parameterInvalidDetails
The details of a parameter_invalid refusal. Always at least one
violation: a refusal that names none would leave the caller with nothing to
fix. All violations are reported at once, not just the first — a caller
fixing parameters one round-trip at a time is a caller who gives up.
const parameterInvalidDetails: ZodType<ParameterInvalidDetails>
References: ParameterInvalidDetails
CancelRejectedDetails
The cancel-rejected-details wire schema, re-exported so app code and the SDK agree on the shape — see API Schemas for its fields.
CancelReturnCode
One goal’s answer to a cancel: the ROS 2 CancelGoal return code its action
server sent — 0 accepted (CANCEL_RETURN_CODES.none), 1 refused
(rejected), 2 unknown goal (unknown_goal_id), 3 already ended
(goal_terminated). The type of return_code in CancelRejectedDetails,
where it is null when that goal’s server did not answer. The wire shape is
contracts’ cancelReturnCode; the alias exists so the reference can
describe it — a JSDoc on an export type { … } from statement does not
survive bundling.
type CancelReturnCode = CancelReturnCode$1
cancelRejectedDetails
The details of a cancel_rejected refusal: every goal the cancel reached,
accepted ones included, each with the CancelGoal return code its action
server answered — compare return_code against CANCEL_RETURN_CODES
(none, rejected, unknown_goal_id, goal_terminated); it is null
when that goal’s server did not answer within the bridge’s bound. Always at
least one goal: the cloud refuses a cancel only because a goal’s server
answered ERROR_REJECTED. Pinned here for the reason
parameterInvalidDetails is: a caller parses it instead of reading the
shape from prose.
const cancelRejectedDetails: ZodType<CancelRejectedDetails>
References: CancelRejectedDetails
CANCEL_RETURN_CODES
The ROS 2 action_msgs/srv/CancelGoal return codes: 0 ERROR_NONE (the
server accepted the cancel request), 1 ERROR_REJECTED (it refused),
2 ERROR_UNKNOWN_GOAL_ID, 3 ERROR_GOAL_TERMINATED (the goal had
already ended). An accepted request is not an ended goal: whether the goal
ends, and how, is what the action’s status reports afterwards, and reaches
the cloud as the goal’s job_update.
const CANCEL_RETURN_CODES: { none: 0; rejected: 1; unknown_goal_id: 2; goal_terminated: 3 }