Verentis

Files & nodes

Working with files & nodes

The operations your apps and engines use most — reading, uploading, updating and listing workspace content.

Most extensions read and write workspace content. This page summarises the operations you'll use and the scopes they require. In apps, prefer the SDK bridge; engines use the language SDK or call the gateway directly.

Common operations

Read / download a filenode.file.read

Fetch a file node's content (bytes) and its MIME type. Used by viewers, editors and any engine that reads inputs.

Upload / create a filenode.node.create

Create a new file node with content. Upload uses node.node.create, not a node.file.write scope (which doesn't exist).

Update a filenode.node.update

Replace an existing file node's content, creating a new version.

Delete a filenode.file.delete

Remove a file.

List a foldernode.node.read-all

Enumerate the nodes under a path.

Record activitynode.journal.create

Append a journal entry describing a change (history/audit).

Files are versioned

Writing new content to a file creates a new version; the node's head points at the latest. This means:

  • An update never silently loses prior content — earlier versions remain addressable.
  • Engines that transform files in place produce a clean history of runs.

See Workspaces, nodes & files for the underlying model.

Uploading files (the contract)

File uploads have one supported shape — follow it exactly; the alternatives are rejected on purpose.

POST /v{version}/files/{path} must be multipart/form-data with:

  • a file part — the content, buffered (so the server can hash and content-sniff it); and
  • an optional metadata JSON part — { "branch": "main", "idempotencyKey": null, "contentHash": null }.

Raw request bodies are rejected

A raw (non-multipart) body returns 400 Bad Request. Node must buffer and seek the content to hash it, and a non-seekable stream can't be. This is exactly how the workspace UI (useFiles.ts), the SDK (files.upload) and the platform's own file gateways upload — do the same and don't invent a new path.

Required scopes. An upload isn't a single write — it also creates parent folders and appends journal entries, and each of those is authorized against your token. A correct upload token therefore carries the full set, not just node.node.create:

node.node.create · node.file.read · node.node.update · node.journal.create

Missing node.journal.create is the classic failure: the write starts but the journal entries — whose events provision the new folder's shard configuration — can't be written, so a later lookup fails. If you assemble the token yourself (e.g. a service acting on a user's behalf), request all four scopes.

Addressing content

A node can be addressed by its id (stable, location-independent) or by its path (human-readable, changes if moved). Prefer ids when you hold a reference across operations; use paths for discovery/listing.

Example: read then write (engine)

from verentis import engine

ctx = engine.context()
src = engine.files.read(ctx.file.id)          # node.file.read

out = transform(src)

engine.files.write(                            # node.node.create / update
    "/outputs/result.json",
    out,
    mime_type="application/json",
)

Example: read a file over HTTP (standalone)

curl https://api.localtest.me/v1/nodes/<nodeId>/content \
  -H "Authorization: Bearer $TOKEN" \
  --output result.bin

Asynchronous side-effects

Some workspace activity (like path/journal updates) happens asynchronously after an upload completes. Don't assume every downstream record exists the instant a write returns; design engines and triggers to be idempotent. See Triggers & schedules.

Next

Publishing

Package and ship your extension to the marketplace.