Budgets and approvals
Per-agent budgets and approval thresholds, enforced on our side: what happens when a purchase needs a person.
An agent spends your money, so every agent has limits it can't change: a monthly budget and an approval threshold. Both are checked on our side before anything is held, on every request. Going over either doesn't fail the purchase: it asks an owner, and the agent carries on once they say yes.
The limits
| Limit | Default | What it does |
|---|---|---|
monthly_budget_credits | 50,000 credits ($50) per UTC calendar month | A live purchase that would take the agent's spend this month over its budget needs approval (over_budget). At 80% the owners get a budget.80pct event and email. |
approval_threshold_credits | 20,000 credits ($20) | A single request whose price × items is above it needs approval (over_threshold). null means never ask. The dashboard offers $5, $20, $50 or never. |
ip_allowlist | empty (any IP) | IP addresses or CIDR ranges the key may be used from. Anything else gets 403 ip_not_allowed. |
Set them on the Agents page, or with PATCH https://api.arettic.com/v1/orgs/{orgId}/agents/{agentId} (a member, or a write org key). An agent key can read its own limits (GET /v1/agent, GET /v1/balance) but never change them.
curl -X PATCH https://api.arettic.com/v1/orgs/$ORG_ID/agents/$AGENT_ID \
-H "Authorization: Bearer $ARETTIC_ORG_KEY" \
-H "Content-Type: application/json" \
-d '{ "monthly_budget_credits": 200000, "approval_threshold_credits": 50000 }'What is checked, in order
- Balance. The org can't pay for price × items: declined,
402 insufficient_credits. No approval can fix that; top up. - Trial limit. An org that has never topped up can spend at most 50 trial credits an hour: declined,
429 trial_limit. - Approval threshold, then monthly budget: over either, the request becomes an approval (below).
The hold step checks the budget again under a lock, so two requests sent at the same moment can't both slip under it.
When a purchase needs approval
The request is not run and nothing is held. The answer is HTTP 202:
{
"status": "approval_required",
"approval_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"reason": "over_threshold",
"amount": { "credits": "24000", "usd": "24.000" },
"expires_at": "2026-10-01T11:10:00.000Z",
"message": "This is above the agent's approval threshold. An owner has been asked to approve it; retry with approval_id once approved."
}- Every owner gets an email with a one-click link to approve or decline, no sign-in needed. They can also decide on the Approvals page, or with
POST /v1/orgs/{orgId}/approvals/{id}/decisionand{ "decision": "approve" }or"reject"(optionalnote). Webhook endpoints getapproval.requestedand thenapproval.decided. - The agent checks
GET /v1/approvals/{id}:statusispending,approved,rejected,expiredorused, withreason,agent,tool,items,amount,expires_at,decided_atanddecision_note. - Once
approved, the agent sends the same request again withapproval_id. It runs without the threshold and budget checks, for exactly the tool, inputs and amount that were approved.
- Approvals expire after 24 hours; using one after that gets
410 approval_expired. Send the request again for a new one. - A declined request gets
403 approval_rejected. - Changing the tool or the inputs, or a price that went up since, gets
409 approval_mismatch. So does using an approval twice, or another agent's. - Sending the same request again while its approval is pending returns the same approval: owners aren't emailed twice.
import { Arettic } from "@arettic/sdk";
const arettic = new Arettic();
const request = { tool_id: "peopledatalabs-enrich-company", inputs };
let result = await arettic.execute(request);
if (result.status === "approval_required") {
const approval = await arettic.waitForApproval(result.approval_id); // polls every 5 s, up to 24 h
if (approval.status !== "approved") throw new Error(`Not approved: ${approval.status}`);
result = await arettic.execute({ ...request, approval_id: result.approval_id });
}import time
from arettic import Arettic
client = Arettic()
request = {"tool_id": "peopledatalabs-enrich-company", "inputs": inputs}
result = client.execute(request)
if result["status"] == "approval_required":
approval_id = result["approval_id"]
while (approval := client.approval(approval_id))["status"] == "pending":
time.sleep(5)
if approval["status"] != "approved":
raise RuntimeError(f"Not approved: {approval['status']}")
result = client.execute(request, approval_id=approval_id)Over MCP, execute returns the same approval_required answer and get_approval checks it; see MCP.
Other guards
- Velocity alerts. If an agent spends more than 3× its usual hourly rate in an hour (and at least 1,000 credits, $1), the owners get an email, at most once a day per agent.
- Revoke at once. Revoking an agent's key on the Agents page (or
POST /v1/orgs/{orgId}/agents/{agentId}/revoke-key) stops it on the next request. Rotating issues a new key and revokes the old one in the same step. - Plan limits. Each plan has a number of agents and seats; going past it gets
402 plan_limit, with a link to upgrade inmessage.
| Plan | Agents | Seats |
|---|---|---|
| Pay as you go | 2 | 1 |
| Pro | 10 | 5 |
| Max | no limit | no limit |