Browse documentationFiles

Resource guide

Files are byte streams

Treat file reads and writes as HTTP byte transfers, not JSON wrappers. Preserve content length, ETag, byte-range responses, cancellation, and error status through your client. Read content types are inferred from known file extensions and otherwise remain application/octet-stream; the bytes are always authoritative.

Address paths deliberately

Normalize workspace-relative paths with the contract path grammar. Encode dynamic URL segments, reject traversal, and do not infer whether a path is a file or folder from its extension.

const encodedPath = segments.map(encodeURIComponent).join('/')
const rangeHeaders = { range: 'bytes=0-65535' }

The snippet illustrates safe path encoding and a bounded byte range; use the endpoint reference for the final public route template before shipping a client.

Separate read and write grants

Tools that index or preview content need files:read. Editors and uploaders need files:write. Request both only when the workflow genuinely performs both roles.

Symlinks are entries, not their targets

Listings and stat report a symbolic link as its own entry: type: "symlink" with a target string holding the link text verbatim. A link is listed whether or not its target exists — a dangling link is a legal, visible entry, because workspaces mirror real development trees where links routinely point at paths that come and go. Listings can therefore now contain a third type value; clients that switch on entry types should treat unrecognized values as opaque entries rather than failing the listing. For a link entry, size is the byte length of the target text and mtime is the link's own modification time.

Addressing a link never silently follows it — only a content read does. A GET on a link path serves the target's bytes when the target resolves inside the workspace, and a successful read through a link is byte-for-byte a read of the target: same ETag, length, range, and conditional behavior. When the target dangles or escapes the workspace, the read fails with 409 symlink_unresolvable carrying the link's target — never a silent fallback. Writes are stricter: a PUT, mkdir, or directory listing addressed at a link path fails with 409 path_is_symlink rather than acting through the link. Directory links in intermediate path segments resolve normally while they stay inside the workspace; link discipline applies to the path's final component.

Make structural changes explicit

Use the JSON action endpoint for directories, moves, copies, recursive deletion, and link creation. Collision overwrites and recursion are opt-in. Recursive work is bounded by the gateway before mutation, so clients should surface a limit error instead of retrying an unsafe operation with broader local semantics.

Create or retarget a link:

{
  "operation": "symlink",
  "path": "current",
  "target": "releases/v2"
}

target is stored verbatim — relative targets are the portable choice, and targets outside the workspace are legal to store but never readable through the link. symlink replaces an existing symlink at path (that is how you retarget) and conflicts with any other existing entry type; replacing a file with a link — or a link with a file — is always an explicit delete followed by a create. This mirrors how PUT overwrites files in place: same-type replacement is direct, cross-type replacement is deliberate.

Links participate in structural operations as ordinary entries. A copy reproduces a link as a link with the same verbatim target — it never follows one — so a relative target is reinterpreted at the destination and may dangle there; the same applies to moves. Deleting a link removes the link itself: a recursive delete of a directory containing a link-to-directory removes the link entry, never the target's tree.

Editors should retain the ETag returned by a read and send it in If-Match when saving. A 412 means the file changed after it was opened; reload it or require the user to deliberately replace the newer version.

Files

6 operations
GET/v1/ws/{workspaceSlug}/files/{path}?list=1

List one workspace directory with file stats

Scope
ws:{workspaceSlug}:files:read
Request
No JSON request body
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "entries": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "path": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "file",
              "directory",
              "symlink"
            ]
          },
          "size": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "mtime": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "path",
          "type",
          "size",
          "mtime"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "entries"
  ],
  "additionalProperties": false
}
Errors
Symlink conflict (409)
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "error": {
          "type": "string",
          "const": "symlink_unresolvable"
        },
        "target": {
          "type": "string"
        }
      },
      "required": [
        "error",
        "target"
      ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "error": {
          "type": "string",
          "const": "path_is_symlink"
        }
      },
      "required": [
        "error"
      ],
      "additionalProperties": false
    }
  ],
  "description": "Symlink conflict (409)"
}
Delivery
Standard response
Retry
Not declared idempotent
GET/v1/ws/{workspaceSlug}/files/{path}?stat=1

Read file or directory metadata without transferring file bytes

Scope
ws:{workspaceSlug}:files:read
Request
No JSON request body
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "entry": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1
        },
        "path": {
          "type": "string"
        },
        "type": {
          "type": "string",
          "enum": [
            "file",
            "directory",
            "symlink"
          ]
        },
        "size": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "mtime": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        "target": {
          "type": "string"
        }
      },
      "required": [
        "name",
        "path",
        "type",
        "size",
        "mtime"
      ],
      "additionalProperties": false
    },
    "etag": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "entry",
    "etag"
  ],
  "additionalProperties": false
}
Delivery
Standard response
Retry
Not declared idempotent
GET/v1/ws/{workspaceSlug}/files/{path}

Stream one file with ETag and single-range support

Scope
ws:{workspaceSlug}:files:read
Request
No JSON request body
Headers
  • RangeRequest one byte range.
  • If-None-MatchReturn 304 when the current ETag matches.
  • If-Modified-SinceReturn 304 when the file is unchanged.
Response
Raw byte stream
Errors
Symlink conflict (409)
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "error": {
          "type": "string",
          "const": "symlink_unresolvable"
        },
        "target": {
          "type": "string"
        }
      },
      "required": [
        "error",
        "target"
      ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "error": {
          "type": "string",
          "const": "path_is_symlink"
        }
      },
      "required": [
        "error"
      ],
      "additionalProperties": false
    }
  ],
  "description": "Symlink conflict (409)"
}
Delivery
Standard response
Retry
Not declared idempotent
PUT/v1/ws/{workspaceSlug}/files/{path}

Atomically write one file, optionally guarded by If-Match

Scope
ws:{workspaceSlug}:files:write
Request
Raw byte stream
Headers
  • If-MatchWrite only when the current ETag matches.
  • If-Unmodified-SinceWrite only when the file is unchanged.
  • Content-TypePreserve the uploaded media type.
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "ok": {
      "type": "boolean",
      "const": true
    },
    "etag": {
      "type": "string"
    },
    "size": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "ok",
    "etag",
    "size"
  ],
  "additionalProperties": false
}
Errors
Error body JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "anyOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "error": {
              "type": "string",
              "const": "symlink_unresolvable"
            },
            "target": {
              "type": "string"
            }
          },
          "required": [
            "error",
            "target"
          ],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "error": {
              "type": "string",
              "const": "path_is_symlink"
            }
          },
          "required": [
            "error"
          ],
          "additionalProperties": false
        }
      ],
      "description": "Symlink conflict (409)"
    },
    {
      "type": "object",
      "properties": {
        "error": {
          "type": "string",
          "const": "storage_full"
        }
      },
      "required": [
        "error"
      ],
      "additionalProperties": false
    }
  ]
}
Delivery
Standard response
Retry
Not declared idempotent
DELETE/v1/ws/{workspaceSlug}/files/{path}

Delete one file or empty directory

Scope
ws:{workspaceSlug}:files:write
Request
No JSON request body
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "ok": {
      "type": "boolean",
      "const": true
    }
  },
  "required": [
    "ok"
  ],
  "additionalProperties": false
}
Delivery
Standard response
Retry
Declared idempotent
POST/v1/ws/{workspaceSlug}/files/actions

Create a directory or symlink, move, copy, or bounded-recursively delete

Scope
ws:{workspaceSlug}:files:write
Request
Request JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "mkdir"
        },
        "path": {
          "type": "string"
        },
        "recursive": {
          "default": false,
          "type": "boolean"
        }
      },
      "required": [
        "operation",
        "path"
      ]
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "move"
        },
        "source": {
          "type": "string"
        },
        "destination": {
          "type": "string"
        },
        "overwrite": {
          "default": false,
          "type": "boolean"
        }
      },
      "required": [
        "operation",
        "source",
        "destination"
      ]
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "copy"
        },
        "source": {
          "type": "string"
        },
        "destination": {
          "type": "string"
        },
        "recursive": {
          "default": false,
          "type": "boolean"
        },
        "overwrite": {
          "default": false,
          "type": "boolean"
        }
      },
      "required": [
        "operation",
        "source",
        "destination"
      ]
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "delete"
        },
        "path": {
          "type": "string"
        },
        "recursive": {
          "default": false,
          "type": "boolean"
        }
      },
      "required": [
        "operation",
        "path"
      ]
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "symlink"
        },
        "path": {
          "type": "string"
        },
        "target": {
          "type": "string",
          "minLength": 1,
          "maxLength": 4096
        }
      },
      "required": [
        "operation",
        "path",
        "target"
      ]
    }
  ]
}
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "ok": {
      "type": "boolean",
      "const": true
    },
    "affectedEntries": {
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "ok"
  ],
  "additionalProperties": false
}
Errors
Error body JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "anyOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "error": {
              "type": "string",
              "const": "symlink_unresolvable"
            },
            "target": {
              "type": "string"
            }
          },
          "required": [
            "error",
            "target"
          ],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "error": {
              "type": "string",
              "const": "path_is_symlink"
            }
          },
          "required": [
            "error"
          ],
          "additionalProperties": false
        }
      ],
      "description": "Symlink conflict (409)"
    },
    {
      "type": "object",
      "properties": {
        "error": {
          "type": "string",
          "const": "storage_full"
        }
      },
      "required": [
        "error"
      ],
      "additionalProperties": false
    }
  ]
}
Delivery
Standard response
Retry
Not declared idempotent