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 filefile.readFetch a file node's content (bytes) and its MIME type. Used by viewers, editors and any engine that reads inputs.
Upload / create a filefile.writeCreate a new file node with content. Upload uses file.write.
Update a filefile.writeReplace an existing file node's content, creating a new version.
Delete a filefile.deleteRemove a file.
List a folderfile.readEnumerate the nodes under a path.
Record activityfile.writeAppend 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
filepart — the content, buffered (so the server can hash and content-sniff it); and - an optional
metadataJSON 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 creates the file and any missing parent folders, and reads them back, so a correct upload token carries both scopes:
file.write · file.read
If you assemble the token yourself (e.g. a service acting on a user's behalf), request both 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) # file.read
out = transform(src)
engine.files.write( # file.write / 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
Package and ship your extension to the marketplace.