Administration Budget pools Pool API & errors

Pool API and errors

Everything the Pools tab does is available over HTTP. Management calls need an admin credential unless the table says otherwise. All amounts are USD strings with up to six decimals.

Endpoints

Method and pathWhoDoes
GET /v1/budget-poolsAny signed-in userLists the pools you can see. Admins get all of them, everyone else gets their own.
POST /v1/budget-poolsAdminCreates a pool.
GET /v1/budget-pools/{id}Admin, lead, memberOne pool with its members and, for admins and leads, its warnings. 404 for anyone else.
PATCH /v1/budget-pools/{id}AdminEdits name, limit, lead, dates, burst, forecast and alert settings.
PUT /v1/budget-pools/{id}/allocationAdmin, leadSets equal or manual shares, and optionally the guaranteed percentage.
POST /v1/budget-pools/{id}/membersAdminAdds a member. Send api_key_id for a key (a service key, an agent's key or a specific developer key) or identity_id for a person, who brings their one free key or is issued one. Exactly one of the two.
DELETE /v1/budget-pools/{id}/members/{api_key_id}AdminRemoves a key from the pool.
GET /v1/budget-pools/{id}/alertsAdmin, lead, memberThe pool's alert history. Members see the pool's alerts and their own share's.
GET /v1/budget-pools/{id}/forecastAdmin, lead, memberProjected run-out date for the pool and each member. 409 if forecast is off.
GET /v1/budget-pools/notificationsAny signed-in userYour in-app pool alerts. unread_only and limit are optional.
POST /v1/budget-pools/notifications/read-allAny signed-in userMarks your pool alerts read.
POST /v1/keys/{id}/revealThe key's owner, signed in to the consoleReveals a key that was issued for them by adding them to a pool. The secret is returned once.
GET /v1/notifications/mineAny signed-in userNotifications addressed to you, such as key_issued. Also POST /v1/notifications/mine/{id}/read and POST /v1/notifications/mine/read-all.
DELETE /v1/budget-pools/{id}AdminArchives the pool and releases its members. Works on any plan.
DELETE /v1/budget-pools/{id}/permanentAdminDeletes the pool. Releases the members of a live pool. Works on any plan.

On a Free plan every call except the two deletes returns 402.

Create a pool

import httpx

r = httpx.post(
  "https://api.forgebench.ai/v1/budget-pools",
  headers={"Authorization": f"Bearer {admin_token}"},
  json={
      "name": "Platform team",
      "monthly_limit_usd": "1000",
      "auto_share": False,
      "guaranteed_pct": 75,
      "identity_ids": [priya, sam],
      "api_key_ids": [ci_pipeline_key],
      "lead_identity_id": priya,
      "allocations": {priya: "300", sam: "250", ci_pipeline_key: "200"},
      "alert_thresholds": [50, 75, 90, 100],
  },
)
r.raise_for_status()
pool = r.json()

Request fields

FieldTypeNotes
namestringUnique in the workspace. 409 pool_name_taken otherwise.
kinddeveloper or agentDefault developer. A developer pool takes keys that are not an agent's credential (people's and service keys) and people by identity_ids. An agent pool takes agent credentials only and no people. Fixed at creation.
limit_unitusd or tokensDefault usd. What the pool is counted in. Fixed at creation: 422 pool_unit_is_fixed if an update names the other unit.
monthly_limit_usdstringFor a usd pool. Not above the workspace's monthly budget.
monthly_limit_tokensintegerFor a tokens pool, up to 1015. Not above the workspace's monthly token cap, if it has one. Send exactly the limit that matches limit_unit.
api_key_idsuuid listKeys to put in the pool. A key can be in only one pool. At most 500.
identity_idsuuid listPeople to add. Each brings their one free key, or is issued one that only they can reveal (POST /v1/keys/{id}/reveal). 422 choose_a_key if they have several free keys, and 422 developer_cannot_hold_key if their role cannot hold a key.
lead_identity_iduuid or nullMust be an active admin or owner, or the owner of one of the pool's keys (422 lead_not_a_member). An existing lead is checked only when you change it, and is cleared if removing a key leaves them neither.
auto_shareboolDefault true. With false, send allocations.
allocationsmap of id to amountIn the pool's unit (whole tokens for a token pool). Only with auto_share: false. Keyed by key id, or by identity id for a person in identity_ids. Keys not listed get a share of zero. The total cannot exceed the guaranteed part of the limit.
guaranteed_pct1 to 100Default 100. Below 100 turns on burst: the rest of the limit is the shared zone.
member_max_usd, member_max_tokensstring or null, integer or nullWith burst, the most one key can spend in total, in the pool's unit. Must be at least their share and at most the limit.
starts_at, ends_atISO timestampsOptional time-box. ends_at must be in the future and after starts_at.
periodmonthly or windowwindow is one limit for the whole span and needs both dates.
forecast_enabledboolDefault false. 422 forecast_needs_dollar_pool on a token pool.
alert_thresholdsint listWhole numbers 1 to 100. Default 50, 75, 90, 95, 100.
alert_in_app_same_as_emailboolDefault true on create. The in-app alert goes to the active users whose email is on an enabled email alert channel that covers the pool. The next three fields are used only when this is false.
alert_notify_lead, alert_notify_admins, alert_extra_identity_idsbool, if_no_lead / always / never, uuid listWho gets the in-app alert. Email goes through alert channels scoped to the pool.

The response is the pool. The fields you will use most:

FieldMeaning
kinddeveloper or agent.
statusscheduled, active, expired or archived.
limit_unitusd or tokens. Every amount comes as a dollar field and a token field; only the pool's own unit is filled and the other is null.
spent_usd, remaining_usd (or spent_tokens, remaining_tokens)Spend this period and what is left of the limit.
shared_zone_usd (or shared_zone_tokens)The part of the limit that is not split into shares.
my_shareThe caller's total across the keys they own in the pool: api_key_ids, share, cap and spend. Null if they own none.
can_manage, can_edit_allocationsWhat the caller may do. Use these to decide which buttons to show.
membersEach key with api_key_id, key_name, its owner (owner_identity_id, owner_name; empty for a service key), pending_reveal (true until the owner reveals a key issued to them), share_usd, max_usd, spent_usd (or the _tokens twins) and is_lead. A pending key's prefix is not shown.
warningsList of code and message. See Shares and burst.

Set shares

import httpx

r = httpx.put(
  f"https://api.forgebench.ai/v1/budget-pools/{pool_id}/allocation",
  headers={"Authorization": f"Bearer {lead_or_admin_token}"},
  json={
      "auto_share": False,
      "guaranteed_pct": 80,
      "allocations": {priya_key: "300", sam_key: "300", ci_key: "200"},
  },
)
r.raise_for_status()
pool = r.json()

guaranteed_pct is optional. Send it to change the burst split in the same save. The allocations are checked against the new guaranteed part, and nothing changes if they do not fit.

What a refused call looks like

These come back from any governed call, for example POST /v1/chat/completions.

// 402 — the pool's monthly limit would be exceeded
{
"detail": {
  "code": "pool_budget_exceeded",
  "message": "Budget pool “Platform team” has reached its $1000.00 limit ($996.00 spent this period). Ask Priya Shah to raise the limit.",
  "limit_kind": "pool_total",
  "pool_id": "6f3c…",
  "pool_name": "Platform team",
  "pool_owner": "Priya Shah",
  "key_id": "b21e…",
  "key_name": "laptop",
  "key_owner": "Ada Lovelace",
  "limit_usd": "1000.000000",
  "spent_usd": "996.000000",
  "attempted_cost_usd": "6.000000"
}
}

// 402 — this key's share is used up; the pool may still have room
{
"detail": {
  "code": "pool_member_share_exceeded",
  "message": "Key “laptop” (Ada Lovelace) has used its $300.00 share of budget pool “Platform team” ($297.00 spent this period). Ask Priya Shah to raise the share.",
  "limit_kind": "pool_member_share",
  "pool_id": "6f3c…",
  "pool_name": "Platform team",
  "pool_owner": "Priya Shah",
  "key_id": "b21e…",
  "key_name": "laptop",
  "key_owner": "Ada Lovelace",
  "limit_usd": "300.000000",
  "spent_usd": "297.000000",
  "attempted_cost_usd": "6.000000"
}
}

In pool_member_share_exceeded, limit_usd is the key's cap (its share, plus the shared zone if burst allows), not the pool's limit. limit_kind says which limit fired: pool_total or pool_member_share (a key's own caps report key_daily or key_monthly). pool_owner and key_owner say whose it is; a service key has no key_owner, and a pool with no lead has no pool_owner.

For either code, queue the work, use a cheaper model, or tell the user. Don't retry in a loop.

Management errors

StatusCodeCause
402plan_requiredThe workspace is on a plan without budget pools.
403admin_requiredThe call needs an admin.
403not_pool_leadOnly the pool's lead or an admin can change shares.
404pool_not_foundNo such pool, or it is not yours to see.
409pool_name_takenAnother pool has this name.
404key_not_found, member_not_foundNo such active key in this workspace (a revoked key cannot join), or that key is not in this pool.
409already_in_poolA key can be in only one pool. The body names the pool. Also returned when a person already has a key in this pool.
409key_not_revealed, already_revealedRotating a key its owner has not revealed yet (only the owner may see its secret), and revealing a key that was already revealed.
409pool_not_active, pool_archivedThe pool is over or archived and cannot be edited.
409forecast_disabledForecast is not turned on for this pool.
422pool_limit_exceeds_workspace_budgetThe limit is above the workspace's monthly budget. The body has workspace_limit_usd.
422allocations_exceed_limitShares add up to more than the guaranteed part of the limit.
422pool_limit_below_allocationsThe new limit or guaranteed percentage is below what is already divided.
422allocation_for_non_member, allocation_negative, allocations_need_manual_mode, share_needs_manual_modeAn invalid share request.
422lead_not_a_memberThe lead must be an admin or an owner, or own one of the pool's keys.
422key_kind_mismatchThe key does not fit the pool: an agent's credential in a developer pool, or a person's or service key in an agent pool.
422agent_pool_takes_agent_keysA person was added to an agent pool.
422choose_a_key, developer_cannot_hold_keyAdding a person: they have several free keys (the body lists them), or their role cannot hold a key.
422window_needs_both_dates, window_end_before_start, window_end_in_pastInvalid dates for a time-boxed pool.
422member_max_needs_burst, member_max_below_share, member_max_above_limitInvalid member_max_usd.
422insufficient_unallocated_headroomA share increase needs more undivided budget than the pool has.

Amounts must be finite, non-negative and at most 999999.999999. One request adds at most 500 keys or people.

Cookie settings

Our Cookie Policy lists every cookie and what it does.