Secrets, Keys & .env
Environment variables, project keys and the .env file.
Secrets are environment variables the app reads on the server (process.env.STRIPE_KEY): values are write-only and never returned. Project API keys (tlm_pk_…) let your own servers call the project's database, files and built-in features. They are not your account key (tlm_sk_…).
#Create Secret
/api/v1/vcaas/projects/:projectId/secretsapi-keyFreeAdd an environment variable. It reaches the preview and the agent at once and the published site at the next publish. The agent never asks for a key in its reply text: when the app needs one, its message carries secretKeysNeeded (Get Agent Status). Let your user choose the environment, add each name here, then prompt again ("the keys are saved, carry on").
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
secretName | string | Yes | Uppercase letters, digits and _, starting with a letter. Names starting with TOTALUM_ (except TOTALUM_VCAAS_API_KEY) and NODE_ENV, NEXT_PUBLIC_APP_URL, BETTER_AUTH_SECRET, NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, TURSO_AUTH_TOKEN, TURSO_DATABASE_URL are reserved |
secretValue | string | Yes | 1–10,000 characters |
environment | string | No | "development" (preview), "production" (published site) or "both" (default) |
Response fields
| Field | Type | Description |
|---|---|---|
data._id | string | Secret id (to delete it) |
data.secretName | string | Name |
data.environment | string | Environment |
data.createdAt | string | ISO 8601 |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"secretName":"OPENWEATHER_KEY","secretValue":"abc123","environment":"both"}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/secretsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/secrets", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"secretName": "OPENWEATHER_KEY",
"secretValue": "abc123",
"environment": "both"
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "_id": "sec_01k6xy", "secretName": "OPENWEATHER_KEY", "environment": "both", "createdAt": "2026-10-08T12:00:00.000Z" } }{ "errors": { "errorCode": "VALIDATION_ERROR", "errorMessage": "secretName: Secret name must be uppercase, separated by underscores, cannot start with a number, and can only contain letters, numbers, and underscores", "errorDetails": { "errors": [{ "value": "openweather", "msg": "Secret name must be uppercase, separated by underscores, cannot start with a number, and can only contain letters, numbers, and underscores", "param": "secretName", "location": "body" }] } }, "data": null }Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_SECRET_FIELDS | 400 | secretName or secretValue is missing |
VALIDATION_ERROR | 400 | The name, value or environment is invalid; errorDetails.errors lists each field |
SECRET_CREATE_FAILED | 400 | A secret with this name already exists in that environment |
RESERVED_SECRET_NAME | 400 | The name is reserved |
SECRET_LIMIT_EXCEEDED | 400 | The project already has 500 secrets |
#Delete Secret
/api/v1/vcaas/projects/:projectId/secrets/:secretIdapi-keyFreeRemove an environment variable. The names and ids of a project's secrets are in Get Project (secrets[]).
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
secretId | string | The secret's _id |
Response fields
| Field | Type | Description |
|---|---|---|
data.success | boolean | true |
Example request
curl -X DELETE -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/secrets/sec_01k6xyconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/secrets/sec_01k6xy", {
method: "DELETE",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "success": true } }#List Project Keys
/api/v1/vcaas/projects/:projectId/api-keysapi-keyFreeThe project's own API keys, revoked ones included (secrets are never listed). Two are managed by Totalum: managed: "app" runs the app itself, managed: "env" is the key in the downloaded .env; rotate them, never revoke them.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.keys[].id | string | Key id |
data.keys[].name | string | Name |
data.keys[].prefix | string | The visible start of the key |
data.keys[].scopes | string[] | Scopes |
data.keys[].env | string | "dev", "live" or "both" |
data.keys[].createdAt | string | null | ISO 8601 |
data.keys[].expiresAt | string | null | Expiry |
data.keys[].revokedAt | string | null | When it was revoked |
data.keys[].managed | string | undefined | "app" or "env" for Totalum's own keys |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keysconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keys", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "keys": [{ "id": "key_01k6xz", "name": "backend", "prefix": "tlm_pk_a1b2", "scopes": ["db:read", "db:write"], "env": "both", "createdAt": "2026-10-08T12:00:00.000Z", "expiresAt": null, "revokedAt": null }] } }#Create Project API Key
/api/v1/vcaas/projects/:projectId/api-keysapi-keyFreeCreate a tlm_pk_ key for calling this project's database, files and built-in features from your servers (with totalum-sdk or HTTP). It works only on this project and only for its scopes. The secret is returned once. Owner (or a member allowed to manage API keys) only.
| Scope | Allows |
|---|---|
db:read, db:write, db:schema | Read rows, write rows, change the schema |
files:read, files:write | Read and upload files (files:write also makes PDFs) |
ai, email, web, scan, speech, vectors, cron, seo | One built-in feature each |
integrations:* | Every built-in feature above |
browser, payments, realtime | Cloud browser, payments, real-time |
logs:read, analytics:read, meter:write | Logs, site analytics, usage reporting |
* | Everything |
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A name |
scopes | string[] | Yes | One or more scopes from the table |
env | string | No | "dev", "live" or both (default): the database environment the key may use |
Response fields
| Field | Type | Description |
|---|---|---|
data.key | object | The key, with the fields of List Project Keys |
data.key.plaintext | string | The secret — shown only now |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"name":"backend","scopes":["db:read","db:write","integrations:*"]}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keysconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keys", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "backend",
"scopes": [
"db:read",
"db:write",
"integrations:*"
]
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "key": { "id": "key_01k6xz", "name": "backend", "prefix": "tlm_pk_a1b2", "scopes": ["db:read", "db:write", "integrations:*"], "env": "both", "createdAt": "2026-10-08T12:00:00.000Z", "expiresAt": null, "revokedAt": null, "plaintext": "tlm_pk_a1b2…" } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
INVALID_SCOPES | 400 | Empty or unknown scopes; the message lists the valid ones |
OWNER_ONLY | 403 | Only the owner or a member allowed to manage API keys |
PROJECT_NOT_READY | 409 | The project is still being set up; retry after Retry-After seconds |
#Rotate Project Key
/api/v1/vcaas/projects/:projectId/api-keys/:keyId/rotateapi-keyFreeReplace a key with a new secret (same name, scopes and environment); the old one stops at once. For the app's own key, the new key reaches the app before the old one stops.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
keyId | string | The key id |
Response fields
| Field | Type | Description |
|---|---|---|
data.key | object | The new key, with plaintext (shown once) |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keys/key_01k6xz/rotateconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keys/key_01k6xz/rotate", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "key": { "id": "key_01k6y0", "name": "backend", "prefix": "tlm_pk_c3d4", "scopes": ["db:read", "db:write", "integrations:*"], "env": "both", "createdAt": "2026-10-08T12:10:00.000Z", "expiresAt": null, "revokedAt": null, "plaintext": "tlm_pk_c3d4…" } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
KEY_NOT_FOUND | 404 | Unknown or already revoked |
OWNER_ONLY | 403 | Only the owner or a member allowed to manage API keys |
#Revoke Project Key
/api/v1/vcaas/projects/:projectId/api-keys/:keyIdapi-keyFreeRevoke a key at once.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
keyId | string | The key id |
Response fields
| Field | Type | Description |
|---|---|---|
data.revoked | boolean | true |
Example request
curl -X DELETE -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keys/key_01k6y0const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/api-keys/key_01k6y0", {
method: "DELETE",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "revoked": true } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
KEY_NOT_FOUND | 404 | Unknown or already revoked |
KEY_MANAGED | 409 | The app's own key or the .env key: rotate it instead |
OWNER_ONLY | 403 | Only the owner or a member allowed to manage API keys |
#Get Project Env
/api/v1/vcaas/projects/:projectId/envapi-keyFreeThe project's .env file for one environment, as plain text (not JSON): the project key, the auth secret, the URLs and that environment's secrets. Save it as .env (dev) or .env.prod (live) next to the downloaded source to run the app locally; never commit it. Owner (or a member allowed to manage API keys) only.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
env | string | Yes | "dev" or "live" |
Response fields
| Field | Type | Description |
|---|---|---|
| (body) | text | The .env file (Content-Disposition: attachment) |
Example request
curl -H "api-key: tlm_sk_your_key" -o .env \
"https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/env?env=dev"const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/env?env=dev", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const text = await res.text();Error codes
| Code | HTTP | Meaning |
|---|---|---|
INVALID_ENV | 400 | env is not dev or live |
OWNER_ONLY | 403 | Only the owner or a member allowed to manage API keys |
