List directory or download file
Section titled “List directory or download file”The unversioned reader at /{path} returns directory listings in HTML by default, JSON when ?json is set, plain text when ?simple is set, or downloads the file as binary content. For files, append ?download to force Content-Disposition: attachment. Revision history (?history), point-in-time reads (?at, ?revision), and unified diffs (?diff) are all supported and are mutually exclusive on a single request.
GET /{path}
Section titled “GET /{path}”Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | File or directory path |
json | query | string | No | Return JSON format instead of HTML |
simple | query | string | No | Return simple text listing |
sort | query | string | No | Sort by field. One of: name, mtime, size |
order | query | string | No | Sort order. One of: asc, desc |
hash | query | string | No | Get SHA256 hash of file (returns plain text hash) |
sha256 | query | string | No | Get SHA256 hash of file (alias for hash) |
base64 | query | string | No | Get file content as base64 encoded string |
edit | query | string | No | Open file in Web UI editor (requires allow-upload permission) |
view | query | string | No | View file in Web UI (read-only mode) |
download | query | string | No | For file paths only: force browser download (Content-Disposition: attachment). Accepted values: empty (?download), 1, or true. For directory paths, ?download is the URL download-manager operation. One of: “, 1, true |
content-type | query | string | No | Override Content-Type header for file downloads |
history | query | string | No | List all revisions of a file. Returns JSON with revisions array, pagination via after_id. Mutually exclusive with at/revision/diff |
at | query | string | No | Read file content at a point in time. Accepts RFC3339 timestamp or Unix milliseconds. Mutually exclusive with history/revision/diff. Composable with ?lines, ?hash, ?base64 |
revision | query | integer | No | Read file content by stable per-path sequence number. Mutually exclusive with history/at/diff. Composable with ?lines, ?hash, ?base64 |
diff | query | string | No | Compute unified diff between two versions. Requires from_seq or from_ts. Optional to_seq or to_ts (defaults to current file). Mutually exclusive with history/at/revision |
from_seq | query | integer | No | Source revision seq number for ?diff. Mutually exclusive with from_ts |
from_ts | query | string | No | Source timestamp for ?diff (RFC3339 or Unix ms). Mutually exclusive with from_seq |
to_seq | query | integer | No | Target revision seq number for ?diff. Mutually exclusive with to_ts. Default: current file on disk |
to_ts | query | string | No | Target timestamp for ?diff (RFC3339 or Unix ms). Mutually exclusive with to_seq |
after_id | query | integer | No | Cursor for ?history pagination. Returns entries with id > after_id |
limit | query | integer | No | Max entries to return for ?history. Default: 100 |
Request Body
Section titled “Request Body”This endpoint does not accept a request body.
Response
Section titled “Response”Returns a directory listing in JSON, HTML, or simple text format, raw binary file content (application/octet-stream) for file paths, or revision history / historical content / unified diff when the corresponding query flags are used.
{ "allow_archive": true, "allow_delete": false, "allow_search": true, "allow_upload": true, "auth": true, "dir_exists": true, "href": "/projects/abc/containers/xyz-1/", "kind": "Index", "paths": [ { "mtime": 1714579200000, "name": "README.md", "path_type": "File", "revisions": 12, "size": 4096 }, { "mtime": 1714665600000, "name": "src", "path_type": "Dir", "revisions": null, "size": 14 } ], "uri_prefix": "/", "user": "dev@example.com"}Returned when the caller does not have permission to read the requested path.
{ "error": "Access forbidden", "success": false}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | User does not have permission to access this path | Contact administrator for read permissions or authenticate with different account |
Returned when the requested file or directory does not exist.
{ "error": "File or directory not found", "success": false}SDK and cURL
Section titled “SDK and cURL”# Default HTML directory listingcurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/"
# JSON directory listingcurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/?json="
# Simple plain text listingcurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/?simple="
# Force a file downloadcurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/README.md?download"
# SHA256 hash of a filecurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/README.md?hash="
# File content as base64curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/README.md?base64="
# Revision history with paginationcurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/README.md?history=&limit=50&after_id=1000"
# Historical content at a specific timestampcurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/README.md?at=2024-05-01T12:00:00Z"
# Unified diff between two revisionscurl -G "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/projects/abc/containers/xyz-1/README.md" \ --data-urlencode "diff=" \ --data-urlencode "from_seq=5" \ --data-urlencode "to_seq=12"// Default HTML directory listingawait client.files.listDirectory("/projects/abc/containers/xyz-1/")
// JSON directory listingawait client.files.listDirectory("/projects/abc/containers/xyz-1/", { json: "" })
// Simple plain text listingawait client.files.listDirectory("/projects/abc/containers/xyz-1/", { simple: "" })
// Sorted directory listingawait client.files.listDirectory("/projects/abc/containers/xyz-1/", { json: "", sort: "mtime", order: "desc" })
// Force a file downloadawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { download: "1" })
// SHA256 hash of a fileawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { hash: "" })
// File content as base64await client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { base64: "" })
// Override Content-Type on downloadawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { download: "true", contentType: "text/markdown" })
// Open file in Web UI editorawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { edit: "" })
// View file in read-only Web UIawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { view: "" })
// Revision history with paginationawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { history: "", limit: 50, after_id: 1000 })
// Historical content at a specific timestampawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { at: "2024-05-01T12:00:00Z" })
// Read by per-path sequence numberawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { revision: 7 })
// Unified diff between two revisionsawait client.files.listDirectory("/projects/abc/containers/xyz-1/README.md", { diff: "", from_seq: 5, to_seq: 12})List directory or download file (v1)
Section titled “List directory or download file (v1)”GET /api/v1/files/{path}
Section titled “GET /api/v1/files/{path}”V1 variant of the directory/file reader. Returns directory listings, file content, grep matches, glob results, line-range slices, revision history, and unified diffs. Supports an optional backend query parameter for remote file backends, and ?zip for streaming a directory as a zip archive. Depending on the query flags used, the response may be application/json, application/octet-stream (binary file content), or application/zip (a streamed directory archive).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | File or directory path |
backend | query | string | No | Backend ID for remote file access |
hash | query | string | No | Get SHA256 hash of file |
sha256 | query | string | No | Get SHA256 hash of file (alias for hash) |
base64 | query | string | No | Get file content as base64 |
preview | query | string | No | Preview archive contents (for zip/tar files). Alias: ?contents |
contents | query | string | No | Alias for ?preview - list archive contents |
stat | query | string | No | Get file/directory metadata (stat) without downloading content |
thumbnail | query | string | No | Return a processed image (resize, format convert, blur, grayscale). Requires the service to be started with --allow-thumbnails; returns 403 when disabled |
grep | query | string | No | Search file/directory contents for regex pattern (or literal if fixed_string=true). Requires --allow-grep |
ignore_case | query | boolean | No | Case-insensitive grep matching. Default: false |
fixed_string | query | boolean | No | Treat grep pattern as literal string, not regex. Default: false |
glob | query | string | No | Find files matching glob pattern (e.g. **/*.rs, src/**/*.{ts,tsx}). Requires --allow-search. Directory paths only |
context | query | integer | No | Number of context lines before/after each grep match. Default: 0 |
max_count | query | integer | No | Max matches per file for grep. Default: 50 |
max_matches | query | integer | No | Total max matches across all files for grep. Default: 500 |
max_depth | query | integer | No | Directory recursion depth for grep. Default: 50 |
max_filesize | query | integer | No | Skip files larger than this (bytes) during grep. Default: 10485760 |
timeout | query | integer | No | Grep timeout in seconds. Default: 30 |
no_ignore | query | boolean | No | Bypass .gitignore filtering during grep. Default: false |
max_results | query | integer | No | Max entries returned for glob search. Default: 1000 |
max_files_scanned | query | integer | No | Max filesystem entries scanned during glob search. Default: 100000 |
sort | query | string | No | Sort glob results by: mtime (default), name, or size. One of: mtime, name, size |
order | query | string | No | Sort order for glob results. Default: desc for mtime, asc for name/size. One of: asc, desc |
lines | query | string | No | Extract specific lines from a file. Formats: 10-50 (range, 1-indexed inclusive), 100 (single line), -20 (last 20 lines / tail), 50- (line 50 to end). Returns text/plain with X-Line-Range header. X-Total-Lines header included when naturally known (scan reached EOF). Max 100,000 lines or 64MB per request |
history | query | string | No | List all revisions of a file. Returns JSON with revisions array, pagination via after_id. Mutually exclusive with at/revision/diff |
at | query | string | No | Read file content at a point in time. Accepts RFC3339 timestamp or Unix milliseconds. Mutually exclusive with history/revision/diff. Composable with ?lines, ?hash, ?base64 |
revision | query | integer | No | Read file content by stable per-path sequence number. Mutually exclusive with history/at/diff. Composable with ?lines, ?hash, ?base64 |
diff | query | string | No | Compute unified diff between two versions. Requires from_seq or from_ts. Optional to_seq or to_ts (defaults to current file). Mutually exclusive with history/at/revision |
from_seq | query | integer | No | Source revision seq number for ?diff. Mutually exclusive with from_ts |
from_ts | query | string | No | Source timestamp for ?diff (RFC3339 or Unix ms). Mutually exclusive with from_seq |
to_seq | query | integer | No | Target revision seq number for ?diff. Mutually exclusive with to_ts. Default: current file on disk |
to_ts | query | string | No | Target timestamp for ?diff (RFC3339 or Unix ms). Mutually exclusive with to_seq |
after_id | query | integer | No | Cursor for ?history pagination. Returns entries with id > after_id |
limit | query | integer | No | Max entries to return for ?history. Default: 100 |
zip | query | string | No | Download a directory as a streaming zip archive (bare flag, e.g. ?zip). Local directories only; requires --allow-archive. Same behavior as the WebDAV-style /{directory}?zip |
Request Body
Section titled “Request Body”This endpoint does not accept a request body.
Response
Section titled “Response”Returns a DirectoryListing, GrepResults, or GlobResults JSON object when the request is for listings/searches, raw binary (application/octet-stream) when downloading file content, or application/zip when streaming a directory via ?zip.
{ "allow_archive": true, "allow_delete": false, "allow_search": true, "allow_upload": true, "auth": true, "dir_exists": true, "href": "/api/v1/files/projects/abc/containers/xyz-1/", "kind": "Index", "paths": [ { "mtime": 1714579200000, "name": "README.md", "path_type": "File", "revisions": 12, "size": 4096 }, { "mtime": 1714665600000, "name": "src", "path_type": "Dir", "revisions": null, "size": 14 } ], "uri_prefix": "/api/v1/files", "user": "dev@example.com"}Returned when reading a historical revision (?at / ?revision) and the file has been deleted or moved at the requested point in time.
{ "error": "File was deleted or moved at the requested point in time"}Returned when a requested revision exists but its content blob is not stored (file was too large or binary at that point in time).
{ "error": "Content blob is not stored for this revision"}Returned when too many concurrent journal queries are in flight against the service.
SDK and cURL
Section titled “SDK and cURL”# JSON directory listingcurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/api/v1/files/projects/abc/containers/xyz-1/"
# File metadata (stat) onlycurl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/api/v1/files/projects/abc/containers/xyz-1/README.md?stat="
# Grep search across a directorycurl -G "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.icu/api/v1/files/projects/abc/containers/xyz-1/src/" \ --data-urlencode "grep=TODO" \ --data-urlencode "ignore_case=true" \ --data-urlencode "max_count=10"// List a directory as JSONawait client.files.get("/projects/abc/containers/xyz-1/")
// Get file metadata without downloading contentawait client.files.get("/projects/abc/containers/xyz-1/README.md", { stat: "" })
// Get SHA256 hashawait client.files.get("/projects/abc/containers/xyz-1/README.md", { hash: "" })
// Get file content as base64await client.files.get("/projects/abc/containers/xyz-1/README.md", { base64: "" })
// Extract a line rangeawait client.files.get("/projects/abc/containers/xyz-1/src/index.ts", { lines: "10-50" })
// Grep across a directoryawait client.files.get("/projects/abc/containers/xyz-1/src/", { grep: "TODO", ignore_case: true, context: 2, max_count: 25})
// Glob searchawait client.files.get("/projects/abc/containers/xyz-1/", { glob: "src/**/*.{ts,tsx}", max_results: 200, sort: "mtime", order: "desc"})
// Generate a thumbnailawait client.files.get("/projects/abc/containers/xyz-1/assets/hero.png", { thumbnail: "width=800&format=webp"})
// Revision history with paginationawait client.files.get("/projects/abc/containers/xyz-1/README.md", { history: "", limit: 50, after_id: 1000 })
// Unified diff between two revisionsawait client.files.get("/projects/abc/containers/xyz-1/README.md", { diff: "", from_seq: 5, to_seq: 12})
// Download a directory as a zip archiveawait client.files.get("/projects/abc/containers/xyz-1/", { zip: "" })
// Read from a remote backendawait client.files.get("/projects/abc/containers/xyz-1/file.dat", { backend: "s3-prod" })