Skip to content
Elemctl
English
Esc
↑↓navigate↵open⌘Jpreview
On this page

The platform underneath

Console API v2, the build file format and the platform behaviours worth knowing about.

This page is about the platform rather than the tool: what the Console API looks like, what a build file is made of, and how the platform behaves in the cases that surprise you the first time. The platform’s own documentation does not cover these corners, and elemctl had to learn them by running into them.

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 a client of your own.

Console API v2 contract

Common prefix: {base}/console/api/v2. Request and response bodies are JSON, apart from the build upload. Field names are in kebab-case.

The newer console reference documents Console API 2.1 as the main version, with the prefix {base}/console/api/v2.1, and keeps 2.0 beside it, working. elemctl stays on 2.0 and takes 2.1 only for what 2.0 has not got: the extensions applied to an application. A console that has no handler for a path answers 401 rather than 404, with the text Handler of HTTP request "[<method>] <path>" in application "console" not found., and that is how a server older than a method refuses it. A renewed token gets the same answer, so elemctl reads the text before the token is renewed and names the answer as a method the server does not know rather than as a failed sign-in.

Applications

  • GET /applications – list. The name query parameter exists, but the platform ignores it and returns the full list. That was verified against a live instance, so filtering by name has to happen on the client.
  • 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 things project-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) or image-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.yml enabled: 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). A build of an extension goes to the same method and is applied beside the build of the application: source.project-version-id of the card goes on naming the build of the application, and the extension shows up in extension-projects of GET /v2.1/applications/{id}/project, with the version of the build in assembly-version. elemctl verifies such an apply by that list.
  • POST /applications/{id}/project/export – the build the application runs, documented under 2.0 and 2.1 alike. No body; the answer is the archive itself, application/octet-stream with no Content-Disposition, holding the files of the build packed anew. The console takes it from the application server, and the extensions applied to the application are not in it. During an update, and after a failed one, the answer is the build uploaded for the update. A build uploaded into a project comes back with the number the server gave it in the manifest. The handler of 2.1 also takes an undocumented extension-id query parameter and then answers with the build of that extension. elemctl apps export saves the archive under {Name} {Version}.xasm.
  • POST /applications/{id}/dumps – create a dump. Body: include-users, include-binary-data (booleans), description (string).
  • GET /applications/{id}/dumps/{dumpId} – dump status.
  • GET /applications/{id}/users – the users connected to the application, each {user-list-id, user-id, presentation, is-admin, token-access-enabled}. The users of the control panel are among them: the platform connects them itself and names such a user by the login. token-access-enabled is the access of the user to the HTTP services of the application by a token; without it a call of a service with the user’s token is refused with a 500 “Token access is denied”.
  • PUT /applications/{id}/users/change-token-access – switch that access. Body: {"user-list-id", "user-id", "enable-access"}, all three required; the answer is the connection as it became. A user who is not connected to the application gets a 500 “Can’t change user token access” that does not say why. The method is documented under v2 and v2.1 alike. elemctl apps token-access finds the connection first and reads the flag back after a switch, and elemctl apps users prints the whole list.
  • GET /me – the user the credentials belong to: id, login, presentation, user-list-id.
  • GET /v2.1/applications/{id}/project – the projects the application runs: application-project, and the extensions applied to it in extension-projects, each with id, project-id, assembly-id, enabled, order, vendor-name, project-name, project-presentation, project-version and assembly-version. id is the id of the extension on the application server: the Ид of the extension’s Проект.yaml, the configuration-id its upload answered with. The console fills assembly-id of an extension with the id of its project, so that field names no build. The same path of 2.0 answers with the application project alone.
  • POST /v2.1/applications/{id}/project/{ExtensionId}/export – the build of an applied extension. No body; the answer is the archive itself, application/octet-stream with no Content-Disposition, holding the files the build was uploaded with. The segment is the id of the extension, in either case of letters. The reference calls the parameter the id of the extension project, but the project-id gets the same bare 500 “Unable to export extension … from application …” as an unknown value. A stopped application exports all the same. 2.0 has no such method. elemctl apps export-extension looks the extension up by any of its names first and saves the archive under {Name} {Version}.xasm.

Application statuses: stable Running, Stopped, Error; transitional Starting, Stopping, Initializing, Updating, Frozen, Creating, Deleting. During transitions the status field may also be empty. The reference describes status as a plain string and lists no values, so this list is what live checks have met. Deleting lasts while the DeleteApplication task runs, and then the application stays in the list under Deleted. UNKNOWN, in capitals, was met on an application whose database files were gone: it could be neither started nor deleted, because a deletion begins with an export of the data. The console gives that status to a server state of the application it has no name for, so nothing says it is final, and the client counts it as neither stable nor transitional: a wait for a status puts up with UNKNOWN for a minute in a row, then stops and names it rather than running out its whole timeout.

Technology version

  • Reading – from the technology-version field 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. The kind of a project is in project-kind: Application, Library or Extension, and Group for the group of projects the list carries beside its members.

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, or add the build to the project that carries the Ид of its Проект.yaml;
    • POST /spaces/{space-id}/projects – the same, with a new project created in that space. The space of an upload into a project goes as the optional space-id query parameter, spelled the way the reference spells it. A live check found the server reading it: an id that is not a UUID is refused with a 400 and an unknown space with a 404, while the PascalCase SpaceId the client used to send went through unread. POST /projects documents no space parameter, and the server reads neither spelling there: a new project uploaded with space-id=not-a-uuid was created all the same. So the space of a new project goes into the path of POST /spaces/{space-id}/projects, which answers an unknown space with a 404 and adds a build whose Ид already has a project to that project, the way POST /projects does. The commit the build was made from goes as commit-id, a query parameter the reference documents for POST /projects/{id}/assemblies, and the server puts it on the build card. The reference lists branch-name, commit-message and modified for the same method too, and a live check found the server taking them and showing none on the card: its branch-name names a branch of group development, not of git, and modified=1 is refused with a 500. The PascalCase CommitId, BranchName and CommitMessage are not parameters of the method at all, and the server ignores them. POST /projects documents no commit parameter, so a build that creates its project carries no commit. elemctl sends the commit alone and keeps the branch, the state of the tree and the directory of the sources in a local registry of uploads. The two uploads treat the version of the archive differently. An upload into a project, POST /projects/{id}/assemblies, does not read it at all: the server takes the base from the Версия of the project descriptor and gives the build the highest number it has ever given in that base plus one. Checked live: an archive of 1.0.0-i1 became 1.0.0-1, the next one, 1.0.0-10001, became 1.0.0-2, a 1.0.0-2 already in the project became 1.0.0-3, and 1.0.1-7 became 1.0.0-502 in a project whose descriptor says 1.0.0. A deleted build keeps its number: the build list no longer shows it, and the server counts on from it all the same. With 1.0.0-4 deleted from the top of the list, the next upload became 1.0.0-5, and with 1.0.0-50 and 1.0.0-51 deleted, it became 1.0.0-52 while the list ended at 1.0.0-8; a build deleted from the middle of the list changes nothing. The count is nowhere to be read: the project card does not carry it, the build list does not show a deleted build, and the card of a deleted build answers with a 404. That upload answers with the card of the build, and its assembly-version is the number the server handed out. An upload without a project id, POST /projects, keeps the version of the archive as it is, a suffix that is not a number included, and answers a version the project already has with a 409 (below). The number of a deleted build is free for it again, and taking it does not wind the count back: an archive of 1.0.0-2 went in under that number after the earlier 1.0.0-2 had been deleted, and the next upload into the project became 1.0.0-8, not 1.0.0-3. So the suffix of a version, the number of a CI run for instance, survives the second way and never the first. elemctl deploy names a version the server will not keep before the build and a build the server numbered otherwise right after the upload. Without an explicit version it counts the number on from the higher of the build list and the local registry of uploads, which remembers the numbers the uploads of this machine got, so a build this machine uploaded and deleted does not throw the count back. A build uploaded from elsewhere and deleted since is in neither of them, and the line after the upload then says that the number is the server’s. The response carries the id of the created build in one of the fields image-id, assembly-id or id, checked in that order. Next to it sits an artifact object naming the project the build landed in: artifact-id is the project id and opens as a project card, configuration-id is the Ид of Проект.yaml, and name is the project presentation. The console shows a project under the presentation of the build uploaded last, the Представление of its Проект.yaml, not under the manifest Name: builds of one Ид uploaded with a new Name and a new presentation each left the project card named after the presentation every time. So a build uploaded into a project under a different name silently renames that project, and uploading a build with the project’s own name puts the name back. elemctl warns about such a mismatch before uploading. An upload without a project id renames a project the same way when its Ид leads to a project that has another name. Nothing can be compared before such an upload: the server chooses the project, and no method finds a project by its Ид. So builds upload reads the project list before the upload and holds the artifact of the answer against it: it names the project the build went into, says whether the upload created it, and warns when the upload renamed it.
  • A project is identified by the Ид of Проект.yaml, the configuration-id of the answer. POST /projects therefore does not always create a project: when a live project carries that Ид, in the space of the upload first and then in any other, the build is simply added to it, whatever the vendor and the name of the manifest, and that project comes back in artifact-id. The Vendor + Name pair is a constraint rather than the key: one project of a space per pair. A live check took the two apart: builds of acme/crm, acme/crm-next and globex/crm with one Ид all went to one project, which took the name of each in turn, and a build of acme/crm with a fresh Ид got a 409. There are two ways to hit a 409 ALREADY_EXISTS. The first is uploading a version that is already there, answered with “Версия сборки … уже присутствует в группе проекта”. The second is a fresh Ид with a vendor and a name another project of the space already holds, answered with “Сборка с именем поставщика … уже зарегистрирована в другом проекте”; the project the console started for that Ид goes with the refusal. A second, throwaway project for the same sources takes a fresh Ид and a fresh pair together.
  • GET /projects/{id}/assemblies – list of builds. Each element contains assembly-version, a string like 1.0-42, and an id in id or image-id. The response is either an array or an object with the list in the items or assemblies field. The method has no pages and reports no total. limit, size, pageSize, count, top, maxResults, page, pageNumber, offset, skip, from and start are all ignored: the answers to every one of them match byte for byte, and neither the headers nor the body carry a counter or a cursor. Verified by live calls on two installations. The platform deletes builds nobody uses, and age has nothing to do with it: the vendor’s help calls this automatic deletion of unused builds. The console does it at one moment only, when an application finishes applying a build: a background job then goes over the builds of that application’s project with the same base version as the build it now runs. A release build stays, and so do a protected build, the project’s default build, a build a live application of the project runs or has had uploaded into it, and the build with the highest number of that base. The rest go, ten at most per apply. A build uploaded without a project id (POST /projects) is protected on arrival, like the build the project’s repository was created from, so no apply removes it, while a build uploaded into a project by its id (POST /projects/{id}/assemblies) is not protected. A library build no remaining build depends on goes with the builds of its group. The builds of an extension project are outside the housekeeping altogether. An extension is applied by the same update of the application, and the job that follows starts from the build the card of the application names: it goes over the application’s own project and takes the unused builds of that base there, while the extension project keeps every build, the ones no application runs included, until they are deleted by hand or go with the project. A live check agreed with the source: the application ran 1.0.0-1 of its project, 1.0.0-2 and 1.0.0-3 had been uploaded into that project by id, and the extension project held 1.0.0-1 to 1.0.0-4. Applying the extension build 1.0.0-2 took 1.0.0-2 of the application project and nothing of the extension project, and applying 1.0.0-4 after it left the extension builds 1.0.0-2 and 1.0.0-3 where they were. So a project nobody applies anything to keeps every build, and a build uploaded for later survives the apply of another one only while its number is the highest. The source of the console in the server distribution says so, and a live check on a project of nine builds agreed with every line of it. Creating an application deleted nothing in two minutes. Applying 1.0.0-3 took 1.0.0-2, 1.0.0-4 and 1.0.0-11 away before the apply returned, and left 1.0.0-1 and 1.0.0-10, both uploaded without a project id, 1.0.0-3, 1.0.0-12, the highest, and both builds of 2.0.0. Applying 1.0.0-12 took 1.0.0-3. Applying 2.0.0-1 took nothing, 1.0.0-12 included, since that apply looked at 2.0.0 alone. Seen earlier: seventeen builds of one day’s series were made and one survived, the one the application runs, while a build from two months earlier is still listed: it is the project’s first, and the upload that creates a project is an upload without a project id. So a listing is not the project’s history and not a page of it. It is what survived, and the client must say so.
  • GET /projects/{id}/assemblies/{version} – build card, DELETE .../{version} – delete. The last segment is the version, the way the method names it: assembly-version or project-version, a string like 1.0-42. The id of the card does not go there: a UUID is answered with a 404 “Assembly with version not found”. Checked live on two installations of different ages, both behave this way. An id is still an address a caller holds: the build list prints it, and an upload answers with one. So elemctl accepts both forms and looks the value up in the build list to get the version. When the address is refused with a 400 or a 404, it tries the id as the segment as well, for an installation that wants that form. The version of a build uploaded into a project is the number the server gave it, not the one of its archive. 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.

Build versions are compared 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 queries project-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>"}, optionally application: {"id": "<id>"}.
  • PUT /branches/{id} – modify. The platform uses optimistic locking, so read the card first, then send a body assembled from the current values. Those are name, kind, deletion-mark and version-stamp, which has to come back exactly as it was. Collapse source-branch and application to {"id": ...}, or to {"name": ...} when there is no id. To rebind to an application, replace application with {"id": "<new app-id>"}.
  • Branch changes are accepted by that same PUT /branches/{id} with an additional body key write-parameters: {"merge": true}.
  • DELETE /branches/{id} – delete the branch.

The tool works only with the documented Console API v2. It neither uses nor describes the internal, undocumented APIs of the platform console.

User lists

A user list holds either the users of an application or the users of the control panel. An application has a list of its own, named after it, and the application card points at it with default-user-list. The control panel has one list 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-lists creates one and 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 type Local authenticates by a password; the rest are external: OIDC, Cas, ActiveDirectory, Esia. Both writes want the whole entry.
  • GET|POST|DELETE /applications/{id}/userlists – the ids of the lists connected to an application. Note the spelling: userlists here, user-lists at the top level. The link carries no settings of its own.
  • GET /user-lists/{id}/users – the users of the list, id and login among their fields. An entry carries the access tokens of the user too, secrets included, so such an answer does not belong in a log as it is.

Worth knowing before you build on this:

  • the rules for parsing an account service response are accepted under the key userPropertiesCalculationRules. Those rules are presentation-rule, email-rule, phone-rule and response-kind. The reference’s own schema calls the field calculation-rules, and 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 an application’s authentication forms is not in the API at all, and neither is the connection setting “users of the list are connected automatically on sign-in”. Those stay in the control panel;
  • a GET of an account service returns the client_secret of 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, so 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 | 1.1
    ProjectKind: Application | Library | Extension
    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), a Release: line (empty value) is added at the end; the file extension is .xlib, for an application and an extension – .xasm.

    An extension (ProjectKind: Extension) takes ManifestVersion: 1.1, every other kind 1.0. The server picks the reader of the manifest by its version, and the reader of 1.0 knows Application and Library alone: an extension under 1.0 is refused on apply with “Unknown project kind: "Extension"”. The console writes the same pair for an extension.

  • 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, on Windows too.

Build file name: {Имя} {Version}.xasm (with a space).

Project metadata comes from Проект.yaml. It is YAML, and parsing the flat top-level “key: value” pairs is enough; skip the nested indented lines. Bilingual sources are a capability the platform declares, and a descriptor written with English keys deploys fine, so every key is read in both spellings: Имя/Name, Поставщик/Vendor, Версия/Version (base, e.g. 1.0) and ВидПроекта/ProjectKind. The value Библиотека or Library means a library, Расширение or Extension an extension; anything else means an application. When both spellings of a key are present, the Russian one wins. The service file names are bilingual too: the platform converter accepts Project.yaml and Проект.yaml, Subsystem.yaml and Подсистема.yaml. An English descriptor also carries English enumeration values, such as VisibilityScope: Global.

When the build version is not set explicitly, it is built as {base version}-{N+1}, where N is the counter from the version of the project’s latest build of the same base version. deploy takes N as the higher of the latest build in the list and the highest number the local registry of uploads remembers for the project in that base: the server counts on from the highest number it has ever given, deleted builds included. A project bumped to a new base version starts from -1 again, whatever counters the old base reached. With no last build of that base, 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. Otherwise a clean CI checkout would produce -1 every time. With no CI number either, the version is {base version}-1.

Git metadata, meaning the commit hash and the branch name, comes from the git repository that contains the project directory. When git is unavailable, the fields stay 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 and in subdirectories too: 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, .venv and all hidden ones (starting with a dot) are excluded;
  • the files .gitignore, .env, .DS_Store and *.xasm, *.xlib files are excluded.

Parsing a built archive

The reverse of a build: from a .xasm or .xlib file you get 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; only the directories tell the truth about the contents:

  • a first-level directory is a subsystem. Подсистема.yaml is optional, and 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, and 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 ВПодсистеме, and 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 behaviour you have to account for

  1. Silent rollback of build apply. When applying a build to the application fails, a compilation error for instance, the platform silently rolls the application back to the previous build and starts it. The Running status does not mean success. A reliable check of the result looks like this:
    • take the application tasks (section 4.6) with status Error or Failed whose start-date is not earlier than the moment the deploy started. Old errors from history do not count;
    • compare the actually applied version, the source.project-version of the application card, with the version of the uploaded build;
    • compare a build of an extension with the extensions of the application instead, extension-projects of GET /v2.1/applications/{id}/project: the card names the build of the application alone. The extension has to run the version of the build and be enabled. A server without Console API 2.1 cannot tell, so there the apply of an extension stays unverified rather than reported as a rollback;
    • for information, make a check GET against the application uri. Codes 401 and 403 are normal for closed applications and do not contradict success.
  2. Empty skeleton on creation. On some platform configurations an application created with a “project” source, meaning image-id set to the project id, comes out empty, with no project data. A reliable source is a specific build in project-version-id, for example the project’s latest build.
  3. Deletion with drafts. If the application’s development environment has unpublished edits, DELETE /applications/{id} returns 400 with FAILED_PRECONDITION in the body. There is no forced deletion in the API, only the control panel, and the tool must provide a clear hint.
  4. Readiness of a new application. After creation, the application sits in transitional statuses and without a uri for some time, so provide for waiting until it is ready: a uri has appeared and the status is stable. An Error status while waiting is an immediate error.
  5. Restart after apply. project/update may restart the application itself. After the call, wait until it leaves the transitional statuses. If the result is not Running, stop it unless it is already Stopped, wait for Stopped, start it and wait for Running. Reasonable waits: about 3 minutes for a stop, about 5 minutes for a start and stabilization, polling every 10 seconds or so.
  6. Error is a final status. A stable Error, after a failed apply for instance, is an immediate failure: surface the error messages of the application tasks right away. Do not stop or restart such an application, and do not keep waiting for another status: from Error it never moves to Stopped, and the wait just burns the whole time budget.
  7. Windows. Temporary files and caches go through tempfile only. Switch console output to UTF-8 with reconfigure for stdout and stderr, otherwise Cyrillic breaks.
  8. The project is identified by its Ид. See the build upload above: a project is identified by the Ид of Проект.yaml, and the Vendor + Name pair of the manifest only has to be free of the other projects of the space. An upload without a project id is not “create a new project”, it is “put it where this Ид belongs”.
  9. Deletion runs in the background and in order. DELETE /applications/{id} returns immediately, and the application lives on for a while with a DeleteApplication task. While it exists, deleting the build it was created from is rejected with a 500. The order for cleanup is: delete the application, wait until its card answers 404 or the status becomes Deleted, and only then delete the build.
  10. 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. That, together with points 8 and 9, is what elemctl probe is 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. The working application is never at risk.
  11. Signing in to a freshly created application. A new application gets its own empty user list, and the card points at it with default-user-list. Password sign-in is off, and it has no account service. So the accounts used to sign in to other applications do not work here. Connecting another application’s user list (POST /applications/{id}/userlists) together with enabling the local sign-in does not change that. 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, because a user has a failed-attempt counter.
  12. A server that is still starting. After a start or an update the server takes minutes to bring its console up: thirteen of them were seen after one update. All that time every console request, the token request included, is answered with a 404 whose text names the console application, Application "console" not found. That is the status a missing application or build gets, so only the text tells them apart. elemctl recognizes the answer in its client and says what it means: the server is starting, repeat once /console answers 302. deploy waits it out by itself, for up to --server-start-timeout seconds (900 by default, 0 turns the wait off), and goes on once the console answers. A request the console refused was never processed, so asking again is safe.
  13. A removal is applied without a question. A build whose sources no longer have a tabular part is applied as it is: the part goes, and its rows go with it. No refusal and no warning, the task ends well and the application keeps running; that was seen on a catalog with a row in the part. An attribute of a tabular part and an attribute of the object go the same way, with their values, and so does a catalog whose description is gone from the sources, with its whole table: that was seen on an empty catalog. A description made anew in its own file, under the same path and name but with a new Ид, is the same loss: a catalog with two rows came out of such an apply empty, and the task ended well. A narrowed length or a changed type recreates the data of the object, so elemctl deploy refuses those unless told otherwise, and it refuses a catalog, a document or a register removed whole the same way. A removed attribute or tabular part it names before the build and goes on: it is a deliberate edit of the sources, and the line says what the apply takes away.

Last updated on September 29, 2026

Was this page helpful?