Spaces

API

A space is a general-purpose file store. This page lists every endpoint on the spaces surface: space management, the content file primitives and the change journal.

AI assisted, human approved — novem uses AI to review and keep our documentation up to date.

The spaces endpoints follow the same conventions as the rest of the novem API: the filesystem metaphor, the HTTP verbs, and the r/w/d permission model. See the API overview for the general rules and the spaces guide for what a space is.

Note: Spaces are an early-access feature. Every endpoint on this page is gated behind a per-account feature flag and returns 404 unless it's enabled for your account.

All paths below are shown in their canonical form, /v1/users/:user/code/spaces/.... The shorthand /v1/code/spaces/... addresses the same tree on your own account. As everywhere on the API, OPTIONS on any path returns the verbs your token may use on it.

VerbPathDescription
GET/v1/users/:user/code/spacesList spaces
PUT/v1/users/:user/code/spaces/:spaceCreate a space
GET/v1/users/:user/code/spaces/:spaceList the space's folder
PATCH/v1/users/:user/code/spaces/:spaceRename the space
DELETE/v1/users/:user/code/spaces/:spaceDelete the space

The number of spaces you can create is plan-limited (see Limits).

The usual writable meta files (POST to write, GET to read, DELETE to clear):

PathDescription
.../:space/nameDisplay name
.../:space/summaryShort summary
.../:space/descriptionLonger description

And the read-only ones (GET):

PathDescription
.../:space/urlThe space's URL
.../:space/shortnameAuto-generated short id

config/access/ssh and config/access/web exist as plain read/write config files but are reserved. They are placeholders for upcoming access protocols and have no effect today.

Files and folders live under .../:space/content. The path after content/ is the full slash-separated path inside the space, nested as deep as you like (up to 64 levels).

VerbPathDescription
GET.../contentList the content root
GET.../content/{path}List a folder, or read a file (see below)
PUT.../content/{path}Create a folder (empty body, parents auto-created)
POST.../content/{path}Write a file — create or replace (parents auto-created)
PATCH.../content/{path}Rename / move a file or folder tree
DELETE.../content/{path}Delete a file or folder (?recursive=true if non-empty)

The verb split follows the novem convention: PUT creates the entry (a folder, the container), POST writes content (a file's bytes).

GET on the content root or any folder returns a JSON array in the standard novem directory shape (name, uri, type (file / dir), the caller's effective permissions, actions, ETag, created_on / last_modified, size), plus a spaces-specific content_type field. Responses carry X-NVM-Type and X-NVM-Permissions headers; a read-only grantee sees r and the GET/OPTIONS actions only.

GET on a file path serves two representations:

  • Accept: application/json → a metadata object: kind, name, path, size, content_type, etag, created_on, last_modified.
  • anything else → the raw bytes, with ETag, Content-Type, Content-Length and a Content-Disposition: attachment header (a space is a file store, not a web host, so blobs always download rather than render).

If-None-Match with the file's current ETag returns 304 Not Modified.

POST streams the request body into the file at {path}, creating any missing parent folders in the same call. Create and replace both return 200; the created flag in the response body carries the distinction. The response also includes the file's new path, etag and size, with the ETag mirrored in the ETag header. An empty body creates an empty file (touch).

Conditional headers give you optimistic concurrency:

HeaderBehavior
If-Match: <etag>Write only if the file's current ETag matches — else 412
If-None-Match: *Create-only — 412 if the path already exists

PUT with an empty body creates a folder, auto-creating missing parents. 201 on create, 200 if the folder already exists (idempotent mkdir), 409 if the path is occupied by a file.

PATCH on a content path renames or moves it. The body mirrors the VDE rename convention:

  • text/plain body → the new sibling name
  • JSON {"name": "x.csv"} → sibling rename
  • JSON {"to": "reports/x.csv"} → space-relative move

Exactly one of name / to. Moves are metadata-only (moving a folder tree of any size is one atomic operation), and missing destination parents are created on the way. POST with an X-NVM-Op: move header (destination in X-NVM-Destination) is a compatibility alias with identical semantics.

Move semantics match POSIX mv: an existing file destination is overwritten by default (the replaced file gets a delete tombstone in the journal). A folder destination is never overwritten (409). If-None-Match: * opts back into no-clobber (412 if the destination exists), If-Match guards the source's ETag. Moving a folder into its own subtree is a 400. There is no copy operation. Use GET + POST instead.

DELETE removes a file, or a folder; non-empty folders require ?recursive=true, otherwise 409. The response reports the number of entries deleted. Every deleted entry writes a tombstone to the change journal. Deletes are final.

CodeMeaning
200Read / list / write / move / delete OK (created flags a fresh file)
201Folder created
304If-None-Match matched on a read
400Invalid path or name, invalid move body, move into own subtree
403You can see the space but lack write access
404Unknown space or path — also returned when you lack read access
409Type clash (file vs folder), non-empty folder, folder destination
412If-Match / If-None-Match precondition failed
413File-size, space-size or file-count limit exceeded

The content store is case-preserving but case-insensitive (Report.csv and report.csv are the same entry), and names are unicode-normalized (NFC) on write so files created from macOS and Linux compare equal. Because spaces are built to be mountable on every platform, names that cannot exist on Windows are rejected with 400:

  • forbidden characters: < > : " / \ | ? * and control characters
  • forbidden names: ., .., and Windows reserved device names (CON, NUL, PRN, AUX, COM1COM9, LPT1LPT9, with or without an extension)
  • no trailing dots or spaces in a segment
  • max 255 bytes (UTF-8) per segment, 1024 bytes per full path, 64 levels deep

Everything else (spaces, unicode letters, emoji, mixed case) is allowed.

Every mutation appends a row to a per-space, gap-free journal: the replayable history that sync clients (and your own tooling) page through.

VerbPathDescription
GET.../:space/changesChanges after a cursor, oldest first

Query parameters:

ParamDescription
sinceReturn changes with seq > since (default 0)
limitPage size, 1–1000 (default 1000)

The response:

{
  "changes": [
    {
      "seq": 4,
      "path": "reports/q1.csv",
      "change": "update",
      "old_path": null,
      "type": "file",
      "etag": "5d41402abc4b2a76b9719d911017c592",
      "size_bytes": 1834,
      "created_on": "2026-06-13T08:00:00Z"
    }
  ],
  "latest_seq": 4,
  "has_more": false
}

change is one of create, update, move, delete. Deletes are tombstones (the row remains after the file is gone) and moves record the previous location in old_path. Resume by passing the highest seq you've seen as the next since.

Space content uses two effective tiers, derived from the standard novem sharing model:

  • read — list folders, read files, and read the changes journal. A caller without read access gets 404 (the space looks nonexistent).
  • write — create, modify, move and delete content. Write implies delete: there is no separate delete tier inside a space.

Spaces cannot be made public yet. Marking the space public does not expose its content. Files written by a shared collaborator count against the owner's quota.

LimitFreeBasicPremiumEnterprise
Spaces per user152020
Max single file size100 MB100 MB100 MB100 MB
Max space size5 GB5 GB5 GB5 GB

Structural limits, independent of plan: at most 1,000,000 files and folders per space, paths at most 1024 bytes and 64 levels deep. Exceeding any limit on a write returns 413.

These folders behave identically across all novem resources, so they're documented once, centrally:

PathBehaves like
.../:space/sharedSharing — list / PUT / DELETE group shares
.../:space/tagsTags — list / PUT / DELETE tags