> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuneplane.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up teams and quotas

> Create teams, set guarantees, price the hours, and turn on borrowing.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://tuneplane.your-company.com/api/teams \
  -H "Authorization: Bearer $TUNEPLANE_CLIENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "ml-platform", "display_name": "ML Platform"}'
```

Or **Administration → Teams → New team** in the console, which is where most of this happens. See
[the Teams page](/en/console/teams).

Teams are part of the commercial edition. A deployment where one department uses the cluster
needs none of this: everybody sits in one team and no team gate ever fires.

## Before you start

* A commercial licence installed. Without one, the Teams entry appears for administrators and the
  page behind it explains what is missing.
* Per-user quotas already set, because team quota applies **alongside** them rather than instead
  of them. See [roles and quotas](/en/ops/roles-and-quotas).

## Create a team

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST .../api/teams -d '{"key": "ml-platform", "display_name": "ML Platform"}'
```

The **key** is permanent. It appears in URLs and on bills, and it accepts lower-case letters,
digits, `-` and `_`, up to 64 characters. Teams nest, so a team can name a parent.

## Put projects in it

A run's team comes from its **project**, never from whoever submitted it:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PUT .../api/teams/project -d '{"project": "support-agent", "team": "ml-platform"}'
```

Both halves of that rule are load-bearing. A person can belong to several teams, so summing a
team's members would charge one run to all of them. And the team is stamped on the job row at
submit, so moving a project between departments does not rewrite last quarter's usage.

## Set the quota

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PUT ".../api/teams/ml-platform/quota?max_concurrent_gpus=64&max_concurrent_jobs=20&daily_gpu_hours=800"
```

Two semantics invert against the per-user ones, and both are deliberate:

|                  | Per-user              | Team                                                                        |
| ---------------- | --------------------- | --------------------------------------------------------------------------- |
| Not configured   | A limit of zero       | **No limit.** Creating a team must not become a way to block everyone in it |
| An administrator | Exempt in some checks | **Not exempt.** Skipping a team's budget spends another department's money  |

## Add members

Add them by hand, or configure single sign-on group mapping and let them arrive on their next
sign-in. Group mapping is the one that scales: a team whose membership is maintained in two
places is a team whose membership is wrong in one of them.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PUT .../api/teams/ml-platform/members -d '{"members": [{"username": "alice", "role": "owner"}]}'
```

Roles inside a team are `owner`, `member` and `viewer`.

## Turn on borrowing

With preemption enabled, a team quota stops being a ceiling and becomes a **guarantee**: a team
may run above it while the cluster is idle, and the overflow becomes reclaimable.

Borrowing and reclaim are **one switch**. Lending capacity without being able to take it back is
worse than the hard cap it replaced: a team that borrowed heavily would sit on cards its owner
cannot get to.

Five conditions must all hold before a running job is reclaimed:

1. The taker's team is under its own guarantee. Otherwise this is one team outbidding another.
2. The victim's team is over its guarantee. Otherwise reclaim breaks the only promise a quota
   makes.
3. It is a different team.
4. The victim's job is past a minimum runtime. Otherwise a busy queue reclaims, admits and
   reclaims all day, and the cluster spends its time writing checkpoints.
5. The victim holds the card series that is short.

Youngest first, deliberately not lowest priority: priority orders who runs next, which is a
different question answered with the same number.

A reclaimed job is paused with its checkpoint kept and returns to `QUEUED`.

## Price the hours

Billing adds a price and re-measures nothing. The hours come from the same metering the
[Usage](/en/console/usage) page shows, because a second definition of "how much did we use" is an
argument nobody can settle.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PUT .../api/billing/teams/ml-platform/budget -d '{"budget_micros": 5000000000}'
```

Money is integer micros end to end. Set a rate per card type; card types with no rate are named
under **unpriced series** rather than being folded silently into zero.

A team over its monthly budget queues rather than fails, and a single run that genuinely cannot
wait can ask for a one-off exception through [Approvals](/en/console/approvals).

## Check it worked

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -H "Authorization: Bearer $TUNEPLANE_CLIENT_TOKEN" \
  https://tuneplane.your-company.com/api/teams/ml-platform/usage
```

Returns the team's hours for the period. Then have somebody in the team submit a job and check
its page names the team, not just the submitter.

A team quota that is refusing work names itself in the gate reason: `teamQuota` wraps the
underlying reason with the team's key, so a queued job says which team's limit it is waiting on.

## Next

[Teams page](/en/console/teams) · [Billing](/en/console/billing) ·
[Working in a team](/en/guides/teams) · [Roles and quotas](/en/ops/roles-and-quotas)
