The platform underneath
Console API v2, the build file format and the platform behaviours worth knowing about.
Everything on this page is about the platform, not about the tool: what the Console API
looks like, what a build file is made of, and how the platform behaves in cases that surprise
you the first time. It is written down because the platform’s own documentation does not cover
these corners, and because elemctl had to learn them the hard way.
You do not need this page to use the tool. It is here for the times when something behaves oddly and you want to know what happens underneath – or when you are writing your own client.
Console API v2 contract
Common prefix: {base}/console/api/v2. Request and response bodies are JSON (except for build upload). Field names are in kebab-case.
Applications
GET /applications– list. Thenamequery parameter exists but the platform IGNORES it and returns the full list (verified against a live instance) – name filtering must be done client-side.GET /applications/{id}– card. Significant response fields:id,status,uri(address of the running application),error(error text, if any),technology-version,date-updated,display-name,publication-context,source(an object with source information, containing among other thingsproject-version– the version of the applied build).POST /applications– create. Body:source– the object{"type": "repository"}plus exactly one of the keys:project-version-id(id of the source build) orimage-id(project id);display-name,publication-context– publication name and path;development-mode– boolean, whether to create a development environment;- optional
space-id,technology-version.
DELETE /applications/{id}– delete.PUT /applications/{id}/status/start– start.PUT /applications/{id}/status/stop– stop.POST /applications/{id}/actions/debug– data for a debug session (ApplicationDebugInfo:{"debug-token": ..., "debug-address": ...}). The request body is empty; requires debugging enabled on the server (config/debug.ymlenabled: true).POST /applications/{id}/project/update– apply a build to the application. Body:{"source": {"type": "repository", "image-id": "<build id>"}}or{"source": {"type": "repository", "project-id": "<id>", "assembly-version": "<version>"}}(assembly-version is optional).POST /applications/{id}/dumps– create a dump. Body:include-users,include-binary-data(booleans),description(string).GET /applications/{id}/dumps/{dumpId}– dump status.
Application statuses: stable Running, Stopped, Error; transitional Starting, Stopping, Initializing, Updating, Frozen, Creating. During transitions the status field may also be empty.
Technology version
- Reading – from the
technology-versionfield of the application card (a dedicated read endpoint is not present in all platform versions – do not use it). - Update:
POST /tasks/group-tasks/update-applications-technology, body{"technology-version": "<version>", "applications": ["<app-id>"]}. Returns a group task; its status –GET /tasks/group-tasks/{taskId}.
Spaces and projects
GET /spaces– list of spaces.GET /projects– list of projects;GET /projects/{id}– card;DELETE /projects/{id}– delete.
Project builds (assemblies)
- Uploading a build file – a binary POST (Content-Type
application/octet-stream, body – the file bytes):POST /projects/{id}/assemblies– add a build to an existing project;POST /projects– create a new project from a build. The only query parameter (optional):SpaceId; note that its name is in PascalCase. The method has NOBranchName/CommitId/CommitMessageparameters: the Console API reference does not list them, and the server ignores them when sent (a direct POST with a real hash answerscommit-id: null) - the commit on a build card only comes from the project’s link to its repository. The response contains the id of the created build in one of the fields:image-id,assembly-id, orid(check in this order), plus anartifactobject naming the project the build landed in:artifact-id(the project id – it opens as a project card),configuration-id(theИдofПроект.yaml) andname(the project presentation). The console shows a project under the name of the last uploaded build (the manifestName), so a build uploaded into a project under a different name silently renames that project; uploading a build with the project’s own name puts the name back.elemctlwarns about such a mismatch before uploading.
- A project is identified by the
Vendor+Namepair of the manifest, not by theИдofПроект.yaml.POST /projectstherefore does not always create a project: when the pair is already known, the build is simply added to the project that owns it, and that project comes back inartifact-id. Two ways to hit a 409ALREADY_EXISTS: uploading a version that is already there (“Версия сборки … уже присутствует в группе проекта”) and trying to register the same vendor+name under another project (“Сборка с именем поставщика … уже зарегистрирована в другом проекте”) – the second one is not worked around by generating a freshИдeither. A second, throwaway project for the same sources can therefore only be had by renaming them. GET /projects/{id}/assemblies– list of builds. Each element containsassembly-version(a string like1.0-42) and an id (idorimage-id). The response may be either an array or an object with the list in theitemsorassembliesfield.GET /projects/{id}/assemblies/{assembly-id}– build card;DELETE .../{assembly-id}– delete. The API addresses a build ONLY by UUID: a version gets a 400 “Version is not a valid UUID”. The client must also accept a version (that is what the user sees), resolving it to an id via the build list (assembly-version/project-version); note that the platform renumbers the manifest version on upload. Deleting a build is rejected with a 500 while an application created from it is still alive; once that application has really disappeared, the very same request succeeds (a build that only took part in an apply, even a rolled-back one, deletes without a fuss).
Comparing build versions: by the numeric suffix after the last hyphen (1.0-10 is newer than 1.0-9; lexicographic comparison gives the wrong order).
Development environment branches
GET /branches– list; optional queriesproject-id,name.GET /branches/{id}– card. Fields:name,kind,project,application,source-branch,deletion-mark,version-stamp.POST /branches– create. Body:name,kind: "development",project: {"id": "<id>"}, optionallyapplication: {"id": "<id>"}.PUT /branches/{id}– modify. The platform uses optimistic locking: first read the card, then send a body assembled from the current values –name,kind,deletion-mark,version-stamp(must be returned as is),source-branchandapplication– collapsed to{"id": ...}(or{"name": ...}if there is no id). To rebind to an application, replaceapplicationwith{"id": "<new app-id>"}.- Accepting branch changes (merge) – the same
PUT /branches/{id}with an additional body keywrite-parameters: {"merge": true}. DELETE /branches/{id}– delete the branch.
The tool works ONLY with the documented Console API v2. Internal (undocumented) platform console APIs are not used and not described.
User lists
A user list holds the users of an application (an application has one of its own, named after it – the application card points at it with default-user-list) or of the control panel (one per installation).
GET /user-lists– the list:id,presentation,space-id; there is no server-side name filter.GET /user-lists/{id}– the full card.POST /user-listscreates one, and it wants the WHOLE card: an incomplete body is answered with a 500 and the misleading text “Failed to parse json”.DELETE /user-lists/{id}removes it.GET|PUT /user-lists/{id}/settings/self-registration–{enabled, phone-required, email-required}, the control panel’s “allow users to register themselves”.GET|POST /user-lists/{id}/settings/account-services-settings,PUT|DELETE .../{account-service-id}– the account services. An entry is{account-service-id, account-service-type, local-id, enabled, create-user-on-auth, additional-settings}; the typeLocalauthenticates by a password, the rest (OIDC,Cas,ActiveDirectory,Esia) are external. Both writes want the whole entry.GET|POST|DELETE /applications/{id}/userlists– the ids of the lists connected to an application. Note the spelling:userlistshere,user-listsat the top level. The link carries no settings of its own.
Worth knowing before you build on this:
- the rules for parsing an account service response (
presentation-rule,email-rule,phone-rule,response-kind) are accepted under the keyuserPropertiesCalculationRules, although the reference’s own schema calls the fieldcalculation-rules– that spelling is answered with a 400. A GET never returns the rules: the setting is write-only, and an API client cannot confirm it applied; - the composition of the authentication FORMS of an application and the connection setting “users of the list are connected automatically on sign-in” are not in the API at all – those stay in the control panel;
- a GET of an account service returns the
client_secretof an OIDC client in cleartext, so such answers do not belong in logs and reports as they are; - an unknown path of the Console API is answered with a 401 carrying “Handler of HTTP request … not found”, not a 404 – when probing for the surface, that is the sign that a method does not exist.
Application tasks
GET /tasks/application-tasks – list of tasks for all applications (there is no server-side filter – filter on the client). Task fields: id, application-id, status (including Error, Failed), operation-type, error-message, start-date (ISO 8601, may end with Z).
Build file format (.xasm / .xlib)
A build file is a ZIP archive (deflate):
-
at the root,
Assembly.yaml– the manifest, flat key-value pairs:ManifestVersion: 1.0 ProjectKind: Application | Library Vendor: <vendor> Name: <project name> Version: <version, e.g. 1.0-42> Created: <UTC, format YYYY.MM.DD HH:MM:SS> BranchName: <git branch name> CommitId: <commit hash>For a library (
ProjectKind: Library), aRelease:line (empty value) is added at the end; the file extension is.xlib, for an application –.xasm. -
then the project files at paths
{vendor}/{name}/...– relative to the repository root. The project directory must follow the scheme{repo}/{vendor}/{name}/Проект.yaml. Path separators in the archive are forward slashes (including on Windows).
Build file name: {Имя} {Version}.xasm (with a space).
Project metadata – from Проект.yaml (YAML; parsing flat top-level key: value pairs is sufficient, skip nested indented lines). Bilingual sources are a declared platform capability – a descriptor written with English keys deploys fine – so every key is read in both spellings: Имя/Name, Поставщик/Vendor, Версия/Version (base, e.g. 1.0), ВидПроекта/ProjectKind (the value Библиотека/Library means a library, otherwise an application). The Russian spelling wins when both are present. The service file names are bilingual too: the platform converter accepts Project.yaml/Проект.yaml and Subsystem.yaml/Подсистема.yaml, and an English descriptor carries English enumeration VALUES as well (VisibilityScope: Global).
Build version, if not set explicitly: {base version}-{N+1}, where N is the counter from the version of the project’s latest build. Without a last build, the suffix comes from the CI run number in the environment – the first numeric value of CI_PIPELINE_IID, GITHUB_RUN_NUMBER, BUILD_NUMBER (in that order) – so a clean CI checkout does not produce -1 on every run; with no CI number either, the version is {base version}-1.
Git metadata (commit hash, branch name) – from the git repository containing the project directory; if git is unavailable, leave them empty.
File selection for the archive:
- only these extensions are included:
.yaml .xbsl .xbql .md .txt .json(sources),.png .svg .jpg .jpeg .gif .webp .ico(images),.css .htm .html .js .woff .woff2 .ttf .eot(web resources); - the extension filter does NOT apply inside a
Ресурсыdirectory (at any level, subdirectories included): by the platform’s own documentation a resource is an arbitrary file, so everything there goes in; - the description files of a SOAP service client are included wherever they lie:
<Client>.Wsdl.<n>and<Client>.Xsd– the platform keeps them next to the project element and reads them by name; - the directories
.git,.claude,.github,__pycache__,node_modules,.venvand all hidden ones (starting with a dot) are excluded; - the files
.gitignore,.env,.DS_Storeand*.xasm,*.xlibfiles are excluded.
Parsing a built archive
The reverse of a build: given a .xasm/.xlib file – the manifest, the project properties from its Проект.yaml inside the archive, and the contents. It is needed to attach a library to a project without unpacking its sources.
The layout inside a project (directories are the only source of truth about the contents):
- a first-level directory is a subsystem;
Подсистема.yamlis optional (a library subsystem may have none at all), so it cannot be relied upon when looking for subsystems; - a nested directory of a subsystem is a package; a package has no description file, every directory contributes a name segment;
- the qualified name of a type:
{vendor}::{name}::{subsystem}[::{package}]::{TypeName}. The same name without the last segment is whatИспользованиеandимпортtake.
Only types with ОбластьВидимости: Глобально are visible outside, in the project that attached the library (the default is ВПодсистеме, the global scope is written explicitly).
Compatibility is checked against the РежимСовместимости property of Проект.yaml. The ВерсияТехнологии property does not exist in Проект.yaml – it belongs to the body of the Console API request that creates an application, not to the project file.
Platform behavioral specifics (must be accounted for)
- Silent rollback of build apply. If applying a build to the application fails (e.g., a compilation error), the platform silently rolls the application back to the previous build and starts it – the
Runningstatus does NOT mean success. A reliable check of the deploy result:- application tasks (section 4.6) with status
Error/Failedwhosestart-dateis not earlier than the moment the deploy started (do not count old errors from history!); - comparison of the actually applied version (
source.project-versionof the application card) with the version of the uploaded build; - for information – a check GET against the application
uri(codes 401/403 are normal for closed applications and do not contradict success).
- application tasks (section 4.6) with status
- Empty skeleton on creation. Creating an application with a “project” source (
image-id= project id) on some platform configurations yields an empty application without project data. A reliable source is a specific build (project-version-id), for example the project’s latest build. - Deletion with drafts. If the application’s development environment has unpublished edits,
DELETE /applications/{id}returns 400 withFAILED_PRECONDITIONin the body. There is no forced deletion in the API – only the control panel; the tool must provide a clear hint. - Readiness of a new application. After creation, the application is in transitional statuses and without a
urifor some time – provide for waiting until ready (aurihas appeared and the status is stable). AnErrorstatus while waiting is an immediate error. - Restart after apply.
project/updatemay restart the application itself. After the call, wait until it leaves the transitional statuses; if the result is notRunning– stop it (if notStopped), wait forStopped, start it, wait forRunning. Reasonable timeouts: waiting for stop ~3 min, for start/stabilization ~5 min, polling every ~10 s. - Error is terminal. A stable
Error(e.g., after a failed apply) is an immediate failure: surface the error messages of the application tasks right away. Do not try to stop/restart such an application and do not keep waiting for another status – fromErrorit does not transition toStopped, and the wait just eats the whole timeout. - Windows. Temporary files and caches – only via
tempfile; switch console output to UTF-8 (reconfigurefor stdout/stderr), otherwise Cyrillic breaks. - The project is identified by vendor and name. See the build upload above: the identity of a project is the
Vendor+Namepair of the manifest. An upload without a project id is not “create a new project”, it is “put it where this pair belongs”. - Deletion is asynchronous and ordered.
DELETE /applications/{id}returns immediately, and the application lives on for a while with aDeleteApplicationtask. While it exists, deleting the build it was created from is rejected with a 500. The order for cleanup: delete the application, wait until its card answers 404 (or the status becomesDeleted), and only then delete the build. - Compilation is the server’s, and it happens on apply. A local build only packs an archive: the syntax, the types and the visibility of the sources are checked by the server compiler when a build is applied or an application is created out of it. There is no separate “compile” endpoint. Together with points 8 and 9 this is what
elemctl probeis built out of: the sources go to their own project as a build with a one-off version, the compiler is reached through a throwaway application, and both are removed afterwards – so the working application is never at risk. - Signing in to a freshly created application. A new application gets its OWN, empty user list (the card points at it with
default-user-list), password sign-in in it is off, and it has no account service. So the accounts used to sign in to other applications do not work here – and connecting another application’s user list (POST /applications/{id}/userlists) together with enabling the local sign-in does NOT change it. What does work is a CONTROL PANEL account: the platform connects its users to the application itself, and they sign in right away. Worth knowing before raising a stand for a task: the way in does not follow from the card, and trying accounts is a bad idea – a user has a failed-attempt counter.