fleetlessfleetlessdocs
Start/Release Notes

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_unavailable and 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 granted joint_states datapoint. 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) and plan_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/plan reads the plan, its limits against usage and any pending change. PUT /api/org/plan/change moves the organisation to a lower plan or to Basic, and DELETE /api/org/plan/change withdraws 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 quota quota_exceeded stays 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_limit with store_bytes, used_bytes and size_bytes.
  • A federated sign-in at the plan’s app-user limit answers plan_limit, not quota_exceeded, so your app can tell that the plan is full. quota_exceeded now means only the app-user protection ceiling. An app on an SDK that predates this reads plan_limit as unexpected_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 answer 409 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, optional or required for 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_url or mcp_login_url unset 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/mcp only takes mcp_enabled now; mcp_login_url moved to .../auth-config/urls, next to the new app_url. See API Reference.

[0.23.4] — 2026-10-01

Added

  • The SDK reference lists the details of cancel_rejected. SDK · Client gains cancelRejectedDetails, CancelRejectedDetails, CANCEL_RETURN_CODES and CancelReturnCode, so a refused cancel is parsed and its return codes compared by name, and API Schemas the cancel-rejected-details field table. JobOrigin is 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, not lost. “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, lost included. A restart on either side is no longer a silent lost: a cloud restart leaves the jobs in flight unknown until their robots answer. A goal nobody at Fleetless started shows up the same way, as a live-only job with origin: 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 gain FLEETLESS_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 CancelGoal return code per goal, answers an accepted cancel with the job still running (its end follows as an update), refuses one the server rejected with 409 cancel_rejected naming every goal, and answers 504 bridge_timeout when the robot does not answer in time. Every cancel that reached the robot writes one job.cancelled audit 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 ends lost after 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 every Job.error.code, action_server_lost included. Both need bridge 5.0.0 or later; options.patienceMs bounds 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 lowercase fleetless, brand marks read fleetless to 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-preview counts what would go — app users, invitations, roles, server keys, OIDC providers and identities — and DELETE /api/apps/:id removes them in one cascade. Owner tier. There is no force flag: 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-config is 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. GET is unchanged and still answers the whole document. The MCP tool follows: console_app_auth_config_put becomes 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. Migration 0007 adds robots.protocol_version and the protocol_notices table. 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/rotate mints 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/assets and let the next sync refill it. Migration 0009 computes each robot’s counter from its existing assets.
  • An asset sync’s state now checks what the store actually holds, not only what the bridge reported. stored and announced on the sync status count the announced files against what arrived; a sync the bridge called clean is marked failed, with an upload_failed entry 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-state chooses the whole-message sensor_msgs/msg/JointState datapoint that drives the joints; a publish that drops or narrows that datapoint clears the choice.
  • @fleetless/sdk’s createClient accepts a credentials option. A CredentialSource answers 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 with tokenStore and serverKey.

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_bandwidth section of fleetless.yaml, whose eight keys, their bounds and the rule that exit_lag_ms stays at or below enter_lag_ms are on the Low Bandwidth reference page; a datapoint says low_bandwidth: keep to keep its rate. Every robot shows the mode as bridge_state.low_bandwidth, and a bridge older than 4.0.0 ignores the section and reports false.
  • 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 and Rotate for 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/JointState datapoint that drives the joints, or clears the robot’s asset store.

Removed

  • bridge_pressure. Read bridge_state.low_bandwidth instead. The three FLEETLESS_UPLINK_* variables and the /fleetless/uplink_kbps topic are gone with it (bridge 4.0.0). Migration 0008 deletes every stored bridge_pressure sample 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_bytes is 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 kind other is 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 read grant_types_supported stop sending you to the login several times a day.
  • Four console tools. console_app_auth_config_get and console_app_auth_config_put read and replace an app’s auth configuration (a strict replace, audited); console_app_invitations_list and console_app_invitation_revoke show and withdraw an app’s pending invitations. Sixty-three console_* tools.

Changed

  • Migration 0006 adds 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_code or refresh_token.

[0.19.0] — 2026-09-17

Added

  • Two discovery routes for app users. GET /api/client/robots lists the robots the caller reaches, and GET /api/robots/:id/datasheet describes what their role lets them do on one — the REST twins of the MCP tools robots_list and robot_describe, from the same server code. @fleetless/sdk 3.1.1 exposes them as client.robots, and ships the jobs.history the reference had described without the package carrying it.
  • An app starter. fleetless/app-starter is a public template repository: the Nuxt UI dashboard with every app-user screen built on @fleetless/sdk 3.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-Origin was 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’s allowed_origins on 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_ms is 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. ROLE selects api, mcp, auth or worker; 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: sticky since 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_session access 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 a client_id, the endpoint reads the standing consent on every request beside the account checks it already made, and a withdrawn client is answered 401 with the WWW-Authenticate challenge 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_session access token now carries client_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 the 401’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/users now lists the org’s Fleetless users — the team who reach the console — and it answers fleetless-user-list-response, not org-user-list-response. Its group_id filter 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-usage and POST /api/org/users/:id/move-group.
  • App assignments are gone: GET /api/org/users/:id/assignments, PUT|DELETE /api/org/users/:id/assignments/:appId and GET /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_access on 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/resource and GET /mcp-stub/resource/:appIdentifier are 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/:clientId and DELETE /api/apps/:id/oauth-clients/:clientId/consent are removed, as is apps.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/grants and DELETE /api/client/grants/:client_id. The only consent left is the MCP one, which has its own routes (see Added). oauth_consent_grants is dropped, so no existing grant survives the migration: every OAuth client an app user had approved is approved no longer.
  • POST /api/client/logout answers 204 with no body. It used to return an idp_logout object 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 same 204.
  • The central MCP login is password-only. GET /mcp/oauth/idp-callback is 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.kind is now developer | app_user | server_key. The end_user case is gone from live answers. It survives in two places on purpose, so old rows still render: auditActor.kind and the job invocationActor.kind still 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-policy and move-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_used and mcp_access_denied. The two invitation codes never had a producer; POST /api/client/invitations/accept answers 410 token_spent to an unknown, expired, revoked and already-spent token alike, because telling them apart says whether a token ever existed.
  • weak_password stays in the enum and has no producer. A password under twelve characters is part of 400 validation_error and names the password field, 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.dev now 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 404 on every unauthenticated per-app path — metadata, register, authorize and 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.beginHostedLogin and auth.completeHostedLogin are removed, with BeginHostedLoginOptions, HostedLoginRequest and CompleteHostedLoginOptions. Use auth.beginOidcLogin / auth.completeOidcLogin, or auth.login for a password.
  • client.grants is removed (GrantsApi, grants.list, grants.revoke). The MCP consents an app user can withdraw are auth.listMcpGrants() and auth.revokeMcpGrant(clientId).
  • auth.passwordResetUrl(), LogoutResult, the no_hosted_login_attempt error code and HttpClient.requestOAuth are 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 own allowed_origins, rate-limited per app, address and IP: POST register, POST verify-email, POST resend-verification, POST password/reset, POST password/reset/confirm and POST invitations/accept, alongside the existing login, refresh, logout, me and password/change. register, resend-verification and password/reset answer 202 with 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/:userId and POST /api/apps/:id/users/:userId/reset-password; invitations at GET|POST /api/apps/:id/invitations, DELETE /api/apps/:id/invitations/:invId and POST /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, plus POST …/:kind/preview and POST …/: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-providers and GET|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 through GET /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/grants and DELETE /api/client/mcp/grants/:clientId from the app user’s own app, and GET /api/apps/:id/users/:userId/mcp-grants and DELETE /api/apps/:id/users/:userId/mcp-grants/:clientId from 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_url with an interaction id. The app reads it with GET /api/client/mcp/interactions/:id and decides with POST …/approve or POST …/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_at records that an address was proved, which status could not carry: unblocking an account now restores the state it had rather than guessing active.

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_id and email.
  • Team invitations for Fleetless users keep their routes and their seven-day life; only their vocabulary changed (team-invite, not user-invite).
  • Reissuing an app invitation answers a new id. The old one stops working at once.
  • mcp.fleetless.dev accepts 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/:id accepts ?force=true or no force parameter; any other value (?force=1 used to delete the robot un-forced) now answers 400 validation_error.
  • history is a reserved slug: a configuration naming an action, service, publisher, datapoint or camera history is refused, because GET /api/robots/:id/jobs/history would 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 routes GET /api/apps/:id/group-usage and GET /api/org/users/:id/usage, which share one schema.
  • openapi.json no 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/sdk 2.1.0: every exported member documented, InvokeOptions exported.

[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.