Projects
Manage the lifecycle of your vibe coding projects.
Projects are the top-level unit of the VCaaS API. Each project has its own database, source code, dev server, and (optionally) a deployment and custom domain. All endpoints require the api-key header and return the standard { "errors": ..., "data": ... } envelope.
#Create Project
/api/v1/vcaas/projectsUses creditsCreate a new vibe coding project.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | 4-35 chars, lowercase letters + numbers + hyphens, must start with letter |
description | string | No | Project description, max 500 characters |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | The project ID |
data.description | string | Project description |
data.plan | string | Always "api" for VCaaS projects |
data.createdAt | string | ISO 8601 creation date |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"projectId":"my-app","description":"A SaaS landing page"}' \
https://api-accounts.totalum.app/api/v1/vcaas/projects{
"errors": null,
"data": {
"projectId": "my-app",
"description": "A SaaS landing page",
"plan": "api",
"createdAt": "2026-03-11T10:30:00.000Z"
}
}{
"errors": {
"errorCode": "PROJECT_ALREADY_EXISTS",
"errorMessage": "A project with this name already exists"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_PROJECT_ID | 400 | projectId is required |
INVALID_PROJECT_NAME | 400 | Invalid format. Use lowercase letters, numbers, and hyphens. Must start with a letter |
INVALID_PROJECT_NAME_LENGTH | 400 | Project name must be between 4 and 35 characters |
PROJECT_ALREADY_EXISTS | 409 | A project with this name already exists |
INSUFFICIENT_CREDITS | 402 | Not enough credits for this operation |
RATE_LIMIT_EXCEEDED | 429 | Maximum 10 project creations per 60 seconds |
#List Projects
/api/v1/vcaas/projectsFreeGet all your projects.
Response fields
| Field | Type | Description |
|---|---|---|
data | array | Array of project objects |
data[].projectId | string | The project ID |
data[].description | string | Project description |
data[].plan | string | Always "api" for VCaaS projects |
data[].createdAt | string | ISO 8601 creation date |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/projects{
"errors": null,
"data": [
{
"projectId": "my-app",
"description": "A SaaS landing page",
"plan": "api",
"createdAt": "2026-03-11T10:30:00.000Z"
}
]
}{
"errors": {
"errorCode": "LIST_PROJECTS_ERROR",
"errorMessage": "Internal error listing projects"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
LIST_PROJECTS_ERROR | 400 | Internal error listing projects |
#Get Project Details
/api/v1/vcaas/projects/:projectIdFreeRetrieve full project info including status, deployment, secrets, and URLs. This is the main polling endpoint for project state.
Path parameters
| Parameter | Type | Description |
|---|---|---|
:projectId | string | The project ID |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | The project ID |
data.description | string | Project description |
data.plan | string | Always "api" for VCaaS projects |
data.agentProcessStatus | string | undefined | "init" (running) | "done" (finished) | "idle" (not started) |
data.agentServerStatus | string | undefined | "Active" | "Creating" | "Starting" | "Archived" | "Unarchiving" | "Archiving" |
data.createdAt | string | ISO 8601 creation date |
data.deployment | object | null | Latest deployment info, null if never deployed |
data.deployment.status | string | "deploying" | "success" | "error" |
data.deployment.createdAt | string | ISO 8601 deployment date |
data.deployment.versionId | string | undefined | Version ID that was deployed |
data.versionRecovery | object | null | Set while a recoverVersion call is running, otherwise null. This is the canonical signal for "is a version recovery in progress" — do NOT poll agentProcessStatus for recovery (the agent is not involved). |
data.versionRecovery.status | string | "recovering" (in progress) | "error" (last recovery failed) |
data.versionRecovery.versionId | string | The version ID currently being / last attempted being recovered |
data.versionRecovery.startedAt | string | ISO 8601 start time |
data.versionRecovery.errorMessage | string | undefined | Present when status="error" — surface this text to the user |
data.secrets | array | List of secret names (values never returned) |
data.secrets[]._id | string | Secret ID (use for deletion) |
data.secrets[].secretName | string | Environment variable name |
data.secrets[].environment | string | "development" | "production" | "both" |
data.customDomain | object | null | Custom domain info, null if none configured |
data.customDomain.hostname | string | The custom domain hostname |
data.customDomain.status | string | "pending_validation" | "pending_deployment" | "active" | "blocked" |
data.customDomain.sslStatus | string | SSL certificate status |
data.customDomain.dnsRecordsToAdd | array | undefined | DNS records to configure: [{ type: "CNAME"|"TXT", name, value }] |
data.temporalDevelopmentProjectUrl | string | null | undefined | Live development preview URL (from the running dev server). May be null/undefined if no server has started yet. |
data.cachedDevelopmentUrl | string | null | undefined | Cached development preview URL (static snapshot, available when server is not active). May be null/undefined if the project has never been archived. |
data.developmentUrlFieldToUse | string | null | undefined | Which field to use for the development preview right now: "temporalDevelopmentProjectUrl" or "cachedDevelopmentUrl". If this field is null or undefined, default to temporalDevelopmentProjectUrl. |
data.productionProjectUrl | string | undefined | Production URL — custom domain if connected, otherwise {projectId}.totalum-project.com |
data.totalCreditsSpent | number | Total credits spent on this project |
data.creditLimits | object | Currently configured monthly credit limits for this project |
data.creditLimits.maxDevelopmentCreditsPerMonth | number | null | Max development credits/month (null = no limit) |
data.creditLimits.maxInfrastructureCreditsPerMonth | number | null | Max infrastructure credits/month (null = no limit) |
data.multiPrompt | object | null | Present only when a multi-prompt batch was started via POST /agent/start with multiPrompt. Same shape as on GET /agent/status. |
Use data.developmentUrlFieldToUse to decide which development URL to display. It returns the name of the response field containing the best URL for the current state. If it returns "cachedDevelopmentUrl", use data.cachedDevelopmentUrl; if it returns "temporalDevelopmentProjectUrl", use data.temporalDevelopmentProjectUrl. If developmentUrlFieldToUse is null or undefined, always fall back to data.temporalDevelopmentProjectUrl. The cached URL is a static snapshot available when the dev server is down (e.g. archived). Once the server is active and a prompt completes, it switches back to the live URL.
You MUST call GET /projects/:projectId and re-read the preview URL fields on these events: (1) when the user navigates to the project page, (2) when the user manually refreshes the page, and (3) every time a prompt finishes (agent status becomes "done"). The preview URL can change between these events. Always re-read developmentUrlFieldToUse after fetching and use it to pick the correct URL. Never cache the preview URL permanently. If you embed a dev preview URL or the production URL in an iframe, always provide an "Open in new tab" button next to it.
Example request
curl -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app{
"errors": null,
"data": {
"projectId": "my-app",
"description": "A SaaS landing page",
"plan": "api",
"agentProcessStatus": "done",
"agentServerStatus": "Active",
"createdAt": "2026-03-11T10:30:00.000Z",
"deployment": {
"status": "success",
"createdAt": "2026-03-11T11:00:00.000Z",
"versionId": "v_abc123"
},
"versionRecovery": null,
"secrets": [
{
"_id": "s1",
"secretName": "STRIPE_KEY",
"environment": "both"
}
],
"customDomain": {
"hostname": "app.mysite.com",
"status": "active",
"sslStatus": "active",
"dnsRecordsToAdd": [
{ "type": "CNAME", "name": "app", "value": "my-app.totalum-project.com" },
{ "type": "TXT", "name": "_cf-custom-hostname.app", "value": "verification-token" }
]
},
"temporalDevelopmentProjectUrl": "https://dev-my-app.totalum.app",
"cachedDevelopmentUrl": "https://my-app-dev-a1b2c3d4.totalum-project.com",
"developmentUrlFieldToUse": "temporalDevelopmentProjectUrl",
"productionProjectUrl": "app.mysite.com",
"totalCreditsSpent": 12.4,
"creditLimits": {
"maxDevelopmentCreditsPerMonth": 100,
"maxInfrastructureCreditsPerMonth": null
},
"multiPrompt": null
}
}{
"errors": {
"errorCode": "PROJECT_NOT_FOUND",
"errorMessage": "Project does not exist or you don't own it"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_PROJECT_ID | 400 | projectId is required |
PROJECT_NOT_FOUND | 404 | Project does not exist or you don't own it |
#Delete Project
/api/v1/vcaas/projects/:projectIdFreePermanently delete a project and all its data.
Path parameters
| Parameter | Type | Description |
|---|---|---|
:projectId | string | The project ID to delete |
Response fields
| Field | Type | Description |
|---|---|---|
data.success | boolean | true on successful deletion |
Example request
curl -X DELETE -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app{ "errors": null, "data": { "success": true } }{
"errors": {
"errorCode": "PROJECT_NOT_FOUND",
"errorMessage": "Project does not exist or you don't own it"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_PROJECT_ID | 400 | projectId is required |
PROJECT_NOT_FOUND | 404 | Project does not exist or you don't own it |
PLAN_NOT_API | 400 | Only API plan projects can be deleted from this API |
#Update Credit Limits
/api/v1/vcaas/projects/:projectId/credit-limitsFreeSet monthly spending caps per project for development and/or infrastructure credits. By default, projects have no credit limits. Set a field to null to remove that limit.
Path parameters
| Parameter | Type | Description |
|---|---|---|
:projectId | string | The project ID |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
maxDevelopmentCreditsPerMonth | number or null | No | Max development credits per month (null to remove) |
maxInfrastructureCreditsPerMonth | number or null | No | Max infrastructure credits per month (null to remove) |
Response fields
| Field | Type | Description |
|---|---|---|
data.creditLimits.maxDevelopmentCreditsPerMonth | number or null | Current development limit |
data.creditLimits.maxInfrastructureCreditsPerMonth | number or null | Current infrastructure limit |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"maxDevelopmentCreditsPerMonth":500,"maxInfrastructureCreditsPerMonth":100}' \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/credit-limitsTo remove a limit, set it to null:
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"maxDevelopmentCreditsPerMonth":null}' \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/credit-limits{
"errors": null,
"data": {
"creditLimits": {
"maxDevelopmentCreditsPerMonth": 500,
"maxInfrastructureCreditsPerMonth": 100
}
}
}{
"errors": {
"errorCode": "PROJECT_NOT_FOUND",
"errorMessage": "Project doesn't exist or you don't own it"
},
"data": null
}By default, projects have no credit limits — all operations are allowed as long as you have credits in your account. When a project reaches its monthly limit, operations in that category return a 403 PROJECT_CREDIT_LIMIT_REACHED error. Limits reset automatically on the 1st of each month.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_LIMIT_FIELDS | 400 | At least one of the two limit fields is required |
INVALID_LIMIT | 400 | Amount must be a positive number |
PROJECT_NOT_FOUND | 404 | Project doesn't exist or you don't own it |
