Node (project, environment, asset, entity)
Included from Basic tierassets
JSON:API types: project, environment, asset, entity
— the resource type is the node's kind. This page covers the whole node
family.
The v2 OpenAPI document declares this resource's schema; the resource-field coverage gate holds this page to it.
Nodes form the target tree every other resource hangs off. A node is
addressed by its path — alternating collection/code pairs:
/api/projects/<code>[/environments/<code>][/assets/<code>][/entities/<code>…]. The
authorisation path is the same string with /api stripped
(e.g. /projects/demo/environments/dev), and it is what RBAC rules and the
node filters elsewhere address. Codes are stored stripped and lowercased
(^[a-z0-9_]+$, max 50).
Endpoints
| Verb | Path | Behaviour |
|---|---|---|
GET | /api/projects | Project index (read-filtered per node). |
POST | /api/projects | Create a project — update on /projects and on the new node's own path, 404-on-deny. |
GET | /api/projects/{project_code} | Project show. |
PATCH/PUT | /api/projects/{project_code} | Update the bare project — same permit list and authorisation as the node-path update below. |
DELETE | /api/projects/{project_code} | Soft delete (the node-path catch-all cannot address a bare project). |
GET | /api/projects/{project_code}/{node_path...} | The node-path dispatch: an even pair count shows one node, an odd count indexes a child collection (filter[archived_eq] supported); a recognised trailing resource segment (changes, workflows, triggers, maintenance, bookmarks, …) dispatches to that resource instead. Shows apply the default include list project, bookmarks, template, template_version, children, children.template, children.template_version. |
POST | …/environments | …/assets | …/entities | Create a child node under the resolved parent. A kind the parent cannot hold is the named 422 (Cannot create an environment under an entity — an entity parents entities only). |
PATCH/PUT | <node path> | Update (name, description, archived, template_version — the last ignored on non-assets). Authorised as update on the node's own path. |
DELETE | <node path> | Soft delete the subtree. |
POST | <node path>/restore | Undo a soft delete; answers the restored node. |
GET/POST | <node path>/destroy | GET previews the per-type destroy manifest; POST is the irreversible cascade, valid only on a soft-deleted node. |
GET | <node path>/export[?with_history=true] | Streams the credential-free zip archive of the subtree. |
Attributes
Common to every kind:
| Attribute | Type | Meaning |
|---|---|---|
code | string | The path segment. Immutable (not in the update permit list). |
name | string | Display name. |
description | string, nullable | Null when never set. |
archived | boolean | Archive flag (update verb). |
created_by | string | Username of the creating user. |
maintenance | object, omitted when absent | The maintenance badge, present while an active window covers the node (its own, or an ancestor's — the innermost wins) or a scheduled one covering it starts within the server's badge horizon (api.maintenance_badge_horizon_minutes, default 60): {reason, until (nullable), window_path, scheduled, starts_at}. until falls back to a scheduled window's hard end; scheduled is true and starts_at set only on the imminent form. window_path names the quiesced node, so an inherited badge points at the window itself. Advisory: a failed read never fails serialization. |
Per-kind additions:
| Kind | Extra attribute | Meaning |
|---|---|---|
project | — | No kind-specific attribute. The former project_type (Standard/Enterprise) is retired: every project holds environments and assets, and its environments hold assets. project_type is still accepted on create and update and ignored, so older clients keep working; it no longer appears in responses. |
asset | task_active | Whether a node background task is currently active on the node. |
asset | mintmodel_valid | Show-only (the shown node, never lists or includes): whether a valid concretised MintModel is stored for the pinned template version's commit. |
asset | mintmodel_invalid_reason | Show-only; null when valid, else no_mintmodel, not_concretised, generation_failed or template_version_changed (the upstream properties_changed recheck needs a properties converge this read tier does not compute). |
asset | erb_file_content | Show-only; null when the pinned template version has no MintModel, else the MintModel/templates/assets/<template code>.json.erb document at the version's pinned commit (the GUI's ERB Template tab). A failed git read serves the error message as the value — the show never fails on it. |
environment | — | No kind-specific attribute. |
entity | — | No kind-specific attribute. |
An entity is the minimal node: no template and no automation — no
actions, changes, MintModel, background tasks, workflows or scheduled
activities, so those sub-resources (and a change targeting the entity,
answered with the named 422) are not found on an entity path. It lives
under a project, environment, asset or another entity (the entities
collection segment) and parents entities only. It keeps the basic node
constructs: properties, settings (converged_properties and
converged_settings work, with an entity converge layer), bookmarks,
maintenance windows, metadata, asset owners, archive and the whole
lifecycle below. Entities are core — no entitlement and no count limit.
The typed v2 shape and the bulk create are
resources/entity; the CLI is opschain entity
(opschain docs cli/entity).
Registered hosts are their own resource with their own API family: an
infrastructure node lives in an infrastructure/ collection inside
the tree but is served as
resources/infrastructure_node — see also the
infrastructure nodes reference.
Relationships
| Name | Kinds | Meaning |
|---|---|---|
properties / settings | all | To-one properties/settings document, with related links /api/properties/<id>, /api/settings/<id>. |
parents | all | To-many: the ancestors, root first (empty for a project). |
parent / project | non-roots | To-one: the immediate parent and the root project (project ref carries a related link). |
bookmarks | all | To-many bookmark refs. |
children | project, environment, entity | To-many, filtered to children the principal can read; refs the source cannot resolve stay. An entity's children are its child entities; entities never appear in a project's, environment's or asset's children — list them through the …/entities collection. |
template / template_version | asset | The pinned template and version (with related links). |
latest_node_background_tasks | asset | To-many task refs. |
Links
self and bookmarks always; changes (all kinds); actions (asset —
catalog entries backed by actions.rb or MintModel — only applicable to
existing MintPress customers and those using the MintPress SDK or the
packs:mintpress feature — additionally carry
runner_requirement: "ruby", marking that they execute as
opschain-action tasks and need a Ruby-capable runner (visibility
only);
scheduled_activities (kinds that can have changes), plus per-child-kind
collection links (environments, assets; entities on an entity),
plus project-only git_remotes, templates,
workflows/workflow_runs — each of these gated by read on the
corresponding …/<section> rule path; asset-only converged_properties
(gated), node_background_tasks, version_history, mintmodels,
mintmodel_history; parent on non-roots.
Meta
can_have_changes, can_have_children, can_have_properties (always true),
can_have_settings (always true), can_have_templates, requires_template,
child_node_types, can_have_workflows, can_have_actions, and an
authorisations object with per-section decisions for the current principal
(e.g. properties {read, update}, changes {read, execute, delete},
secrets {settings, properties, mintmodel} — superuser-only — and
self {update, delete}).
Lifecycle: delete, destroy, restore, export
The opschain admin nodes CLI family (delete, restore, destroy,
export, import) drives these lanes.
- Soft delete (
DELETE) tombstones the subtree: hidden from every list and route, path reserved, new work refused, fully reversible. It refuses while runs are active beneath (named 422) and emitsapi:nodes:deleted. - Restore (
POST …/restore) undoes exactly that delete and emitsapi:nodes:restored. Only the root of a delete resolves — a node tombstoned as part of an ancestor's delete is restored through that ancestor. - Destroy (
POST …/destroy) is valid only on a soft-deleted node — two deliberate steps; a live node answers the named 422 ("…Destroying is a two-step operation…"). It cascades every stored row beneath except the events table (the audit persists; chains keep rendering), releases unreferenced artifact blobs and emitsapi:nodes:destroyedwith the manifest. The response (and the GET preview) is anode_destroy_manifestresource:{path, destroyed, counts (per-type row counts), note}— the preview'snoteis the standing export reminder. - Export (
GET …/export) streams a credential-free zip;with_history=trueadds the properties/settings version trails.
Every lifecycle verb authorises read plus the node's deletable rule
action, 404-on-deny.
A node left behind by a promote or demote is archived
(conversion_state promoted or demoted, with a converted_to link)
and otherwise an ordinary node: it can be edited, unarchived, deleted and
restored; only a further conversion waits until it is unarchived — see
resources/entity ("Promote and demote").
The lifecycle events name the node's kind; an entity's create, update
and delete request events (api:nodes:create, api:nodes:update,
api:nodes:destroy) carry kind: entity in their data as well, so an
audit query can tell entities apart from the other kinds.
API v2
The per-object estate services serve the same rows over /api/v2
(opschain docs api-v2), authorised exactly as the v1 lanes
above (404-on-deny) and audited with the same api:projects:* /
api:nodes:* events:
ProjectService—ListProjects/GetProject(code)/CreateProject/UpdateProject/DeleteProject(the soft-delete lane; restore/destroy/export remain v1-only).EnvironmentService—ListEnvironments(project_code)and the node-path-addressedGetEnvironment/UpdateEnvironment/DeleteEnvironment, plusCreateEnvironment(project_code, …).EntityService—ListEntities(parent_path)(keyset, code-ascending, with code/search/archived filters),GetEntity(node_path),CreateEntity, the set-basedBulkCreateEntities(at most 1000 per batch, per-item refusals),UpdateEntityandDeleteEntity— see resources/entity. Entities are left out ofNodeService.GetNodeHierarchyunless the request setsinclude_entities, which requiresroot_pathand nests the entities under the root (itself possibly an entity) at any depth.AssetService—ListAssets(parent_path)rows carry the pinned template identity andtask_active— and the pinned version'stemplate_version_state/template_version_has_mintmodeland the row'slatest_background_tasks(the v1 asset-list include set);GetAsset(node_path)is the server-shaped composite (MintModel validity, readable child assets, the latest background tasks, and the show-onlyerb_file_content);ListTemplateVersionHistory(node_path)serves the templated node'sversion_historywindows (a non-templated node refuses with the v1 message);CreateAssetcrosses the licenceassetsgate;UpdateAsset.template_versionrepoints the pinned version.
Lists take a typed archived filter (the v1 filter[archived_eq]) and
cursor-paginate code-ascending; list/get responses carry the v1
meta.authorisations pair for the governing rule path. Since
the three gets also carry the full v1 node-show meta — the node-kind
shape flags and the per-subresource authorisation sections, rendered
through the one rule the v1 serializer uses — beside the gap-C
secrets_authorisations triple. The estate read model is PostgreSQL-only:
the embedded store answers the typed
ERROR_REASON_STORE_POSTGRES_REQUIRED posture.
Related pages
opschain docs maintenance-windows— the badge's source of truth.opschain docs resources/node_comparison— comparing two nodes.opschain docs resources/mintmodel— the asset'smintmodels/mintmodel_historynode-path family and the generate trigger.