Spending & Rate Limits
Keep every project's spending and traffic under control.
Three independent controls, all per project. Every limit of an account and a project, and which ones Totalum can raise, is on Limits.
- Credit limits — a monthly cap on development credits (prompts, publishes, builder actions) and on infrastructure credits (what the running app uses).
- Feature limits — switch each built-in feature of the app (AI, e-mail, web scraping…) on or off and give it its own monthly budget.
- Rate limit — requests per minute each visitor (IP) may send to the published site.
A refused call never reaches a provider and costs nothing. Budgets reset on the 1st of each month (UTC).
#Get Credit Limits
/api/v1/vcaas/projects/:projectId/credit-limitsapi-keyFreeThe project's monthly caps and what it spent this month (UTC), per category.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.creditLimits.maxDevelopmentCreditsPerMonth | number | null | The development cap; null = no cap |
data.creditLimits.maxInfrastructureCreditsPerMonth | number | null | The infrastructure cap; null = no cap |
data.usedThisMonth | object | null | { development, infrastructure } credits spent this month; null when it cannot be read right now |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/credit-limitsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/credit-limits", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "creditLimits": { "maxDevelopmentCreditsPerMonth": 5000, "maxInfrastructureCreditsPerMonth": 300 }, "usedThisMonth": { "development": 42.5, "infrastructure": 3.1 } } }#Update Credit Limits
/api/v1/vcaas/projects/:projectId/credit-limitsapi-keyFreeSet the project's monthly caps. null removes a cap. Defaults for a project created through the API: a development cap of 5,000 credits a month and an infrastructure cap of 300 credits a month. Both can also be set when the project is created (Launch Project, Create Project).
At the development cap a prompt is refused with 403 PROJECT_CREDIT_LIMIT_REACHED, and a running prompt that crosses it stops (keeping what it built). At the infrastructure cap the app's paid built-in calls are refused until the next month or until you raise it. You get the project.credit_limit.reached webhook and an e-mail.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
maxDevelopmentCreditsPerMonth | number | null | No¹ | Positive number, or null for no cap |
maxInfrastructureCreditsPerMonth | number | null | No¹ | Positive number, or null for no cap |
¹ At least one of the two.
Response fields
| Field | Type | Description |
|---|---|---|
data.creditLimits.maxDevelopmentCreditsPerMonth | number | null | When sent: the new development cap |
data.creditLimits.maxInfrastructureCreditsPerMonth | number | null | When sent: the new infrastructure cap |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"maxDevelopmentCreditsPerMonth":500,"maxInfrastructureCreditsPerMonth":null}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/credit-limitsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/credit-limits", {
method: "PATCH",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"maxDevelopmentCreditsPerMonth": 500,
"maxInfrastructureCreditsPerMonth": null
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "creditLimits": { "maxDevelopmentCreditsPerMonth": 500, "maxInfrastructureCreditsPerMonth": null } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
MISSING_LIMIT_FIELDS | 400 | Neither cap was sent |
INVALID_CREDIT_LIMITS | 400 | The body is not an object |
INVALID_LIMIT | 400 | A cap is not a positive number or null |
OWNER_ONLY | 403 | Only the account owner can change the caps |
#List Projects at Their Limit
/api/v1/vcaas/projects/credit-limits/at-limitapi-keyFreeThe account's projects that reached a monthly credit limit this month (UTC), with their caps and spend — the list behind the platform's "projects at their monthly limit" notice. Raise one with Update Credit Limits.
Response fields
| Field | Type | Description |
|---|---|---|
data.projects | object[] | The projects at a limit (empty when none) |
data.projects[].projectId | string | The project id |
data.projects[].label | string | null | Its display name |
data.projects[].createdVia | string | null | "ui" (the platform) or "api" |
data.projects[].creditLimits | object | { maxDevelopmentCreditsPerMonth, maxInfrastructureCreditsPerMonth } |
data.projects[].usedThisMonth | object | { development, infrastructure } credits spent this month |
data.projects[].atLimit | object | { development, infrastructure }: true for each limit reached |
data.resetsAt | string | When the limits reset (the start of next month, UTC) |
data.canEdit | boolean | Whether this caller may raise them (the account owner) |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/credit-limits/at-limitconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/credit-limits/at-limit", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "projects": [{ "projectId": "barber-bookings", "label": "Barber bookings", "createdVia": "api", "creditLimits": { "maxDevelopmentCreditsPerMonth": 5000, "maxInfrastructureCreditsPerMonth": 300 }, "usedThisMonth": { "development": 812.4, "infrastructure": 300.2 }, "atLimit": { "development": false, "infrastructure": true } }], "resetsAt": "2026-11-01T00:00:00.000Z", "canEdit": true } }#Get Feature Limits
/api/v1/vcaas/projects/:projectId/limitsapi-keyFreeEvery built-in feature of the app: whether it is on, its monthly budgets and what it used this month. Every feature is on by default, with no budget of its own.
| Feature | Covers |
|---|---|
ai | Every AI call below |
ai_text | Chat, structured output, vision, embeddings, reranking |
ai_image | Image generation and editing |
ai_video | Video generation |
speech | Speech-to-text and text-to-speech |
web | Web scraping, crawling, search, extraction |
browser | Cloud browser sessions |
scan | Document scanning (OCR) |
storage | File uploads and stored files |
email | Sending e-mail |
pdf | PDFs, screenshots, SEO pings |
vectors | Vector search |
realtime | Real-time rooms |
cron | Scheduled job runs |
mobile | Mobile app builds |
database | The database (cannot be switched off) |
worker | The site itself (cannot be switched off) |
all | Everything: one switch and one total monthly budget, in credits |
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.plan | string | The account's plan |
data.features | object | Feature → its entry |
data.features.<feature>.enabled | boolean | false = switched off |
data.features.<feature>.parent | string | undefined | "ai" for ai_text, ai_image, ai_video (the parent's switch and budget also apply) |
data.features.<feature>.monthlyCreditCap | number | undefined | Budget in credits, when set |
data.features.<feature>.monthlyUnitCap | number | undefined | Budget in the feature's unit, when set |
data.features.<feature>.unit | string | calls, recipients, pages, bytes, session_minutes, room_minutes, executions, builds, rows, requests or credits |
data.features.<feature>.consumed | object | { credits, units } this month |
data.features.<feature>.period | object | { from, to }: the month |
data.features.<feature>.planCeiling | object | Highest budget your plan allows: { monthlyCreditCap, monthlyUnitCap } (null = none) |
data.features.<feature>.source | string | plan (default), user (you set it) or ceo (set by Totalum support) |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/limitsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/limits", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{
"errors": null,
"data": {
"plan": "starter",
"features": {
"ai_video": { "enabled": false, "parent": "ai", "unit": "calls", "consumed": { "credits": 0, "units": 0 }, "period": { "from": "2026-10-01T00:00:00.000Z", "to": "2026-11-01T00:00:00.000Z" }, "planCeiling": { "monthlyCreditCap": null, "monthlyUnitCap": 20 }, "source": "user" },
"all": { "enabled": true, "monthlyCreditCap": 300, "unit": "credits", "consumed": { "credits": 12.4, "units": 0 }, "period": { "from": "2026-10-01T00:00:00.000Z", "to": "2026-11-01T00:00:00.000Z" }, "planCeiling": { "monthlyCreditCap": null, "monthlyUnitCap": null }, "source": "user" }
}
}
}The answer lists every feature; the example shows two.
Concurrency. Besides budgets, two features have per-project concurrency limits: browser (3 open sessions, 1 on the free plan; a call up to 60 s) and web (1 call at a time, up to 10 s). A call over the limit waits in a fair queue, then answers 429 CONCURRENCY_QUEUE_TIMEOUT (or CONCURRENCY_QUEUE_FULL) with errorDetails.retryAfter. A project makes at most 100 built-in calls per second in total; above it a call answers 429 RATE_LIMITED with Retry-After.
Busy hours. When an app spends half of what would trigger the emergency stop within one hour, its image, video and speech AI calls answer 429 AI_MEDIA_PAUSED_THIS_HOUR until that hour ends (UTC), with Retry-After; text AI and everything else keep working. They come back by themselves at the next hour; releasing an emergency-stop pause also brings them back.
#Set Feature Limits
/api/v1/vcaas/projects/:projectId/limitsapi-keyFreeSwitch features on or off and set monthly budgets. Send only the features you change; null removes a budget. Account owner only. Setting the budget of all also sets the project's infrastructure credit limit.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
features | object | Yes | Feature → { enabled?, monthlyCreditCap?, monthlyUnitCap? } |
Response fields
| Field | Type | Description |
|---|---|---|
data.plan | string | The account's plan |
data.features | object | Every feature after the change, as in Get Feature Limits |
Example request
curl -X PUT \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"features":{"ai_video":{"enabled":false},"ai":{"monthlyCreditCap":200},"all":{"monthlyCreditCap":500}}}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/limitsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/limits", {
method: "PUT",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"features": {
"ai_video": {
"enabled": false
},
"ai": {
"monthlyCreditCap": 200
},
"all": {
"monthlyCreditCap": 500
}
}
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "plan": "starter", "features": { "ai_video": { "enabled": false, "parent": "ai", "unit": "calls", "consumed": { "credits": 0, "units": 0 }, "period": { "from": "2026-10-01T00:00:00.000Z", "to": "2026-11-01T00:00:00.000Z" }, "planCeiling": { "monthlyCreditCap": null, "monthlyUnitCap": 20 }, "source": "user" } } } }What the app's code gets when a feature is refused (show a message, do not retry):
| App error | HTTP | Meaning |
|---|---|---|
FEATURE_DISABLED | 403 | The feature (or ai, or all) is switched off; errorDetails.feature names it |
FEATURE_LIMIT_REACHED | 429 | Its budget is spent; errorDetails: feature, limit, used, unit, resetsAt |
AI_MEDIA_PAUSED_THIS_HOUR | 429 | The app used a lot of credits this hour, so image, video and speech AI wait until the hour ends (UTC); Retry-After and errorDetails.resetsAt say when. Text AI and everything else keep working |
Error codes
| Code | HTTP | Meaning |
|---|---|---|
UNKNOWN_FEATURE | 400 | Not a feature of the table above |
INVALID_LIMIT | 400 | A negative or non-integer budget, or a monthlyUnitCap on all |
FEATURE_NOT_DISABLABLE | 400 | database and worker cannot be switched off |
LIMIT_ABOVE_PLAN_CEILING | 400 | A budget above your plan's ceiling |
OWNER_ONLY | 403 | Only the account owner can change budgets |
#Get Rate Limit
/api/v1/vcaas/projects/:projectId/rate-limitapi-keyFreeHow many requests per minute each visitor (IP) may send to the published site.
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Response fields
| Field | Type | Description |
|---|---|---|
data.perIpPerMinute | number | null | Requests per minute per IP; null = off |
data.allowlistCidrs | string[] | IP ranges that are never limited (up to 50) |
data.verifiedBotsExempt | boolean | Search engines and other verified bots are not limited |
data.planDefault | number | Your plan's default |
data.planMax | number | null | The highest value your plan allows |
data.canDisable | boolean | Whether your plan may switch it off |
data.source | string | "plan" or "user" |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rate-limitconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rate-limit", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "perIpPerMinute": 5000, "allowlistCidrs": [], "verifiedBotsExempt": true, "planDefault": 5000, "planMax": 60000, "canDisable": true, "source": "plan" } }#Update Rate Limit
/api/v1/vcaas/projects/:projectId/rate-limitapi-keyFreeChange the per-visitor limit, the allowlist or the bot exemption. Send only what changes.
Separately, each visitor may cause at most 20 paid built-in calls a minute (AI, web, browser, screenshots), so nobody can spend the project's credits by calling a public AI page in a loop; over it the app's call answers 429 RATE_LIMIT_EXCEEDED with Retry-After. Ask the agent to change it ("let each visitor send 60 AI messages a minute").
Path parameters
| Parameter | Type | Description |
|---|---|---|
projectId | string | The project id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
perIpPerMinute | number | null | No | From 10 to 60,000 on paid plans (5,000 on the free plan, the default everywhere); null = off (paid plans only) |
allowlistCidrs | string[] | No | Up to 50 IP ranges (203.0.113.0/24) |
verifiedBotsExempt | boolean | No | Exempt verified bots |
Response fields
| Field | Type | Description |
|---|---|---|
data | object | The setting after the change, as in Get Rate Limit |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"perIpPerMinute":300}' \
https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rate-limitconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/rate-limit", {
method: "PATCH",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"perIpPerMinute": 300
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "perIpPerMinute": 300, "allowlistCidrs": [], "verifiedBotsExempt": true, "planDefault": 5000, "planMax": 60000, "canDisable": true, "source": "user" } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | A value is out of range |
RATE_LIMIT_ABOVE_PLAN | 400 | Above your plan's maximum (errorDetails.planMax) |
INVALID_CIDR | 400 | An allowlist entry is not an IPv4 or IPv6 range |
RATE_LIMIT_DISABLE_NOT_ALLOWED | 403 | The free plan cannot switch the limit off |
