Protocol route reference
The canonical Studio Backend Protocol route table (protocol version 1), from
@jxsuite/protocol
. The dev server is the reference implementation; any backend serving these shapes can host Studio. Optional routes back optional platform-adapter members — Studio degrades without them as described.
Session / project
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
activate |
POST |
/__studio/activate |
Bind the backend to a project root |
no |
— |
project |
GET |
/__studio/project |
Root project metadata {name, root} |
no |
— |
projectInfo |
GET |
/__studio/project-info |
Probe a directory: {isSiteProject, projectConfig?, directories?} |
no |
— |
resolveSite |
GET |
/__studio/resolve-site |
Resolve the owning site of a file path {sitePath, projectConfig?, fileRelPath?} |
no |
— |
sites |
GET |
/__studio/sites |
Enumerate site projects [{config, path}] (backs listProjects) |
yes |
The welcome screen's Projects catalogue stays hidden. |
findProject |
GET |
/__studio/find-project |
Locate a project directory by name outside the root |
yes |
openProject falls back to config-matching only. |
locateDirectory |
GET |
/__studio/locate-directory |
Resolve the absolute path of a showDirectoryPicker() folder by the id in its .jx-loc-id |
yes |
The New Project Location field loses its Browse… button and is typed by hand. |
createProject |
POST |
/__studio/create-project |
Scaffold a project at a caller-chosen destination → {root, config} |
no |
— |
starters |
GET |
/__studio/starters |
Starter templates (StarterInfo[]) |
yes |
The New Project picker offers only blank/templates. |
importSite |
POST |
/__studio/import-site |
Clone a live website into a project; streams NDJSON progress |
yes |
The New Project Import tab is unavailable. |
Filesystem
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
files |
GET |
/__studio/files |
List a directory (DirEntry[]) in stable path order; with ?glob=
, search matching files project-wide |
no |
— |
fileRead |
GET |
/__studio/file |
Read a file's text content |
no |
— |
fileWrite |
PUT |
/__studio/file |
Write a file's text content |
no |
— |
fileDelete |
DELETE |
/__studio/file |
Delete a file |
no |
— |
fileUpload |
POST |
/__studio/file/upload |
Upload binary content to a path → UploadResult {path, size?}.
path
is the answer, not an echo: a store that de-duplicates, suffixes, or normalizes names reports what it really wrote |
no |
— |
documentRaw |
GET |
/__studio/raw/ |
A project file as its own bytes, at its own URL — the mount a canvas renders against when the editor origin does not serve the site's URL space. The project-relative path is appended to this prefix |
yes |
The canvas cannot address project files by URL, so a host whose origin does not serve the site URL space renders no component $refs and no images. |
fileRename |
POST |
/__studio/file/rename |
Rename/move (+ refactor report).
from
/
to
are server-root-relative; every path in the report is relative to the active project (specs/server.md §4.1) |
no |
— |
references |
GET |
/__studio/references |
Where a file or a component tag is used (?path=&tag=, at least one) → ReferencesResult {files, filesReferencing, refsTotal} — the read side of the rename refactor's own walker, cached until the backend's watcher sees the tree move (backs findReferences).
?path=
is server-root-relative and every path in the answer is relative to the active project; a target outside that project is a 400, never a zero-result 200 (specs/server.md §4.1) |
yes |
Usage counts are hidden: no "Used on N pages" in the inspector, no Selection: Find Usages, and delete/rename confirmations state no reference count. |
locate |
POST |
/__studio/locate |
Find a file by name → {path | null} |
no |
— |
collab |
GET |
/__studio/collab |
Realtime co-editing: a WebSocket upgrade speaking the @jxsuite/collab wire envelope (one socket per project, documents multiplexed by path; y-protocols sync + project-level awareness in lib0 binary frames — see @jxsuite/collab/envelope for the frame layout and the docEpoch/doc-reset lifecycle). A plain GET (no Upgrade) answers {collab, protocols, version} as the capability probe, and
protocols
is the subprotocol negotiation: the client offers one of them as Sec-WebSocket-Protocol and the server echoes it, or the client offers none when the server advertises none, because RFC 6455 §4.1 fails a connection whose offer went unechoed. |
yes |
Realtime co-editing is unavailable; Studio edits solo with file-level saves |
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
components |
GET |
/__studio/components |
Discover components (ComponentMeta[]) |
no |
— |
cem |
GET |
/__studio/cem |
Custom-elements manifest of an npm dependency |
yes |
Dependency components lose prop/slot metadata. |
formats |
GET |
/__studio/formats |
The project's format registry entries plus a sibling
extensions
array — per enabled extension its manifest identity and project-section contributions (ExtensionsInfo[]; backs listFormats and listExtensions). |
yes |
Only .json documents open (backs listFormats); descriptor-contributed settings sections do not appear. |
projectSchemas |
GET |
/__studio/project-schemas |
The project's generated entry documents (project.schema.json / document.schema.json), PRE-BUNDLED into self-contained schemas — {project, document} (backs fetchProjectSchemas). Regenerated on demand when missing or older than project.json. |
yes |
The JSON editor falls back to the bundled core schemas (extension sections get no editor validation/completion). |
format |
POST |
/__studio/format |
Dispatch a format capability {format, action: parse|serialize, source?|doc?} |
yes |
Non-JSON documents cannot be parsed/serialized (backs formatAction). |
pluginSchema |
GET |
/__studio/plugin-schema |
Extract a $studio schema from a class source |
yes |
Plugin property panels fall back to generic JSON editing. |
codeFormat |
POST |
/__studio/code/format |
Format posted source {code, path?} → {code, errors} |
yes |
Code editors skip format-on-open/save (codeService returns null). |
codeMinify |
POST |
/__studio/code/minify |
Minify posted source {code} → {code} |
yes |
Compiled-output minification is skipped. |
codeLint |
POST |
/__studio/code/lint |
Lint posted source {code, path?} → {diagnostics} |
yes |
Code editors show no lint markers. |
Site build
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
buildSite |
POST |
/__studio/build |
Build or render the site → {routes, files, errors, mode?, url} |
yes |
Build Site reports that this target cannot build. |
previewSite |
POST |
/__studio/preview |
Preview the working tree at a route → {routes, files, errors, mode, url, reused} |
yes |
Open in Browser falls back to buildSite. |
previewOverlay |
POST |
/__studio/preview/overlay |
Publish (POST) or retract (DELETE) a document's unsaved bytes → 204 |
yes |
A live preview shows the last saved state instead of the editor's. |
Packages
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
packages |
GET |
/__studio/packages |
List dependencies (PackageInfo[]) |
no |
— |
packagesAdd |
POST |
/__studio/packages/add |
Add a dependency |
no |
— |
packagesRemove |
POST |
/__studio/packages/remove |
Remove a dependency |
no |
— |
packagesInstall |
POST |
/__studio/packages/install |
Run the package manager install |
yes |
Install/reinstall affordances are hidden; manifest-only edits still work. |
packagesNeedsInstall |
GET |
/__studio/packages/needs-install |
Whether node_modules is stale |
yes |
The install-on-open prompt never shows. |
packagesVersions |
GET |
/__studio/packages/versions |
Newest published version of each dependency (PackageVersionInfo[]) |
yes |
The Latest column stays empty and the update affordances are hidden. |
packagesSetVersions |
POST |
/__studio/packages/set-versions |
Rewrite dependency ranges and install |
yes |
Bulk version updates are hidden. |
Git
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
gitStatus |
GET |
/__studio/git/status |
Working-tree status (GitStatusResult) |
no |
— |
gitBranches |
GET |
/__studio/git/branches |
Branch list (GitBranchesResult) |
no |
— |
gitLog |
GET |
/__studio/git/log |
Recent commits (GitLogEntry[]) |
no |
— |
gitStage |
POST |
/__studio/git/stage |
Stage files |
no |
— |
gitUnstage |
POST |
/__studio/git/unstage |
Unstage files |
no |
— |
gitCommit |
POST |
/__studio/git/commit |
Commit staged (else all dirty) files |
no |
— |
gitPush |
POST |
/__studio/git/push |
Push (cloud: sync check — commits land on push) |
no |
— |
gitPull |
POST |
/__studio/git/pull |
Pull/fast-forward; 409 {conflicts} on overlap |
no |
— |
gitFetch |
POST |
/__studio/git/fetch |
Refresh remote tracking state |
no |
— |
gitCheckout |
POST |
/__studio/git/checkout |
Switch branches |
no |
— |
gitCreateBranch |
POST |
/__studio/git/create-branch |
Create a branch |
no |
— |
gitDiff |
GET |
/__studio/git/diff |
Unified diff of dirty files (or one path) |
no |
— |
gitShow |
GET |
/__studio/git/show |
File content at a ref |
no |
— |
gitDiscard |
POST |
/__studio/git/discard |
Discard working changes |
no |
— |
gitInit |
POST |
/__studio/git/init |
Initialize a repository |
no |
— |
gitAddRemote |
POST |
/__studio/git/add-remote |
Add a remote |
no |
— |
gitClone |
POST |
/__studio/git/clone |
Clone a repository |
yes |
The welcome screen hides Clone Git Repository. |
gitPr |
POST |
/__studio/git/pr |
Open a pull request → PullRequestInfo |
yes |
Studio falls back to a direct GitHub API call with the user's token. |
Data surface (connector domain owner console)
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
dataConnections |
GET |
/__studio/data/connections |
Connector connections with configured/missingSecrets/isDefault state, reachable table names, and registry-descriptor provider metadata (DataConnectionsResponse; backs dataConnections) |
yes |
The connections settings section shows no status, and data-domain actions stay hidden. |
dataConnectionTest |
POST |
/__studio/data/connections/test |
Probe a connection {connection} → DataConnectionTestResult (backs dataConnectionTest) |
yes |
The Test Connection action is hidden. |
dataPush |
POST |
/__studio/data/push |
Additive schema push {connection?, dryRun?} → DataPushResult (plan of DataPushStep[], applied, warnings/errors; backs dataPush) |
yes |
The Push Schema action is hidden; schemas deploy via
jx db push
instead. |
dataRows |
GET |
/__studio/data/rows |
Page a table (DataRowsQuery params) → DataRowsResult {rows, total, columns} (backs dataRows) |
yes |
The data grid is unavailable. |
dataInsertRow |
POST |
/__studio/data/rows |
Insert a row {table, connection?, values} → {row} (backs dataInsertRow) |
yes |
The data grid hides its add-row footer. |
dataUpdateRow |
PUT |
/__studio/data/rows |
Update a row keyed on its primary key {table, connection?, pk, set} → {row} (backs dataUpdateRow) |
yes |
Data grid cells are read-only. |
dataDeleteRow |
DELETE |
/__studio/data/rows |
Delete a row (?table=&pk=&connection=) → {ok} (backs dataDeleteRow) |
yes |
The data grid hides row deletion. |
Secrets (names only)
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
secretsList |
GET |
/__studio/secrets |
Configured secret env-var NAMES, never values (SecretsListResponse; backs listSecrets) |
yes |
Secret fields cannot show set/unset state. |
secretsSet |
PUT |
/__studio/secrets |
Write/remove secrets in the backend store (.dev.vars locally) {set?, remove?} → names (backs setSecrets) |
yes |
The secret form control renders disabled; secrets are edited in .dev.vars by hand. |
AI proxy
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
aiChat |
POST |
/__studio/ai/chat |
StreamEvent SSE chat proxy {messages, tools, systemPrompt, model} |
no |
— |
aiModels |
GET |
/__studio/ai/models |
Model catalogue (AiModelsResponse) |
no |
— |
Cloudflare publish surface
| Route |
Method |
Path |
Summary |
Optional |
Degradation |
cfProxy |
POST |
/__studio/cf/proxy |
Allowlisted Cloudflare API passthrough {path, method?, body?} (backs cfApi) |
yes |
The Publish panel explains the git-push publishing path instead. |
Failures
Every failure is an RFC 9457 problem document at
application/problem+json
. The
type
URI is what a client keys on;
detail
is the line a human reads, and
title
describes the type rather than the occurrence. The status belongs to the type — a type answerable with two statuses is two types.
error
is emitted as a deprecated alias of
detail
for one release, so a client written against the older shape keeps working. Do not write new readers against it.
The request
| Name |
Status |
Type |
Title |
Extensions |
invalidRequest |
400 |
https://jxsuite.com/problems/invalid-request |
The request was malformed or incomplete |
— |
notFound |
404 |
https://jxsuite.com/problems/not-found |
The requested resource does not exist |
— |
methodNotAllowed |
405 |
https://jxsuite.com/problems/method-not-allowed |
That method is not allowed on this route |
— |
conflict |
409 |
https://jxsuite.com/problems/conflict |
The request conflicts with the current state |
conflicts |
payloadTooLarge |
413 |
https://jxsuite.com/problems/payload-too-large |
The request body is too large |
— |
Access
| Name |
Status |
Type |
Title |
Extensions |
unauthorized |
401 |
https://jxsuite.com/problems/unauthorized |
Credentials are missing or not accepted |
— |
forbidden |
403 |
https://jxsuite.com/problems/forbidden |
The request was refused |
— |
needsInstallationAccess |
403 |
https://jxsuite.com/problems/needs-installation-access |
The GitHub App is not installed for that account |
installUrl |
pathOutsideProject |
403 |
https://jxsuite.com/problems/path-outside-project |
The path resolves outside the active project |
— |
Backend state
| Name |
Status |
Type |
Title |
Extensions |
noActiveProject |
409 |
https://jxsuite.com/problems/no-active-project |
No project is active on this backend |
— |
capabilityUnavailable |
501 |
https://jxsuite.com/problems/capability-unavailable |
This backend does not provide that capability |
— |
upstreamFailure |
502 |
https://jxsuite.com/problems/upstream-failure |
An upstream service failed |
upstream |
internalError |
500 |
https://jxsuite.com/problems/internal-error |
The backend failed to complete the request |
— |