AI Agent
Prompt the agent that builds and changes your app, and follow it live.
The AI agent writes and changes the project's code from natural-language prompts. A run is asynchronous: Run AI Agent answers at once with status: "init", then you poll Get Agent Status every 10–15 seconds until status is "done". Every finished run that changed files is saved as a new version and deployed to the project's development preview — never to the published site, which only changes when you publish. The agent checks its work in its own browser and reads the app's logs (development and live); when a user says "it doesn't work", it reads the error logs first.
#Run AI Agent
/api/v1/vcaas/projects/:projectId/agent/startapi-keyMetered, at least 3 creditsSend one prompt to the agent (or a multi-prompt batch). One run at a time per project. Once the running prompt's change is in the preview and the run is only finishing, the next prompt is accepted and waits: it starts as soon as that run ends. One prompt can wait; Stop Agent cancels the waiting one first.
A prompt is priced by the work the agent does: at least 3 credits, usually 3 to 50. A question that changes nothing is answered in seconds for 3 credits and creates no version. A run that fails without producing anything is refunded. The agent works up to about 40 minutes on one prompt, then wraps up and saves what it built; most prompts take a few minutes.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes¹ | The instruction, up to 40,000 characters |
tier | string | No | "auto" (default), "cheap", "normal" or "expensive" — see Agent tiers. model and modelTier are accepted as aliases |
inputFiles | object[] | No | Up to 10 files for this prompt. Say in the prompt what each one is for ("use this as the logo") |
inputFiles[].name | string | Yes | File name |
inputFiles[].imageDescription | string | No | What the file shows |
inputFiles[].url | string | Yes | Public URL, or the url returned by Upload File |
multiPrompt | object | No | Run a batch of prompts one after another, unattended. Omit for normal use |
multiPrompt.prompts | string[] | No² | 1–50 prompts, run in order |
multiPrompt.letTotalumDecide | boolean | No² | true = Totalum plans the prompts from prompt (the goal) |
¹ Not needed when multiPrompt.prompts is given.
² Exactly one of prompts or letTotalumDecide: true.
fastMode and effort are accepted for compatibility and ignored (an ignored option is listed in data.warnings; the run still starts).
#Agent tiers
| Tier | Use it for |
|---|---|
"auto" | The default. Totalum picks the tier for each prompt from the prompt, the recent conversation and the project: cheap for small edits and questions, normal for builds and features, expensive only when a fix keeps failing or you ask for the best |
"cheap" | The lowest price for real work: landing pages, redesigns, small apps, edits |
"normal" | Full apps and features, with more thinking |
"expensive" | The most capable agent, for complex apps and hard bugs. Uses the most credits |
With "auto", the run's final message carries autoMode: true and modelUsed: the tier that ran ("cheap", "normal" or "expensive"), charged as that tier.
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | Project id |
data.status | string | Always "init" |
data.message | string | How to follow the run |
data.expectedMinutes | number | undefined | Estimated minutes — an estimate, never a deadline |
data.expectedFinishAt | string | undefined | Start + expectedMinutes (ISO 8601) |
data.warnings | string[] | undefined | Options that were ignored |
data.runId | string | undefined | The run's id (single prompt) |
data.multiPrompt.totalPrompts | number | undefined | Batches only: number of prompts (absent while Totalum plans them) |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"prompt":"Add a contact form that emails me every message"}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/startconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/start", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"prompt": "Add a contact form that emails me every message"
}),
});
const { errors, data } = await res.json();{
"errors": null,
"data": {
"projectId": "barber-bookings",
"status": "init",
"message": "Process started. Estimated time: about 6 minutes. This is only an approximation… Fetch GET /api/v1/vcaas/projects/barber-bookings/agent/status every 10-15 seconds to track progress; the status there is the only reliable signal that the run has finished.",
"expectedMinutes": 6,
"expectedFinishAt": "2026-10-08T11:16:00.000Z",
"runId": "run_01k6xc4n7d"
}
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_PROMPT | 400 | prompt is missing |
INVALID_MULTI_PROMPT | 400 | multiPrompt needs exactly one of prompts or letTotalumDecide: true |
TOO_MANY_PROMPTS | 400 | More than 50 prompts |
INVALID_PROMPT_ITEM | 400 | A prompt in prompts is empty |
VALIDATION_ERROR | 400 | The prompt is over 40,000 characters, or a field has the wrong type |
INSUFFICIENT_CREDITS | 402 | Your balance is below 1 credit |
PROJECT_CREDIT_LIMIT_REACHED | 403 | The project's monthly development cap is reached — raise it with Update Credit Limits |
AGENT_RUNNING | 409 | A prompt is already running and its change is not in the preview yet, or a prompt is already waiting (a batch answers 400 AGENT_ALREADY_RUNNING) |
PROJECT_PAUSED_BY_USER | 409 | The project is paused — resume it first |
OPERATION_IN_PROGRESS | 409 | A restore, import or other long operation holds the project |
PROJECT_NOT_FOUND | 404 | No such project in your account |
RATE_LIMITED | 429 | Your account sent more prompts this minute than its plan allows, or has the most prompts running at once (all projects together, see Limits) — retry after Retry-After seconds |
If your balance reaches 0 during a run, the run stops: what it built is saved as a version, it ends with INSUFFICIENT_CREDITS, and you are charged only what it used. A run that crosses the project's development cap stops the same way with PROJECT_CREDIT_LIMIT_REACHED.
#Get Agent Status
/api/v1/vcaas/projects/:projectId/agent/statusapi-keyFreeWhether a prompt is running, and the messages of the current (or last) run. Poll every 10–15 seconds. When status becomes "done", read the preview URL again from Get Project.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | Project id |
data.status | string | "init" (running), "done" (the last run finished) or "idle" (never prompted) |
data.startedAt | string | null | Start of the current or last run |
data.runId | string | undefined | The current or last run |
data.realtimeConversation | object[] | The run's messages, oldest first (the user's prompt is in Get Full Conversation) |
data.realtimeConversation[].author | string | "user" or "agent" |
data.realtimeConversation[].message | string | Text |
data.realtimeConversation[].messageType | string | "regular", "starting", "building" (a status note), "finished" (the run's summary), "error", "limit-reached" or "individual" (a one-off notice) |
data.realtimeConversation[].createdAt | string | ISO 8601 |
data.realtimeConversation[].versionId | string | undefined | The version this message created |
data.realtimeConversation[].secretKeysNeeded | object | undefined | Secrets the app needs: { NAME: { isProvided, description } }. Add them with Create Secret |
data.realtimeConversation[].files | object[] | undefined | Files attached to the message |
data.realtimeConversation[].autoMode | boolean | undefined | true on the final message of an auto run |
data.realtimeConversation[].modelUsed | string | undefined | The tier an auto run used |
data.creditsSpent | number | undefined | What the last run cost, once "done" |
data.expectedMinutes | number | null | Estimate of the running prompt |
data.expectedFinishAt | string | null | Start + estimate |
data.multiPrompt | object | null | The batch, when one exists: { status, totalPrompts, currentPromptIndex, prompts[{ order, prompt, status }], startedAt, updatedAt }. status: "executing", "paused", "done", "cancelled" |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/statusconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/status", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{
"errors": null,
"data": {
"projectId": "barber-bookings",
"status": "done",
"startedAt": "2026-10-08T11:10:00.000Z",
"realtimeConversation": [
{
"author": "agent",
"message": "Added a contact form on /contact. Every message is saved and e-mailed to you. I sent a test message: saved, 200.",
"messageType": "finished",
"versionId": "ver_01k6xd2m8q",
"createdAt": "2026-10-08T11:15:40.000Z",
"autoMode": true,
"modelUsed": "normal"
}
],
"creditsSpent": 7.4,
"expectedMinutes": null,
"expectedFinishAt": null,
"multiPrompt": null,
"runId": "run_01k6xc4n7d"
}
}For a few seconds after a run ends, the preview may answer 503 with the header x-totalum-preview: updating while the new version goes live. Retry after the retry-after header; the preview never serves the previous version in that window.
#Get Agent Progress
/api/v1/vcaas/projects/:projectId/agent/progressapi-keyFreeA step-by-step view of the current run (or the last one): its stage, the files it changed, the builds and checks it ran. Use it to show a live "building" timeline. data is null before the first run.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.runId | string | The run |
data.state | string | "queued", "provisioning", "running", "finishing", "done", "failed", "cancelled" or "incomplete" |
data.stage | string | Where the run is: "queued", "sandbox", "starting", "thinking", "working", "build", "deploy", "fixing", "wrapping-up", "entry-page", "done", "failed", "cancelled", "incomplete" |
data.startedAt | string | Start time |
data.finishedAt | string | undefined | End time |
data.turns | number | Agent turns so far |
data.steps | object[] | Up to the 40 latest steps: { at, kind, file?, task?, ok?, added?, removed? }. kind: read, search, create, edit, delete, command, web, browser, integration, helper, plan, waiting; task (commands): build, test, install, database, check, save, other |
data.totalSteps | number | All steps of the run |
data.files | object[] | Files changed: { path, change: "create" | "edit" | "delete", added, removed } |
data.counts | object | { reads, searches, edits, commands, failedCommands } |
data.build | object | undefined | { ok } of the last build |
data.tests | object | undefined | { runs, failed, failedFlows? }: the checks the agent ran on the pages it changed |
data.versionId | string | undefined | The version the run saved |
data.failure | string | undefined | Why it failed: INSUFFICIENT_CREDITS, PROJECT_CREDIT_LIMIT_REACHED, DEPLOY_FAILED, RUN_TIMED_OUT, RUN_INTERRUPTED, CONTENT_BLOCKED, … or OTHER |
data.creditsSpent | number | undefined | What the run cost, once it ended |
data.previewAt | string | undefined | When the preview last started serving this run's new build (reload your preview frame when it changes) |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/progressconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/progress", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{
"errors": null,
"data": {
"runId": "run_01k6xc4n7d",
"state": "running",
"stage": "working",
"startedAt": "2026-10-08T11:10:00.000Z",
"turns": 12,
"steps": [
{ "at": "2026-10-08T11:12:01.000Z", "kind": "edit", "file": "src/app/contact/page.tsx", "added": 64, "removed": 0 },
{ "at": "2026-10-08T11:12:30.000Z", "kind": "command", "task": "database", "ok": true }
],
"totalSteps": 18,
"files": [{ "path": "src/app/contact/page.tsx", "change": "create", "added": 64, "removed": 0 }],
"counts": { "reads": 6, "searches": 2, "edits": 4, "commands": 3, "failedCommands": 0 }
}
}#Get Full Conversation
/api/v1/vcaas/projects/:projectId/agent/full-conversationapi-keyFreeThe project's whole chat: every prompt and every agent message, oldest first (the latest 1,000).
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | Project id |
data.conversation | object[] | Messages with the fields of realtimeConversation[] in Get Agent Status |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/full-conversationconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/full-conversation", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{
"errors": null,
"data": {
"projectId": "barber-bookings",
"conversation": [
{ "author": "user", "message": "Add a contact form that emails me every message", "messageType": "regular", "createdAt": "2026-10-08T11:10:00.000Z" },
{ "author": "agent", "message": "Added a contact form on /contact…", "messageType": "finished", "versionId": "ver_01k6xd2m8q", "createdAt": "2026-10-08T11:15:40.000Z" }
]
}
}#Stop Agent
/api/v1/vcaas/projects/:projectId/agent/stopapi-keyFreeStop the running prompt (and the rest of a multi-prompt batch). What the run built so far is kept; you pay only for what it used.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.message | string | "Agent process cancelled successfully" |
data.paused | boolean | false |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/stopconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/stop", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "message": "Agent process cancelled successfully", "paused": false } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
NO_PROCESS_RUNNING | 400 | No prompt is running |
#Resume Multi-Prompt Batch
/api/v1/vcaas/projects/:projectId/agent/multi-prompt/resumeapi-keyThe prompts it runsWhen a prompt of a batch fails, the batch pauses there (multiPrompt.status: "paused") because the next prompts build on it. This runs the failed prompt again, then the rest. Stop Agent cancels the batch instead.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.projectId | string | Project id |
data.status | string | "init" |
data.message | string | Which prompt the batch continues from |
data.multiPrompt.totalPrompts | number | Prompts in the batch |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/multi-prompt/resumeconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/multi-prompt/resume", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "projectId": "barber-bookings", "status": "init", "message": "Multi-prompt run continued from prompt 2.", "multiPrompt": { "totalPrompts": 3 } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
INVALID_AUTO_STATUS | 400 | No batch is paused |
AGENT_RUNNING | 400 | A prompt is running |
#Clear Agent Context
/api/v1/vcaas/projects/:projectId/agent/clear-contextapi-keyFreeThe next prompt starts with a fresh agent memory: it works from the project's current code and its change history, plus the messages sent after the clear. Restate anything from before that still matters.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.message | string | "Agent context cleared" |
data.contextClearPending | boolean | true until the next prompt starts |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/clear-contextconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/clear-context", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "message": "Agent context cleared", "contextClearPending": true } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
AGENT_RUNNING | 400 | A prompt is running |
#Get System Prompt
/api/v1/vcaas/projects/:projectId/agent/system-promptapi-keyFreeThe project's own instructions for the agent, added to every prompt (house style, rules, context).
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.systemPrompt | string | null | The instructions, or null |
data.updatedAt | string | null | When they were last set |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/system-promptconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/system-prompt", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "systemPrompt": "Write every page in Spanish. Use our brand blue #1d4ed8.", "updatedAt": "2026-10-08T09:00:00.000Z" } }#Set System Prompt
/api/v1/vcaas/projects/:projectId/agent/system-promptapi-keyFreeSet or clear the project's instructions for the agent.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
systemPrompt | string | null | Yes | Up to 20,000 characters; null or "" clears it |
systemPromptBase64 | string | No | The same text as base64 (UTF-8), instead of systemPrompt |
Response fields
| Field | Type | Description |
|---|---|---|
data.systemPrompt | string | null | The saved instructions |
data.updatedAt | string | When they were saved |
Example request
curl -X PUT \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"systemPrompt":"Write every page in Spanish. Use our brand blue #1d4ed8."}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/system-promptconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/system-prompt", {
method: "PUT",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"systemPrompt": "Write every page in Spanish. Use our brand blue #1d4ed8."
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "systemPrompt": "Write every page in Spanish. Use our brand blue #1d4ed8.", "updatedAt": "2026-10-08T09:00:00.000Z" } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | Not text, or over 20,000 characters |
#List MCP Servers
/api/v1/vcaas/projects/:projectId/agent/mcp-serversapi-keyFreeThe MCP servers you added for the agent to use while it builds (header values are never returned).
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.servers | object[] | The servers |
data.servers[].id | string | Server id |
data.servers[].name | string | Name |
data.servers[].transport | string | "http" or "sse" |
data.servers[].url | string | Server URL |
data.servers[].headerNames | string[] | Names of the headers sent (values are write-only) |
data.servers[].enabled | boolean | Whether the agent uses it |
data.servers[].verifiedAt | string | When it last answered the handshake |
data.servers[].tools | string[] | Its tools |
data.servers[].createdAt | string | When it was added |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-serversconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-servers", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "servers": [{ "id": "mcp_01k6xe", "name": "crm", "transport": "http", "url": "https://mcp.example.com/mcp", "headerNames": ["Authorization"], "enabled": true, "verifiedAt": "2026-10-08T09:00:00.000Z", "tools": ["find_customer"], "createdAt": "2026-10-08T09:00:00.000Z" }] } }#Verify MCP Server
/api/v1/vcaas/projects/:projectId/agent/mcp-servers/verifyapi-keyFreeCheck that a server answers the MCP handshake and lists at least one tool, without saving it.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
mcp | object | Yes | The server |
mcp.name | string | Yes | 2–41 characters: lowercase letters, digits, _, -. Not totalum, playwright, figma, github or context7 |
mcp.transport | string | Yes | "http" (streamable HTTP) or "sse" |
mcp.url | string | Yes | A public https URL |
mcp.headers | object | No | Up to 20 headers, e.g. { "Authorization": "Bearer …" } |
Response fields
| Field | Type | Description |
|---|---|---|
data.ok | boolean | true |
data.serverInfo | object | { name, version } |
data.protocolVersion | string | MCP protocol version |
data.tools | object[] | { name, description? } |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"mcp":{"name":"crm","transport":"http","url":"https://mcp.example.com/mcp","headers":{"Authorization":"Bearer abc"}}}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-servers/verifyconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-servers/verify", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mcp": {
"name": "crm",
"transport": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer abc"
}
}
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "ok": true, "serverInfo": { "name": "crm", "version": "1.0.0" }, "protocolVersion": "2025-06-18", "tools": [{ "name": "find_customer" }] } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
MCP_VERIFY_FAILED | 400 | The handshake failed. errorDetails: { stage: "connect" | "initialize" | "tools_list" | "auth", message, authHint? } |
VALIDATION_ERROR | 400 | A field is invalid or the name is reserved |
#Add MCP Server
/api/v1/vcaas/projects/:projectId/agent/mcp-serversapi-keyFreeVerify and add an MCP server for the agent (at most 10 per project). Same body as Verify MCP Server.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
mcp | object | Yes | { name, transport, url, headers? } as in Verify MCP Server |
Response fields
| Field | Type | Description |
|---|---|---|
data.server | object | The stored server, with the fields of servers[] in List MCP Servers |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"mcp":{"name":"crm","transport":"http","url":"https://mcp.example.com/mcp"}}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-serversconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-servers", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mcp": {
"name": "crm",
"transport": "http",
"url": "https://mcp.example.com/mcp"
}
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "server": { "id": "mcp_01k6xe", "name": "crm", "transport": "http", "url": "https://mcp.example.com/mcp", "headerNames": [], "enabled": true, "verifiedAt": "2026-10-08T09:00:00.000Z", "tools": ["find_customer"], "createdAt": "2026-10-08T09:00:00.000Z" } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
MCP_LIMIT_REACHED | 400 | The project already has 10 servers |
MCP_VERIFY_FAILED | 400 | The server did not pass the handshake |
VALIDATION_ERROR | 400 | A field is invalid or the name is taken or reserved |
#Update MCP Server
/api/v1/vcaas/projects/:projectId/agent/mcp-servers/:mcpIdapi-keyFreeTurn a server on or off, or change it (a changed configuration is verified again).
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
mcpId | string | The server id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | No | Use it or not |
mcp | object | No | A new { name, transport, url, headers? } |
Response fields
| Field | Type | Description |
|---|---|---|
data.server | object | The server after the change |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"enabled":false}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-servers/mcp_01k6xeconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-servers/mcp_01k6xe", {
method: "PATCH",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"enabled": false
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "server": { "id": "mcp_01k6xe", "name": "crm", "transport": "http", "url": "https://mcp.example.com/mcp", "headerNames": [], "enabled": false, "verifiedAt": "2026-10-08T09:00:00.000Z", "tools": ["find_customer"], "createdAt": "2026-10-08T09:00:00.000Z" } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
MCP_SERVER_NOT_FOUND | 404 | No such server |
MCP_VERIFY_FAILED | 400 | The new configuration did not pass the handshake |
#Delete MCP Server
/api/v1/vcaas/projects/:projectId/agent/mcp-servers/:mcpIdapi-keyFreeRemove an MCP server.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
mcpId | string | The server id |
Response fields
| Field | Type | Description |
|---|---|---|
data.deleted | 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/agent/mcp-servers/mcp_01k6xeconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/agent/mcp-servers/mcp_01k6xe", {
method: "DELETE",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "deleted": true } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
MCP_SERVER_NOT_FOUND | 404 | No such server |
