# Totalum App Builder API — Project Files

This file is the complete reference for this area. The short guide for agents is https://www.totalum.app/totalum-api.md.

- Base URL: `https://builder-api.totalum.app/api/v1/vcaas`
- Authentication: header `api-key: tlm_sk_…` on every request, from a server only.
- Response envelope: `{ "errors": null | { "errorCode", "errorMessage", "errorDetails?" }, "data": … }`.


---

# 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](https://www.totalum.app/docs/api/versions) and deployed to the development preview**; [publish](https://www.totalum.app/docs/api/deployments) to put it live.

## Get Project Tree

```endpoint
method: GET
path: /api/v1/vcaas/projects/:projectId/files/tree
cost: Free
```

The 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**

```bash
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"
```

:::success Success · 200 OK
```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

```endpoint
method: GET
path: /api/v1/vcaas/projects/:projectId/files/content
cost: Free
```

The 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**

```bash
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"
```

:::success Success · 200 OK
```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"
  }
}
```
:::

:::danger Error · 404
```json
{
  "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

```endpoint
method: PUT
path: /api/v1/vcaas/projects/:projectId/files/content
cost: Free
```

Create 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**

```bash
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/content
```

:::success Success · 200 OK
```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

```endpoint
method: POST
path: /api/v1/vcaas/projects/:projectId/rebuild
cost: 2 credits
```

Build the current version again and redeploy the preview — for example after the preview failed to build. Takes 1–4 minutes; poll [Get Rebuild Status](#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**

```bash
curl -X POST -H "api-key: tlm_sk_your_key" \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rebuild
```

:::success Success · 200 OK
```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

```endpoint
method: GET
path: /api/v1/vcaas/projects/:projectId/rebuild/status
cost: Free
```

The 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**

```bash
curl -H "api-key: tlm_sk_your_key" \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rebuild/status
```

:::success Success · 200 OK
```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" } }
```
:::
