Project Files
Read and edit your app's code file by file.
Work on the code directly instead of through prompts: list the tree, read a file, write it back. Every write is saved as a new version and deployed to the development preview; publish to put it live.
#Get Project Tree
/api/v1/vcaas/projects/:projectId/files/treeapi-keyFreeThe project's files and folders, as a flat, sorted list. node_modules, .next, .git, dist and other build output are never included.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project ID |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
path | string | No | Limit the tree to one subfolder, e.g. src/app. Default: the whole project |
limit | number | No | Page size. Default 2000, max 5000 |
offset | number | No | Entries to skip. Default 0 |
Response fields
| Field | Type | Description |
|---|---|---|
data.entries[].path | string | Path relative to the project root, e.g. src/app/page.tsx |
data.entries[].name | string | File or folder name |
data.entries[].type | string | "file" or "folder" |
data.entries[].size | number | undefined | Size in bytes. Files only |
data.entries[].depth | number | 0 at the project root |
data.totalEntries | number | Entries matching the request before paging |
data.offset | number | The offset used |
data.limit | number | The limit used |
data.hasMore | boolean | true when more entries follow |
data.commitSha | string | null | Git commit the tree was read from |
data.filesCount | number | Files tracked in the project, before exclusions |
Entries are sorted the way a file explorer shows them: folders before files, alphabetically, within each parent.
Example request
curl -H "api-key: tlm_sk_your_key" \
"https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files/tree?path=src/app&limit=100"const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files/tree?path=src/app&limit=100", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{
"errors": null,
"data": {
"entries": [
{ "path": "src/app", "name": "app", "type": "folder", "depth": 1 },
{ "path": "src/app/layout.tsx", "name": "layout.tsx", "type": "file", "size": 1284, "depth": 2 },
{ "path": "src/app/page.tsx", "name": "page.tsx", "type": "file", "size": 3902, "depth": 2 }
],
"totalEntries": 3,
"offset": 0,
"limit": 100,
"hasMore": false,
"commitSha": "a1b2c3d",
"filesCount": 47
}
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
INVALID_PATH | 400 | The path is absolute, contains .., or points inside an excluded folder |
PROJECT_NOT_FOUND | 404 | Project does not exist or you don't own it |
PROJECT_TOO_LARGE | 413 | The project archive is too large to browse through the API — download it instead |
#Get File Content
/api/v1/vcaas/projects/:projectId/files/contentapi-keyFreeThe complete content of one file. Files up to 1 MB.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project ID |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Path relative to the project root, e.g. src/app/page.tsx |
Response fields
| Field | Type | Description |
|---|---|---|
data.path | string | The normalised path |
data.name | string | File name |
data.size | number | Size of the stored file in bytes |
data.encoding | string | "utf8" for text, "base64" for binary |
data.content | string | The file content, in encoding |
data.commitSha | string | null | Git commit the file was read from |
Binary files (anything with a NUL byte in the first 8 KB) come back base64-encoded, so they survive a round trip through PUT /files/content.
Example request
curl -H "api-key: tlm_sk_your_key" \
"https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files/content?path=src/app/page.tsx"const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files/content?path=src/app/page.tsx", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{
"errors": null,
"data": {
"path": "src/app/page.tsx",
"name": "page.tsx",
"size": 62,
"encoding": "utf8",
"content": "export default function Page() {\n return <h1>Hello</h1>;\n}\n",
"commitSha": "a1b2c3d"
}
}{
"errors": {
"errorCode": "FILE_NOT_FOUND",
"errorMessage": "No file at \"src/app/missing.tsx\" in this project."
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
INVALID_PATH | 400 | The path is absolute, contains .., or points inside an excluded folder |
PROJECT_NOT_FOUND | 404 | Project does not exist or you don't own it |
FILE_NOT_FOUND | 404 | No file at that path |
FILE_TOO_LARGE | 413 | The file is larger than 1 MB — download the source archive instead |
#Write File Content
/api/v1/vcaas/projects/:projectId/files/contentapi-keyFreeCreate or replace one file (up to 512 KB). Missing folders are created. The write is saved as a version and the preview is rebuilt with it (a new file under public/ can make the preview answer 503 updating for a minute or two).
If the save is still in progress after about 45 seconds (the project's environment was asleep), the call answers 202 with saved: false: read the file again a minute later before relying on it, and do not send it again.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Path from the project root, e.g. src/app/page.tsx |
content | string | Yes | The complete new content ("" empties the file) |
encoding | string | No | "utf8" (default) or "base64" for binary files |
baseSha256 | string | No | SHA-256 (hex) of the file as you read it: if it changed since (the agent, a restore), the write is refused with 409 FILE_CHANGED |
Response fields
| Field | Type | Description |
|---|---|---|
data.path | string | Normalised path |
data.created | boolean | true when the file is new |
data.bytesWritten | number | undefined | Bytes written (saved writes only) |
data.commitSha | string | undefined | The commit of the new version |
data.filesCount | number | undefined | Files in the project after the write |
data.rebuildRequired | boolean | Always false: the write deploys itself |
data.saved | boolean | undefined | false on a 202 (still saving) |
data.versionId | string | undefined | On a 202: the version being saved |
data.message | string | undefined | On a 202: what to do |
Example request
curl -X PUT \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"path":"src/app/hello/page.tsx","content":"export default function Hello() {\n return <h1>Hello</h1>;\n}\n"}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files/contentconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files/content", {
method: "PUT",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"path": "src/app/hello/page.tsx",
"content": "export default function Hello() {\n return <h1>Hello</h1>;\n}\n"
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "path": "src/app/hello/page.tsx", "bytesWritten": 58, "created": true, "commitSha": "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3", "filesCount": 97, "rebuildRequired": false } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
INVALID_PATH | 400 | Absolute, contains .., or inside an excluded folder |
MISSING_CONTENT | 400 | content is missing or not a string |
INVALID_ENCODING | 400 | Not utf8 or base64 |
FILE_TOO_LARGE | 413 | Over 512 KB |
FILE_CHANGED | 409 | The file changed since your baseSha256 |
AGENT_RUNNING | 409 | A prompt is running |
OPERATION_IN_PROGRESS | 409 | A publish, restore or import is running |
RATE_LIMITED | 429 | More than 300 writes a minute |
#Rebuild Project
/api/v1/vcaas/projects/:projectId/rebuildapi-key2 creditsBuild the current version again and redeploy the preview — for example after the preview failed to build. Takes 1–4 minutes; poll Get Rebuild Status.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | Project id |
data.status | string | "rebuilding" |
data.startedAt | string | ISO 8601 |
data.message | string | Next step |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rebuildconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rebuild", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "projectId": "barber-bookings", "status": "rebuilding", "startedAt": "2026-10-08T12:20:00.000Z", "message": "Rebuild started. It usually takes 1-4 minutes. Poll GET /projects/barber-bookings/rebuild/status until status is \"success\" or \"error\"." } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
REBUILD_RUNNING | 409 | A rebuild is already running |
AGENT_RUNNING | 409 | A prompt is running |
DEPLOYMENT_RUNNING | 409 | A publish is running |
INSUFFICIENT_CREDITS | 402 | Not enough credits |
#Get Rebuild Status
/api/v1/vcaas/projects/:projectId/rebuild/statusapi-keyFreeThe newest rebuild. A failed rebuild is status: "error" in a 200 answer.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | Project id |
data.status | string | "idle" (never rebuilt), "rebuilding", "success" or "error" |
data.startedAt | string | null | ISO 8601 |
data.finishedAt | string | null | ISO 8601 |
data.elapsedSeconds | number | null | Duration so far, or total |
data.livePreviewUrl | string | undefined | The preview, after a success |
data.errorCode | string | undefined | On "error" |
data.errorMessage | string | undefined | On "error" |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rebuild/statusconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rebuild/status", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "projectId": "barber-bookings", "status": "success", "startedAt": "2026-10-08T12:20:00.000Z", "finishedAt": "2026-10-08T12:22:10.000Z", "elapsedSeconds": 130, "livePreviewUrl": "https://barber-bookings-dev-temporal-link-preview-k3y9x2ab.totalum-project.com" } }