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.

Page directory entries

Add ?list=1 to a directory path and walk the universal collection envelope. Listings are ordered by name and path, limit defaults to 50 and caps at 100, and nextCursor: null ends the walk. The cursor is opaque and bound to both the workspace and directory; pass it back unchanged with the same path. Directory mutations can change later pages, so key consumers by path and make repeated processing harmless. A malformed or rebound listing cursor returns 400 {"error":"invalid_cursor"}; other invalid listing query fields return invalid_request.

Listings have no entry ceiling: a directory of any size pages out completely. The 1,000-entry bound applies to operations that reproduce a folder — archive downloads, recursive copies, and publishes — not to reading its listing.

{
  "items": [
    {
      "name": "notes.md",
      "path": "notes.md",
      "type": "file",
      "size": 128,
      "mtime": "2026-08-26T12:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Ask for a path that may not exist

A missing path answers 404 — the drive's ordinary way of saying a folder is gone rather than empty. When your client is asking after a file that is allowed not to exist (an agent's agent.json, a skills folder that was never created), add ?optional=1 to the read, stat, or listing and a missing path answers an empty 204 instead. Everything else — a present file, a symlink conflict, a workspace that is missing, provisioning, or paused — answers exactly as it would without the flag.

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.

Mint a grant for direct uploads

Writing a file through this API carries its bytes across it, which is right for a document and impossible for something multi-gigabyte. For anything a person picked off disk, mint a short-lived grant with a credential that has files:write and send the bytes to the workspace VM instead. pathPrefix is the drive-relative folder the grant covers; an empty string is the drive root.

{
  "pathPrefix": "agents/research/uploads"
}

The response's vmBaseUrl and uploadPath identify the VM's direct files endpoint, while uploadToken is the bearer for that upload. Append one URL-encoded filename to uploadPath and send the bytes with PUT directly to vmBaseUrl; do not send the workspace API key to the VM. expiresAt is the latest time a new upload may begin, not a deadline for finishing one — a transfer already under way is not cut off when it passes.

The grant covers that one folder tree and is write-only. Clients choose a visible filename before uploading and handle same-name collisions themselves.

A single file has a published size limit, and it is enforced on the Content-Length the request declares — so an upload that cannot be accepted is refused before its bytes are sent, with a 413 naming the limit in limitBytes. A request that declares no length is not measured against it and is bounded only by the workspace's free space.

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

8 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
Query
Query parameters JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "cursor": {
      "type": "string",
      "minLength": 1
    },
    "limit": {
      "default": 50,
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "list": {
      "type": "string",
      "const": "1"
    },
    "optional": {
      "description": "Answer a missing path with an empty 204 instead of 404.",
      "type": "string",
      "const": "1"
    }
  },
  "required": [
    "list"
  ],
  "additionalProperties": false
}
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "items": {
      "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
      }
    },
    "nextCursor": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "items",
    "nextCursor"
  ],
  "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": "invalid_cursor"
        }
      },
      "required": [
        "error"
      ],
      "additionalProperties": false,
      "description": "Opaque collection cursor is malformed or bound to another walk (400)"
    }
  ]
}
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
Query
Query parameters JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "optional": {
      "description": "Answer a missing path with an empty 204 instead of 404.",
      "type": "string",
      "const": "1"
    }
  }
}
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
Query
Query parameters JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "optional": {
      "description": "Answer a missing path with an empty 204 instead of 404.",
      "type": "string",
      "const": "1"
    }
  }
}
Headers
  • Range — Request one byte range.
  • If-None-Match — Return 304 when the current ETag matches.
  • If-Modified-Since — Return 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-Match — Write only when the current ETag matches.
  • If-Unmodified-Since — Write only when the file is unchanged.
  • Content-Type — Preserve 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": [
    {
      "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
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "error": {
          "type": "string",
          "const": "file_too_large"
        },
        "limitBytes": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "error",
        "limitBytes"
      ],
      "additionalProperties": false,
      "description": "File larger than the Drive accepts (413)"
    }
  ]
}
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
POST/v1/ws/{workspaceSlug}/file-downloads

Create a short-lived, headerless reference to one workspace file

Scope
ws:{workspaceSlug}:files:read
Request
Request JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "path": {
      "type": "string"
    }
  },
  "required": [
    "path"
  ],
  "additionalProperties": false
}
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "uri": {
      "type": "string",
      "format": "uri"
    },
    "name": {
      "type": "string",
      "minLength": 1
    },
    "mimeType": {
      "type": "string",
      "minLength": 1
    },
    "size": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    }
  },
  "required": [
    "uri",
    "name",
    "mimeType",
    "size"
  ],
  "additionalProperties": false
}
Delivery
Standard response
Retry
Not declared idempotent
POST/v1/ws/{workspaceSlug}/file-uploads

Create a short-lived direct upload session for one Drive path prefix

Scope
ws:{workspaceSlug}:files:write
Request
Request JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "pathPrefix": {
      "type": "string"
    }
  },
  "required": [
    "pathPrefix"
  ],
  "additionalProperties": false
}
Response
Response JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "vmBaseUrl": {
      "type": "string",
      "format": "uri"
    },
    "uploadPath": {
      "type": "string",
      "minLength": 1
    },
    "uploadToken": {
      "type": "string",
      "minLength": 1
    },
    "expiresAt": {
      "type": "number"
    }
  },
  "required": [
    "vmBaseUrl",
    "uploadPath",
    "uploadToken",
    "expiresAt"
  ],
  "additionalProperties": false
}
Delivery
Standard response
Retry
Not declared idempotent