AI Agent
Drive the AI agent that builds and edits your project, in single- or multi-prompt mode.
The AI agent builds and modifies your project from natural-language prompts. Agent runs are asynchronous — you start a run, then poll for status. All endpoints require the api-key header.
#Run AI Agent
/api/v1/vcaas/projects/:projectId/agent/startUses creditsStart the AI agent with a prompt to build or modify your project. Supports two modes:
- Single-prompt (default, recommended): omit
multiPrompt. One prompt, 10 to 30 minutes, 10 to 40 credits. - Multi-prompt (rare, opt-in): include
multiPrompt. A sequential batch that runs unsupervised. Each prompt still takes 10 to 30 minutes and 10 to 40 credits, so a 10-prompt batch can run for several hours and burn hundreds of credits.
Only use multi-prompt when a single job genuinely requires several sequential AI runs AND the user has explicitly accepted the cost and time. For everything else, send one prompt at a time — it is faster, cheaper, and lets the user steer between steps. Multi-prompt is unsupervised: no human approval step, no pause between prompts. If unsure, do NOT pass multiPrompt.
Returns immediately with status "init". Poll GET /agent/status every 10–15s and show realtimeConversation messages live. Single-prompt: complete when status is "done". Multi-prompt: poll multiPrompt.status until "done"/"cancelled" — the per-prompt status flips between "init" and "done" repeatedly. After every prompt completes you MUST refresh the preview URL via GET /projects/:projectId → developmentUrlFieldToUse (default temporalDevelopmentProjectUrl if null/undefined).
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project to run the agent on |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes¹ | Single-prompt: the user instruction. Multi-prompt + letTotalumDecide: the high-level goal Totalum breaks down. Multi-prompt + prompts[]: ignored. |
inputFiles | array | No | Reference images/files for the agent |
inputFiles[].name | string | Yes | File name |
inputFiles[].imageDescription | string | Yes | Description of the image content |
inputFiles[].url | string | Yes | Public URL or uploaded file URL |
multiPrompt | object | No | Opt into the unsupervised multi-prompt batch mode. Omit for normal use. |
multiPrompt.prompts | string[] | No² | Up to 50 prompts to run sequentially. Each item must be a non-empty string. |
multiPrompt.letTotalumDecide | boolean | No² | When true, Totalum plans the prompt list from the top-level prompt. |
¹ prompt is required unless multiPrompt.prompts is provided (then it is ignored).
² When multiPrompt is present, exactly one of prompts (non-empty array, ≤ 50 items) or letTotalumDecide=true must be set.
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | The project ID |
data.status | string | Always "init" on success — for both single and multi-prompt mode |
data.message | string | Instructions on how to poll for status (multi-prompt includes the cost/time warning) |
data.multiPrompt | object | undefined | Present only when the request had multiPrompt. Detailed batch status is on GET /agent/status under multiPrompt. |
data.multiPrompt.totalPrompts | number | undefined | Known when the caller provided prompts; absent while Totalum is still planning a letTotalumDecide batch. |
Example request
# Single-prompt (recommended)
curl -X POST -H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"prompt":"Create a landing page with a contact form","inputFiles":[]}' \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/agent/start
# Multi-prompt with explicit list
curl -X POST -H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"multiPrompt":{"prompts":["Set up auth","Add a Stripe checkout","Wire up the dashboard"]}}' \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/agent/start
# Multi-prompt with Totalum planning
curl -X POST -H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"prompt":"Build a full SaaS billing system with auth, Stripe, and a customer portal","multiPrompt":{"letTotalumDecide":true}}' \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/agent/start{
"errors": null,
"data": {
"projectId": "my-app",
"status": "init",
"message": "Process started, can take from 10 to 30 minutes (10 to 40 credits). Fetch GET .../agent/status every 10-15 seconds to track progress."
}
}Multi-prompt response also includes multiPrompt:
{
"errors": null,
"data": {
"projectId": "my-app",
"status": "init",
"message": "Multi-prompt run started with 3 prompts. ⚠️ Multi-prompt mode is expensive and slow — only use it when a job genuinely requires multiple sequential AI runs. Each prompt typically takes 10 to 30 minutes and costs 10 to 40 credits...",
"multiPrompt": { "totalPrompts": 3 }
}
}{
"errors": { "errorCode": "AGENT_RUNNING", "errorMessage": "An agent is already running on this project" },
"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 |
MISSING_PROMPT | 400 | prompt is required (single-prompt mode, or letTotalumDecide=true — it is the planner goal) |
INVALID_MULTI_PROMPT | 400 | multiPrompt was set but neither (or both) of prompts[] and letTotalumDecide=true were provided |
TOO_MANY_PROMPTS | 400 | multiPrompt.prompts exceeds the 50-item limit |
INVALID_PROMPT_ITEM | 400 | A multiPrompt.prompts entry is not a non-empty string |
AGENT_RUNNING | 409 | An agent is already running on this project |
AUTO_EXECUTION_ALREADY_ACTIVE | 409 | A previous multi-prompt batch is still active (executing/paused/awaiting approval). Cancel it first. |
DEPLOYMENT_RUNNING | 409 | Cannot start agent while a deployment is in progress |
RECOVERY_RUNNING | 409 | Cannot start agent while a version recovery is in progress — poll versionRecovery until null, then retry |
INSUFFICIENT_CREDITS | 402 | Minimum 50 credits required to start agent (multi-prompt typically needs many more) |
PROMPT_SECURITY_VIOLATION | 400 | Prompt failed security validation |
#Get Agent Status
/api/v1/vcaas/projects/:projectId/agent/statusFreePoll to track agent progress and get real-time messages. Poll every 10–15 seconds.
When status becomes "done", you MUST refresh the preview URL by calling GET /projects/:projectId and reading developmentUrlFieldToUse ("temporalDevelopmentProjectUrl" for the live server or "cachedDevelopmentUrl" for a cached snapshot). If developmentUrlFieldToUse is null or undefined, default to temporalDevelopmentProjectUrl. The URL can change after each agent run.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project to poll |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | The project ID |
data.status | string | "init" (a prompt is currently running) | "done" (last prompt finished) | "idle" (never run). For multi-prompt runs this flips between "init" and "done" repeatedly as the batch advances — poll multiPrompt.status to track the whole batch. |
data.startedAt | string | null | ISO 8601 start time of the current prompt (not the whole batch) |
data.realtimeConversation | array | Single-prompt: messages from the current run. Multi-prompt: messages from the whole batch (scoped to multiPrompt.startedAt). |
data.realtimeConversation[].author | string | "user" | "agent" |
data.realtimeConversation[].message | string | The message text |
data.realtimeConversation[].messageType | string | "regular" | "starting" | "building" | "finished" | "error" | "limit-reached" |
data.realtimeConversation[].createdAt | string | ISO 8601 message date |
data.realtimeConversation[].versionId | string | undefined | Version created at this step |
data.realtimeConversation[].secretKeysNeeded | object | undefined | Secrets the agent needs (key: { isProvided, description }) |
data.realtimeConversation[].gitDiffUrl | string | undefined | Diff URL for this step |
data.creditsSpent | number | undefined | Credits spent on the current prompt (present when status is "done") |
data.multiPrompt | object | null | Present only when a multi-prompt batch exists. The canonical signal for "is a multi-prompt run in flight" — poll its status until "done"/"cancelled". |
data.multiPrompt.status | string | "planning" | "executing" | "paused" | "done" | "cancelled" |
data.multiPrompt.totalPrompts | number | Number of prompts in the batch (0 during planning) |
data.multiPrompt.currentPromptIndex | number | Index of the in-flight prompt; -1 before the first one starts |
data.multiPrompt.prompts | array | Ordered list of prompts in the batch |
data.multiPrompt.prompts[].order | number | Position in the batch |
data.multiPrompt.prompts[].prompt | string | The prompt text |
data.multiPrompt.prompts[].status | string | "pending" | "executing" | "done" | "failed" |
data.multiPrompt.startedAt | string | ISO 8601 start time of the whole batch |
data.multiPrompt.updatedAt | string | ISO 8601 last change to the batch state |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/agent/status{
"errors": null,
"data": {
"projectId": "my-app",
"status": "init",
"startedAt": "2026-03-11T10:35:00.000Z",
"realtimeConversation": [
{
"author": "agent",
"message": "Creating the project structure...",
"messageType": "building",
"createdAt": "2026-03-11T10:36:00.000Z"
},
{
"author": "agent",
"message": "Project built successfully!",
"messageType": "finished",
"versionId": "v_abc123",
"gitDiffUrl": "https://...",
"secretKeysNeeded": {
"STRIPE_SECRET_KEY": { "isProvided": false, "description": "Required for processing payments" }
},
"createdAt": "2026-03-11T10:50:00.000Z"
}
],
"creditsSpent": 3.2,
"multiPrompt": null
}
}Multi-prompt example (running prompt 2 of 3):
{
"errors": null,
"data": {
"projectId": "my-app",
"status": "init",
"startedAt": "2026-03-11T11:05:00.000Z",
"realtimeConversation": [ /* messages from the whole batch */ ],
"multiPrompt": {
"status": "executing",
"totalPrompts": 3,
"currentPromptIndex": 1,
"prompts": [
{ "order": 0, "prompt": "Set up auth", "status": "done" },
{ "order": 1, "prompt": "Add Stripe checkout", "status": "executing" },
{ "order": 2, "prompt": "Wire up dashboard", "status": "pending" }
],
"startedAt": "2026-03-11T10:35:00.000Z",
"updatedAt": "2026-03-11T11:05:00.000Z"
}
}
}{
"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 |
#Get Full Conversation
/api/v1/vcaas/projects/:projectId/agent/full-conversationFreeRetrieve the complete conversation history across all agent runs (not just the current one).
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project whose conversation to fetch |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | The project ID |
data.conversation | array | All messages from all agent runs |
data.conversation[].author | string | "user" | "agent" |
data.conversation[].message | string | The message text |
data.conversation[].messageType | string | "regular" | "starting" | "building" | "finished" | "error" | "limit-reached" |
data.conversation[].createdAt | string | ISO 8601 message date |
data.conversation[].versionId | string | undefined | Version created at this step |
data.conversation[].secretKeysNeeded | object | undefined | API keys the agent needs (key: { isProvided, description }). Add missing keys as project secrets. |
data.conversation[].gitDiffUrl | string | undefined | Diff URL for this step |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/agent/full-conversation{
"errors": null,
"data": {
"projectId": "my-app",
"conversation": [
{ "author": "user", "message": "Create a landing page", "messageType": "regular", "createdAt": "..." },
{ "author": "agent", "message": "Building project structure...", "messageType": "building", "createdAt": "..." },
{
"author": "agent",
"message": "Project built successfully!",
"messageType": "finished",
"versionId": "v_abc123",
"gitDiffUrl": "https://...",
"secretKeysNeeded": {
"STRIPE_SECRET_KEY": { "isProvided": false, "description": "Required for processing payments" }
},
"createdAt": "..."
}
]
}
}{
"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 |
#Stop Agent
/api/v1/vcaas/projects/:projectId/agent/stopFreeSend a stop signal to a running agent.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project whose agent to stop |
Response fields
| Field | Type | Description |
|---|---|---|
data.message | string | Confirmation message |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/agent/stop{ "errors": null, "data": { "message": "Agent stop signal sent" } }{
"errors": { "errorCode": "NO_PROCESS_RUNNING", "errorMessage": "No agent process is currently running" },
"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 |
NO_PROCESS_RUNNING | 400 | No agent process is currently running |
#Concurrency rules
- Only one heavy operation at a time per project: agent, deployment, version recovery, or server start.
- If deploy or recover is called and the server is not active, it auto-starts (charges
START_SERVERcredits) and returnsSERVER_NOT_READY. PollGET /projects/:projectIduntilagentServerStatusis"Active", then retry. - Agent start IS allowed during server operations (the backend waits internally).
#Complete integration flow
- Ensure project exists —
POST /projects, body{ "projectId": "my-app" }. Create one if the user doesn't already have a project. - Send prompt to AI agent —
POST /projects/my-app/agent/start, body{ "prompt": "Build a SaaS landing page" }. Async, takes 6–15 min. - Poll agent status (every 10–15s) —
GET /projects/my-app/agent/status."init"= working (showrealtimeConversation),"done"= finished (creditsSpent= total). On"done", immediately callGET /projects/:projectIdand refresh the preview URL.- 3.1 Refresh preview URL (on page load, page refresh, and after every prompt finishes) —
GET /projects/my-app, readdevelopmentUrlFieldToUse: usecachedDevelopmentUrl(static snapshot while the server is archived) ortemporalDevelopmentProjectUrl(live dev server); defaulttemporalDevelopmentProjectUrlifnull/undefined. Reload the iframe when the URL changes.
- 3.1 Refresh preview URL (on page load, page refresh, and after every prompt finishes) —
- Publish —
POST /projects/my-app/deployments/deploy. Builds, deploys, and assigns a public URL. Takes 1–3 min. - Poll deployment status (every 10–15s) —
GET /projects/my-app/deployments/status; on"success", getproductionProjectUrlfromGET /projects/my-app. - (Optional) Custom domain —
PUT /projects/my-app/domain, body{ "hostname": "app.yourdomain.com" }; configure the returned DNS records; pollcustomDomain.statusuntil"active".
- Agent and deployment are ASYNCHRONOUS — always poll for status.
- Show real-time agent conversation messages during generation.
- NEVER call the Totalum API from frontend code — always go through your backend server.
- ALWAYS refresh the dev preview URL (
GET /projects/:projectId) on page load, page refresh, and after each prompt; usedevelopmentUrlFieldToUse; defaulttemporalDevelopmentProjectUrlifnull/undefined. - On the dev preview, don't show the production URL; display an open-in-blank link so the user always sees the realtime dev preview (production may be outdated until published).
- Keep the API key secret — it must never leave your backend server.
#Global error codes
All errors are shaped as { "errors": { "errorCode": "CODE", "errorMessage": "..." }, "data": null }.
| Code | HTTP | Meaning |
|---|---|---|
INSUFFICIENT_CREDITS | 402 | Not enough credits for this operation |
PROJECT_CREDIT_LIMIT_REACHED | 403 | Project monthly credit limit reached for that category |
PROJECT_NOT_ALLOWED | 403 | API key doesn't have access to this project |
PROJECT_NOT_FOUND | 404 | Project doesn't exist or you don't own it |
AGENT_RUNNING | 409 | Agent already running on this project |
DEPLOYMENT_RUNNING | 409 | A deployment is in progress |
RECOVERY_RUNNING | 409 | A version recovery is in progress |
SERVER_NOT_READY | 409 | Server auto-starting, poll until Active |
NO_DEPLOYMENT | 404 | No deployment found. Deploy first and wait until status is "success" |
MISSING_PROMPT | 400 | prompt field is required |
MISSING_PROJECT_ID | 400 | projectId field is required |
INVALID_PROJECT_NAME | 400 | Invalid projectId format |
INVALID_PROJECT_NAME_LENGTH | 400 | Project name must be 4-35 characters |
PROJECT_ALREADY_EXISTS | 409 | A project with this ID already exists |
PLAN_NOT_API | 400 | Only API plan projects can be deleted |
PROMPT_SECURITY_VIOLATION | 400 | Prompt failed security validation |
NO_PROCESS_RUNNING | 400 | No agent process is currently running |
MISSING_FILE | 400 | file field is required (multipart) |
UPLOAD_FAILED | 500 | File upload failed |
MISSING_VERSION_ID | 400 | versionId is required |
MISSING_SECRET_FIELDS | 400 | secretName and secretValue are required |
INVALID_SECRET_KEY_NAME | 400 | Invalid secret key name format |
MISSING_SECRET_ID | 400 | secretId is required |
MISSING_HOSTNAME | 400 | hostname is required |
MISSING_TABLE_NAME | 400 | tableName is required |
GET_ACCOUNT_ERROR | 400 | Internal error fetching account info |
LIST_PROJECTS_ERROR | 400 | Internal error listing projects |
RATE_LIMIT_EXCEEDED | 429 | Too many requests (max 10 project creations per 60s) |
MISSING_WEBHOOK_FIELDS | 400 | url and event are required |
INVALID_WEBHOOK_URL | 400 | Webhook URL must use HTTPS |
INVALID_WEBHOOK_EVENT | 400 | Event not in allowed list |
WEBHOOK_EVENT_ALREADY_EXISTS | 409 | A webhook for this event already exists |
WEBHOOK_NOT_FOUND | 404 | Webhook not found |
MISSING_GITHUB_FIELDS | 400 | token and repositoryFullName are required |
GITHUB_VALIDATION_FAILED | 400 | Token invalid, missing permissions, or repo not found |
GITHUB_SECRET_STORE_ERROR | 400 | Failed to store GitHub credentials |
GITHUB_SYNC_FAILED | 400 | Initial sync with GitHub failed |
GITHUB_NOT_CONNECTED | 400 | GitHub is not connected to this project |
GITHUB_PULL_ERROR | 400 | Failed to pull changes from GitHub |
MISSING_LIMIT_FIELDS | 400 | At least one credit limit field is required |
INVALID_LIMIT | 400 | Credit limit must be a positive number |
INVALID_SYNC_DIRECTION | 400 | syncDirection must be "totalum_to_github" or "github_to_totalum" |
