Release notes
All notable changes to the Fleetless platform — the cloud, the console and the documentation. The SDK and the bridge carry their own changelogs.
[0.23.9] — 2026-10-05
Added
- Paying for a plan. Plus, Pro and the add-ons are paid on Mollie’s hosted checkout by credit card, PayPal or Apple Pay, in euros or US dollars. A company or a person pays with the VAT that applies to it. Upgrades and add-ons take effect at once and are charged for the rest of the period; reductions and cancellations take effect at the period’s end. See Paying and Changes within a period.
- Invoices. Every charge gets its own invoice, numbered
FL-<year>-<n>without gaps, with a PDF in the console and by email. - Failed payments. A failed renewal is retried on the third and seventh day; on the fourteenth the organisation is locked until the open invoice is paid or the owner chooses what stays on Basic. See Failed payments.
- Billing errors.
billing_unavailable,payment_provider_unavailableand the billing validation and state rules. See the API reference.
[0.23.8] — 2026-10-05
Changed
- The app starter has a 3D tab. The robot page renders the robot’s synced
URDF with
assets.prepareUrdfScene, textures and.dae-embedded images included, and moves its joints from a grantedjoint_statesdatapoint. See the app starter.
[0.23.7] — 2026-10-04
Added
- Every organisation is on a plan: Basic, Plus, Pro or Enterprise. A plan sets the seats, robots, apps, app users, live video and asset storage an organisation has, how long history and the audit log are kept, and which features it can use. Pro can buy add-ons. The table, the prices, what is counted and what happens at a limit are in Plans and Limits.
- Plan errors.
plan_limit(409) andplan_required(403) say which limit or feature refused a request and what lifts it;org_locked(403) says why an organisation may only move to Basic. See the API reference. - The organisation’s plan.
GET /api/org/planreads the plan, its limits against usage and any pending change.PUT /api/org/plan/changemoves the organisation to a lower plan or to Basic, andDELETE /api/org/plan/changewithdraws that change before its period ends.
Changed
- Choosing what stays. Moving to a smaller plan whose limits the organisation exceeds asks the owner which robots, apps, app users and developers stay. Everything else is deleted when the period ends.
- App-user limits follow the plan. At the limit, creating, inviting or
self-registering an app user answers
409 plan_limit; the protection quotaquota_exceededstays as a higher ceiling behind it. - Asset storage is the organisation’s, sized by the plan. The robots share
the robots times the plan’s allowance per robot, and a file that does not
fit answers
409 plan_limitwithstore_bytes,used_bytesandsize_bytes. - A federated sign-in at the plan’s app-user limit answers
plan_limit, notquota_exceeded, so your app can tell that the plan is full.quota_exceedednow means only the app-user protection ceiling. An app on an SDK that predates this readsplan_limitasunexpected_response; see Manage users and roles.
Fixed
- One asset file over one gigabyte is refused with its own code. An
upload of a single file larger than one gigabyte answers
413 file_too_large, naming the limit and the file’s size, on every plan and for the URDF too. It used to answer409 quota_exceeded, as if the storage were full. See the API reference.
[0.23.6] — 2026-10-02
Changed
- The app-starter signs in by emailed code and handles two-factor. The app-starter recipe describes sign-in by password or emailed code, the two-factor challenge, recovery codes, the setup an app can require and Account › Security. Pointing the app’s mailed links and MCP sign-in into the starter is now optional: Fleetless’s hosted pages answer them until the starter runs somewhere reachable, and the agent prompt asks before it sets the URLs.
[0.23.5] — 2026-10-02
Added
- Developers sign in by emailed code or passkey; passwords are gone. Fleetless users can add passkeys and an authenticator app, with ten recovery codes, in Settings › Profile, and an owner can require a second factor for the whole organisation in Settings › General. See Sign-in and two-factor.
- Every app chooses its own sign-in methods and two-factor policy.
Password and/or emailed code, and two-factor
off,optionalorrequiredfor that app’s users — see Create an App. - An app’s mailed links and MCP sign-in no longer need a page of your
own. Leave
invite_url,verify_url,reset_urlormcp_login_urlunset and Fleetless serves a hosted page instead, carrying the app’s name, logo and accent colour.
Changed
- The developer portal (console sign-in, sign-up and the central MCP sign-in) has the console’s own look, replacing the terminal-styled pages.
PUT /api/apps/:id/auth-config/mcponly takesmcp_enablednow;mcp_login_urlmoved to.../auth-config/urls, next to the newapp_url. See API Reference.
[0.23.4] — 2026-10-01
Added
- The SDK reference lists the details of
cancel_rejected. SDK · Client gainscancelRejectedDetails,CancelRejectedDetails,CANCEL_RETURN_CODESandCancelReturnCode, so a refused cancel is parsed and its return codes compared by name, and API Schemas thecancel-rejected-detailsfield table.JobOriginis on SDK · Actions, services, publishers & jobs.
Fixed
- A republish no longer stops goals Fleetless did not start. When a
republish changes or removes an action whose job is
unknown, the cancel the cloud sends stops only that job, and external goals on the action keep running; only an explicit cancel still reaches them. Needs bridge 6.1.0.
[0.23.3] — 2026-09-30
Changed
- A job the cloud lost sight of is
unknown, notlost. “When the link drops” and the API reference now describe the non-final state: the slug stays occupied, and only the bridge’s own word — still running, an outcome, or “I don’t know this job” — resolves it,lostincluded. A restart on either side is no longer a silentlost: a cloud restart leaves the jobs in flightunknownuntil their robots answer. A goal nobody at Fleetless started shows up the same way, as a live-only job withorigin: external, cancellable like any other. Needs bridge 6.0.0 and protocol 5, the only version now served — Getting Started covers the one-time hard cut, with no 90-day window. Its environment variables gainFLEETLESS_STATE_DIR, where the bridge keeps the job ↔ goal mapping it recognises its jobs by after a restart. - A cancel is answered by the robot’s action server. Cancelling a
job is new: the cloud waits for the
server’s
CancelGoalreturn code per goal, answers an accepted cancel with the job stillrunning(its end follows as an update), refuses one the server rejected with409 cancel_rejectednaming every goal, and answers504 bridge_timeoutwhen the robot does not answer in time. Every cancel that reached the robot writes onejob.cancelledaudit event per goal, with its return code, a refused cancel included.
[0.23.2] — 2026-09-29
Changed
- Jobs end when the action server or the bridge goes away. “When the
link drops” now says what
actually happens: a job keeps running through an ordinary dead zone (watch
bridge_state.online, not the job), and only endslostafter five minutes offline — correctable if the real outcome arrives later. A bridge restart is the one case with no later correction. The API reference gains a table of everyJob.error.code,action_server_lostincluded. Both need bridge 5.0.0 or later;options.patienceMsbounds only acceptance there.
[0.23.1] — 2026-09-24
Changed
- The brand in the browser is
fleetless, or the logo. Tab titles on the docs, cloud and console surfaces begin with lowercasefleetless, brand marks readfleetlessto a screen reader, and the docs start page’s heading is the logo. Running text keeps its capital F.
[0.23.0] — 2026-09-23
The documentation changed; the product did not.
[0.22.0] — 2026-09-22
Added
- An app can be deleted.
GET /api/apps/:id/deletion-previewcounts what would go — app users, invitations, roles, server keys, OIDC providers and identities — andDELETE /api/apps/:idremoves them in one cascade. Owner tier. There is noforceflag: the preview is the guard, and a count you have not read is not a confirmation. Deleting an app does not touch the robots attached to it.
Changed
- The app’s auth configuration is written in three pieces, not one.
PUT /api/apps/:id/auth-configis replaced by…/auth-config/registration(self_registration,allowed_domains,allowed_origins),…/auth-config/urls(the three mailed-link templates) and…/auth-config/mcp(mcp_enabled,mcp_login_url). Each is strict over its own fields and merges server-side, so a write to one slice never disturbs another, and a client built against an older shape cannot silently clear a setting it does not know about.GETis unchanged and still answers the whole document. The MCP tool follows:console_app_auth_config_putbecomes three tools of the same names. - The console’s app detail page is sub-pages, not tabs. Ten routes under a shared layout, in four labelled groups — App, People, Sign-in, Integrations — plus Delete app. Settings, Robots, Server keys, Roles, Users, Mails, Registration, App URLs, MCP and Identity providers are each a link now, so a deep link, the back button and a reload all land where you left off. Below 1181 px the nav moves into a drawer.
[0.21.1] — 2026-09-22
Added
- Old bridges keep working. The cloud serves a window of bridge protocol versions: a version is deprecated by the release that supersedes it and served for 90 more days. Every robot shows its protocol status in the console and the API (
protocol_status,protocol_version,protocol). Your organisation’s Fleetless users get a mail when a version is deprecated, 14 days before it stops, when a robot is refused for it, and when a newer bridge package is published. Migration0007addsrobots.protocol_versionand theprotocol_noticestable. The compatibility table on Getting Started is generated from the contracts. - A bridge token can be rotated without deleting the robot.
POST /api/robots/:id/token/rotatemints a new token and closes the robot’s current bridge connection; the old token is refused at the bridge’s next hello, and the robot is offline until the new token is on it. It is Owner tier, and the new token is shown once. - Each robot’s asset store holds one gigabyte, with no per-file limit. A full store is never a dead end: the URDF itself is exempt from the gate, reconcile after every sync frees whatever the new URDF stopped referencing, and an Owner can clear a robot’s store outright with
DELETE /api/robots/:id/assetsand let the next sync refill it. Migration0009computes each robot’s counter from its existing assets. - An asset sync’s
statenow checks what the store actually holds, not only what the bridge reported.storedandannouncedon the sync status count the announced files against what arrived; a sync the bridge called clean is markedfailed, with anupload_failedentry naming each missing file, when the store holds none of them. - The console’s URDF page can watch the robot move.
PUT /api/robots/:id/urdf/joint-statechooses the whole-messagesensor_msgs/msg/JointStatedatapoint that drives the joints; a publish that drops or narrows that datapoint clears the choice. @fleetless/sdk’screateClientaccepts acredentialsoption. ACredentialSourceanswers what the bearer is right now and what to do when it expires, for an embedder that already holds a session and refreshes it itself — the console is the first. Mutually exclusive withtokenStoreandserverKey.
Changed
- The documentation is restructured. Four groups; the reference folds into
fleetless.yaml(seven pages), API and SDK. Disconnect behaviour, cameras and URDF are sections of Exposing a Robot; Create an App and Manage Users and Roles replace Apps, App Users & Roles and the identity guide. Getting Started is the bridge: install, start, the environment. Every paragraph is three sentences or fewer, and a test says so. - The three recipes are gone. The app starter is the recipe; the MCP description advice lives on the MCP page, the URDF traps on Exposing a Robot.
- The bridge has a low-bandwidth mode. It measures whether datapoints still arrive in time and, when they do not, caps every datapoint to a configurable rate, pauses backfill and reduces or stops cameras until the link recovers. Configure it as ROS parameters on the bridge node or in a new
low_bandwidthsection offleetless.yaml, whose eight keys, their bounds and the rule thatexit_lag_msstays at or belowenter_lag_msare on the Low Bandwidth reference page; a datapoint sayslow_bandwidth: keepto keep its rate. Every robot shows the mode asbridge_state.low_bandwidth, and a bridge older than 4.0.0 ignores the section and reportsfalse. - The robot’s detail page is sub-pages, not one screen. Overview, Datapoints, Actions, Services, Cameras, Publishers and URDF are routes under a shared layout; each list is a table whose row opens a modal at
?open=<slug>, so one datapoint or one action is a link. Overview carries an edit modal for the robot’s details andRotatefor the bridge token (Owner tier, shown once); the configuration editor’s buttons moved into the status strip, with Findings, Introspection and Versions as tabs beside them. - The URDF page renders the robot. It loads the stored URDF and meshes with three.js and reads the chosen joint-state datapoint live; its configuration modal syncs URDF and meshes from the robot, picks the
sensor_msgs/msg/JointStatedatapoint that drives the joints, or clears the robot’s asset store.
Removed
bridge_pressure. Readbridge_state.low_bandwidthinstead. The threeFLEETLESS_UPLINK_*variables and the/fleetless/uplink_kbpstopic are gone with it (bridge 4.0.0). Migration0008deletes every storedbridge_pressuresample and credits those bytes back to the organisation’s retention usage, which had been charged for them.- The organisation’s asset quota and the per-file cap.
max_asset_storage_bytesis gone; storage is now a limit per robot, not per organisation, and the 64 MiB ceiling on a single file is gone with it. The asset kindotheris also gone — assets narrow to what a URDF needs:urdf,mesh,texture. - The robot detail’s node tree and live rail. The left-hand node tree, the right-hand live-value rail and the
?node=query are gone; each exposure kind gets its own page instead.
[0.20.0] — 2026-09-18
Added
- MCP sessions last. Both MCP authorization servers issue a refresh token
with every exchange and rotate it on
grant_type=refresh_token. It lives ninety days from its last use; a block, a password change, a withdrawn consent or a second presentation ends it. Clients that readgrant_types_supportedstop sending you to the login several times a day. - Four console tools.
console_app_auth_config_getandconsole_app_auth_config_putread and replace an app’s auth configuration (a strict replace, audited);console_app_invitations_listandconsole_app_invitation_revokeshow and withdraw an app’s pending invitations. Sixty-threeconsole_*tools.
Changed
- Migration
0006adds one nullable column,refresh_tokens.client_id. Existing sessions are untouched. - Contracts
1.2.0: the MCP token request is a discriminated union again,authorization_codeorrefresh_token.
[0.19.0] — 2026-09-17
Added
- Two discovery routes for app users.
GET /api/client/robotslists the robots the caller reaches, andGET /api/robots/:id/datasheetdescribes what their role lets them do on one — the REST twins of the MCP toolsrobots_listandrobot_describe, from the same server code.@fleetless/sdk3.1.1 exposes them asclient.robots, and ships thejobs.historythe reference had described without the package carrying it. - An app starter.
fleetless/app-starteris a public template repository: the Nuxt UI dashboard with every app-user screen built on@fleetless/sdk3.1.1. The app-auth recipe points at it.
Fixed
- A browser app on its own origin can read robot data. The per-app
Access-Control-Allow-Originwas answered under/api/client/only; every robot-scoped REST route (/api/robots/:id/…) went out with the deployment’s own list, so an app user could sign in and then read nothing but an opaque network error. The cloud now answers an app’sallowed_originson every route the app user’s bearer reaches, refusals included; preflights are allowed everywhere. No credentials, no cookies, and the console is unchanged.
[0.18.1] — 2026-09-15
Changed
- Nothing you can call. The REST API, the realtime protocol, the console and the MCP server behave exactly as in 0.18.0, and no migration runs. A build-time check now reads the proxy configuration from where it lives, and the platform has a release to practise rolling back from.
[0.18.0] — 2026-09-11
Changed
- History writes are batched. The cloud used to insert every datapoint
as it arrived, one row per statement. It now collects rows for up to a
hundred milliseconds or a thousand rows, whichever comes first, and writes
them in one statement. Nothing changes on the wire, and
timestamp_msis still the bridge’s capture time. - Datapoint samples older than thirty days are compressed. TimescaleDB compresses chunks per robot and slug once they are a month old. Queries read compressed and uncompressed chunks alike; the disk does not.
- The cloud starts in one role.
ROLEselectsapi,mcp,authorworker; only the worker runs the ownership sweeps. A single process running all four is still the default.
Fixed
- The documentation’s “On this page” rail now follows the scroll. It had
been
position: stickysince the redesign, in a flex row that stretched it to the article’s full height — sticky with nowhere to travel. It keeps its own height now, and scrolls inside itself on pages with more headings than viewport. - Withdrawing an MCP consent now stops the client at its very next call.
In 0.17.0 it reached only the next authorization: the
mcp_sessionaccess token named its subject and not the client it had been minted for, so the app’s MCP endpoint could check the account and had nothing to look a consent up by. A withdrawn client therefore kept working for the rest of its token — up to fifteen minutes. That window is closed. The token carries aclient_id, the endpoint reads the standing consent on every request beside the account checks it already made, and a withdrawn client is answered401with theWWW-Authenticatechallenge that sends it back to the app’s consent screen. It ends one client: the person’s other clients and their ordinary use of the app are untouched, which is still what blocking the account is for. The documentation and the console copy that described the window have been corrected. - Every
mcp_sessionaccess token now carriesclient_id, and one minted without it is refused. A token issued before this release has no such claim and stops being accepted at deploy — its MCP client re-authorizes, which is what the401’s challenge asks it to do. Tolerating one for the rest of its lifetime was rejected as unwritable: nothing in a token says which deploy minted it, so that branch would have had no way to end. What it costs is bounded by the fifteen minutes those tokens live and by there being no refresh grant to carry one further.
[0.17.0] — 2026-09-06
Fleetless now has two identity spaces that nothing joins. The people who configure robots in the console are Fleetless users; the people who use a developer’s app are app users, and they belong to exactly one app. The organisation-wide user pool that tried to be both is gone, and with it every mechanism that connected the two — groups, app assignments, impersonation and the app-branded pages Fleetless used to render for somebody else’s users.
An app user now signs in through a JSON API the developer’s own UI calls. Fleetless shows an app user no page. That is not a new rule; it is the product rule this release finally applies to authentication.
Breaking
The organisation’s user pool, and everything built on it.
- The org-wide user pool is gone.
GET /api/org/usersnow lists the org’s Fleetless users — the team who reach the console — and it answersfleetless-user-list-response, notorg-user-list-response. Itsgroup_idfilter is gone: there is nothing left to narrow by. - Groups are gone with no successor:
GET|POST /api/org/groups,GET|PATCH|DELETE /api/org/groups/:id,GET|PUT|DELETE /api/org/groups/:id/oidc-provider,GET /api/org/groups/:id/usage,PUT /api/apps/:id/group,GET /api/apps/:id/group-usageandPOST /api/org/users/:id/move-group. - App assignments are gone:
GET /api/org/users/:id/assignments,PUT|DELETE /api/org/users/:id/assignments/:appIdandGET /api/org/users/:id/usage. - Impersonation is gone:
GET|POST /oauth/impersonate. An org admin no longer enters an app as one of its users, because an app’s users are not the org’s users. - The org-level federation policy is gone:
GET|PUT /api/org/federation. Federation is configured per app now (see Added). - A Fleetless user always has MCP access. The per-user override is gone, and so
is
mcp_accesson an invitation.
The app-level OAuth server, and the pages that went with it.
GET|POST /login,GET /oauth/authorize,POST /oauth/token,POST /oauth/register,GET|POST /oauth/consent,GET /oauth/idp-callback,GET /.well-known/oauth-authorization-server,GET /.well-known/oauth-authorization-server/:appIdentifier,GET /.well-known/oauth-protected-resource,GET /.well-known/oauth-protected-resource/mcp-stub/resource/:appIdentifier,GET /mcp-stub/resourceandGET /mcp-stub/resource/:appIdentifierare all gone. OAuth 2.1 now exists only for MCP. Apps use the JSON client auth API exclusively, and dynamic client registration at app level is gone with it:GET|POST /api/apps/:id/oauth-clients,DELETE /api/apps/:id/oauth-clients/:clientIdandDELETE /api/apps/:id/oauth-clients/:clientId/consentare removed, as isapps.accepts_dynamic_clients.- Per-app branding is gone:
GET|PUT|DELETE /api/apps/:id/branding. It existed to skin pages Fleetless no longer serves. - Consent grants for apps are gone:
GET /api/client/grantsandDELETE /api/client/grants/:client_id. The only consent left is the MCP one, which has its own routes (see Added).oauth_consent_grantsis dropped, so no existing grant survives the migration: every OAuth client an app user had approved is approved no longer. POST /api/client/logoutanswers204with no body. It used to return anidp_logoutobject reporting what was left of the session at an identity provider; that belonged to the hosted login, where Fleetless owned the browser. An app that wants to end a provider session redirects there itself. A refresh token the server does not recognise gets the same204.- The central MCP login is password-only.
GET /mcp/oauth/idp-callbackis gone: a Fleetless user signs in to the central endpoint with a Fleetless password, and there is no org federation policy left to send them elsewhere.
The wire vocabulary.
clientIdentity.kindis nowdeveloper | app_user | server_key. Theend_usercase is gone from live answers. It survives in two places on purpose, so old rows still render:auditActor.kindand the jobinvocationActor.kindstill admit it, and nothing writes it any more.- Seven schemas were renamed with the model:
org-user→fleetless-user,org-user-list-response→fleetless-user-list-response,user-invite→pending-team-invite,user-invite-list-response→pending-team-invite-list-response,create-user-invite-request→create-team-invite-request,accept-user-invite-request→accept-team-invite-request,patch-user-request→patch-fleetless-user-request. - Twenty-seven further schemas were deleted with their routes, among them
app-assignment,branding-config,client-logout-response,consent-grant-summary,consent-grant-list-response,consent-revoke-response,consent-decision,group-list-response,group-oidc-provider,group-usage-response,impersonation-choice,oauth-client,oauth-client-list-response,oauth-login-request,oidc-callback-error,org-federation-policyandmove-user-group-request. Forty-three new ones arrived with the surfaces below. - Seven error codes were retired:
group_in_use,group_not_deletable,identity_conflict,identity_not_provisioned,invite_expired,invite_usedandmcp_access_denied. The two invitation codes never had a producer;POST /api/client/invitations/acceptanswers410 token_spentto an unknown, expired, revoked and already-spent token alike, because telling them apart says whether a token ever existed. weak_passwordstays in the enum and has no producer. A password under twelve characters is part of400 validation_errorand names thepasswordfield, which is what a form needs to mark it.
The MCP endpoint moved.
- One app’s MCP server is at
mcp.fleetless.dev/<app_identifier>— on the API host,/mcp/<app_identifier>— and is its own authorization server, with its own dynamic client registration, authorize, token and discovery documents. An app user reaches their app’s endpoint and no other. - The central endpoint at
mcp.fleetless.devnow serves Fleetless users only. A developer handing an app user the central URL was always handing them something that could not work; now it says so. - An app identifier that would collide with a static segment of that host is reserved and refused at creation.
- An app with MCP switched off answers
404on every unauthenticated per-app path — metadata,register,authorizeand the transport — the same as an identifier no app carries. Nothing an anonymous caller can reach tells the two apart.
@fleetless/sdk 3.0.0 (its own changelog has the detail).
auth.beginHostedLoginandauth.completeHostedLoginare removed, withBeginHostedLoginOptions,HostedLoginRequestandCompleteHostedLoginOptions. Useauth.beginOidcLogin/auth.completeOidcLogin, orauth.loginfor a password.client.grantsis removed (GrantsApi,grants.list,grants.revoke). The MCP consents an app user can withdraw areauth.listMcpGrants()andauth.revokeMcpGrant(clientId).auth.passwordResetUrl(),LogoutResult, theno_hosted_login_attempterror code andHttpClient.requestOAuthare removed.
The console.
- Users, Groups, Federation, an app’s branding panel, its OAuth clients panel and the end-user grants page are gone. Settings → Team is the Fleetless-user list; an app’s users live on the app.
Added
- The client auth API at
/api/client/*, public, CORS-answered only for the app’s ownallowed_origins, rate-limited per app, address and IP:POST register,POST verify-email,POST resend-verification,POST password/reset,POST password/reset/confirmandPOST invitations/accept, alongside the existinglogin,refresh,logout,meandpassword/change.register,resend-verificationandpassword/resetanswer202with an empty body for every request policy allows — a known address and an unknown one alike, in status, body and timing. - Per-app auth settings:
GET|PUT /api/apps/:id/auth-config— self-registration on or off,allowed_domains[],allowed_origins[],mcp_enabled, and the four app-hosted URLs Fleetless mails into (invite_url,verify_url,reset_url,mcp_login_url), each carrying its placeholder exactly once and https unless it is on localhost. - App users, managed by the developer:
GET|POST /api/apps/:id/users,GET|PATCH|DELETE /api/apps/:id/users/:userIdandPOST /api/apps/:id/users/:userId/reset-password; invitations atGET|POST /api/apps/:id/invitations,DELETE /api/apps/:id/invitations/:invIdandPOST /api/apps/:id/invitations/:invId/reissue. An app user’s email is unique per app, case-insensitively, so the same address may exist in several apps of one org as unrelated accounts. - Liquid mail templates per app and per kind (
invite,verify,reset):GET /api/apps/:id/mail-templates,GET|PUT|DELETE /api/apps/:id/mail-templates/:kind, plusPOST …/:kind/previewandPOST …/:kind/test. Strict-mode LiquidJS, two engines so the html part escapes exactly once, a built-in default per kind, and a fallback to that default with an audit event if a stored template fails to render. - OIDC per app:
GET|POST /api/apps/:id/oidc-providersandGET|PATCH|DELETE /api/apps/:id/oidc-providers/:providerId, with discovery run at write time and the client secret write-only and encrypted. An app user signs in throughGET /api/client/providers→GET /api/client/oidc/:slug/start→ the identity provider → the callback →POST /api/client/oidc/exchange. Several providers per app; one resolution rule regardless of which door was used. - MCP consent an app user can withdraw:
GET /api/client/mcp/grantsandDELETE /api/client/mcp/grants/:clientIdfrom the app user’s own app, andGET /api/apps/:id/users/:userId/mcp-grantsandDELETE /api/apps/:id/users/:userId/mcp-grants/:clientIdfrom the console. In this release, withdrawing stopped the next authorization only; a live access token kept working for up to fifteen minutes, and the console copy said so. See Unreleased — that window is closed, and the copy corrected. - The MCP consent screen is the app’s own page: the per-app authorize step
renders nothing and redirects to
mcp_login_urlwith an interaction id. The app reads it withGET /api/client/mcp/interactions/:idand decides withPOST …/approveorPOST …/deny. - An app-user quota,
max_end_users, counted per org across its apps. One counting function serves the console gauge and all four doors that create an app user. Registration asks the quota before it looks at the address, so every registration is refused identically at the limit and the enumeration rule holds. - The console gained three tabs on an app — Users, Auth and Mails — with the mail templates edited in Monaco with Liquid highlighting, a preview and a test send.
app_users.email_verified_atrecords that an address was proved, whichstatuscould not carry: unblocking an account now restores the state it had rather than guessingactive.
Changed
- Access to an app is now the app user’s
role_id, which must belong to that user’s own app — enforced in the store and again in the token guard. The permission check itself is mechanically what it was. - An access token for an app user carries
kind: app_user,app_id,sub,role_idandemail. - Team invitations for Fleetless users keep their routes and their seven-day
life; only their vocabulary changed (
team-invite, notuser-invite). - Reissuing an app invitation answers a new id. The old one stops working at once.
mcp.fleetless.devaccepts an app identifier as its first path segment and rewrites it onto the cloud’s/mcp/<identifier>, including the trailing-slash form an address bar produces.
[0.16.0] — 2026-09-05
Breaking
- The invitation routes moved from
/api/org/users/invitations…to/api/org/invitations…(POST,GET,DELETE …/:id,POST …/:id/reissue,POST …/accept). The old paths are gone; a request to them fails. The mailed accept link is unchanged. DELETE /api/robots/:idaccepts?force=trueor noforceparameter; any other value (?force=1used to delete the robot un-forced) now answers400 validation_error.historyis a reserved slug: a configuration naming an action, service, publisher, datapoint or camerahistoryis refused, becauseGET /api/robots/:id/jobs/historywould shadow it.
Changed
- Every documented route names what it answers: seven list and update responses
gained schemas (
app-list-response,role-list-response,server-key-list-response,oauth-client-list-response,patch-org-response,patch-robot-response,put-robot-details-response), byte responses declare their content type, and nine routes declare their query parameters through eight new query schemas:GET /oauth/authorize,POST /oauth/register,GET /api/org/alerts,GET /api/org/health,GET /api/org/users,DELETE /api/robots/:id,GET /api/robots/:id/assets/missing, and the two move-preview routesGET /api/apps/:id/group-usageandGET /api/org/users/:id/usage, which share one schema. openapi.jsonno longer carries editor hints or unreferenced components.
[0.15.0] — 2026-09-05
Added
- The API reference is generated from the route manifest the cloud is tested
against: API Routes, API Schemas and
openapi.json. - The SDK reference is generated from the SDK’s own source, six pages under the SDK Reference.
@fleetless/sdk2.1.0: every exported member documented,InvokeOptionsexported.
[0.14.0] — 2026-09-04
Added
- The public site at fleetless.dev, the documentation at docs.fleetless.dev.
Changed
- Developer sign-up is closed during the beta; the landing page collects a waiting list.