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), cursor and cycle, any YYYY-MM-DD day 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) and cycleCostMicroCents (the same usage at list price).
  • GET /api/v1/groups/{id} — cycle as above. One group, the same fields, plus members (each current member's spendMicroCents and costMicroCents in the cycle), formerMembers (everyone not in the group now who left it during the cycle, with theirs and leftAt) and daily (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's cycleSpendMicroCents is the sum of its members' and former members' rows on that page. An id that is not a live group of this organisation is 404.
  • POST /api/v1/groups — body { "name": "Finance" } (1–120 characters, unique in any letter case). Needs an Idempotency-Key.
  • PATCH /api/v1/groups/{id} — any of name, monthlySpendLimit (a whole number of micro-cents, 0 included) and clearMonthlySpendLimit: 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 — limit and cursor; each member's userId, displayName and addedAt.
  • 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, any YYYY-MM-DD day 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) and cycleCostMicroCents (the same usage at list price).
  • GET /api/v1/billing-groups/{id} — cycle as above. The group, its cycle spend, members (each current member's spendMicroCents and costMicroCents), formerMembers (everyone not in the group now who left it during the cycle or was charged to it in it, with theirs and leftAt) and daily (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 an Idempotency-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) beside onDemandEnabled and organisationLimitMicroCents. 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) or member (name, else email address);
    • direction — asc or desc (default desc);
    • limit (1–200, default 50) and cursor. A page token is only good for the sort it was issued under.

    Each row adds email, displayName and joinedAt to 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 (0 lets them spend nothing past the plan), null clears it. An address that is not a member here is 404.

  • 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

StatusCodeWhenWhat to do
400invalid_requestA 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.
401unauthenticatedThe 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.
403forbiddenA group spend limit on a plan without group limits.Move to Enterprise.
404not_foundNo such group, billing group or member in this organisation.List first.
409conflictA group or billing-group name already in use.Pick another name.
429rate_limitedA 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.