Reading Files
Section titled “Reading Files”Read files, list directories, search contents, and download archives over HTTP. This page covers the three read endpoints exposed by the Files service. The first (/{path}) returns HTML by default for browser browsing, JSON when requested. The second (/api/v1/files/{path}) returns JSON, binary, or zip and exposes the full search and archive surface (?grep, ?glob, ?stat, ?zip, ?lines, ?thumbnail, ?history, ?at, ?revision, ?diff). The third (/files/) lists the root directory and can be embedded in another page as a framed file browser.
All three endpoints are reached on the Files service hostname: https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com.
List directory or download file
Section titled “List directory or download file”GET /{path}
Section titled “GET /{path}”Browse a directory or download a single file in its raw form. By default the response is HTML rendered for browser navigation; pass ?json to receive a structured listing.
This endpoint accepts no request body.
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. Allowed values: name, mtime, size. |
order | query | string | No | Sort order. Allowed values: 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 | Open file in a read-only Web UI page: the text is shown with no Save, rename, move or delete. |
download | query | string | No | For file paths only: force browser download (Content-Disposition: attachment). Accepted values: empty, 1, or true. For directory paths, ?download is the URL download-manager operation. |
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 a revisions array; paginate 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. |
theme | query | string | No | HTML page only: colour theme of the page. Default oc-1. Allowed values: oc-1, aura, ayu, carbonfox, catppuccin, dracula, gruvbox, monokai, nightowl, nord, onedarkpro, shadesofpurple, solarized, tokyonight, vesper. |
colorScheme | query | string | No | HTML page only: light or dark colour scheme. Without it the page follows the system setting. Allowed values: light, dark. |
font | query | string | No | HTML page only: monospace font of the editor and listing. Allowed values: ibm-plex-mono, cascadia-code, fira-code, hack, inconsolata, intel-one-mono, iosevka, jetbrains-mono, meslo-lgs, roboto-mono, source-code-pro, ubuntu-mono. |
fontSize | query | integer | No | HTML page only: editor font size in pixels. Default 14. |
embedderOrigin | query | string | No | HTML page only: origin of the page that embeds this one, such as https://app.example.com. The page then accepts theme and layout messages from that origin and tells it when it is ready. Only https origins are accepted. |
chromeless | query | boolean | No | HTML page only: hide the header, sidebar, preview, footer and borders at once. Each can be turned back on with its own parameter set to false. |
borderless | query | boolean | No | HTML page only: hide the page borders. |
hideHeader | query | boolean | No | HTML page only: hide the header bar. |
hideSidebar | query | boolean | No | HTML page only: hide the sidebar. |
hidePreview | query | boolean | No | HTML page only: hide the preview pane. |
hideFooter | query | boolean | No | HTML page only: hide the footer. |
embedBg | query | string | No | HTML page only: transparent lets the background of the embedding page show through. |
Responses
Section titled “Responses”A directory listing, the raw file bytes (with Content-Disposition: attachment when ?download is set), revision history (with ?history), historical content (with ?at or ?revision), or a unified diff (with ?diff). The body shape depends on the negotiated content type and the query flags. application/octet-stream is the raw file body; text/html is the browser-rendered file manager page.
Directory listing in JSON:
{ "href": "/", "kind": "Index", "user": null, "auth": false, "dir_exists": true, "uri_prefix": "/", "allow_delete": false, "allow_upload": false, "allow_archive": false, "allow_search": true, "paths": [ { "name": "README.md", "path_type": "File", "size": 2048, "mtime": 1717000000000, "revisions": null }, { "name": "src", "path_type": "Dir", "size": 12, "mtime": 1717000123000, "revisions": null } ]}A query parameter, the path itself, or a header did not pass validation.
{ "success": false, "error": "Invalid request", "code": "INVALID_PARAMETER"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETER | Invalid request | A query parameter, a header or the request body is not one this operation accepts, or a copy or a move names a destination inside the directory it copies or moves; the message names what was refused | Correct the request |
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
The caller is authenticated but not permitted to read the requested path.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
No file or directory exists at the requested path.
{ "success": false, "error": "File or directory not found", "code": "RESOURCE_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RESOURCE_NOT_FOUND | Not found | Nothing is at this path, or the path is private to the server and answers as if absent | Check the path |
Returned only when the request names a remote backend via the type query parameter: uploads from before a service restart are still pending on that backend. The response includes a Retry-After header with the seconds to wait before retrying.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later"}Returned only when the request names a remote backend via the type query parameter: the service cannot connect to it right now, or a journal read could not check the paths it reads in time. The response includes a Retry-After header.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; retry later. The server’s storage check did not pass (for example, the mount directory and the directory of the backends’ own mounts overlap), so no mount or backend connection can start, and backend file operations are refused. The refusal comes before the mount or backend is looked up, so an unknown id is answered 503, not 404, until the check passes. Existing mounts show the error, and the mount list still answers. Other files are served as usual. Nothing was changed. | Retry after the time given in Retry-After |
SDK and cURL
Section titled “SDK and cURL”import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
// Directory listing as JSON, sorted by mtime descawait client.files.ui.getPage('/', { json: '', sort: 'mtime', order: 'desc' });
// Download a single fileawait client.files.ui.getPage('/README.md', { download: 'true' });
// Read file content as base64await client.files.ui.getPage('/data/config.json', { base64: '' });curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/?json&sort=mtime&order=desc"curl -O "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/README.md?download=true"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/data/config.json?base64"List directory JSON or download file
Section titled “List directory JSON or download file”GET /api/v1/files/{path}
Section titled “GET /api/v1/files/{path}”Programmatic equivalent of the browser-friendly endpoint. Returns JSON by default, with the full search and archive surface exposed via query parameters: ?grep for content search, ?glob for path search, ?stat for metadata, ?zip for a streaming archive download, ?lines for ranged reads, ?thumbnail for processed images, and ?history / ?at / ?revision / ?diff for the journal modes.
This endpoint accepts no request body.
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 or directory metadata (stat) without downloading content |
thumbnail | query | string | No | Return a processed image (resize, format convert, blur, grayscale), shaped by format, size, width, height, resize, quality, blur, grayscale and bg: the same image as the WebDAV-style /{image}?thumbnail. Only where the deployment enabled thumbnail processing; returns 403 when disabled. |
format | query | string | No | With thumbnail: output image format. Allowed values: jpeg, png, webp, gif, bmp. Default: jpeg. |
size | query | string | No | With thumbnail: target size as WIDTHxHEIGHT |
width | query | integer | No | With thumbnail: target width in pixels |
height | query | integer | No | With thumbnail: target height in pixels |
resize | query | string | No | With thumbnail: how the image fits the target size. Allowed values: fit, fill, cover, exact. Default: fit. |
quality | query | string | No | With thumbnail: output quality. Allowed values: low, medium, high. Default: medium. |
blur | query | number | No | With thumbnail: Gaussian blur radius |
grayscale | query | string | No | With thumbnail: convert to grayscale |
bg | query | string | No | With thumbnail: background color for transparent areas |
grep | query | string | No | Search file or directory contents for a regex pattern (or literal if fixed_string=true). Only where the deployment enabled content search. |
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 | Without grep, finds files and folders matching this glob (e.g. **/*.rs, src/**/*.{ts,tsx}); directory paths only, where the deployment enabled search. With grep, the content-search file filter. |
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 | Also search files excluded by .gitignore (inside a git repository), .ignore, .git/info/exclude and the global git excludes. Default: false. |
hidden | query | boolean | No | Also search names starting with ., which are otherwise skipped unless an ignore file whitelists them. .git folders found inside the searched folder are still skipped, and the server’s configured hidden paths still apply. Refused with 403 for an account that may only list this path. 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. Allowed values: mtime, name, size. |
order | query | string | No | Sort order for glob results. Default: desc for mtime, asc for name / size. Allowed values: 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 a revisions array; paginate 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, and only where the deployment enabled archive downloads (403 otherwise). Same behavior as the WebDAV-style /{directory}?zip. |
q | query | string | No | Two roles. On a directory (without thumbnail): search the names and paths below it for this text, case-insensitive; at most 512 bytes of UTF-8, measured before and after lowercasing (longer is refused with 400, not truncated); only where the deployment enabled search (403 otherwise). With thumbnail: JPEG quality, 1-100 (default 85); only JPEG output uses it. |
Range | header | string | No | File download only: ask for part of the file, as ‘bytes=first-last’, ‘bytes=first-’ or ‘bytes=-suffix_length’. A last position past the end is clamped to the last byte, and a suffix longer than the file selects all of it. One satisfiable range answers 206 with Content-Range; several answer 206 as multipart/byteranges for a local file, while a remote file (with backend) answers 200 with the whole file. Ranges that cannot be satisfied are dropped from a list, and 416 comes only when none is left. A malformed header, another unit or more than 100 ranges is ignored (200, whole file). HEAD ignores Range. |
If-Range | header | string | No | File download only: honour Range only if the file still has this ETag, exactly; otherwise answer 200 with the whole file. A date never matches, since two versions saved within the same second share it. A remote file’s (with backend) ETag is weak, so with If-Range a remote file is always sent whole. |
Responses
Section titled “Responses”The body is one of:
- a
files_FileListingJSON object for a folder (or for?q), - a
files_FileStatResponseJSON object for?stat, - a
files_FileHashResponseJSON object for?hash, - a
files_FileBase64ResponseJSON object for?base64, - a
files_ArchiveContentsJSON object for?preview/?contents, - a
files_GrepResultsJSON object for?grep, - a
files_GlobResultsJSON object for?glob, - the raw file body as
application/octet-stream, - a streaming zip archive as
application/zip(when?zipis set on a directory), - revision history (with
?history), historical content (with?ator?revision), or a unified diff (with?diff).
File content comes as text/plain for ?lines and with the file’s own Content-Type otherwise.
Directory listing:
{ "path": "/api/v1/files/", "count": 2, "entries": [ { "name": "src", "is_dir": true, "size": 8, "modified": 1717000123 }, { "name": "index.ts", "is_dir": false, "size": 4096, "modified": 1717000456, "revisions": 14 } ]}Grep results:
{ "path": "/api/v1/files/src", "pattern": "TODO", "matches": [ { "path": "/api/v1/files/src/index.ts", "line_number": 42, "column_byte": 4, "line": "// TODO: refactor parser", "context_before": [], "context_after": [] } ], "total_matches": 1, "total_files_matched": 1, "total_files_searched": 8, "truncated": false, "duration_ms": 17}Glob results:
{ "path": "/api/v1/files/src", "pattern": "**/*.ts", "entries": [ { "name": "/api/v1/files/src/index.ts", "is_dir": false, "size": 4096, "modified": 1717000456 } ], "count": 1, "total_scanned": 12, "truncated": false, "duration_ms": 4}Part of a file, for a satisfiable Range: one range as the file’s own type with Content-Range, or several (local files only) as multipart/byteranges, each part with its own Content-Range. The body is raw bytes, not JSON.
HTTP/1.1 206 Partial ContentContent-Type: application/octet-streamContent-Range: bytes 0-1023/4096ETag: "abc123"Last-Modified: Thu, 30 May 2024 12:00:00 GMTAccept-Ranges: bytes
<binary range payload>File download: If-None-Match names the file’s current ETag or, without If-None-Match, the file is unchanged since If-Modified-Since. The 304 response has no body; the ETag and Last-Modified are sent.
The ETag of a file download (on 200, 206 and 304) is an opaque value to send back in If-Match, If-None-Match or If-Range. A local file’s changes when the file is written or replaced, as far as its filesystem records the change: a same-size rewrite within one tick of a filesystem with coarse timestamps keeps it. On most local filesystems it also changes on a change of the file’s mode, owner or link count; reading the file never changes it. On an overlay filesystem it follows the modification time and the size only, and on a mounted remote or other network filesystem the modification time in milliseconds and the size: there a change of mode, owner or link count keeps it, and so does a same-size replacement or rewrite that keeps the modification time (on a mounted remote, any same-size change within the same millisecond). It is weak (W/"...") for a remote file (with backend), built from its modification time in whole seconds and its size.
Invalid request: the path, a parameter, a header or the body.
{ "success": false, "error": "Invalid path", "code": "INVALID_PATH"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
The request is refused because the account may not access this path or where it leads, the deployment has not enabled this operation, or the path is one this server does not write or follow.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
Uploads from before a service restart are still pending on the named backend. The response includes a Retry-After header with the seconds to wait before retrying.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later"}The filesystem can also refuse the path itself, for example when a component of the path is a file where a directory has to be; that answer carries the code below.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PATH_CONFLICT | Path conflict | The filesystem refused the paths of this request, so nothing was changed: the destination is of the wrong kind or not empty, a component of the path is a file where a directory has to be, the source and the destination are the same file or on different filesystems, a directory would move into itself, or a file the request needs cannot be opened now (nothing is listening on it, or another program holds a lease on it) | Choose another destination, or retry when the file is free |
The journal query (?at or ?revision) targeted a point at which the file did not exist: it had been deleted or moved.
{ "error": "File was deleted or moved at the requested point in time"}File download: If-Match names no current ETag of the file (a weak ETag never matches it) or, without If-Match, the file changed after If-Unmodified-Since.
File download: no range in Range overlaps the file, or several ranges of a local file add up to more than 64 MiB. A Content-Range: bytes */size header is sent to indicate the file’s size.
The revision exists in the journal but its content blob was not retained (the file was too large or binary at that point in time).
{ "error": "Content blob not stored for this revision"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ARCHIVE_TOO_LARGE | Folder too large for one archive | The folder holds more than 100000 files, more than 1000000 files and folders in all, or folders nested deeper than 50 levels: one ZIP archive holds a folder whole or is not sent, so nothing was sent. The message names the limit. | Download the folder’s subfolders separately |
Too many journal queries are in flight. Back off and retry.
With backend: ?zip on a folder or ?thumbnail on a file of a remote backend. This surface does not build either from remote storage; a remote folder can be zipped on the WebDAV-style surface (GET /{directory}?zip with the remote connection parameters).
{ "success": false, "error": "This operation is not supported for remote backends on this surface"}The service cannot carry out this request right now, or a journal read could not check the paths it reads in time. The response includes a Retry-After header. The message is Storage check timed out; retry.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; retry later. The server’s storage check did not pass (for example, the mount directory and the directory of the backends’ own mounts overlap), so no mount or backend connection can start, and backend file operations are refused. The refusal comes before the mount or backend is looked up, so an unknown id is answered 503, not 404, until the check passes. Existing mounts show the error, and the mount list still answers. Other files are served as usual. Nothing was changed. | Retry after the time given in Retry-After |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_UNREACHABLE | Remote backend unreachable | A read of a remote (through ?backend=, or one-off through ?type=) could not reach it: the remote did not answer within 30 seconds (504), or it refused the connection, its TLS or the credentials (502). Nothing was read. | Check the connection details and credentials (for a saved backend, GET /api/v1/backends/{id}/test, and PUT /api/v1/backends/{id} to update them), or retry once the remote is reachable |
SDK and cURL
Section titled “SDK and cURL”import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
// Directory listingawait client.files.get('/');
// Stat a fileawait client.files.get('/README.md', { stat: '' });
// Grep for TODO across srcawait client.files.get('/src', { grep: 'TODO', ignore_case: true, max_count: 50 });
// Glob for TypeScript filesawait client.files.get('/src', { glob: '**/*.ts', sort: 'mtime', order: 'desc' });
// Read lines 10-50await client.files.get('/README.md', { lines: '10-50' });
// Point-in-time readawait client.files.get('/README.md', { at: '2024-05-30T12:00:00Z' });
// Unified diff between two revisionsawait client.files.get('/README.md', { diff: '', from_seq: 12, to_seq: 17 });curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/files/"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/files/README.md?stat"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/files/src?grep=TODO&ignore_case=true&max_count=50"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/files/src?glob=**/*.ts&sort=mtime&order=desc"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/files/README.md?lines=10-50"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/files/README.md?at=2024-05-30T12:00:00Z"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/api/v1/files/README.md?diff=&from_seq=12&to_seq=17"List the root directory
Section titled “List the root directory”GET /files/
Section titled “GET /files/”List the contents of the root directory the Files service is serving. The response is the file manager page (text/html) by default, the same listing as GET /{path} for the root folder, or, with ?json, a structured JSON document. With ?download the browser is forced to save the response rather than render it.
This endpoint is also the embed view: pass the page in an <iframe> and it will lay itself out as a framed file browser. Set embedderOrigin to the embedding page’s origin so the page can communicate its theme and layout back to the parent. For details on framing and the postMessage protocol the page uses, see the embed URLs guide.
This endpoint accepts no request body.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
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. Allowed values: name, mtime, size. |
order | query | string | No | Sort order. Allowed values: 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 | Open file in a read-only Web UI page: the text is shown with no Save, rename, move or delete. |
download | query | string | No | For file paths only: force browser download (Content-Disposition: attachment). Accepted values: empty, 1, or true. For directory paths, ?download is the URL download-manager operation. |
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 a revisions array; paginate 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. |
theme | query | string | No | HTML page only: colour theme of the page. Default oc-1. Allowed values: oc-1, aura, ayu, carbonfox, catppuccin, dracula, gruvbox, monokai, nightowl, nord, onedarkpro, shadesofpurple, solarized, tokyonight, vesper. |
colorScheme | query | string | No | HTML page only: light or dark colour scheme. Without it the page follows the system setting. Allowed values: light, dark. |
font | query | string | No | HTML page only: monospace font of the editor and listing. Allowed values: ibm-plex-mono, cascadia-code, fira-code, hack, inconsolata, intel-one-mono, iosevka, jetbrains-mono, meslo-lgs, roboto-mono, source-code-pro, ubuntu-mono. |
fontSize | query | integer | No | HTML page only: editor font size in pixels. Default 14. |
embedderOrigin | query | string | No | HTML page only: origin of the page that embeds this one, such as https://app.example.com. The page then accepts theme and layout messages from that origin and tells it when it is ready. Only https origins are accepted. |
chromeless | query | boolean | No | HTML page only: hide the header, sidebar, preview, footer and borders at once. Each can be turned back on with its own parameter set to false. |
borderless | query | boolean | No | HTML page only: hide the page borders. |
hideHeader | query | boolean | No | HTML page only: hide the header bar. |
hideSidebar | query | boolean | No | HTML page only: hide the sidebar. |
hidePreview | query | boolean | No | HTML page only: hide the preview pane. |
hideFooter | query | boolean | No | HTML page only: hide the footer. |
embedBg | query | string | No | HTML page only: transparent lets the background of the embedding page show through. |
Responses
Section titled “Responses”A directory listing, the raw bytes of the response (with Content-Disposition: attachment when ?download is set), or the browser-rendered file manager page (text/html).
Directory listing in JSON:
{ "href": "/files/", "kind": "Index", "user": null, "auth": false, "dir_exists": true, "uri_prefix": "/files/", "allow_delete": false, "allow_upload": false, "allow_archive": false, "allow_search": true, "paths": [ { "name": "README.md", "path_type": "File", "size": 2048, "mtime": 1717000000000, "revisions": null }, { "name": "src", "path_type": "Dir", "size": 12, "mtime": 1717000123000, "revisions": null } ]}A query parameter, the path itself, or a header did not pass validation.
{ "success": false, "error": "Invalid request", "code": "INVALID_PARAMETER"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETER | Invalid request | A query parameter, a header or the request body is not one this operation accepts, or a copy or a move names a destination inside the directory it copies or moves; the message names what was refused | Correct the request |
INVALID_PATH | Invalid path | The request path is not valid: it does not decode, it has a . or .. component, it is outside the configured path prefix, or it is over the filesystem’s limits (a name over 255 bytes, a path over 4095 bytes) | Send a path of plain components under the served directory, within the filesystem’s limits |
The caller is authenticated but not permitted to read the root directory.
{ "success": false, "error": "Access forbidden", "code": "ACCESS_FORBIDDEN"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ACCESS_FORBIDDEN | Access forbidden | The request is refused: the account may not access this path or where it leads (a symlink to a path the account may not access), the deployment has not enabled this operation (uploads, deletes, touches, permission and owner changes, renames, extraction, thumbnails), or the path is one this server does not write or follow (the served directory itself, a symlink when symlinks are not allowed, a read-only or immutable file, a pipe, a socket or a device, which has no content to read or copy) | Use an account with access to the path, or ask the deployment’s administrator to enable the operation; a pipe, a socket or a device is never read |
The root directory does not exist or is not accessible.
{ "success": false, "error": "File or directory not found", "code": "RESOURCE_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RESOURCE_NOT_FOUND | Not found | Nothing is at this path, or the path is private to the server and answers as if absent | Check the path |
Returned only when the request names a remote backend via the type query parameter: uploads from before a service restart are still pending on that backend. The response includes a Retry-After header with the seconds to wait before retrying.
{ "success": false, "error": "Uploads from before a service restart are still pending on this backend; retry later"}Returned only when the request names a remote backend via the type query parameter: the service cannot connect to it right now, or a journal read could not check the paths it reads in time. The response includes a Retry-After header.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
MOUNT_STORAGE_UNAVAILABLE | Mounts unavailable | Mounts are unavailable until the server’s storage check passes; retry later. The server’s storage check did not pass (for example, the mount directory and the directory of the backends’ own mounts overlap), so no mount or backend connection can start, and backend file operations are refused. The refusal comes before the mount or backend is looked up, so an unknown id is answered 503, not 404, until the check passes. Existing mounts show the error, and the mount list still answers. Other files are served as usual. Nothing was changed. | Retry after the time given in Retry-After |
curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/files/?json"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/files/?json&sort=mtime&order=desc"curl "https://{projectId}-{containerId}-files-1.{server}.containers.hoody.com/files/?chromeless=true&colorScheme=dark&embedBg=transparent"