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.
| Verb | Path | Description |
|---|---|---|
GET | /v1/users/:user/code/spaces | List spaces |
PUT | /v1/users/:user/code/spaces/:space | Create a space |
GET | /v1/users/:user/code/spaces/:space | List the space's folder |
PATCH | /v1/users/:user/code/spaces/:space | Rename the space |
DELETE | /v1/users/:user/code/spaces/:space | Delete 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):
| Path | Description |
|---|---|
.../:space/name | Display name |
.../:space/summary | Short summary |
.../:space/description | Longer description |
And the read-only ones (GET):
| Path | Description |
|---|---|
.../:space/url | The space's URL |
.../:space/shortname | Auto-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).
| Verb | Path | Description |
|---|---|---|
GET | .../content | List 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-Lengthand aContent-Disposition: attachmentheader (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:
| Header | Behavior |
|---|---|
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/plainbody → 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.
| Code | Meaning |
|---|---|
200 | Read / list / write / move / delete OK (created flags a fresh file) |
201 | Folder created |
304 | If-None-Match matched on a read |
400 | Invalid path or name, invalid move body, move into own subtree |
403 | You can see the space but lack write access |
404 | Unknown space or path — also returned when you lack read access |
409 | Type clash (file vs folder), non-empty folder, folder destination |
412 | If-Match / If-None-Match precondition failed |
413 | File-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,COM1–COM9,LPT1–LPT9, 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.
| Verb | Path | Description |
|---|---|---|
GET | .../:space/changes | Changes after a cursor, oldest first |
Query parameters:
| Param | Description |
|---|---|
since | Return changes with seq > since (default 0) |
limit | Page 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
changesjournal. A caller without read access gets404(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.
| Limit | Free | Basic | Premium | Enterprise |
|---|---|---|---|---|
| Spaces per user | 1 | 5 | 20 | 20 |
| Max single file size | 100 MB | 100 MB | 100 MB | 100 MB |
| Max space size | 5 GB | 5 GB | 5 GB | 5 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:
| Path | Behaves like |
|---|---|
.../:space/shared | Sharing — list / PUT / DELETE group shares |
.../:space/tags | Tags — list / PUT / DELETE tags |