platforms/devplace/rest_project_filesystem.md

DevPlace REST: Project Virtual Filesystem

The per-project file API: listing the tree, reading and writing text files, uploading binaries, mkdir, move and delete, with the relative POSIX path rules and the traversal rejections that guard them.

Each project carries a full virtual filesystem - directories and files - so a project can hold a complete software project. Reading is public (anyone can browse a project's tree); creating, editing, uploading, moving and deleting require the project owner. Text files are editable inline; binary files are uploaded and served from /static/uploads/project_files/....

Paths are relative POSIX paths inside the project (for example src/main.py). Parent directories are created automatically on write, upload and mkdir. Paths containing .., null bytes or empty segments are rejected.

Every endpoint follows the shared Conventions & Errors (auth, content negotiation, status codes); see Authentication for the four ways to sign requests.

GET /projects/{project_slug}/files - List a project's files

Return the flat list of files and directories in a project.

Minimal role: Public

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.

Sample response

{
  "project": {
    "uid": "PROJECT_UID",
    "slug": "PROJECT_SLUG"
  },
  "files": [
    {
      "path": "src/main.py",
      "name": "main.py",
      "type": "file",
      "is_binary": false,
      "size": 42
    }
  ],
  "is_owner": false
}

GET /projects/{project_slug}/files/raw - Read a project file

Return one file's metadata and (for text files) its content.

Minimal role: Public

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathquerystringyesRelative file path inside the project.

Sample response

{
  "path": "src/main.py",
  "name": "main.py",
  "type": "file",
  "is_binary": false,
  "mime_type": "text/plain",
  "size": 42,
  "url": null,
  "content": "print('hello')\n"
}

POST /projects/{project_slug}/files/write - Write a text file

Create or overwrite a text file; parent directories are created automatically.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathformstringyesRelative file path.
contentformtextareayesFull file content (max 400000 chars).

Owner only; non-owners get 403. Invalid paths return 400.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "src/main.py",
    "type": "file"
  }
}

GET /projects/{project_slug}/files/lines - Read a line range

Read a 1-indexed inclusive line range of a text file. Returns lines plus total_lines for targeting edits.

Minimal role: Public

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathquerystringyesRelative file path.
startqueryintegernoFirst line, 1-indexed (default 1).
endqueryintegernoLast line inclusive; omit or -1 for end of file.

Text files only; binary, directory, or missing paths return 404.

Sample response

{
  "path": "src/main.py",
  "start": 1,
  "end": 2,
  "total_lines": 2,
  "lines": [
    "import os",
    "print(os.getcwd())"
  ],
  "content": "import os\nprint(os.getcwd())"
}

POST /projects/{project_slug}/files/replace-lines - Replace a line range

Replace lines start..end (inclusive) with new content; empty content deletes the range. Leaves the rest of the file untouched.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathformstringyesRelative file path.
startformintegeryesFirst line to replace (1-indexed).
endformintegeryesLast line to replace (inclusive).
contentformtextareanoReplacement text (empty deletes the range).

Owner only. The preferred way to edit a large file; avoids rewriting the whole file.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "src/main.py",
    "type": "file"
  }
}

POST /projects/{project_slug}/files/insert-lines - Insert lines

Insert content before a 1-indexed line. Use at=1 to prepend and at=total_lines+1 to append.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathformstringyesRelative file path.
atformintegeryesInsert before this 1-indexed line.
contentformtextareayesText to insert.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "src/main.py",
    "type": "file"
  }
}

POST /projects/{project_slug}/files/delete-lines - Delete a line range

Delete lines start..end (inclusive) from a text file.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathformstringyesRelative file path.
startformintegeryesFirst line to delete (1-indexed).
endformintegeryesLast line to delete (inclusive).

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "src/main.py",
    "type": "file"
  }
}

POST /projects/{project_slug}/files/append - Append to a file

Append content as new lines at the end of a text file; grow a large file across calls without resending it.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathformstringyesRelative file path.
contentformtextareayesText to append.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "log.txt",
    "type": "file"
  }
}

POST /projects/{project_slug}/files/upload - Upload a file into a project

Upload a file into a directory (parents created); text decodes to an editable file, otherwise stored as binary.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
fileformfileyesThe file to upload.
pathformstringnoTarget directory, empty for the root.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "assets/logo.png",
    "type": "file",
    "is_binary": true
  }
}

POST /projects/{project_slug}/files/mkdir - Create a directory

Create a directory and any missing parents.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathformstringyesRelative directory path.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "src/components",
    "type": "dir"
  }
}

POST /projects/{project_slug}/files/move - Move or rename

Move or rename a file or directory (and its descendants).

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
from_pathformstringyesExisting path.
to_pathformstringyesNew path.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "src/new.py"
  }
}

POST /projects/{project_slug}/files/delete - Delete a file or directory

Delete a file, or a directory and everything under it. Project owner or an administrator; soft-deleted (restorable from admin trash). Blocked while the project is read-only.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathformstringyesRelative path to delete.

Sample response

{
  "ok": true,
  "redirect": "/projects/PROJECT_SLUG/files",
  "data": {
    "path": "src/old.py"
  }
}

POST /projects/{project_slug}/files/zip - Queue a zip of files

Archive the whole tree, or a subtree via the path query. Returns the job uid and status URL to poll with /zips/{uid}.

Minimal role: Public

Parameters

NameInTypeRequiredDescription
project_slugpathstringyesProject slug or uid.
pathquerystringnoRelative file or directory to archive; empty for the whole project.

Sample response

{
  "uid": "ZIP_JOB_UID",
  "status_url": "/zips/ZIP_JOB_UID"
}