API Reference
Teams
List teams, fetch a single team's snapshot, and enumerate the workload and namespace assignments a team owns.
Last updated
Three endpoints cover the team surface: a paginated list, a single-team lookup by UUID, and a paginated list of the workloads and namespaces a team owns. Teams sit one level above a workload and one level below a department. The list and detail endpoints accept ?cost_mode=, and cost.current_run_rate_hourly is computed at the requested mode. The assignments endpoint carries no cost: ?cost_mode= or ?basis= there returns 422. See Cost Modes.
Each assignment binds one entity (a workload UID or a namespace name) to one team with a weight_percentage. An entity can be split across up to ten teams; the weights for that entity sum to 100. /v1/teams/{team_id}/assignments returns one row per (entity, team) pair — a namespace checkout split 60/40 across Payments and Platform shows up as two assignment rows, one in each team's list. The model is documented in Cost Attribution: Assignment Workbench.
The team list sorts by cost.current_run_rate_hourly descending. Pagination is cursor-based; see Pagination & Filtering.
List teams
GET /v1/teams
Required scope: teams:read
Team inventory across the tenant, sorted by hourly run-rate cost descending. The cost is the hourly run rate of the team's assigned workloads over the last seven complete days (UTC), weighted by assignment percentage, at the requested cost_mode. It is not a month-to-date figure.
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
limit | integer | 100 | 1..500 | Page size. |
cursor | string | Opaque pagination token from a previous response. | ||
include_total | boolean | false | true, false | Include meta.pagination.total_count. |
sort | string | cost | cost, id | cost puts the highest run rate first. id gives an order that holds while you walk every page. See Sort policy. |
cost_mode | string | fully_loaded | fully_loaded, workload_only | Cost attribution mode. See Cost Modes. |
department_id | string (csv) | UUID list | Restrict to teams in specific departments. Teams without a department are excluded when this filter is set. | |
origin | string (csv) | Common: k8s, kubeadapt | Restrict by where the team was created. k8s means the team was mirrored from a Kubernetes label; kubeadapt means it was created in the UI. Not strictly validated — any string is accepted. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/teams?cost_mode=fully_loaded&limit=50"Example response
1{
2 "data": [
3 {
4 "id": "7a3b9c12-4d8e-4f01-bc2a-9e8f7a6b5c4d",
5 "kind": "Team",
6 "metadata": {
7 "name": "Payments",
8 "description": "Owns checkout, cart, billing, and the payment-gateway integration.",
9 "origin": "kubeadapt",
10 "owner_email": "payments-lead@acme.io",
11 "department": {
12 "id": "e1f2a3b4-c5d6-4e7f-a8b9-c0d1e2f3a4b5",
13 "name": "Engineering"
14 },
15 "created_at": "2025-11-12T14:22:00Z",
16 "updated_at": "2026-05-14T08:01:00Z",
17 "last_seen_at": "2026-05-21T10:29:00Z"
18 },
19 "assigned_workloads": 28,
20 "assigned_pvs": 4,
21 "cost": {
22 "current_run_rate_hourly": { "amount": "12.4000", "currency": "USD" },
23 "cost_mode": "fully_loaded"
24 }
25 },
26 {
27 "id": "1f8e7d6c-5b4a-4039-8271-6e5d4c3b2a1f",
28 "kind": "Team",
29 "metadata": {
30 "name": "Data Platform",
31 "description": "Stream processing, batch ETL, and the feature store.",
32 "origin": "k8s",
33 "owner_email": "data-platform@acme.io",
34 "department": {
35 "id": "e1f2a3b4-c5d6-4e7f-a8b9-c0d1e2f3a4b5",
36 "name": "Engineering"
37 },
38 "created_at": "2025-09-04T09:11:00Z",
39 "updated_at": "2026-05-19T17:42:00Z",
40 "last_seen_at": "2026-05-21T10:29:00Z"
41 },
42 "assigned_workloads": 42,
43 "assigned_pvs": 6,
44 "cost": {
45 "current_run_rate_hourly": { "amount": "18.7200", "currency": "USD" },
46 "cost_mode": "fully_loaded"
47 }
48 }
49 ],
50 "meta": {
51 "request_id": "0196f1c2-a8f1-7f3e-8d27-bde637d00e94",
52 "applied_at": "2026-05-21T10:30:00Z",
53 "pagination": {
54 "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6IjFmOGU3ZDZjLTViNGEtNDAzOS04MjcxLTZlNWQ0YzNiMmExZiIsImFmdGVyX3ZhbHVlIjoiRGF0YSBQbGF0Zm9ybSIsInF1ZXJ5X2hhc2giOiJhM2I0YzVkNmU3ZjhnOWEwYjFjMmQzZTRmNWc2IiwiaXNzdWVkX2F0IjoxNzE2Mjg1MDAwfQ",
55 "has_more": true,
56 "limit": 50
57 },
58 "cost_mode": "fully_loaded"
59 }
60}Example error response
1{
2 "data": null,
3 "meta": {
4 "request_id": "0196f1c2-485e-7bfb-ae2c-d85f6bb4fac1",
5 "applied_at": "2026-05-21T10:30:05Z"
6 },
7 "error": {
8 "code": "TEAM_NOT_FOUND",
9 "message": "team not found"
10 }
11}Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | Malformed limit, malformed cursor, malformed department_id UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. See Authentication. |
| 403 | FORBIDDEN | Token lacks teams:read. See Permission Scopes. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or query parameters changed since issue. |
| 422 | INVALID_COST_MODE | cost_mode value not in fully_loaded, workload_only. |
| 422 | VALIDATION_ERROR | sort or basis value not accepted. |
| 429 | RATE_LIMITED | Per-key quota exceeded. See Retry-After header. See Error Handling. |
Get team by ID
GET /v1/teams/{team_id}
Required scope: teams:read
Single team body. Same shape as the list elements.
Path parameters
| Name | Type | Description |
|---|---|---|
team_id | UUID | Team identifier returned by the list endpoint. |
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
cost_mode | string | fully_loaded | fully_loaded, workload_only | Cost attribution mode. See Cost Modes. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/teams/7a3b9c12-4d8e-4f01-bc2a-9e8f7a6b5c4d?cost_mode=workload_only"Example response
Returns a single Team object. Same shape as one element of the List teams response above, wrapped directly under data (no array, no pagination in meta). workload_only leaves out the team's share of idle capacity and cluster overhead, so it returns a lower current_run_rate_hourly than fully_loaded. See Cost Modes.
1{
2 "data": {
3 "id": "7a3b9c12-4d8e-4f01-bc2a-9e8f7a6b5c4d",
4 "kind": "Team",
5 "metadata": { "name": "Payments", "origin": "kubeadapt", "owner_email": "payments-lead@acme.io", "department": { "id": "e1f2a3b4-c5d6-4e7f-a8b9-c0d1e2f3a4b5", "name": "Engineering" }, "...": "..." },
6 "assigned_workloads": 28,
7 "assigned_pvs": 4,
8 "cost": { "current_run_rate_hourly": { "amount": "9.1800", "currency": "USD" }, "cost_mode": "workload_only" }
9 },
10 "meta": {
11 "request_id": "0196f1c2-0c53-7be0-a980-a7062597b601",
12 "applied_at": "2026-05-21T10:30:00Z",
13 "cost_mode": "workload_only"
14 }
15}For the complete field list, see the canonical example in List teams or the OpenAPI spec.
Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | team_id is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks teams:read. |
| 404 | TEAM_NOT_FOUND | No team with this ID exists in the tenant. |
| 422 | INVALID_COST_MODE | cost_mode value not in fully_loaded, workload_only. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
List team assignments
GET /v1/teams/{team_id}/assignments
Required scope: teams:read
List of TeamAssignment records for this team. An assignment binds an entity, either a workload UID or a namespace name, to the team with a weight_percentage. The percentage is the team's share of that entity's cost; when one entity is split across multiple teams, the percentages across all owning teams sum to 100.
The schema carries id, kind, and metadata. There are no cost.* fields on this resource; the cost lives on the parent team (and on the workload or namespace the assignment points to). The endpoint does not accept ?cost_mode= (422 INVALID_COST_MODE) or ?basis= (422 VALIDATION_ERROR).
Path parameters
| Name | Type | Description |
|---|---|---|
team_id | UUID | Team identifier returned by the list endpoint. |
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
limit | integer | 100 | 1..500 | Page size. |
cursor | string | Opaque pagination token from a previous response. | ||
entity_type | string (csv) | workload, namespace, pv | Restrict by entity kind. Any other value returns 422 VALIDATION_ERROR with the allowed values in details[0].allowed. | |
cluster_id | string (csv) | UUID list | Restrict by cluster. Useful for chargeback reports scoped to one production cluster. | |
source | string (csv) | Common: user_manual, k8s_label, namespace_auto, backfill_v1 | Restrict by how the assignment was created. user_manual is the Assignment Workbench; k8s_label is the label reconciler; namespace_auto is the cluster-onboarding default. Not strictly validated. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/teams/7a3b9c12-4d8e-4f01-bc2a-9e8f7a6b5c4d/assignments?source=user_manual,k8s_label&limit=50"Example response
1{
2 "data": [
3 {
4 "id": "2c4e6a8b-1d3f-4051-9273-6b8a0c2d4e6f",
5 "kind": "TeamAssignment",
6 "metadata": {
7 "team": {
8 "id": "7a3b9c12-4d8e-4f01-bc2a-9e8f7a6b5c4d",
9 "name": "Payments"
10 },
11 "cluster": {
12 "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
13 "name": "prod-eu-west-1"
14 },
15 "entity_type": "workload",
16 "entity_identifier": "b9f1a342-7c5e-4d8a-9e2b-1234567890ab",
17 "entity_name": "cart-service",
18 "entity_namespace": "payments",
19 "weight_percentage": 100.0,
20 "source": "user_manual",
21 "assigned_by_user_id": "u-12345",
22 "created_at": "2026-05-07T09:14:00Z",
23 "updated_at": "2026-05-07T09:14:00Z"
24 }
25 },
26 {
27 "id": "8d0e2a4c-3f1b-4593-a715-2c4e6a8b0d1f",
28 "kind": "TeamAssignment",
29 "metadata": {
30 "team": {
31 "id": "7a3b9c12-4d8e-4f01-bc2a-9e8f7a6b5c4d",
32 "name": "Payments"
33 },
34 "cluster": {
35 "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
36 "name": "prod-eu-west-1"
37 },
38 "entity_type": "namespace",
39 "entity_identifier": "checkout",
40 "entity_name": "checkout",
41 "entity_namespace": "checkout",
42 "weight_percentage": 60.0,
43 "source": "k8s_label",
44 "assigned_by_user_id": null,
45 "created_at": "2026-04-22T11:30:00Z",
46 "updated_at": "2026-05-18T03:11:00Z"
47 }
48 }
49 ],
50 "meta": {
51 "request_id": "0196f1c2-065b-7a04-adb5-1fffac93cff8",
52 "applied_at": "2026-05-21T10:30:00Z",
53 "pagination": {
54 "has_more": false,
55 "limit": 50
56 }
57 }
58}The first row is a 100% workload assignment created by a human in the Assignment Workbench. The second row is a 60% namespace assignment created automatically by the label reconciler (because the checkout namespace carries kubeadapt.io/team=Payments,Platform and the parsed weight for Payments is 60). The remaining 40% of the checkout namespace cost is owned by another team and appears as a separate row in that team's assignments.
Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | team_id is not a valid UUID, malformed limit, malformed cursor. |
| 400 | INVALID_CLUSTER_ID | A cluster_id value in the filter is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks teams:read. |
| 404 | TEAM_NOT_FOUND | No team with this ID exists in the tenant. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or query parameters changed. |
| 422 | INVALID_COST_MODE | The request carries cost_mode. Assignments have no cost. |
| 422 | VALIDATION_ERROR | The request carries basis, or entity_type is not workload, namespace or pv. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
Field notes
originiskubeadaptfor teams created in the UI/API,k8sfor teams mirrored from a configured Kubernetes label key (Settings → Label Keys). Origins are not strictly validated; treat unknown values askubeadaptfor routing.assigned_workloadsandassigned_pvscount distinct entities assigned to the team regardless of weight. A workload split 60/40 across two teams counts as 1 toward both teams.metadata.departmentis omitted entirely (notnull) when a team has no department. The team's cost then does not roll up. See Departments.last_seen_atis the latest cost-data heartbeat across any of the team's entities. A team whose workloads have all been deleted reportslast_seen_atfrom the last day any of them ran.- On assignments,
entity_identifieris the k8smetadata.uidwhenentity_type: workloadand the namespace name whenentity_type: namespace.entity_namespacecarries the namespace name in both cases.
Filtering patterns
1GET /v1/teams?cost_mode=fully_loaded # top spenders, invoice view
2GET /v1/teams?cost_mode=workload_only # team footprint, no overhead
3GET /v1/teams?department_id={dept_id} # teams under a department
4GET /v1/teams?origin=k8s # mirrored from labels
5GET /v1/teams/{team_id}/assignments?cluster_id={id} # team ownership in one cluster
6GET /v1/teams/{team_id}/assignments?entity_type=namespace # namespace-level assignments
7GET /v1/teams/{team_id}/assignments?source=user_manual # Assignment Workbench audit trailSee also
- Departments: the layer above teams. A department aggregates its teams' costs.
- Cost Modes. Fully Loaded vs Workload Only, with worked examples.
- Cost Attribution: Overview: the model behind teams, departments, and assignments.
- Assignment Workbench: the UI behind
source=user_manual. - Label-based attribution: the reconciler behind
source=k8s_label. - Workloads: the resource referenced by
entity_type=workloadassignments. - Cost Explorer: chargeback queries grouped by workload, namespace, or any other dimension, scoped to the clusters your key can see.