Groups, billing groups and spend
Developer·7 minutes to read
What it does
Three things an administrator manages for the team, each through the same engine as its screen:
- Groups (Organisation → Groups): named sets of members. A group can carry a monthly spend limit that each of its members gets; it is the limit the Groups page sets, and a member's runs stop at it. A group's spend is read per billing cycle too: what the people in it now, and the people who left it during the cycle, spent in that cycle.
- Billing groups (Organisation → Usage → Billing groups): cost centres. A member is in one at most, and each call is charged to the group the member was in when it ran. A group's spend is read per billing cycle — the calendar month (UTC) that billing-group budgets count in.
- Spend: each member's extra usage this cycle beside their own limit, searched, sorted and paged, and a member's own limit set by id or email address.
Amounts are whole micro-cents (a micro-cent is a millionth of a cent). Billing groups and spend limits are hosted only.
Authentication
Reads take any key whose person can see the rows: a read key reaches GET /groups, GET /groups/{id}, GET /groups/{id}/members and the billing-group reads. Every write, and GET /spend, needs an admin key from a current owner or admin. A group spend limit needs the Enterprise plan.
Request
Groups
GET /api/v1/groups—limit(1–200, default 50),cursorandcycle, anyYYYY-MM-DDday of the billing cycle to read (today, UTC, by default). Each group:id,name,memberCount,monthlySpendLimit(micro-cents,null= none),createdAt,updatedAt(the last rename or spend-limit change),cycleStart,cycleEnd,cycleSpendMicroCents(what its current and former members spent in the cycle) andcycleCostMicroCents(the same usage at list price).GET /api/v1/groups/{id}—cycleas above. One group, the same fields, plusmembers(each current member'sspendMicroCentsandcostMicroCentsin the cycle),formerMembers(everyone not in the group now who left it during the cycle, with theirs andleftAt) anddaily(one entry per day of the cycle up to today). A member's figure is their own spend in the cycle as the organisation usage page shows it, so the group'scycleSpendMicroCentsis the sum of its members' and former members' rows on that page. An id that is not a live group of this organisation is404.POST /api/v1/groups— body{ "name": "Finance" }(1–120 characters, unique in any letter case). Needs anIdempotency-Key.PATCH /api/v1/groups/{id}— any ofname,monthlySpendLimit(a whole number of micro-cents,0included) andclearMonthlySpendLimit: true. A limit and a clear together are refused. The limit is written first and the rename last; a name another group holds is refused before anything is written.DELETE /api/v1/groups/{id}— the group is kept for the audit log and disappears from every list.GET /api/v1/groups/{id}/members—limitandcursor; each member'suserId,displayNameandaddedAt.POST /api/v1/groups/{id}/members— body{ "userIds": [...] }, 1 to 500 values, each a member id or email address. Answers{ "addedCount": n }; people already in the group are skipped and not counted.POST /api/v1/groups/{id}/members/remove— the same body. Answers{ "removedCount": n }; a member who is not in the group is skipped.DELETE /api/v1/groups/{id}/members/{userId}— one person out of the group.
In both membership calls, a value that is not a member of this organisation is a 400 whose details[].path names its position (userIds.3), and nobody is added or removed.
Billing groups
GET /api/v1/billing-groups—cycle, anyYYYY-MM-DDday of the cycle to read (today by default). Each group carries its members, its budget,cycleStart,cycleEnd,cycleSpendMicroCents(what its members were charged in the cycle while in it) andcycleCostMicroCents(the same usage at list price).GET /api/v1/billing-groups/{id}—cycleas above. The group, its cycle spend,members(each current member'sspendMicroCentsandcostMicroCents),formerMembers(everyone not in the group now who left it during the cycle or was charged to it in it, with theirs andleftAt) anddaily(one entry per day of the cycle up to today). The cycle spend is the sum of the members' and former members' — the per-member rows the usage page shows.POST /api/v1/billing-groups— body{ "name": "Sales" }. Needs anIdempotency-Key.PATCH /api/v1/billing-groups/{id}— body{ "name": "Growth" }; unique among the live groups in any letter case.DELETE /api/v1/billing-groups/{id}— its members are taken out and its history reads as Unassigned.POST /api/v1/billing-groups/{id}/members— body{ "userIds": [...] }(ids or emails, 1 to 500). Each person is moved out of any other billing group. Answers{ "addedCount": n }, not counting people already in this group.POST /api/v1/billing-groups/{id}/members/remove— the same body; answers{ "removedCount": n }.
The same 400 rule as for groups applies to both membership calls. The spend figures come from the hourly roll-up the usage page reads.
Spend
-
GET /api/v1/spend— the standard page envelope (data,nextCursor) besideonDemandEnabledandorganisationLimitMicroCents. Query:q— a substring of the member's name or email address, any letter case;sort—amount(spend, the default),date(when the member joined) ormember(name, else email address);direction—ascordesc(defaultdesc);limit(1–200, default 50) andcursor. A page token is only good for the sort it was issued under.
Each row adds
email,displayNameandjoinedAtto the member's spend and limits. Every page of one sort together is the whole list, each member once. -
PUT /api/v1/members/{userId}/spend-limit—{userId}is a member id or an email address (URL-encoded). Body{ "limitMicroCents": 0 }: a whole number sets the member's own limit (0lets them spend nothing past the plan),nullclears it. An address that is not a member here is404. -
PUT /api/v1/spend-limits— body{ "updates": [{ "userId": "…", "limitMicroCents": 5000000 }] }, up to 200 members, all or none.limits, the field this route took first, is still read, with its cap of 100.
curl -sS "$BOTSEON_API_URL/api/v1/spend?sort=amount&direction=desc&limit=20" \
-H "authorization: Bearer $BOTSEON_API_KEY" \
-H "accept: application/json"
Response
POST /api/v1/groups/{id}/members:
{ "addedCount": 2 }
GET /api/v1/billing-groups/{id}?cycle=2026-10-01 (trimmed):
{
"id": "6f1e2d3c-4b5a-4968-8776-655443322110",
"name": "Sales",
"cycleStart": "2026-10-01",
"cycleEnd": "2026-10-31",
"cycleSpendMicroCents": 3400000,
"cycleCostMicroCents": 3400000,
"members": [{ "userId": "8c1d4e5f-2a3b-4c5d-6e7f-8a9b0c1d2e3f", "spendMicroCents": 2400000, "costMicroCents": 2400000 }],
"formerMembers": [{ "userId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "spendMicroCents": 1000000, "costMicroCents": 1000000, "leftAt": "2026-10-12T08:00:00.000Z" }],
"daily": [{ "day": "2026-10-01", "spendMicroCents": 0, "costMicroCents": 0 }]
}
Errors
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | invalid_request | A malformed body or query, a cycle that is not a date (on the group and billing-group reads), a sort the list does not take, a page token from another sort, userIds empty or over 500, a value in userIds that is not a member here, a limit and a clear together, over 200 updates. | Read details. |
| 401 | unauthenticated | The key is missing or refused, a read key on a write, or an admin key whose creator is no longer an admin. | Use an admin key from a current admin. |
| 403 | forbidden | A group spend limit on a plan without group limits. | Move to Enterprise. |
| 404 | not_found | No such group, billing group or member in this organisation. | List first. |
| 409 | conflict | A group or billing-group name already in use. | Pick another name. |
| 429 | rate_limited | A bucket is full, including the 250-a-minute bucket of the two spend-limit PUTs. | Wait for Retry-After. |
Rate limits
The same buckets as every other route. PUT /api/v1/members/{userId}/spend-limit and PUT /api/v1/spend-limits share one more: 250 requests a minute per key. See Envelopes, errors, idempotency and paging.
Last verified against build e725eab.