# Totalum App Builder API — Source Code & Uploads

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": … }`.


---

# Source Code & Uploads

> Take your code anywhere; give the agent your files.

The source code is yours: a standard Next.js project you can run locally or host yourself ([For developers](https://www.totalum.app/docs/developers/self-hosting)). Uploaded files are stored in the project's file storage.

## Download Source Code

```endpoint
method: GET
path: /api/v1/vcaas/projects/:projectId/source-code
cost: 2 credits
```

A short-lived link to the project's current code as an archive. With the project's `.env` ([Get Project Env](https://www.totalum.app/docs/api/secrets#get-project-env)) it runs locally with `npm ci && npm run dev`. Downloading source code is included on paid plans.

**Path parameters**

| Parameter | Type | Description |
|---|---|---|
| `projectId` | string | The project id |

**Query parameters**

| Field | Type | Required | Description |
|---|---|---|---|
| `intent` | string | No | `view` when you only show the code (for example in a code viewer), which works on every plan |

**Response fields**

| Field | Type | Description |
|---|---|---|
| `data.filesCount` | number | Files in the archive |
| `data.lastCommitSha` | string \| undefined | Commit of the current version |
| `data.downloadUrl` | string \| null | Signed download URL (valid a few minutes) |

**Example request**

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

:::success Success · 200 OK
```json
{ "errors": null, "data": { "filesCount": 96, "lastCommitSha": "9f2c1ab4d3e5f60718293a4b5c6d7e8f90a1b2c3", "downloadUrl": "https://files.totalum.app/source/barber-bookings.tar.gz?sig=…" } }
```
:::

**Error codes**

| Code | HTTP | Meaning |
|---|---|---|
| `PLATFORM_CAPABILITY_QUOTA_REACHED` | 403 | Source download is not included on your plan (`upgradePlan` says which is needed) |
| `INSUFFICIENT_CREDITS` | 402 | Not enough credits |

## Upload File

```endpoint
method: POST
path: /api/v1/vcaas/projects/:projectId/files/upload
cost: Free
```

Upload one file (up to **20 MB**) as `multipart/form-data` with the field `file`. Use the returned `url` in `inputFiles` of [Run AI Agent](https://www.totalum.app/docs/api/agent#run-ai-agent). For a bigger file, pass its own public URL in `inputFiles` instead; no upload is needed.

**Path parameters**

| Parameter | Type | Description |
|---|---|---|
| `projectId` | string | The project id |

**Body parameters** (multipart/form-data)

| Field | Type | Required | Description |
|---|---|---|---|
| `file` | file | Yes | The file, up to 20 MB |
| `private` | string | No | `"true"` = stored privately (served only through signed links) |

**Response fields**

| Field | Type | Description |
|---|---|---|
| `data.fileNameId` | string | The stored file's key: the `key` that [List Files](#list-files) shows and [Delete Files](#delete-files) takes |
| `data.url` | string | Its URL (for `inputFiles`); `""` for a private file |
| `data.private` | boolean \| undefined | `true` for a private file |
| `data.createdAt` | string | When it was stored (ISO 8601) |

**Example request**

```bash
curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -F "file=@./logo.png" \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files/upload
```

:::success Success · 200 OK
```json
{
  "errors": null,
  "data": {
    "fileNameId": "p/barber-bookings/01JC8Z3K4M5N6P7Q8R9S0T1V2W.png",
    "url": "https://files.totalum-project.com/p/barber-bookings/01JC8Z3K4M5N6P7Q8R9S0T1V2W.png",
    "createdAt": "2026-11-01T10:15:00.000Z"
  }
}
```
:::

**Error codes**

| Code | HTTP | Meaning |
|---|---|---|
| `MISSING_FILE` | 400 | No multipart field `file` |
| `FILE_TOO_LARGE` | 413 | Over 20 MB — pass the file's own URL in `inputFiles` instead |
| `RATE_LIMITED` | 429 | More than 300 uploads a minute |

## List Files

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

The files stored in the project, newest first: the ones uploaded through [Upload File](#upload-file) and the ones the app itself stored. Projects on the new engine only; a legacy project answers `400 ENGINE_NOT_SUPPORTED`.

**Path parameters**

| Parameter | Type | Description |
|---|---|---|
| `projectId` | string | The project id |

**Query parameters**

| Field | Type | Required | Description |
|---|---|---|---|
| `folder` | string | No | Only the files stored in this folder or its subfolders |
| `type` | string | No | Content type, e.g. `application/pdf`, or its first part, e.g. `image` |
| `name` | string | No | Part of the file name (not case-sensitive) |
| `createdFrom` | string | No | Stored at or after this time (ISO 8601) |
| `createdTo` | string | No | Stored at or before this time (ISO 8601) |
| `cursor` | string | No | `nextCursor` of the previous page |
| `limit` | number | No | Page size. Default `100`, max `100` |

**Response fields**

| Field | Type | Description |
|---|---|---|
| `data.items[].key` | string | The file's key (the `fileNameId` of its upload) |
| `data.items[].fileName` | string | File name |
| `data.items[].fileSize` | number | Size in bytes |
| `data.items[].contentType` | string | e.g. `image/png` |
| `data.items[].visibility` | string | `"public-by-link"` or `"private"` |
| `data.items[].createdAt` | string | When it was stored (ISO 8601) |
| `data.nextCursor` | string \| null | Pass it as `cursor` for the next page; `null` on the last page |

**Example request**

```bash
curl -H "api-key: tlm_sk_your_key" \
  "https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files?type=image&limit=20"
```

:::success Success · 200 OK
```json
{
  "errors": null,
  "data": {
    "items": [
      {
        "key": "p/barber-bookings/01JC8Z3K4M5N6P7Q8R9S0T1V2W.png",
        "fileName": "logo.png",
        "fileSize": 48213,
        "contentType": "image/png",
        "visibility": "public-by-link",
        "createdAt": "2026-11-01T10:15:00.000Z"
      }
    ],
    "nextCursor": null
  }
}
```
:::

**Error codes**

| Code | HTTP | Meaning |
|---|---|---|
| `VALIDATION_ERROR` | 400 | A filter is invalid, e.g. `limit` over 100 or a date that is not ISO 8601 |
| `ENGINE_NOT_SUPPORTED` | 400 | A legacy project |
| `PROJECT_NOT_FOUND` | 404 | Project does not exist or you don't own it |

## Delete Files

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

Delete stored files by key, for example when a user asks you to erase their documents. Up to 100 keys per call. Free at any balance, and safe to repeat: a key that is already deleted (or never existed) comes back in `notFound`, not as an error. Projects on the new engine only; a legacy project answers `400 ENGINE_NOT_SUPPORTED`.

**What happens to a deleted file**

- Its public URL stops being served everywhere within 1 hour: a location of Cloudflare's network that cached the file can keep serving its copy for up to 1 hour after the delete, then the URL answers `410 Gone` (`404` once the recovery copy is gone). A browser that already downloaded the file can keep its own copy for up to 1 hour after that download.
- Signed URLs to it stop working at once, including the ones created before the delete.
- It disappears from [List Files](#list-files) and stops counting toward the project's storage.
- A recovery copy is kept for 30 days, then removed for good (the nightly backup copy too).
- [Deleting the project](https://www.totalum.app/docs/api/projects#delete-project) deletes all of its files.

**Path parameters**

| Parameter | Type | Description |
|---|---|---|
| `projectId` | string | The project id |

**Body parameters**

| Field | Type | Required | Description |
|---|---|---|---|
| `keys` | string[] | Yes | 1 to 100 file keys: the `fileNameId` of [Upload File](#upload-file), the `key` of [List Files](#list-files) |

**Response fields**

| Field | Type | Description |
|---|---|---|
| `data.deleted` | string[] | The keys deleted by this call |
| `data.notFound` | string[] | The keys with no file to delete (already deleted, or never stored) |

**Example request**

```bash
curl -X DELETE \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"keys": ["p/barber-bookings/01JC8Z3K4M5N6P7Q8R9S0T1V2W.png"]}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/files
```

:::success Success · 200 OK
```json
{
  "errors": null,
  "data": {
    "deleted": ["p/barber-bookings/01JC8Z3K4M5N6P7Q8R9S0T1V2W.png"],
    "notFound": []
  }
}
```
:::

:::danger Error · 403
```json
{
  "errors": {
    "errorCode": "FILE_KEY_FORBIDDEN",
    "errorMessage": "Not files of project barber-bookings: p/other-project/01JC8Z3K4M5N6P7Q8R9S0T1V2X.pdf",
    "errorDetails": { "keys": ["p/other-project/01JC8Z3K4M5N6P7Q8R9S0T1V2X.pdf"] }
  },
  "data": null
}
```
:::

**Error codes**

| Code | HTTP | Meaning |
|---|---|---|
| `VALIDATION_ERROR` | 400 | `keys` is missing, empty, over 100, or not all strings |
| `ENGINE_NOT_SUPPORTED` | 400 | A legacy project |
| `FILE_KEY_FORBIDDEN` | 403 | A key is not one of this project's files (another project's, or malformed). Nothing is deleted |
| `PROJECT_NOT_FOUND` | 404 | Project does not exist or you don't own it |
