# Teams

> Everything a shared workspace gets on top of one account. Roles, policies that bind members, quiet hours, an audit log with a scheduled export, secrets scoped to boxes or members, and a live activity page.

URL: https://prized.dev/docs/teams

A [workspace](https://prized.dev/docs/workspaces) is one account for a team. This page is what the team gets on top of it. Every rule here is off until the owner turns it on, and nothing here changes a workspace of one.

## Roles [#roles]

| Role       | Can                                                                                                                                                                                                                                                   |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Owner      | Everything: the plan and billing, the domain and invite link, roles, policies, quiet-hours exemptions, the audit export, and removing members. No policy binds the owner. The owner is the account that created the workspace; ownership never moves. |
| Member     | Create, connect to, resize and delete every box in the workspace; use secrets, snapshots and environments; read the audit log, the activity page and the policies. Policies bind members.                                                             |
| Contractor | A member fenced to the boxes they own. Policies bind contractors too.                                                                                                                                                                                 |

**Changing a role.** **Dashboard → Workspace → Members** shows a role select on every row but the owner's (owner only): member or contractor. The API form is `PATCH /api/v1/workspace/members/{userId}` with `{"role": "member"}` or `{"role": "contractor"}`, from a dashboard session only (`400 invalid_role`, `409 is_owner`, `404 not_found`). Every change is an audit row, `member.role`. Demoting a member to contractor also revokes the phone passwords and browser-terminal tickets they minted for boxes they do not own and closes their CLI's open tunnels to those boxes (recorded as `token.revoke`); their own boxes keep everything. A phone or browser session already open ends with that connection, as any revoked SSH session does.

**What a contractor sees and does:**

* **Their own boxes only.** The box list, a box page and its tabs, every lifecycle action, `prized ssh`, the tunnel, browser and phone sessions, metrics, snapshots and the edge API cover the boxes they created; another member's box reads as not found everywhere. A contractor's SSH certificate names one box, so it opens nothing else in the workspace.
* **Shared config is read-only.** A contractor lists environments and upgrades their own boxes to a new version, but creating, editing or deleting an environment answers `403 not_allowed`: a rewritten environment reaches boxes they cannot see.
* **Restricted boxes only.** The new-box form shows &#x2A;*Restricted (safe for third parties)** on and locked, so no credential of yours reaches a contractor's box ([Safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties)). An API create with `"restricted": false` is `403 policy_denied` with rule `contractor_restricted`.
* **No workspace-wide reads.** The audit log, the activity page's member list, the policies and the workspace secrets answer `403 not_allowed`, and the dashboard says `Your role does not include ...` in each place. In Members they see their own row. They can still leave.
* **No secrets unless granted.** A contractor's box receives a secret only when it was granted to that box or to the contractor ([Scoped secrets](#scoped-secrets)).

**Who owns a box.** The member who created it. The box your plan launches for you and boxes Prized creates on your behalf belong to the workspace owner, and so do boxes from before roles existed. The box object carries `ownerUserId` and `quietHoursExempt` (`GET /api/v1/boxes`, `prized box info --json`).

> A CLI token created before workspaces existed belongs to nobody, so no per-member rule binds it: it reads as a plain member, no spend or box cap applies to it, and the audit rows it produces name no member (the page shows `a CLI token`). Only the owner can retire those tokens ([CLI tokens](https://prized.dev/docs/workspaces#cli-tokens)).

## Policies [#policies]

**Dashboard → Workspace → Policies** (`/dashboard/workspace/policies`). The owner edits; members read the rules that bind them; contractors see `Your role does not include the workspace policies.` Every rule is off by default and each saves on its own. Policies bind members and contractors, never the owner, except quiet hours, which pause every box.

| Rule                    | Key                  | Range                                           | What it does                                                                                                 |
| ----------------------- | -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Spend per member        | `memberSpendCapUsd`  | $1 to $1,000,000 a billing cycle, whole dollars | Caps what one member's boxes may draw in a cycle. Checked on create, wake and resize.                        |
| Boxes per member        | `memberBoxLimit`     | 1 to 100                                        | Caps a member's live boxes (running or paused; deleted ones do not count). Checked on create, fork and wake. |
| Largest size per member | `memberMaxTier`      | Any size                                        | The biggest size a member may create or resize to.                                                           |
| Auto-pause floor        | `autoPauseFloorMin`  | 30 minutes to 7 days                            | Members' boxes must auto-pause within this long of going idle: shorter is fine, longer or off is not.        |
| Quiet hours             | `quietHours`         | One window a day, per weekday                   | Pauses every running box in the workspace inside the window; see [Quiet hours](#quiet-hours).                |
| Audit log retention     | `auditRetentionDays` | 7 to 365 days, default 90                       | How long [audit rows](#audit-log) are kept.                                                                  |

A refused action answers `403 policy_denied` with `rule` and `message`; the CLI prints the message (exit 5), the dashboard shows it next to the control that asked. The sentences, and the fine print behind each rule:

* **Spend cap.** `workspace policy: your boxes have used $X of the $Y each member may spend this billing cycle`. The cycle is the month ending at your plan's period end, or the UTC calendar month without a plan. Spend is attributed to the box's owner (usage the member's boxes drew); the workspace still pays one bill.
* **Box limit.** Create and fork refuse at the limit: `workspace policy: each member may have 3 boxes and you already have 3`. A wake refuses only when the member is over it: `... delete one before waking this box`.
* **Largest size.** `workspace policy: members' boxes go up to Small, and Medium is above that`. The ladder is `Nano < Micro < Extra Small < Small < Medium < Large < Extra Large`; a Fast box compares by its price.
* **Auto-pause floor.** `workspace policy: members' boxes must auto-pause within 2 hours of going idle; pick that or shorter`. On create, a box with no auto-pause setting takes the floor; an explicit off or a longer window is refused. A member's box created before the floor is paused by the idle sweep as if it had the floor (its stored setting is not rewritten). On the box page a member's **Auto-pause** menu stops at the floor and the row says `Workspace policy: auto-pause within 2 hours.`

`GET /api/v1/workspace/policies` reads the rules (owners and members, session or CLI token); `PATCH` writes them from the dashboard alone. Every change is a `policy.update` audit row. Routes and errors are on the [API reference](https://prized.dev/docs/api#policies).

## Quiet hours [#quiet-hours]

A window each day in which the workspace's boxes are paused, the owner's included: `start` and `end` as `HH:MM`, the weekdays it applies to, the weekdays that are quiet all day, and one IANA timezone. The dashboard control is a start and end time, weekday checkboxes, all-day weekday checkboxes, and a timezone select that defaults to your browser's.

* **When a box pauses.** The reconciler pauses every running, non-exempt box on its next pass inside the window. A box woken inside the window (a member connects at night) stays up 15 minutes, then is paused again.
* **Across midnight.** A window that ends before it starts crosses midnight and belongs to the day it started on: `22:00` to `06:00` on Friday runs into Saturday morning.
* **All-day days.** A day in the all-day list is quiet from midnight to midnight.
* **Exempting a box.** The owner marks a box **Exempt from quiet hours** on its box page (`PATCH /api/v1/boxes/{id}` with `{"quietHoursExempt": true}`; anyone else is `403 not_owner`). An exempt box is never touched by the window.
* **In the log.** Each pause is a `box.pause` row with reason `quiet_hours` (the idle sweep's pauses say `auto_pause`, a deadline's `pause_deadline`), and `reconciler:quiet_hours` in the box page's event list.

A quiet-hours pause is exactly a [pause](https://prized.dev/docs/boxes#pause-and-resume): sleep rate from that moment, disk kept, processes per the size table.

## Audit log [#audit-log]

**Dashboard → Workspace → Audit** (`/dashboard/workspace/audit`): who did what, on which box, when. Owners and members read it; contractors see `Your role does not include the audit log.`

| Control          | What it does                                                                                                                                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Filters          | Member, box, kind (grouped as Sessions, Boxes, Snapshots, Environments, Secrets, Members, Workspace) and range: last 24 hours, 7 days (the default), 30 days, 90 days, or everything kept                                                              |
| Rows             | When; member (name or email, else `a CLI token`, `a browser session`, `a phone`, `Prized` for the system, `a removed member`) with the credential kind beneath; action; box; detail on one line, all of it on hover. **Load more** pages 100 at a time |
| **reported**     | A muted label on every row a client posted rather than the control plane observed; see below                                                                                                                                                           |
| **Download CSV** | The current filters and range as a file, up to 50,000 rows                                                                                                                                                                                             |
| **Export**       | Owners only: the [scheduled export](#scheduled-export)                                                                                                                                                                                                 |

### Two origins [#two-origins]

* **`server`: the control plane saw it.** Box lifecycle and settings, CLI tokens, snapshots, environments, secrets, membership and roles, policies, close and reopen, the export settings, every user SSH certificate minted, and every browser terminal opened. These rows are observations.
* **`client`, shown as reported: the CLI or the box told us.** The control plane never sees a command run over ssh, a file copied, or a prompt started: the CLI resolves the box, wakes it, mints a certificate, and then talks to the box directly. So `prized exec`, `prized cp`, `prized prompt`, `prized desktop` and the box's edge API post a row afterwards. A reported row is a claim by a credential in your workspace, not an observation: an offline CLI, an older CLI, or a failed report means no row, and nothing checks the claim against what ran.

Every row names the member (`actorUserId`) and the credential that acted (`actorTokenId`: the CLI token, the browser ticket, or the phone password). Browser terminal and phone sessions are attributed to the member who opened them.

### What is recorded [#what-is-recorded]

Server-observed kinds and their detail. Names and hosts, never values, contents, keys or tokens:

| Kind                                                                                                                 | Detail                                                                                                                                                                                                                                                                |
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `box.create`                                                                                                         | `tier`, `region`, `hostname`, `restricted`, `from` (a snapshot or template)                                                                                                                                                                                           |
| `box.wake`, `box.delete`                                                                                             |                                                                                                                                                                                                                                                                       |
| `box.pause`                                                                                                          | `reason`: `quiet_hours`, `auto_pause`, `pause_deadline`, or none for a pause you asked for                                                                                                                                                                            |
| `box.resize`, `box.move_region`                                                                                      | `from`, `to`                                                                                                                                                                                                                                                          |
| `box.settings`                                                                                                       | The keys changed: `autoPauseMin`, `pauseAt`, `autoSnapshotHours`, `autoSnapshotKeep`, `quietHoursExempt`                                                                                                                                                              |
| `box.env`                                                                                                            | `count`                                                                                                                                                                                                                                                               |
| `box.fork`                                                                                                           | On the source box: `from`, `fromHostname`, `to`, `toHostname`, `tier`, `snapshotId`, `keepSnapshot`                                                                                                                                                                   |
| `box.connect`                                                                                                        | One per user SSH certificate minted: `keyId`, and `scope` (`terminal`, `monitor`, `mobile-ssh`) for a browser or phone session. Every `prized ssh`, mosh, exec, cp, mount, sync and prompt from the CLI, and every browser terminal or phone session, starts with one |
| `box.session`                                                                                                        | A browser terminal opened: `scope`, `ticketId`                                                                                                                                                                                                                        |
| `snapshot.create`                                                                                                    | `snapshotId`, `name`, `kind`, `templateName`                                                                                                                                                                                                                          |
| `snapshot.delete`, `snapshot.template`                                                                               |                                                                                                                                                                                                                                                                       |
| `environment.create`, `environment.update`, `environment.delete`                                                     | Update carries `change` (`metadata`, `config`, `var_set`, `var_remove`, `file_set`, `file_remove`, `repo_add`, `repo_remove`, `toggles`, `default`, `upgrade`) and `key` (the var name, file path, repo, or toggle names); never a value or a file body               |
| `secret.create`, `secret.update`, `secret.delete`, `secret.mount`, `secret.unmount`, `secret.grant`, `secret.revoke` | Names and hosts                                                                                                                                                                                                                                                       |
| `member.add`                                                                                                         | `via`: `first_contact`, `domain`, `invite`                                                                                                                                                                                                                            |
| `member.remove`, `member.leave`, `member.role`, `invite.rotate`, `invite.revoke`                                     |                                                                                                                                                                                                                                                                       |
| `token.create`                                                                                                       | `name`, `tokenId`                                                                                                                                                                                                                                                     |
| `token.revoke`                                                                                                       | `tokenId`, or `all: true` with `count`                                                                                                                                                                                                                                |
| `policy.update`                                                                                                      | `keys`                                                                                                                                                                                                                                                                |
| `audit.export`                                                                                                       | `action` (`set` or `clear`), `host`                                                                                                                                                                                                                                   |
| `workspace.settings`, `workspace.retention`, `workspace.reopen`                                                      |                                                                                                                                                                                                                                                                       |
| `workspace.close`                                                                                                    | `purgeAfter`, `boxesSuspended`, `membersRemoved`, `cliTokensRevoked`                                                                                                                                                                                                  |

Client-reported kinds. Never stdout, stderr, file contents, the prompt beyond its first line, or a VNC password:

| Kind                           | Detail                                                           | Who reports it                                                                                                             |
| ------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `exec`                         | `command` (up to 512 characters), `cwd`, `detached`, `processId` | `prized exec` in its foreground, `--json` and `--detach` forms (not `--status`, `--kill`, `--ps`); the edge's `POST /exec` |
| `file.upload`, `file.download` | `path`, `bytes`, `files`, `inline`, `directory`                  | `prized cp` after the copy (a copy that moved nothing is not reported); the edge's upload, download and files routes       |
| `prompt`                       | `firstLine`, `provider`, `runId`, `queued`                       | `prized prompt` once the box answers started or queued; the edge's `POST /prompts`                                         |
| `desktop.start`                | `mode`, `profile`                                                | `prized desktop` and `prized browser` after the stack starts (not `--stop`); the dashboard's Desktop tab                   |

A report rides the credential that did the work (no new flags, nothing to configure), has a 3 second budget, and never fails or delays the action; a failed report is silent.

### From the CLI [#from-the-cli]

```bash
prized team log                                   # the last 24 hours
prized team log --since 7d --kind exec,prompt --member ana@acme.com
prized team log --box mango --limit 500 --json
```

`prized team log` prints TIME (local), MEMBER, KIND, BOX, DETAIL and ORIGIN (`server` or `reported`); an empty window prints `nothing in the last 24h`. `--since` takes a duration with a unit (`m`, `h`, `d`; a bare number exits 2), `--kind` a comma list, `--box` a name or id, `--member` an address, `--limit` 1 to 1000 (default 100). Contractors exit 5 with `your role in this workspace does not include the audit log`. Flags and the `--json` shape are on [CLI: Team](https://prized.dev/docs/cli#team).

### From the API [#from-the-api]

`GET /api/v1/audit` takes the same filters (`since`, `until`, `kind`, `box`, `actor`, `origin`), pages by `before` and `limit`, and answers `{events, nextBefore}`; `format=csv` streams the CSV instead. `POST /api/v1/audit` is how the CLI and the box post a reported row. Every parameter and error is on the [API reference](https://prized.dev/docs/api#audit-log).

### CSV [#csv]

**Download CSV** on the page, or `GET /api/v1/audit?format=csv` with the same filters: every row of the range up to 50,000, as `audit-YYYYMMDD-HHMMSS.csv` with the columns `id,at,kind,origin,actor_kind,actor_user_id,actor_token_id,box_id,detail`. `detail` is one JSON column, because its keys vary by kind.

### Scheduled export [#scheduled-export]

Owners see an **Export** section on the Audit page: a URL and a write-only secret, **Save** (or **Replace**), **Remove**, and a status line with the last delivery and the last error. Members see no Export section.

* **The destination.** An `https` URL of up to 2,048 characters with no credentials or fragment, and a secret of 16 to 256 characters. The secret is encrypted at rest under the control plane's secrets master key and never shown again.
* **Each delivery.** Once an hour, one `POST` per batch: the JSON body `{"v": 1, "workspaceId": "...", "events": [...]}` with events oldest first, up to 500 per batch and 20 batches per pass; headers `Content-Type: application/json`, `X-Prized-Signature: sha256=<hex HMAC-SHA256 of the body under your secret>`, `X-Prized-Event-Id-From` and `X-Prized-Event-Id-To`. Ten seconds per request; redirects are never followed.
* **Acknowledging.** Any `2xx` is delivered and the cursor advances. Anything else lands on the status line (`http_<status>`, `redirect_refused_<status>`, `fetch_failed: <message>`, `timeout`, `url_not_https`) and the same batch is sent again next hour: at-least-once and in order, so dedupe on `id`.
* **From when.** Delivery starts with the rows newer than the moment you saved the destination; the CSV is for history.
* **Where it lands.** Your endpoint. That copy is outside Prized, under your control and your retention. There is no S3 or other hosted destination.

```bash
# verify a delivery: the X-Prized-Signature header must equal sha256=<this hex>
openssl dgst -sha256 -hmac "$SECRET" body.json
```

Setting or clearing the destination is itself an `audit.export` row.

### Retention [#retention]

Rows are kept 90 days by default; the owner sets 7 to 365 days under Policies, and an hourly sweep deletes older rows. A row outlives the token, the member and the box it names, and goes when the workspace is erased ([Data retention](https://prized.dev/docs/data-retention#records-retained-after-deletion)).

[Zero data retention](https://prized.dev/docs/data-retention#zero-data-retention) does not shorten the window, because audit rows are workspace metadata, not box data. With it on, the `exec`, `file.upload`, `file.download` and `prompt` rows are still written but with an empty detail (the page shows `no detail kept (zero data retention)`); every other kind keeps its detail.

## Scoped secrets [#scoped-secrets]

A [secret](https://prized.dev/docs/credentials#secrets-for-agents) is workspace-wide until you grant it to someone.

* **No grants: every box, except a contractor's.** A secret with no grants can be mounted on, and is served by the broker to, every box whose owner is not a contractor. Secrets from before grants existed work exactly as they did.
* **The first grant narrows it.** From then on only the boxes named, and the boxes owned by the members named, may mount it or receive it. Removing the last grant makes it workspace-wide again.
* **A contractor's box gets nothing it was not granted**, to the box or to its owner.
* **Revoking is quick.** Grants are checked when a secret is mounted and again on every fetch by the box's broker, so a revoked grant stops at the box's next refresh: seconds through the long-poll, 15 at most. The mount stays listed on the box page as **no longer granted** until you unmount it.

**Dashboard.** On **Secrets**, the new-secret form has **Available to** (all boxes, selected boxes, or boxes owned by selected members), the table has an available-to column, and **Details** on a row shows the placeholder, the grants with **Revoke**, **grant a box** and **grant a member** selects, and the usage table below. On a box's **Secrets** tab the picker lists only what that box may mount, with one line for the rest: `not available to this box: X, Y (not granted to this box or its owner)`. Contractors see `Your role does not include workspace secrets.` on both.

**CLI.** `prized secrets ls --box mango` lists what a box may mount; `prized secrets grant NAME --box BOX` or `--member EMAIL` grants, `revoke` takes it back, `grants NAME` lists who has it, `usage NAME` what it was spent on. Flags and JSON are on [CLI: Secrets](https://prized.dev/docs/cli#secrets). The CLI never sets or shows a value.

**API.** `GET /api/v1/secrets?box={id}`, `POST /api/v1/secrets/{id}/grants`, `DELETE /api/v1/secrets/{id}/grants/{grantId}`, `GET /api/v1/secrets/{id}/usage`; a mount the box may not have is `403 secret_not_granted`. Bodies and errors are on the [API reference](https://prized.dev/docs/api#secrets).

### Usage [#usage]

One use is one HTTPS request to an allowlisted host in which the broker swapped the placeholder for the value, counted at the swap whatever the upstream answered. Not counted: a refused plain-HTTP request, a host outside the allowlist, a request that carried no placeholder.

| Fact                   | Detail                                                                                                                                                                       |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| What is recorded       | The placeholder, the host, the count, the first and last time. Never a path, header name, body or value; the broker never logs a placeholder or pairs a host with a value    |
| When it reaches Prized | The broker on the box (`prized-secretsd`) flushes its counters every 60 seconds and at shutdown, under the box's own agent token; a failed flush keeps them for the next try |
| Why counts are a floor | A broker that crashes between two flushes loses up to one interval                                                                                                           |
| Where you read it      | **Details** on the Secrets page (box, host, first, last, requests), `prized secrets usage NAME`, `GET /api/v1/secrets/{id}/usage`                                            |

## Team activity [#team-activity]

**Dashboard → Workspace → Activity** (`/dashboard/workspace/activity`): what the team is doing right now and what it costs.

| Section | What it shows                                                                                                                                                                                                                   |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Now     | Boxes running, sessions attached, spend today, spend this cycle with its dates. Refreshes every 15 seconds while the tab is visible; a failed poll keeps the last data and says `Live update failed; showing data from Ns ago.` |
| Boxes   | Each live box with its owner (`(you)` for yours), state, idle time, cpu and memory, attached sessions, last prompt, last exec, spend today and this cycle                                                                       |
| Members | Role, boxes, sessions, last active, spend this cycle. Not shown to contractors                                                                                                                                                  |
| Recent  | The newest 50 audit rows, client rows labelled `reported by the CLI or the box`, and a link to the Audit page                                                                                                                   |

Contractors see their own boxes and `Your role shows the boxes you own.`

| Term                   | Means                                                                                                                                                                                                                                                                                                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Attached session       | A live tunnel lease from a device to a box, opened when it connects (the CLI, a browser terminal, or a phone), released when the CLI exits cleanly, expired after 12 hours otherwise. It says a device is attached, never what is typed or shown. A member's session count covers the leases their CLI tokens opened                                                            |
| Last prompt, last exec | From reported audit rows, so an older CLI leaves them blank                                                                                                                                                                                                                                                                                                                     |
| Today                  | Since UTC midnight                                                                                                                                                                                                                                                                                                                                                              |
| Cycle                  | The plan's period (the month ending at its period end), or the UTC calendar month without a plan                                                                                                                                                                                                                                                                                |
| Spend                  | Metered compute the workspace was charged for, attributed to the box that drew it and bucketed by the UTC hour it was charged in; both windows count whole hour buckets. Attribution, not billing: the workspace pays one bill. A box with no owner shows `No owner` and is attributed to nobody. Counting starts when this feature shipped; earlier usage has no per-box split |

```bash
prized team status
prized team status --json
```

`prized team status` prints BOX, OWNER, STATE, IDLE, SESSIONS and SPEND TODAY for every live box (IDLE is `15m`, `active`, or `-` when the box is not running), then MEMBER, ROLE, BOXES, SESSIONS, SPEND (CYCLE) and LAST ACTIVE, then one line: `3 boxes running, 2 sessions attached, $4.10 today, $61.25 this cycle (Sep 1 to Oct 1)`. Contractors get their own boxes and `Showing the boxes you own (contractor role).` `--json` prints the object `GET /api/v1/team/status` answers ([API reference](https://prized.dev/docs/api#team)).

## What is not covered [#what-is-not-covered]

* **Reported rows are claims.** Any credential in the workspace can post one; an offline or older client posts none; nothing checks a report against what ran on the box. Read them as what your tools said they did.
* **What happens inside an ssh session.** Only the connect is recorded (`box.connect`), never the commands typed in it.
* **Tokens that belong to nobody.** A CLI token from before workspaces is bound by no per-member policy, and its rows name no member.
* **A hosted export destination.** The scheduled export posts to your endpoint; there is no S3 or other Prized-side destination.
* **Exact usage counts.** Secret usage is flushed every 60 seconds and read as a floor.
* **Operator access.** Access by Prized staff to the AWS account or the database is not in this log; see [Security and trust](https://prized.dev/docs/security#staff).
